Skip to content

Commit 974b352

Browse files
Add implementation of now, sysdate, localtime and similar functions (#92)
Signed-off-by: Yury Fridlyand <[email protected]>
1 parent ce15448 commit 974b352

File tree

22 files changed

+1489
-32
lines changed

22 files changed

+1489
-32
lines changed

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

Lines changed: 24 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

@@ -21,6 +24,11 @@ public class QueryContext {
2124
*/
2225
private static final String REQUEST_ID_KEY = "request_id";
2326

27+
/**
28+
* Timestamp when SQL plugin started to process current request.
29+
*/
30+
private static final String REQUEST_PROCESSING_STARTED = "request_processing_started";
31+
2432
/**
2533
* Generates a random UUID and adds to the {@link ThreadContext} as the request id.
2634
* <p>
@@ -48,6 +56,22 @@ public static String getRequestId() {
4856
return id;
4957
}
5058

59+
public static void recordProcessingStarted() {
60+
ThreadContext.put(REQUEST_PROCESSING_STARTED, LocalDateTime.now().toString());
61+
}
62+
63+
/**
64+
* Get recorded previously time indicating when processing started for the current query.
65+
* @return A LocalDateTime object
66+
*/
67+
public static LocalDateTime getProcessingStartedTime() {
68+
if (ThreadContext.containsKey(REQUEST_PROCESSING_STARTED)) {
69+
return LocalDateTime.parse(ThreadContext.get(REQUEST_PROCESSING_STARTED));
70+
}
71+
// This shouldn't happen outside of unit tests
72+
return LocalDateTime.now();
73+
}
74+
5175
/**
5276
* Wraps a given instance of {@link Runnable} into a new one which gets all the
5377
* entries from current ThreadContext map.

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: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,19 @@
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 java.util.function.Supplier;
32+
import javax.annotation.Nullable;
2833
import lombok.experimental.UtilityClass;
34+
import org.opensearch.sql.common.utils.QueryContext;
2935
import org.opensearch.sql.data.model.ExprDateValue;
3036
import org.opensearch.sql.data.model.ExprDatetimeValue;
3137
import org.opensearch.sql.data.model.ExprIntegerValue;
@@ -84,6 +90,89 @@ public void register(BuiltinFunctionRepository repository) {
8490
repository.register(to_days());
8591
repository.register(week());
8692
repository.register(year());
93+
94+
repository.register(now());
95+
repository.register(current_timestamp());
96+
repository.register(localtimestamp());
97+
repository.register(localtime());
98+
repository.register(sysdate());
99+
repository.register(curtime());
100+
repository.register(current_time());
101+
repository.register(curdate());
102+
repository.register(current_date());
103+
}
104+
105+
/**
106+
* NOW() returns a constant time that indicates the time at which the statement began to execute.
107+
*/
108+
private LocalDateTime now(@Nullable Integer fsp) {
109+
return formatLocalDateTime(QueryContext::getProcessingStartedTime, fsp);
110+
}
111+
112+
private FunctionResolver now(FunctionName functionName) {
113+
return define(functionName,
114+
impl(() -> new ExprDatetimeValue(now((Integer)null)), DATETIME),
115+
impl((v) -> new ExprDatetimeValue(now(v.integerValue())), DATETIME, INTEGER)
116+
);
117+
}
118+
119+
private FunctionResolver now() {
120+
return now(BuiltinFunctionName.NOW.getName());
121+
}
122+
123+
private FunctionResolver current_timestamp() {
124+
return now(BuiltinFunctionName.CURRENT_TIMESTAMP.getName());
125+
}
126+
127+
private FunctionResolver localtimestamp() {
128+
return now(BuiltinFunctionName.LOCALTIMESTAMP.getName());
129+
}
130+
131+
private FunctionResolver localtime() {
132+
return now(BuiltinFunctionName.LOCALTIME.getName());
133+
}
134+
135+
/**
136+
* SYSDATE() returns the time at which it executes.
137+
*/
138+
private LocalDateTime sysDate(@Nullable Integer fsp) {
139+
return formatLocalDateTime(LocalDateTime::now, fsp);
140+
}
141+
142+
private FunctionResolver sysdate() {
143+
return define(BuiltinFunctionName.SYSDATE.getName(),
144+
impl(() -> new ExprDatetimeValue(sysDate(null)), DATETIME),
145+
impl((v) -> new ExprDatetimeValue(sysDate(v.integerValue())), DATETIME, INTEGER)
146+
);
147+
}
148+
149+
private FunctionResolver curtime(FunctionName functionName) {
150+
return define(functionName,
151+
impl(() -> new ExprTimeValue(sysDate(null).toLocalTime()), TIME),
152+
impl((v) -> new ExprTimeValue(sysDate(v.integerValue()).toLocalTime()), TIME, INTEGER)
153+
);
154+
}
155+
156+
private FunctionResolver curtime() {
157+
return curtime(BuiltinFunctionName.CURTIME.getName());
158+
}
159+
160+
private FunctionResolver current_time() {
161+
return curtime(BuiltinFunctionName.CURRENT_TIME.getName());
162+
}
163+
164+
private FunctionResolver curdate(FunctionName functionName) {
165+
return define(functionName,
166+
impl(() -> new ExprDateValue(sysDate(null).toLocalDate()), DATE)
167+
);
168+
}
169+
170+
private FunctionResolver curdate() {
171+
return curdate(BuiltinFunctionName.CURDATE.getName());
172+
}
173+
174+
private FunctionResolver current_date() {
175+
return curdate(BuiltinFunctionName.CURRENT_DATE.getName());
87176
}
88177

89178
/**
@@ -114,6 +203,10 @@ private FunctionResolver adddate() {
114203
return add_date(BuiltinFunctionName.ADDDATE.getName());
115204
}
116205

206+
private FunctionResolver date_add() {
207+
return add_date(BuiltinFunctionName.DATE_ADD.getName());
208+
}
209+
117210
/**
118211
* Extracts the date part of a date and time value.
119212
* Also to construct a date type. The supported signatures:
@@ -127,10 +220,6 @@ private FunctionResolver date() {
127220
impl(nullMissingHandling(DateTimeFunction::exprDate), DATE, TIMESTAMP));
128221
}
129222

130-
private FunctionResolver date_add() {
131-
return add_date(BuiltinFunctionName.DATE_ADD.getName());
132-
}
133-
134223
/**
135224
* Specify a start date and subtract a temporal amount to the date.
136225
* The return type depends on the date type and the interval unit. Detailed supported signatures:
@@ -739,4 +828,26 @@ private ExprValue exprYear(ExprValue date) {
739828
return new ExprIntegerValue(date.dateValue().getYear());
740829
}
741830

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

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
*/

core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,13 @@
2727
import java.util.Collections;
2828
import java.util.LinkedHashMap;
2929
import java.util.Map;
30+
import java.util.function.Function;
31+
import java.util.stream.Stream;
3032
import org.junit.jupiter.api.Test;
3133
import org.junit.jupiter.api.extension.ExtendWith;
34+
import org.junit.jupiter.params.ParameterizedTest;
35+
import org.junit.jupiter.params.provider.Arguments;
36+
import org.junit.jupiter.params.provider.MethodSource;
3237
import org.opensearch.sql.analysis.symbol.Namespace;
3338
import org.opensearch.sql.analysis.symbol.Symbol;
3439
import org.opensearch.sql.ast.dsl.AstDSL;
@@ -45,6 +50,7 @@
4550
import org.opensearch.sql.exception.SemanticCheckException;
4651
import org.opensearch.sql.expression.DSL;
4752
import org.opensearch.sql.expression.Expression;
53+
import org.opensearch.sql.expression.FunctionExpression;
4854
import org.opensearch.sql.expression.HighlightExpression;
4955
import org.opensearch.sql.expression.config.ExpressionConfig;
5056
import org.opensearch.sql.expression.window.aggregation.AggregateWindowFunction;
@@ -537,6 +543,49 @@ public void match_phrase_prefix_all_params() {
537543
);
538544
}
539545

546+
private static Stream<Arguments> functionNames() {
547+
var dsl = new DSL(new ExpressionConfig().functionRepository());
548+
return Stream.of(
549+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::now,
550+
"now", true),
551+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::current_timestamp,
552+
"current_timestamp", true),
553+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::localtimestamp,
554+
"localtimestamp", true),
555+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::localtime,
556+
"localtime", true),
557+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::sysdate,
558+
"sysdate", true),
559+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::curtime,
560+
"curtime", true),
561+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::current_time,
562+
"current_time", true),
563+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::curdate,
564+
"curdate", false),
565+
Arguments.of((Function<Expression[], FunctionExpression>)dsl::current_date,
566+
"current_date", false));
567+
}
568+
569+
@ParameterizedTest(name = "{1}")
570+
@MethodSource("functionNames")
571+
public void now_like_functions(Function<Expression[], FunctionExpression> function,
572+
String name,
573+
Boolean hasFsp) {
574+
assertAnalyzeEqual(
575+
function.apply(new Expression[]{}),
576+
AstDSL.function(name));
577+
578+
if (hasFsp) {
579+
assertAnalyzeEqual(
580+
function.apply(new Expression[]{DSL.ref("integer_value", INTEGER)}),
581+
AstDSL.function(name, field("integer_value")));
582+
583+
assertAnalyzeEqual(
584+
function.apply(new Expression[]{DSL.literal(3)}),
585+
AstDSL.function(name, intLiteral(3)));
586+
}
587+
}
588+
540589
@Test
541590
void highlight() {
542591
assertAnalyzeEqual(new HighlightExpression(DSL.literal("fieldA")),

0 commit comments

Comments
 (0)