Skip to content

Commit e28ac78

Browse files
Add implementation of now, sysdate, localtime and similar functions (opensearch-project#754)
* Add implementation of `now`, `sysdate`, `localtime` and similar functions (#92) Signed-off-by: Yury-Fridlyand <[email protected]> * Rework on `now` function implementation (#113) Signed-off-by: Yury-Fridlyand <[email protected]> * Minor SQL ANTLR clean-up. Signed-off-by: Yury-Fridlyand <[email protected]> Signed-off-by: Yury-Fridlyand <[email protected]>
1 parent fa8d5bd commit e28ac78

File tree

26 files changed

+1562
-35
lines changed

26 files changed

+1562
-35
lines changed

common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66

77
package org.opensearch.sql.common.utils;
88

9+
import java.time.LocalDateTime;
910
import java.util.Map;
1011
import java.util.Optional;
1112
import java.util.UUID;
1213
import org.apache.logging.log4j.ThreadContext;
1314

1415
/**
1516
* Utility class for recording and accessing context for the query being executed.
17+
* Implementation Details: context variables is being persisted statically in the thread context
18+
* @see: @ThreadContext
1619
*/
1720
public class QueryContext {
1821

core/src/main/java/org/opensearch/sql/analysis/AnalysisContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
package org.opensearch.sql.analysis;
88

99
import java.util.ArrayList;
10+
import java.util.HashMap;
1011
import java.util.List;
12+
import java.util.Map;
1113
import java.util.Objects;
1214
import lombok.Getter;
15+
import org.opensearch.sql.expression.Expression;
1316
import org.opensearch.sql.expression.NamedExpression;
1417

1518
/**
@@ -23,13 +26,26 @@ public class AnalysisContext {
2326
@Getter
2427
private final List<NamedExpression> namedParseExpressions;
2528

29+
/**
30+
* Storage for values of functions which return a constant value.
31+
* We are storing the values there to use it in sequential calls to those functions.
32+
* For example, `now` function should the same value during processing a query.
33+
*/
34+
@Getter
35+
private final Map<String, Expression> constantFunctionValues;
36+
2637
public AnalysisContext() {
2738
this(new TypeEnvironment(null));
2839
}
2940

41+
/**
42+
* Class CTOR.
43+
* @param environment Env to set to a new instance.
44+
*/
3045
public AnalysisContext(TypeEnvironment environment) {
3146
this.environment = environment;
3247
this.namedParseExpressions = new ArrayList<>();
48+
this.constantFunctionValues = new HashMap<>();
3349
}
3450

3551
/**

core/src/main/java/org/opensearch/sql/analysis/ExpressionAnalyzer.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.opensearch.sql.ast.expression.Case;
2525
import org.opensearch.sql.ast.expression.Cast;
2626
import org.opensearch.sql.ast.expression.Compare;
27+
import org.opensearch.sql.ast.expression.ConstantFunction;
2728
import org.opensearch.sql.ast.expression.EqualTo;
2829
import org.opensearch.sql.ast.expression.Field;
2930
import org.opensearch.sql.ast.expression.Function;
@@ -169,6 +170,19 @@ public Expression visitRelevanceFieldList(RelevanceFieldList node, AnalysisConte
169170
ImmutableMap.copyOf(node.getFieldList())));
170171
}
171172

173+
@Override
174+
public Expression visitConstantFunction(ConstantFunction node, AnalysisContext context) {
175+
var valueName = node.getFuncName();
176+
if (context.getConstantFunctionValues().containsKey(valueName)) {
177+
return context.getConstantFunctionValues().get(valueName);
178+
}
179+
180+
var value = visitFunction(node, context);
181+
value = DSL.literal(value.valueOf(null));
182+
context.getConstantFunctionValues().put(valueName, value);
183+
return value;
184+
}
185+
172186
@Override
173187
public Expression visitFunction(Function node, AnalysisContext context) {
174188
FunctionName functionName = FunctionName.of(node.getFuncName());

core/src/main/java/org/opensearch/sql/ast/AbstractNodeVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.opensearch.sql.ast.expression.Case;
1616
import org.opensearch.sql.ast.expression.Cast;
1717
import org.opensearch.sql.ast.expression.Compare;
18+
import org.opensearch.sql.ast.expression.ConstantFunction;
1819
import org.opensearch.sql.ast.expression.EqualTo;
1920
import org.opensearch.sql.ast.expression.Field;
2021
import org.opensearch.sql.ast.expression.Function;
@@ -116,6 +117,10 @@ public T visitRelevanceFieldList(RelevanceFieldList node, C context) {
116117
return visitChildren(node, context);
117118
}
118119

120+
public T visitConstantFunction(ConstantFunction node, C context) {
121+
return visitChildren(node, context);
122+
}
123+
119124
public T visitUnresolvedAttribute(UnresolvedAttribute node, C context) {
120125
return visitChildren(node, context);
121126
}

core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.opensearch.sql.ast.expression.Case;
2020
import org.opensearch.sql.ast.expression.Cast;
2121
import org.opensearch.sql.ast.expression.Compare;
22+
import org.opensearch.sql.ast.expression.ConstantFunction;
2223
import org.opensearch.sql.ast.expression.DataType;
2324
import org.opensearch.sql.ast.expression.EqualTo;
2425
import org.opensearch.sql.ast.expression.Field;
@@ -234,6 +235,10 @@ public static Function function(String funcName, UnresolvedExpression... funcArg
234235
return new Function(funcName, Arrays.asList(funcArgs));
235236
}
236237

238+
public static Function constantFunction(String funcName, UnresolvedExpression... funcArgs) {
239+
return new ConstantFunction(funcName, Arrays.asList(funcArgs));
240+
}
241+
237242
/**
238243
* CASE
239244
* WHEN search_condition THEN result_expr
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
7+
package org.opensearch.sql.ast.expression;
8+
9+
import java.util.List;
10+
import lombok.EqualsAndHashCode;
11+
import org.opensearch.sql.ast.AbstractNodeVisitor;
12+
13+
/**
14+
* Expression node that holds a function which should be replaced by its constant[1] value.
15+
* [1] Constant at execution time.
16+
*/
17+
@EqualsAndHashCode(callSuper = false)
18+
public class ConstantFunction extends Function {
19+
20+
public ConstantFunction(String funcName, List<UnresolvedExpression> funcArgs) {
21+
super(funcName, funcArgs);
22+
}
23+
24+
@Override
25+
public <R, C> R accept(AbstractNodeVisitor<R, C> nodeVisitor, C context) {
26+
return nodeVisitor.visitConstantFunction(this, context);
27+
}
28+
}

core/src/main/java/org/opensearch/sql/expression/DSL.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,42 @@ public FunctionExpression match_bool_prefix(Expression... args) {
682682
return compile(BuiltinFunctionName.MATCH_BOOL_PREFIX, args);
683683
}
684684

685+
public FunctionExpression now(Expression... args) {
686+
return compile(BuiltinFunctionName.NOW, args);
687+
}
688+
689+
public FunctionExpression current_timestamp(Expression... args) {
690+
return compile(BuiltinFunctionName.CURRENT_TIMESTAMP, args);
691+
}
692+
693+
public FunctionExpression localtimestamp(Expression... args) {
694+
return compile(BuiltinFunctionName.LOCALTIMESTAMP, args);
695+
}
696+
697+
public FunctionExpression localtime(Expression... args) {
698+
return compile(BuiltinFunctionName.LOCALTIME, args);
699+
}
700+
701+
public FunctionExpression sysdate(Expression... args) {
702+
return compile(BuiltinFunctionName.SYSDATE, args);
703+
}
704+
705+
public FunctionExpression curtime(Expression... args) {
706+
return compile(BuiltinFunctionName.CURTIME, args);
707+
}
708+
709+
public FunctionExpression current_time(Expression... args) {
710+
return compile(BuiltinFunctionName.CURRENT_TIME, args);
711+
}
712+
713+
public FunctionExpression curdate(Expression... args) {
714+
return compile(BuiltinFunctionName.CURDATE, args);
715+
}
716+
717+
public FunctionExpression current_date(Expression... args) {
718+
return compile(BuiltinFunctionName.CURRENT_DATE, args);
719+
}
720+
685721
private FunctionExpression compile(BuiltinFunctionName bfn, Expression... args) {
686722
return (FunctionExpression) repository.compile(bfn.getName(), Arrays.asList(args.clone()));
687723
}

core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
import static org.opensearch.sql.expression.function.FunctionDSL.impl;
2020
import static org.opensearch.sql.expression.function.FunctionDSL.nullMissingHandling;
2121

22+
import java.math.BigDecimal;
23+
import java.math.RoundingMode;
2224
import java.time.LocalDate;
25+
import java.time.LocalDateTime;
2326
import java.time.LocalTime;
2427
import java.time.format.DateTimeFormatter;
2528
import java.time.format.TextStyle;
2629
import java.util.Locale;
2730
import java.util.concurrent.TimeUnit;
31+
import javax.annotation.Nullable;
2832
import lombok.experimental.UtilityClass;
2933
import org.opensearch.sql.data.model.ExprDateValue;
3034
import org.opensearch.sql.data.model.ExprDatetimeValue;
@@ -85,6 +89,84 @@ public void register(BuiltinFunctionRepository repository) {
8589
repository.register(to_days());
8690
repository.register(week());
8791
repository.register(year());
92+
93+
repository.register(now());
94+
repository.register(current_timestamp());
95+
repository.register(localtimestamp());
96+
repository.register(localtime());
97+
repository.register(sysdate());
98+
repository.register(curtime());
99+
repository.register(current_time());
100+
repository.register(curdate());
101+
repository.register(current_date());
102+
}
103+
104+
/**
105+
* NOW() returns a constant time that indicates the time at which the statement began to execute.
106+
* `fsp` argument support is removed until refactoring to avoid bug where `now()`, `now(x)` and
107+
* `now(y) return different values.
108+
*/
109+
private FunctionResolver now(FunctionName functionName) {
110+
return define(functionName,
111+
impl(() -> new ExprDatetimeValue(formatNow(null)), DATETIME)
112+
);
113+
}
114+
115+
private FunctionResolver now() {
116+
return now(BuiltinFunctionName.NOW.getName());
117+
}
118+
119+
private FunctionResolver current_timestamp() {
120+
return now(BuiltinFunctionName.CURRENT_TIMESTAMP.getName());
121+
}
122+
123+
private FunctionResolver localtimestamp() {
124+
return now(BuiltinFunctionName.LOCALTIMESTAMP.getName());
125+
}
126+
127+
private FunctionResolver localtime() {
128+
return now(BuiltinFunctionName.LOCALTIME.getName());
129+
}
130+
131+
/**
132+
* SYSDATE() returns the time at which it executes.
133+
*/
134+
private FunctionResolver sysdate() {
135+
return define(BuiltinFunctionName.SYSDATE.getName(),
136+
impl(() -> new ExprDatetimeValue(formatNow(null)), DATETIME),
137+
impl((v) -> new ExprDatetimeValue(formatNow(v.integerValue())), DATETIME, INTEGER)
138+
);
139+
}
140+
141+
/**
142+
* Synonym for @see `now`.
143+
*/
144+
private FunctionResolver curtime(FunctionName functionName) {
145+
return define(functionName,
146+
impl(() -> new ExprTimeValue(formatNow(null).toLocalTime()), TIME)
147+
);
148+
}
149+
150+
private FunctionResolver curtime() {
151+
return curtime(BuiltinFunctionName.CURTIME.getName());
152+
}
153+
154+
private FunctionResolver current_time() {
155+
return curtime(BuiltinFunctionName.CURRENT_TIME.getName());
156+
}
157+
158+
private FunctionResolver curdate(FunctionName functionName) {
159+
return define(functionName,
160+
impl(() -> new ExprDateValue(formatNow(null).toLocalDate()), DATE)
161+
);
162+
}
163+
164+
private FunctionResolver curdate() {
165+
return curdate(BuiltinFunctionName.CURDATE.getName());
166+
}
167+
168+
private FunctionResolver current_date() {
169+
return curdate(BuiltinFunctionName.CURRENT_DATE.getName());
88170
}
89171

90172
/**
@@ -742,4 +824,24 @@ private ExprValue exprYear(ExprValue date) {
742824
return new ExprIntegerValue(date.dateValue().getYear());
743825
}
744826

827+
/**
828+
* Prepare LocalDateTime value. Truncate fractional second part according to the argument.
829+
* @param fsp argument is given to specify a fractional seconds precision from 0 to 6,
830+
* the return value includes a fractional seconds part of that many digits.
831+
* @return LocalDateTime object.
832+
*/
833+
private LocalDateTime formatNow(@Nullable Integer fsp) {
834+
var res = LocalDateTime.now();
835+
if (fsp == null) {
836+
fsp = 0;
837+
}
838+
var defaultPrecision = 9; // There are 10^9 nanoseconds in one second
839+
if (fsp < 0 || fsp > 6) { // Check that the argument is in the allowed range [0, 6]
840+
throw new IllegalArgumentException(
841+
String.format("Invalid `fsp` value: %d, allowed 0 to 6", fsp));
842+
}
843+
var nano = new BigDecimal(res.getNano())
844+
.setScale(fsp - defaultPrecision, RoundingMode.DOWN).intValue();
845+
return res.withNano(nano);
846+
}
745847
}

core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,16 @@ public enum BuiltinFunctionName {
8484
TO_DAYS(FunctionName.of("to_days")),
8585
WEEK(FunctionName.of("week")),
8686
YEAR(FunctionName.of("year")),
87-
87+
// `now`-like functions
88+
NOW(FunctionName.of("now")),
89+
CURDATE(FunctionName.of("curdate")),
90+
CURRENT_DATE(FunctionName.of("current_date")),
91+
CURTIME(FunctionName.of("curtime")),
92+
CURRENT_TIME(FunctionName.of("current_time")),
93+
LOCALTIME(FunctionName.of("localtime")),
94+
CURRENT_TIMESTAMP(FunctionName.of("current_timestamp")),
95+
LOCALTIMESTAMP(FunctionName.of("localtimestamp")),
96+
SYSDATE(FunctionName.of("sysdate")),
8897
/**
8998
* Text Functions.
9099
*/

0 commit comments

Comments
 (0)