diff --git a/build.gradle b/build.gradle
index 28ecdc9dbe2..8a2f9046bfc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,8 @@
buildscript {
ext {
opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT")
+ spring_version = "5.3.22"
+ jackson_version = "2.13.3"
isSnapshot = "true" == System.getProperty("build.snapshot", "true")
buildVersionQualifier = System.getProperty("build.version_qualifier", "")
version_tokens = opensearch_version.tokenize('-')
diff --git a/common/src/main/java/org/opensearch/sql/common/utils/LogUtils.java b/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java
similarity index 53%
rename from common/src/main/java/org/opensearch/sql/common/utils/LogUtils.java
rename to common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java
index 2f8c22c059b..b3991230545 100644
--- a/common/src/main/java/org/opensearch/sql/common/utils/LogUtils.java
+++ b/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java
@@ -6,20 +6,29 @@
package org.opensearch.sql.common.utils;
+import java.time.LocalDateTime;
import java.util.Map;
+import java.util.Optional;
import java.util.UUID;
import org.apache.logging.log4j.ThreadContext;
/**
- * Utility class for generating/accessing the request id from logging context.
+ * Utility class for recording and accessing context for the query being executed.
+ * Implementation Details: context variables is being persisted statically in the thread context
+ * @see: @ThreadContext
*/
-public class LogUtils {
+public class QueryContext {
/**
* The key of the request id in the context map.
*/
private static final String REQUEST_ID_KEY = "request_id";
+ /**
+ * Timestamp when SQL plugin started to process current request.
+ */
+ private static final String REQUEST_PROCESSING_STARTED = "request_processing_started";
+
/**
* Generates a random UUID and adds to the {@link ThreadContext} as the request id.
*
@@ -29,8 +38,10 @@ public class LogUtils {
* call this method twice on the same thread within the lifetime of the request.
*
*/
- public static void addRequestId() {
- ThreadContext.put(REQUEST_ID_KEY, UUID.randomUUID().toString());
+ public static String addRequestId() {
+ var id = UUID.randomUUID().toString();
+ ThreadContext.put(REQUEST_ID_KEY, id);
+ return id;
}
/**
@@ -38,8 +49,27 @@ public static void addRequestId() {
* @return the current request id from {@link ThreadContext}.
*/
public static String getRequestId() {
- final String requestId = ThreadContext.get(REQUEST_ID_KEY);
- return requestId;
+ var id = ThreadContext.get(REQUEST_ID_KEY);
+ if (null == id) {
+ id = addRequestId();
+ }
+ return id;
+ }
+
+ public static void recordProcessingStarted() {
+ ThreadContext.put(REQUEST_PROCESSING_STARTED, LocalDateTime.now().toString());
+ }
+
+ /**
+ * Get recorded previously time indicating when processing started for the current query.
+ * @return A LocalDateTime object
+ */
+ public static LocalDateTime getProcessingStartedTime() {
+ if (ThreadContext.containsKey(REQUEST_PROCESSING_STARTED)) {
+ return LocalDateTime.parse(ThreadContext.get(REQUEST_PROCESSING_STARTED));
+ }
+ // This shouldn't happen outside of unit tests
+ return LocalDateTime.now();
}
/**
@@ -57,7 +87,7 @@ public static Runnable withCurrentContext(final Runnable task) {
};
}
- private LogUtils() {
+ private QueryContext() {
throw new AssertionError(
getClass().getCanonicalName() + " is a utility class and must not be initialized");
}
diff --git a/core/build.gradle b/core/build.gradle
index 342d5673cd7..1fa3e19e269 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -40,8 +40,8 @@ repositories {
dependencies {
api group: 'com.google.guava', name: 'guava', version: '31.0.1-jre'
- api group: 'org.springframework', name: 'spring-context', version: '5.3.22'
- api group: 'org.springframework', name: 'spring-beans', version: '5.3.22'
+ api group: 'org.springframework', name: 'spring-context', version: "${spring_version}"
+ api group: 'org.springframework', name: 'spring-beans', version: "${spring_version}"
api group: 'org.apache.commons', name: 'commons-lang3', version: '3.10'
api group: 'com.facebook.presto', name: 'presto-matching', version: '0.240'
api group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
@@ -49,7 +49,7 @@ dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '2.1'
- testImplementation group: 'org.springframework', name: 'spring-test', version: '5.3.22'
+ testImplementation group: 'org.springframework', name: 'spring-test', version: "${spring_version}"
testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.12.4'
testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.12.4'
}
diff --git a/core/src/main/java/org/opensearch/sql/expression/DSL.java b/core/src/main/java/org/opensearch/sql/expression/DSL.java
index bd2d0756139..a094d2e4873 100644
--- a/core/src/main/java/org/opensearch/sql/expression/DSL.java
+++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java
@@ -682,6 +682,42 @@ public FunctionExpression match_bool_prefix(Expression... args) {
return compile(BuiltinFunctionName.MATCH_BOOL_PREFIX, args);
}
+ public FunctionExpression now(Expression... args) {
+ return compile(BuiltinFunctionName.NOW, args);
+ }
+
+ public FunctionExpression current_timestamp(Expression... args) {
+ return compile(BuiltinFunctionName.CURRENT_TIMESTAMP, args);
+ }
+
+ public FunctionExpression localtimestamp(Expression... args) {
+ return compile(BuiltinFunctionName.LOCALTIMESTAMP, args);
+ }
+
+ public FunctionExpression localtime(Expression... args) {
+ return compile(BuiltinFunctionName.LOCALTIME, args);
+ }
+
+ public FunctionExpression sysdate(Expression... args) {
+ return compile(BuiltinFunctionName.SYSDATE, args);
+ }
+
+ public FunctionExpression curtime(Expression... args) {
+ return compile(BuiltinFunctionName.CURTIME, args);
+ }
+
+ public FunctionExpression current_time(Expression... args) {
+ return compile(BuiltinFunctionName.CURRENT_TIME, args);
+ }
+
+ public FunctionExpression curdate(Expression... args) {
+ return compile(BuiltinFunctionName.CURDATE, args);
+ }
+
+ public FunctionExpression current_date(Expression... args) {
+ return compile(BuiltinFunctionName.CURRENT_DATE, args);
+ }
+
private FunctionExpression compile(BuiltinFunctionName bfn, Expression... args) {
return (FunctionExpression) repository.compile(bfn.getName(), Arrays.asList(args.clone()));
}
diff --git a/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java b/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java
index c4de0e13ad9..46061e7ec52 100644
--- a/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java
+++ b/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java
@@ -18,11 +18,17 @@
import static org.opensearch.sql.expression.function.FunctionDSL.impl;
import static org.opensearch.sql.expression.function.FunctionDSL.nullMissingHandling;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.format.TextStyle;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
import lombok.experimental.UtilityClass;
+import org.opensearch.sql.common.utils.QueryContext;
import org.opensearch.sql.data.model.ExprDateValue;
import org.opensearch.sql.data.model.ExprDatetimeValue;
import org.opensearch.sql.data.model.ExprIntegerValue;
@@ -78,6 +84,89 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(to_days());
repository.register(week());
repository.register(year());
+
+ repository.register(now());
+ repository.register(current_timestamp());
+ repository.register(localtimestamp());
+ repository.register(localtime());
+ repository.register(sysdate());
+ repository.register(curtime());
+ repository.register(current_time());
+ repository.register(curdate());
+ repository.register(current_date());
+ }
+
+ /**
+ * NOW() returns a constant time that indicates the time at which the statement began to execute.
+ */
+ private LocalDateTime now(@Nullable Integer fsp) {
+ return formatLocalDateTime(QueryContext::getProcessingStartedTime, fsp);
+ }
+
+ private FunctionResolver now(FunctionName functionName) {
+ return define(functionName,
+ impl(() -> new ExprDatetimeValue(now((Integer)null)), DATETIME),
+ impl((v) -> new ExprDatetimeValue(now(v.integerValue())), DATETIME, INTEGER)
+ );
+ }
+
+ private FunctionResolver now() {
+ return now(BuiltinFunctionName.NOW.getName());
+ }
+
+ private FunctionResolver current_timestamp() {
+ return now(BuiltinFunctionName.CURRENT_TIMESTAMP.getName());
+ }
+
+ private FunctionResolver localtimestamp() {
+ return now(BuiltinFunctionName.LOCALTIMESTAMP.getName());
+ }
+
+ private FunctionResolver localtime() {
+ return now(BuiltinFunctionName.LOCALTIME.getName());
+ }
+
+ /**
+ * SYSDATE() returns the time at which it executes.
+ */
+ private LocalDateTime sysDate(@Nullable Integer fsp) {
+ return formatLocalDateTime(LocalDateTime::now, fsp);
+ }
+
+ private FunctionResolver sysdate() {
+ return define(BuiltinFunctionName.SYSDATE.getName(),
+ impl(() -> new ExprDatetimeValue(sysDate(null)), DATETIME),
+ impl((v) -> new ExprDatetimeValue(sysDate(v.integerValue())), DATETIME, INTEGER)
+ );
+ }
+
+ private FunctionResolver curtime(FunctionName functionName) {
+ return define(functionName,
+ impl(() -> new ExprTimeValue(sysDate(null).toLocalTime()), TIME),
+ impl((v) -> new ExprTimeValue(sysDate(v.integerValue()).toLocalTime()), TIME, INTEGER)
+ );
+ }
+
+ private FunctionResolver curtime() {
+ return curtime(BuiltinFunctionName.CURTIME.getName());
+ }
+
+ private FunctionResolver current_time() {
+ return curtime(BuiltinFunctionName.CURRENT_TIME.getName());
+ }
+
+ private FunctionResolver curdate(FunctionName functionName) {
+ return define(functionName,
+ impl(() -> new ExprDateValue(sysDate(null).toLocalDate()), DATE)
+ );
+ }
+
+ private FunctionResolver curdate() {
+ return curdate(BuiltinFunctionName.CURDATE.getName());
+ }
+
+ private FunctionResolver current_date() {
+ return curdate(BuiltinFunctionName.CURRENT_DATE.getName());
}
/**
@@ -108,6 +197,10 @@ private FunctionResolver adddate() {
return add_date(BuiltinFunctionName.ADDDATE.getName());
}
+ private FunctionResolver date_add() {
+ return add_date(BuiltinFunctionName.DATE_ADD.getName());
+ }
+
/**
* Extracts the date part of a date and time value.
* Also to construct a date type. The supported signatures:
@@ -121,10 +214,6 @@ private FunctionResolver date() {
impl(nullMissingHandling(DateTimeFunction::exprDate), DATE, TIMESTAMP));
}
- private FunctionResolver date_add() {
- return add_date(BuiltinFunctionName.DATE_ADD.getName());
- }
-
/**
* Specify a start date and subtract a temporal amount to the date.
* The return type depends on the date type and the interval unit. Detailed supported signatures:
@@ -679,4 +768,26 @@ private ExprValue exprYear(ExprValue date) {
return new ExprIntegerValue(date.dateValue().getYear());
}
+ /**
+ * Prepare LocalDateTime value.
+ * @param supplier A function which returns LocalDateTime to format.
+ * @param fsp argument is given to specify a fractional seconds precision from 0 to 6,
+ * the return value includes a fractional seconds part of that many digits.
+ * @return LocalDateTime object.
+ */
+ private LocalDateTime formatLocalDateTime(Supplier supplier,
+ @Nullable Integer fsp) {
+ var res = supplier.get();
+ if (fsp == null) {
+ return res;
+ }
+ var defaultPrecision = 9; // There are 10^9 nanoseconds in one second
+ if (fsp < 0 || fsp > 6) { // Check that the argument is in the allowed range [0, 6]
+ throw new IllegalArgumentException(
+ String.format("Invalid `fsp` value: %d, allowed 0 to 6", fsp));
+ }
+ var nano = new BigDecimal(res.getNano())
+ .setScale(fsp - defaultPrecision, RoundingMode.DOWN).intValue();
+ return res.withNano(nano);
+ }
}
diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java
index cd343284530..e4c3a8a9a0f 100644
--- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java
+++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java
@@ -82,7 +82,16 @@ public enum BuiltinFunctionName {
TO_DAYS(FunctionName.of("to_days")),
WEEK(FunctionName.of("week")),
YEAR(FunctionName.of("year")),
-
+ // `now`-like functions
+ NOW(FunctionName.of("now")),
+ CURDATE(FunctionName.of("curdate")),
+ CURRENT_DATE(FunctionName.of("current_date")),
+ CURTIME(FunctionName.of("curtime")),
+ CURRENT_TIME(FunctionName.of("current_time")),
+ LOCALTIME(FunctionName.of("localtime")),
+ CURRENT_TIMESTAMP(FunctionName.of("current_timestamp")),
+ LOCALTIMESTAMP(FunctionName.of("localtimestamp")),
+ SYSDATE(FunctionName.of("sysdate")),
/**
* Text Functions.
*/
diff --git a/core/src/main/java/org/opensearch/sql/planner/logical/LogicalPlanDSL.java b/core/src/main/java/org/opensearch/sql/planner/logical/LogicalPlanDSL.java
index 4185d55c559..cdd3d3a103b 100644
--- a/core/src/main/java/org/opensearch/sql/planner/logical/LogicalPlanDSL.java
+++ b/core/src/main/java/org/opensearch/sql/planner/logical/LogicalPlanDSL.java
@@ -18,7 +18,6 @@
import org.opensearch.sql.expression.Expression;
import org.opensearch.sql.expression.LiteralExpression;
import org.opensearch.sql.expression.NamedExpression;
-import org.opensearch.sql.expression.ParseExpression;
import org.opensearch.sql.expression.ReferenceExpression;
import org.opensearch.sql.expression.aggregation.NamedAggregator;
import org.opensearch.sql.expression.window.WindowDefinition;
diff --git a/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java b/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java
index 72db4025522..e11b1484dd5 100644
--- a/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java
+++ b/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java
@@ -27,8 +27,13 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.opensearch.sql.analysis.symbol.Namespace;
import org.opensearch.sql.analysis.symbol.Symbol;
import org.opensearch.sql.ast.dsl.AstDSL;
@@ -45,6 +50,7 @@
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.expression.DSL;
import org.opensearch.sql.expression.Expression;
+import org.opensearch.sql.expression.FunctionExpression;
import org.opensearch.sql.expression.HighlightExpression;
import org.opensearch.sql.expression.config.ExpressionConfig;
import org.opensearch.sql.expression.window.aggregation.AggregateWindowFunction;
@@ -537,6 +543,49 @@ public void match_phrase_prefix_all_params() {
);
}
+ private static Stream functionNames() {
+ var dsl = new DSL(new ExpressionConfig().functionRepository());
+ return Stream.of(
+ Arguments.of((Function)dsl::now,
+ "now", true),
+ Arguments.of((Function)dsl::current_timestamp,
+ "current_timestamp", true),
+ Arguments.of((Function)dsl::localtimestamp,
+ "localtimestamp", true),
+ Arguments.of((Function)dsl::localtime,
+ "localtime", true),
+ Arguments.of((Function)dsl::sysdate,
+ "sysdate", true),
+ Arguments.of((Function)dsl::curtime,
+ "curtime", true),
+ Arguments.of((Function)dsl::current_time,
+ "current_time", true),
+ Arguments.of((Function)dsl::curdate,
+ "curdate", false),
+ Arguments.of((Function)dsl::current_date,
+ "current_date", false));
+ }
+
+ @ParameterizedTest(name = "{1}")
+ @MethodSource("functionNames")
+ public void now_like_functions(Function function,
+ String name,
+ Boolean hasFsp) {
+ assertAnalyzeEqual(
+ function.apply(new Expression[]{}),
+ AstDSL.function(name));
+
+ if (hasFsp) {
+ assertAnalyzeEqual(
+ function.apply(new Expression[]{DSL.ref("integer_value", INTEGER)}),
+ AstDSL.function(name, field("integer_value")));
+
+ assertAnalyzeEqual(
+ function.apply(new Expression[]{DSL.literal(3)}),
+ AstDSL.function(name, intLiteral(3)));
+ }
+ }
+
@Test
void highlight() {
assertAnalyzeEqual(new HighlightExpression(DSL.literal("fieldA")),
diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/NowLikeFunctionTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/NowLikeFunctionTest.java
new file mode 100644
index 00000000000..76a7e4be464
--- /dev/null
+++ b/core/src/test/java/org/opensearch/sql/expression/datetime/NowLikeFunctionTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+
+package org.opensearch.sql.expression.datetime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opensearch.sql.data.type.ExprCoreType.DATE;
+import static org.opensearch.sql.data.type.ExprCoreType.DATETIME;
+import static org.opensearch.sql.data.type.ExprCoreType.TIME;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.temporal.Temporal;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.opensearch.sql.data.type.ExprCoreType;
+import org.opensearch.sql.expression.DSL;
+import org.opensearch.sql.expression.Expression;
+import org.opensearch.sql.expression.ExpressionTestBase;
+import org.opensearch.sql.expression.FunctionExpression;
+import org.opensearch.sql.expression.config.ExpressionConfig;
+
+
+public class NowLikeFunctionTest extends ExpressionTestBase {
+ private static Stream functionNames() {
+ var dsl = new DSL(new ExpressionConfig().functionRepository());
+ return Stream.of(
+ Arguments.of((Function)dsl::now,
+ "now", DATETIME, true, (Supplier)LocalDateTime::now),
+ Arguments.of((Function)dsl::current_timestamp,
+ "current_timestamp", DATETIME, true, (Supplier)LocalDateTime::now),
+ Arguments.of((Function)dsl::localtimestamp,
+ "localtimestamp", DATETIME, true, (Supplier)LocalDateTime::now),
+ Arguments.of((Function)dsl::localtime,
+ "localtime", DATETIME, true, (Supplier)LocalDateTime::now),
+ Arguments.of((Function)dsl::sysdate,
+ "sysdate", DATETIME, true, (Supplier)LocalDateTime::now),
+ Arguments.of((Function)dsl::curtime,
+ "curtime", TIME, true, (Supplier)LocalTime::now),
+ Arguments.of((Function)dsl::current_time,
+ "current_time", TIME, true, (Supplier)LocalTime::now),
+ Arguments.of((Function)dsl::curdate,
+ "curdate", DATE, false, (Supplier)LocalDate::now),
+ Arguments.of((Function)dsl::current_date,
+ "current_date", DATE, false, (Supplier)LocalDate::now));
+ }
+
+ private Temporal extractValue(FunctionExpression func) {
+ switch ((ExprCoreType)func.type()) {
+ case DATE: return func.valueOf(null).dateValue();
+ case DATETIME: return func.valueOf(null).datetimeValue();
+ case TIME: return func.valueOf(null).timeValue();
+ // unreachable code
+ default: throw new IllegalArgumentException(String.format("%s", func.type()));
+ }
+ }
+
+ private long getDiff(Temporal sample, Temporal reference) {
+ if (sample instanceof LocalDate) {
+ return Period.between((LocalDate) sample, (LocalDate) reference).getDays();
+ }
+ return Duration.between(sample, reference).toSeconds();
+ }
+
+ /**
+ * Check how NOW-like functions are processed.
+ * @param function Function
+ * @param name Function name
+ * @param resType Return type
+ * @param hasFsp Whether function has fsp argument
+ * @param referenceGetter A callback to get reference value
+ */
+ @ParameterizedTest(name = "{1}")
+ @MethodSource("functionNames")
+ public void test_now_like_functions(Function function,
+ @SuppressWarnings("unused") // Used in the test name above
+ String name,
+ ExprCoreType resType,
+ Boolean hasFsp,
+ Supplier referenceGetter) {
+ // Check return types:
+ // `func()`
+ FunctionExpression expr = function.apply(new Expression[]{});
+ assertEquals(resType, expr.type());
+ if (hasFsp) {
+ // `func(fsp = 0)`
+ expr = function.apply(new Expression[]{DSL.literal(0)});
+ assertEquals(resType, expr.type());
+ // `func(fsp = 6)`
+ expr = function.apply(new Expression[]{DSL.literal(6)});
+ assertEquals(resType, expr.type());
+
+ for (var wrongFspValue: List.of(-1, 10)) {
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> function.apply(new Expression[]{DSL.literal(wrongFspValue)}).valueOf(null));
+ assertEquals(String.format("Invalid `fsp` value: %d, allowed 0 to 6", wrongFspValue),
+ exception.getMessage());
+ }
+ }
+
+ // Check how calculations are precise:
+ // `func()`
+ assertTrue(Math.abs(getDiff(
+ extractValue(function.apply(new Expression[]{})),
+ referenceGetter.get()
+ )) <= 1);
+ if (hasFsp) {
+ // `func(fsp)`
+ assertTrue(Math.abs(getDiff(
+ extractValue(function.apply(new Expression[]{DSL.literal(0)})),
+ referenceGetter.get()
+ )) <= 1);
+ }
+ }
+}
diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst
index f1d4d987a37..e4a44213cca 100644
--- a/docs/user/dql/functions.rst
+++ b/docs/user/dql/functions.rst
@@ -1328,9 +1328,210 @@ NOW
Description
>>>>>>>>>>>
+Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss.nnnnnn' format. The value is expressed in the cluster time zone.
+`NOW()` returns a constant time that indicates the time at which the statement began to execute. This differs from the behavior for `SYSDATE() <#sysdate>`_, which returns the exact time at which it executes.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Argument type: (optional) INTEGER
+
+Return type: DATETIME
+
+Specifications:
+
+1. NOW() -> DATETIME
+2. NOW(INTEGER) -> DATETIME
+
+Example::
+
+ > SELECT NOW(), NOW(0);
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+
+ | NOW() | NOW(0) |
+ |----------------------------+---------------------|
+ | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 |
+ +----------------------------+---------------------+
+
+
+CURRENT_TIMESTAMP
+-----------------
+
+Description
+>>>>>>>>>>>
+
+`CURRENT_TIMESTAMP` and `CURRENT_TIMESTAMP()` are synonyms for `NOW() <#now>`_.
+
+Example::
+
+ > SELECT CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(0), CURRENT_TIMESTAMP;
+ fetched rows / total rows = 1/1
+ +----------------------------+------------------------+----------------------------+
+ | CURRENT_TIMESTAMP() | CURRENT_TIMESTAMP(0) | CURRENT_TIMESTAMP |
+ |----------------------------+------------------------+----------------------------|
+ | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19 | 2022-08-02 15:54:19.209361 |
+ +----------------------------+------------------------+----------------------------+
+
+
+LOCALTIMESTAMP
+--------------
+
+Description
+>>>>>>>>>>>
+
+`LOCALTIMESTAMP` and `LOCALTIMESTAMP()` are synonyms for `NOW() <#now>`_.
+
+Example::
+
+ > SELECT LOCALTIMESTAMP(), LOCALTIMESTAMP(0), LOCALTIMESTAMP;
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+----------------------------+
+ | LOCALTIMESTAMP() | LOCALTIMESTAMP(0) | LOCALTIMESTAMP |
+ |----------------------------+---------------------+----------------------------|
+ | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19 | 2022-08-02 15:54:19.209361 |
+ +----------------------------+---------------------+----------------------------+
+
+
+LOCALTIME
+---------
+
+Description
+>>>>>>>>>>>
+
+`LOCALTIME` and `LOCALTIME()` are synonyms for `NOW() <#now>`_.
+
+Example::
+
+ > SELECT LOCALTIME(), LOCALTIME(0), LOCALTIME;
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+----------------------------+
+ | LOCALTIME() | LOCALTIME(0) | LOCALTIME |
+ |----------------------------+---------------------+----------------------------|
+ | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19 | 2022-08-02 15:54:19.209361 |
+ +----------------------------+---------------------+----------------------------+
+
+
+SYSDATE
+-------
+
+Description
+>>>>>>>>>>>
+
+Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss.nnnnnn'.
+SYSDATE() returns the time at which it executes. This differs from the behavior for `NOW() <#now>`_, which returns a constant time that indicates the time at which the statement began to execute.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Argument type: (optional) INTEGER
+
+Return type: DATETIME
+
+Specifications:
+
+1. SYSDATE() -> DATETIME
+2. SYSDATE(INTEGER) -> DATETIME
+
+Example::
+
+ > SELECT SYSDATE(), SYSDATE(0);
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+
+ | SYSDATE() | SYSDATE(0) |
+ |----------------------------+---------------------|
+ | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 |
+ +----------------------------+---------------------+
+
+
+CURTIME
+-------
+
+Description
+>>>>>>>>>>>
+
+Returns the current time as a value in 'hh:mm:ss.nnnnnn'.
+CURTIME() returns the time at which it executes as `SYSDATE() <#sysdate>`_ does.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Argument type: (optional) INTEGER
+
+Return type: TIME
+
Specifications:
-1. NOW() -> DATE
+1. CURTIME() -> TIME
+2. CURTIME(INTEGER) -> TIME
+
+Example::
+
+ > SELECT CURTIME(), CURTIME(0);
+ fetched rows / total rows = 1/1
+ +-----------------+--------------+
+ | CURTIME() | CURTIME(0) |
+ |-----------------+--------------|
+ | 15:39:05.173069 | 15:39:05 |
+ +-----------------+--------------+
+
+
+CURRENT_TIME
+------------
+
+Description
+>>>>>>>>>>>
+
+`CURRENT_TIME` and `CURRENT_TIME()` are synonyms for `CURTIME() <#curtime>`_.
+
+Example::
+
+ > SELECT CURRENT_TIME(), CURRENT_TIME(0), CURRENT_TIME;
+ fetched rows / total rows = 1/1
+ +------------------+-------------------+-----------------+
+ | CURRENT_TIME() | CURRENT_TIME(0) | CURRENT_TIME |
+ |------------------+-------------------+-----------------|
+ | 15:39:05.173069 | 15:39:05 | 15:39:05.173069 |
+ +------------------+-------------------+-----------------+
+
+
+CURDATE
+-------
+
+Description
+>>>>>>>>>>>
+
+Returns the current time as a value in 'YYYY-MM-DD'.
+CURDATE() returns the time at which it executes as `SYSDATE() <#sysdate>`_ does.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Return type: DATE
+
+Specifications:
+
+CURDATE() -> DATE
+
+Example::
+
+ > SELECT CURDATE();
+ fetched rows / total rows = 1/1
+ +-------------+
+ | CURDATE() |
+ |-------------|
+ | 2022-08-02 |
+ +-------------+
+
+
+CURRENT_DATE
+------------
+
+Description
+>>>>>>>>>>>
+
+`CURRENT_DATE` and `CURRENT_DATE()` are synonyms for `CURDATE() <#curdate>`_.
+
+Example::
+
+ > SELECT CURRENT_DATE(), CURRENT_DATE;
+ fetched rows / total rows = 1/1
+ +------------------+----------------+
+ | CURRENT_DATE() | CURRENT_DATE |
+ |------------------+----------------|
+ | 2022-08-02 | 2022-08-02 |
+ +------------------+----------------+
QUARTER
diff --git a/docs/user/ppl/functions/datetime.rst b/docs/user/ppl/functions/datetime.rst
index 5be5686c34d..5a653c3e64b 100644
--- a/docs/user/ppl/functions/datetime.rst
+++ b/docs/user/ppl/functions/datetime.rst
@@ -497,9 +497,210 @@ NOW
Description
>>>>>>>>>>>
+Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss.nnnnnn' format. The value is expressed in the cluster time zone.
+`NOW()` returns a constant time that indicates the time at which the statement began to execute. This differs from the behavior for `SYSDATE() <#sysdate>`_, which returns the exact time at which it executes.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Argument type: (optional) INTEGER
+
+Return type: DATETIME
+
+Specifications:
+
+1. NOW() -> DATETIME
+2. NOW(INTEGER) -> DATETIME
+
+Example::
+
+ > source=people | eval `NOW()` = NOW(), `NOW(0)` = NOW(0) | fields `NOW()`, `NOW(0)`
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+
+ | NOW() | NOW(0) |
+ |----------------------------+---------------------|
+ | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 |
+ +----------------------------+---------------------+
+
+
+CURRENT_TIMESTAMP
+-----------------
+
+Description
+>>>>>>>>>>>
+
+`CURRENT_TIMESTAMP` and `CURRENT_TIMESTAMP()` are synonyms for `NOW() <#now>`_.
+
+Example::
+
+ > source=people | eval `CURRENT_TIMESTAMP()` = CURRENT_TIMESTAMP(), `CURRENT_TIMESTAMP(0)` = CURRENT_TIMESTAMP(0), `CURRENT_TIMESTAMP` = CURRENT_TIMESTAMP | fields `CURRENT_TIMESTAMP()`, `CURRENT_TIMESTAMP(0)`, `CURRENT_TIMESTAMP`
+ fetched rows / total rows = 1/1
+ +----------------------------+------------------------+----------------------------+
+ | CURRENT_TIMESTAMP() | CURRENT_TIMESTAMP(0) | CURRENT_TIMESTAMP |
+ |----------------------------+------------------------+----------------------------|
+ | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19 | 2022-08-02 15:54:19.209361 |
+ +----------------------------+------------------------+----------------------------+
+
+
+LOCALTIMESTAMP
+--------------
+
+Description
+>>>>>>>>>>>
+
+`LOCALTIMESTAMP` and `LOCALTIMESTAMP()` are synonyms for `NOW() <#now>`_.
+
+Example::
+
+ > source=people | eval `LOCALTIMESTAMP()` = LOCALTIMESTAMP(), `LOCALTIMESTAMP(0)` = LOCALTIMESTAMP(0), `LOCALTIMESTAMP` = LOCALTIMESTAMP | fields `LOCALTIMESTAMP()`, `LOCALTIMESTAMP(0)`, `LOCALTIMESTAMP`
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+----------------------------+
+ | LOCALTIMESTAMP() | LOCALTIMESTAMP(0) | LOCALTIMESTAMP |
+ |----------------------------+---------------------+----------------------------|
+ | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19 | 2022-08-02 15:54:19.209361 |
+ +----------------------------+---------------------+----------------------------+
+
+
+LOCALTIME
+---------
+
+Description
+>>>>>>>>>>>
+
+`LOCALTIME` and `LOCALTIME()` are synonyms for `NOW() <#now>`_.
+
+Example::
+
+ > source=people | eval `LOCALTIME()` = LOCALTIME(), `LOCALTIME(0)` = LOCALTIME(0), `LOCALTIME` = LOCALTIME | fields `LOCALTIME()`, `LOCALTIME(0)`, `LOCALTIME`
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+----------------------------+
+ | LOCALTIME() | LOCALTIME(0) | LOCALTIME |
+ |----------------------------+---------------------+----------------------------|
+ | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19 | 2022-08-02 15:54:19.209361 |
+ +----------------------------+---------------------+----------------------------+
+
+
+SYSDATE
+-------
+
+Description
+>>>>>>>>>>>
+
+Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss.nnnnnn'.
+SYSDATE() returns the time at which it executes. This differs from the behavior for `NOW() <#now>`_, which returns a constant time that indicates the time at which the statement began to execute.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Argument type: (optional) INTEGER
+
+Return type: DATETIME
+
Specifications:
-1. NOW() -> DATE
+1. SYSDATE() -> DATETIME
+2. SYSDATE(INTEGER) -> DATETIME
+
+Example::
+
+ > source=people | eval `SYSDATE()` = SYSDATE(), `SYSDATE(0)` = SYSDATE(0) | fields `SYSDATE()`, `SYSDATE(0)`
+ fetched rows / total rows = 1/1
+ +----------------------------+---------------------+
+ | SYSDATE() | SYSDATE(0) |
+ |----------------------------+---------------------|
+ | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 |
+ +----------------------------+---------------------+
+
+
+CURTIME
+-------
+
+Description
+>>>>>>>>>>>
+
+Returns the current time as a value in 'hh:mm:ss.nnnnnn'.
+CURTIME() returns the time at which it executes as `SYSDATE() <#sysdate>`_ does.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Argument type: (optional) INTEGER
+
+Return type: TIME
+
+Specifications:
+
+1. CURTIME() -> TIME
+2. CURTIME(INTEGER) -> TIME
+
+Example::
+
+ > source=people | eval `CURTIME()` = CURTIME(), `CURTIME(0)` = CURTIME(0) | fields `CURTIME()`, `CURTIME(0)`
+ fetched rows / total rows = 1/1
+ +-----------------+--------------+
+ | CURTIME() | CURTIME(0) |
+ |-----------------+--------------|
+ | 15:39:05.173069 | 15:39:05 |
+ +-----------------+--------------+
+
+
+CURRENT_TIME
+------------
+
+Description
+>>>>>>>>>>>
+
+`CURRENT_TIME` and `CURRENT_TIME()` are synonyms for `CURTIME() <#curtime>`_.
+
+Example::
+
+ > source=people | eval `CURRENT_TIME()` = CURRENT_TIME(), `CURRENT_TIME(0)` = CURRENT_TIME(0), `CURRENT_TIME` = CURRENT_TIME | fields `CURRENT_TIME()`, `CURRENT_TIME(0)`, `CURRENT_TIME`
+ fetched rows / total rows = 1/1
+ +------------------+-------------------+-----------------+
+ | CURRENT_TIME() | CURRENT_TIME(0) | CURRENT_TIME |
+ |------------------+-------------------+-----------------|
+ | 15:39:05.173069 | 15:39:05 | 15:39:05.173069 |
+ +------------------+-------------------+-----------------+
+
+
+CURDATE
+-------
+
+Description
+>>>>>>>>>>>
+
+Returns the current time as a value in 'YYYY-MM-DD'.
+CURDATE() returns the time at which it executes as `SYSDATE() <#sysdate>`_ does.
+If the argument is given, it specifies a fractional seconds precision from 0 to 6, the return value includes a fractional seconds part of that many digits.
+
+Return type: DATE
+
+Specifications:
+
+CURDATE() -> DATE
+
+Example::
+
+ > source=people | eval `CURDATE()` = CURDATE() | fields `CURDATE()`
+ fetched rows / total rows = 1/1
+ +-------------+
+ | CURDATE() |
+ |-------------|
+ | 2022-08-02 |
+ +-------------+
+
+
+CURRENT_DATE
+------------
+
+Description
+>>>>>>>>>>>
+
+`CURRENT_DATE` and `CURRENT_DATE()` are synonyms for `CURDATE() <#curdate>`_.
+
+Example::
+
+ > source=people | eval `CURRENT_DATE()` = CURRENT_DATE(), `CURRENT_DATE` = CURRENT_DATE | fields `CURRENT_DATE()`, `CURRENT_DATE`
+ fetched rows / total rows = 1/1
+ +------------------+----------------+
+ | CURRENT_DATE() | CURRENT_DATE |
+ |------------------+----------------|
+ | 2022-08-02 | 2022-08-02 |
+ +------------------+----------------+
QUARTER
diff --git a/integ-test/build.gradle b/integ-test/build.gradle
index 864b4df0971..429c360a1ba 100644
--- a/integ-test/build.gradle
+++ b/integ-test/build.gradle
@@ -53,9 +53,9 @@ configurations.all {
// enforce 1.1.3, https://www.whitesourcesoftware.com/vulnerability-database/WS-2019-0379
resolutionStrategy.force 'commons-codec:commons-codec:1.13'
resolutionStrategy.force 'com.google.guava:guava:31.0.1-jre'
- resolutionStrategy.force 'com.fasterxml.jackson.core:jackson-core:2.13.3'
- resolutionStrategy.force 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.13.3'
- resolutionStrategy.force 'com.fasterxml.jackson.core:jackson-databind:2.13.3'
+ resolutionStrategy.force "com.fasterxml.jackson.core:jackson-core:${jackson_version}"
+ resolutionStrategy.force "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${jackson_version}"
+ resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
}
dependencies {
diff --git a/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java b/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java
index 15f0261f0d0..5c339cc7bb4 100644
--- a/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java
+++ b/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java
@@ -31,6 +31,7 @@
import java.nio.file.Paths;
import java.util.Locale;
+import static com.google.common.base.Strings.isNullOrEmpty;
import static org.opensearch.sql.legacy.TestUtils.createIndexByRestClient;
import static org.opensearch.sql.legacy.TestUtils.getAccountIndexMapping;
import static org.opensearch.sql.legacy.TestUtils.getBankIndexMapping;
@@ -71,6 +72,8 @@ public abstract class SQLIntegTestCase extends OpenSearchSQLRestTestCase {
public static final String TRANSIENT = "transient";
public static final Integer DEFAULT_QUERY_SIZE_LIMIT =
Integer.parseInt(System.getProperty("defaultQuerySizeLimit", "200"));
+ public static final Integer DEFAULT_MAX_RESULT_WINDOW =
+ Integer.parseInt(System.getProperty("defaultMaxResultWindow", "10000"));
public boolean shouldResetQuerySizeLimit() {
return true;
@@ -161,6 +164,15 @@ protected static void wipeAllClusterSettings() throws IOException {
updateClusterSettings(new ClusterSetting("transient", "*", null));
}
+ protected void setMaxResultWindow(String indexName, Integer window) throws IOException {
+ updateIndexSettings(indexName, "{ \"index\": { \"max_result_window\":" + window + " } }");
+ }
+
+ protected void resetMaxResultWindow(String indexName) throws IOException {
+ updateIndexSettings(indexName,
+ "{ \"index\": { \"max_result_window\": " + DEFAULT_MAX_RESULT_WINDOW + " } }");
+ }
+
/**
* Provide for each test to load test index, data and other setup work
*/
@@ -378,6 +390,18 @@ public String toString() {
}
}
+ protected static JSONObject updateIndexSettings(String indexName, String setting)
+ throws IOException {
+ Request request = new Request("PUT", "/" + indexName + "/_settings");
+ if (!isNullOrEmpty(setting)) {
+ request.setJsonEntity(setting);
+ }
+ RequestOptions.Builder restOptionsBuilder = RequestOptions.DEFAULT.toBuilder();
+ restOptionsBuilder.addHeader("Content-Type", "application/json");
+ request.setOptions(restOptionsBuilder);
+ return new JSONObject(executeRequest(request));
+ }
+
protected String makeRequest(String query) {
return makeRequest(query, 0);
}
diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java
index fcbfc27710c..2db47d2d20c 100644
--- a/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java
+++ b/integ-test/src/test/java/org/opensearch/sql/ppl/DateTimeFunctionIT.java
@@ -7,12 +7,31 @@
package org.opensearch.sql.ppl;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DATE;
+import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_PEOPLE2;
import static org.opensearch.sql.util.MatcherUtils.rows;
import static org.opensearch.sql.util.MatcherUtils.schema;
import static org.opensearch.sql.util.MatcherUtils.verifySchema;
import static org.opensearch.sql.util.MatcherUtils.verifySome;
import java.io.IOException;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableMap;
+import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.opensearch.sql.common.utils.StringUtils;
@@ -22,6 +41,7 @@ public class DateTimeFunctionIT extends PPLIntegTestCase {
@Override
public void init() throws IOException {
loadIndex(Index.DATE);
+ loadIndex(Index.PEOPLE2);
}
@Test
@@ -463,4 +483,167 @@ public void testDateFormatISO8601() throws IOException {
verifyDateFormat(date, "date", dateFormat, dateFormatted);
}
+ private List> nowLikeFunctionsData() {
+ return List.of(
+ ImmutableMap.builder()
+ .put("name", "now")
+ .put("hasFsp", true)
+ .put("hasShortcut", false)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "current_timestamp")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "localtimestamp")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "localtime")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "sysdate")
+ .put("hasFsp", true)
+ .put("hasShortcut", false)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "curtime")
+ .put("hasFsp", true)
+ .put("hasShortcut", false)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalTime::now)
+ .put("parser", (BiFunction) LocalTime::parse)
+ .put("serializationPattern", "HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "current_time")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalTime::now)
+ .put("parser", (BiFunction) LocalTime::parse)
+ .put("serializationPattern", "HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "curdate")
+ .put("hasFsp", false)
+ .put("hasShortcut", false)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalDate::now)
+ .put("parser", (BiFunction) LocalDate::parse)
+ .put("serializationPattern", "uuuu-MM-dd")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "current_date")
+ .put("hasFsp", false)
+ .put("hasShortcut", true)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalDate::now)
+ .put("parser", (BiFunction) LocalDate::parse)
+ .put("serializationPattern", "uuuu-MM-dd")
+ .build()
+ );
+ }
+
+ private long getDiff(Temporal sample, Temporal reference) {
+ if (sample instanceof LocalDate) {
+ return Period.between((LocalDate) sample, (LocalDate) reference).getDays();
+ }
+ return Duration.between(sample, reference).toSeconds();
+ }
+
+ @Test
+ public void testNowLikeFunctions() throws IOException {
+ // Integration test framework sets for OpenSearch instance a random timezone.
+ // If server's TZ doesn't match localhost's TZ, time measurements for `now` would differ.
+ // We should set localhost's TZ now and recover the value back in the end of the test.
+ var testTz = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone(System.getProperty("user.timezone")));
+
+ for (var funcData : nowLikeFunctionsData()) {
+ String name = (String) funcData.get("name");
+ Boolean hasFsp = (Boolean) funcData.get("hasFsp");
+ Boolean hasShortcut = (Boolean) funcData.get("hasShortcut");
+ Boolean constValue = (Boolean) funcData.get("constValue");
+ Supplier referenceGetter = (Supplier) funcData.get("referenceGetter");
+ BiFunction parser =
+ (BiFunction) funcData.get("parser");
+ String serializationPatternStr = (String) funcData.get("serializationPattern");
+
+ var serializationPattern = new DateTimeFormatterBuilder()
+ .appendPattern(serializationPatternStr)
+ .optionalStart()
+ .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+ .toFormatter();
+
+ Temporal reference = referenceGetter.get();
+ double delta = 2d; // acceptable time diff, secs
+ if (reference instanceof LocalDate)
+ delta = 1d; // Max date delta could be 1 if test runs on the very edge of two days
+ // We ignore probability of a test run on edge of month or year to simplify the checks
+
+ var calls = new ArrayList() {{
+ add(name + "()");
+ }};
+ if (hasShortcut)
+ calls.add(name);
+ if (hasFsp)
+ calls.add(name + "(0)");
+
+ // Column order is: func(), func, func(0)
+ // shortcut ^ fsp ^
+ // Query looks like:
+ // source=people2 | eval `now()`=now() | fields `now()`;
+ JSONObject result = executeQuery("source=" + TEST_INDEX_PEOPLE2
+ + " | eval " + calls.stream().map(c -> String.format("`%s`=%s", c, c)).collect(Collectors.joining(","))
+ + " | fields " + calls.stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")));
+
+ var rows = result.getJSONArray("datarows");
+ JSONArray firstRow = rows.getJSONArray(0);
+ for (int i = 0; i < rows.length(); i++) {
+ var row = rows.getJSONArray(i);
+ if (constValue)
+ assertTrue(firstRow.similar(row));
+
+ int column = 0;
+ assertEquals(0,
+ getDiff(reference, parser.apply(row.getString(column++), serializationPattern)), delta);
+
+ if (hasShortcut) {
+ assertEquals(0,
+ getDiff(reference, parser.apply(row.getString(column++), serializationPattern)), delta);
+ }
+ if (hasFsp) {
+ assertEquals(0,
+ getDiff(reference, parser.apply(row.getString(column), serializationPattern)), delta);
+ }
+ }
+ }
+
+ TimeZone.setDefault(testTz);
+ }
}
diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/HeadCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/HeadCommandIT.java
index 1ae45ab4694..48c489ce109 100644
--- a/integ-test/src/test/java/org/opensearch/sql/ppl/HeadCommandIT.java
+++ b/integ-test/src/test/java/org/opensearch/sql/ppl/HeadCommandIT.java
@@ -14,6 +14,7 @@
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.jupiter.api.Test;
public class HeadCommandIT extends PPLIntegTestCase {
@@ -26,6 +27,7 @@ public void beforeTest() throws IOException {
@After
public void afterTest() throws IOException {
resetQuerySizeLimit();
+ resetMaxResultWindow(TEST_INDEX_ACCOUNT);
}
@Override
@@ -60,6 +62,76 @@ public void testHeadWithNumber() throws IOException {
rows("Nanette", 28));
}
+ @Ignore("Fix https://github.com/opensearch-project/sql/issues/703#issuecomment-1211422130")
+ @Test
+ public void testHeadWithNumberLargerThanQuerySizeLimit() throws IOException {
+ setQuerySizeLimit(5);
+ JSONObject result =
+ executeQuery(String.format(
+ "source=%s | fields firstname, age | head 10", TEST_INDEX_ACCOUNT));
+ verifyDataRows(result,
+ rows("Amber", 32),
+ rows("Hattie", 36),
+ rows("Nanette", 28),
+ rows("Dale", 33),
+ rows("Elinor", 36),
+ rows("Virginia", 39),
+ rows("Dillard", 34),
+ rows("Mcgee", 39),
+ rows("Aurelia", 37),
+ rows("Fulton", 23));
+ }
+
+ @Test
+ public void testHeadWithNumberLargerThanMaxResultWindow() throws IOException {
+ setMaxResultWindow(TEST_INDEX_ACCOUNT, 10);
+ JSONObject result =
+ executeQuery(String.format(
+ "source=%s | fields firstname, age | head 15", TEST_INDEX_ACCOUNT));
+ verifyDataRows(result,
+ rows("Amber", 32),
+ rows("Hattie", 36),
+ rows("Nanette", 28),
+ rows("Dale", 33),
+ rows("Elinor", 36),
+ rows("Virginia", 39),
+ rows("Dillard", 34),
+ rows("Mcgee", 39),
+ rows("Aurelia", 37),
+ rows("Fulton", 23),
+ rows("Burton", 31),
+ rows("Josie", 32),
+ rows("Hughes", 30),
+ rows("Hall", 25),
+ rows("Deidre", 33));
+ }
+
+ @Ignore("Fix https://github.com/opensearch-project/sql/issues/703#issuecomment-1211422130")
+ @Test
+ public void testHeadWithLargeNumber() throws IOException {
+ setQuerySizeLimit(5);
+ setMaxResultWindow(TEST_INDEX_ACCOUNT, 10);
+ JSONObject result =
+ executeQuery(String.format(
+ "source=%s | fields firstname, age | head 15", TEST_INDEX_ACCOUNT));
+ verifyDataRows(result,
+ rows("Amber", 32),
+ rows("Hattie", 36),
+ rows("Nanette", 28),
+ rows("Dale", 33),
+ rows("Elinor", 36),
+ rows("Virginia", 39),
+ rows("Dillard", 34),
+ rows("Mcgee", 39),
+ rows("Aurelia", 37),
+ rows("Fulton", 23),
+ rows("Burton", 31),
+ rows("Josie", 32),
+ rows("Hughes", 30),
+ rows("Hall", 25),
+ rows("Deidre", 33));
+ }
+
@Test
public void testHeadWithNumberAndFrom() throws IOException {
JSONObject result =
diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java
index d19c3719b6d..91d02bca513 100644
--- a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java
+++ b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java
@@ -7,6 +7,7 @@
package org.opensearch.sql.sql;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK;
+import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_PEOPLE2;
import static org.opensearch.sql.legacy.plugin.RestSqlAction.QUERY_API_ENDPOINT;
import static org.opensearch.sql.util.MatcherUtils.rows;
import static org.opensearch.sql.util.MatcherUtils.schema;
@@ -15,7 +16,23 @@
import static org.opensearch.sql.util.TestUtils.getResponseBody;
import java.io.IOException;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
+import java.util.TimeZone;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import com.google.common.collect.ImmutableMap;
+import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.opensearch.client.Request;
@@ -30,6 +47,7 @@ public class DateTimeFunctionIT extends SQLIntegTestCase {
public void init() throws Exception {
super.init();
loadIndex(Index.BANK);
+ loadIndex(Index.PEOPLE2);
}
@Test
@@ -452,6 +470,166 @@ public void testDateFormat() throws IOException {
verifyDateFormat(date, "date", dateFormat, dateFormatted);
}
+ private List> nowLikeFunctionsData() {
+ return List.of(
+ ImmutableMap.builder()
+ .put("name", "now")
+ .put("hasFsp", true)
+ .put("hasShortcut", false)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "current_timestamp")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "localtimestamp")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "localtime")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", true)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "sysdate")
+ .put("hasFsp", true)
+ .put("hasShortcut", false)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalDateTime::now)
+ .put("parser", (BiFunction) LocalDateTime::parse)
+ .put("serializationPattern", "uuuu-MM-dd HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "curtime")
+ .put("hasFsp", true)
+ .put("hasShortcut", false)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalTime::now)
+ .put("parser", (BiFunction) LocalTime::parse)
+ .put("serializationPattern", "HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "current_time")
+ .put("hasFsp", true)
+ .put("hasShortcut", true)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalTime::now)
+ .put("parser", (BiFunction) LocalTime::parse)
+ .put("serializationPattern", "HH:mm:ss")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "curdate")
+ .put("hasFsp", false)
+ .put("hasShortcut", false)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalDate::now)
+ .put("parser", (BiFunction) LocalDate::parse)
+ .put("serializationPattern", "uuuu-MM-dd")
+ .build(),
+ ImmutableMap.builder()
+ .put("name", "current_date")
+ .put("hasFsp", false)
+ .put("hasShortcut", true)
+ .put("constValue", false)
+ .put("referenceGetter", (Supplier) LocalDate::now)
+ .put("parser", (BiFunction) LocalDate::parse)
+ .put("serializationPattern", "uuuu-MM-dd")
+ .build()
+ );
+ }
+
+ private long getDiff(Temporal sample, Temporal reference) {
+ if (sample instanceof LocalDate) {
+ return Period.between((LocalDate) sample, (LocalDate) reference).getDays();
+ }
+ return Duration.between(sample, reference).toSeconds();
+ }
+
+ @Test
+ public void testNowLikeFunctions() throws IOException {
+ // Integration test framework sets for OpenSearch instance a random timezone.
+ // If server's TZ doesn't match localhost's TZ, time measurements for `now` would differ.
+ // We should set localhost's TZ now and recover the value back in the end of the test.
+ var testTz = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone(System.getProperty("user.timezone")));
+
+ for (var funcData : nowLikeFunctionsData()) {
+ String name = (String) funcData.get("name");
+ Boolean hasFsp = (Boolean) funcData.get("hasFsp");
+ Boolean hasShortcut = (Boolean) funcData.get("hasShortcut");
+ Boolean constValue = (Boolean) funcData.get("constValue");
+ Supplier referenceGetter = (Supplier) funcData.get("referenceGetter");
+ BiFunction parser =
+ (BiFunction) funcData.get("parser");
+ String serializationPatternStr = (String) funcData.get("serializationPattern");
+
+ var serializationPattern = new DateTimeFormatterBuilder()
+ .appendPattern(serializationPatternStr)
+ .optionalStart()
+ .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+ .toFormatter();
+
+ Temporal reference = referenceGetter.get();
+ double delta = 2d; // acceptable time diff, secs
+ if (reference instanceof LocalDate)
+ delta = 1d; // Max date delta could be 1 if test runs on the very edge of two days
+ // We ignore probability of a test run on edge of month or year to simplify the checks
+
+ var calls = new ArrayList() {{
+ add(name + "()");
+ }};
+ if (hasShortcut)
+ calls.add(name);
+ if (hasFsp)
+ calls.add(name + "(0)");
+
+ // Column order is: func(), func, func(0)
+ // shortcut ^ fsp ^
+ JSONObject result = executeQuery("select " + String.join(", ", calls) + " from " + TEST_INDEX_PEOPLE2);
+
+ var rows = result.getJSONArray("datarows");
+ JSONArray firstRow = rows.getJSONArray(0);
+ for (int i = 0; i < rows.length(); i++) {
+ var row = rows.getJSONArray(i);
+ if (constValue)
+ assertTrue(firstRow.similar(row));
+
+ int column = 0;
+ assertEquals(0,
+ getDiff(reference, parser.apply(row.getString(column++), serializationPattern)), delta);
+
+ if (hasShortcut) {
+ assertEquals(0,
+ getDiff(reference, parser.apply(row.getString(column++), serializationPattern)), delta);
+ }
+ if (hasFsp) {
+ assertEquals(0,
+ getDiff(reference, parser.apply(row.getString(column), serializationPattern)), delta);
+ }
+ }
+ }
+
+ TimeZone.setDefault(testTz);
+ }
+
protected JSONObject executeQuery(String query) throws IOException {
Request request = new Request("POST", QUERY_API_ENDPOINT);
request.setJsonEntity(String.format(Locale.ROOT, "{\n" + " \"query\": \"%s\"\n" + "}", query));
diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/AsyncRestExecutor.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/AsyncRestExecutor.java
index e6406e8b3ee..4ad6e557771 100644
--- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/AsyncRestExecutor.java
+++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/AsyncRestExecutor.java
@@ -19,13 +19,13 @@
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestStatus;
import org.opensearch.sql.common.setting.Settings;
+import org.opensearch.sql.common.utils.QueryContext;
import org.opensearch.sql.legacy.esdomain.LocalClusterState;
import org.opensearch.sql.legacy.exception.SqlParseException;
import org.opensearch.sql.legacy.metrics.MetricName;
import org.opensearch.sql.legacy.metrics.Metrics;
import org.opensearch.sql.legacy.query.QueryAction;
import org.opensearch.sql.legacy.query.join.BackOffRetryStrategy;
-import org.opensearch.sql.legacy.utils.LogUtils;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.Transports;
@@ -73,13 +73,13 @@ public void execute(Client client, Map params, QueryAction query
if (isBlockingAction(queryAction) && isRunningInTransportThread()) {
if (LOG.isDebugEnabled()) {
LOG.debug("[{}] Async blocking query action [{}] for executor [{}] in current thread [{}]",
- LogUtils.getRequestId(), name(executor), name(queryAction), Thread.currentThread().getName());
+ QueryContext.getRequestId(), name(executor), name(queryAction), Thread.currentThread().getName());
}
async(client, params, queryAction, channel);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("[{}] Continue running query action [{}] for executor [{}] in current thread [{}]",
- LogUtils.getRequestId(), name(executor), name(queryAction), Thread.currentThread().getName());
+ QueryContext.getRequestId(), name(executor), name(queryAction), Thread.currentThread().getName());
}
doExecuteWithTimeMeasured(client, params, queryAction, channel);
}
@@ -110,18 +110,18 @@ private void async(Client client, Map params, QueryAction queryA
doExecuteWithTimeMeasured(client, params, queryAction, channel);
} catch (IOException | SqlParseException | OpenSearchException e) {
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
- LOG.warn("[{}] [MCB] async task got an IO/SQL exception: {}", LogUtils.getRequestId(),
+ LOG.warn("[{}] [MCB] async task got an IO/SQL exception: {}", QueryContext.getRequestId(),
e.getMessage());
channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage()));
} catch (IllegalStateException e) {
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
- LOG.warn("[{}] [MCB] async task got a runtime exception: {}", LogUtils.getRequestId(),
+ LOG.warn("[{}] [MCB] async task got a runtime exception: {}", QueryContext.getRequestId(),
e.getMessage());
channel.sendResponse(new BytesRestResponse(RestStatus.INSUFFICIENT_STORAGE,
"Memory circuit is broken."));
} catch (Throwable t) {
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
- LOG.warn("[{}] [MCB] async task got an unknown throwable: {}", LogUtils.getRequestId(),
+ LOG.warn("[{}] [MCB] async task got an unknown throwable: {}", QueryContext.getRequestId(),
t.getMessage());
channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR,
String.valueOf(t.getMessage())));
@@ -132,7 +132,7 @@ private void async(Client client, Map params, QueryAction queryA
// Preserve context of calling thread to ensure headers of requests are forwarded when running blocking actions
threadPool.schedule(
- LogUtils.withCurrentContext(runnable),
+ QueryContext.withCurrentContext(runnable),
new TimeValue(0L),
SQL_WORKER_THREAD_POOL_NAME
);
@@ -152,7 +152,7 @@ private void doExecuteWithTimeMeasured(Client client,
Duration elapsed = Duration.ofNanos(System.nanoTime() - startTime);
int slowLogThreshold = LocalClusterState.state().getSettingValue(Settings.Key.SQL_SLOWLOG);
if (elapsed.getSeconds() >= slowLogThreshold) {
- LOG.warn("[{}] Slow query: elapsed={} (ms)", LogUtils.getRequestId(), elapsed.toMillis());
+ LOG.warn("[{}] Slow query: elapsed={} (ms)", QueryContext.getRequestId(), elapsed.toMillis());
}
}
}
diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorAsyncRestExecutor.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorAsyncRestExecutor.java
index 0dc1fe301f5..7bb64215022 100644
--- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorAsyncRestExecutor.java
+++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorAsyncRestExecutor.java
@@ -17,11 +17,11 @@
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestStatus;
import org.opensearch.sql.common.setting.Settings;
+import org.opensearch.sql.common.utils.QueryContext;
import org.opensearch.sql.legacy.esdomain.LocalClusterState;
import org.opensearch.sql.legacy.metrics.MetricName;
import org.opensearch.sql.legacy.metrics.Metrics;
import org.opensearch.sql.legacy.query.join.BackOffRetryStrategy;
-import org.opensearch.sql.legacy.utils.LogUtils;
import org.opensearch.threadpool.ThreadPool;
public class CursorAsyncRestExecutor {
@@ -57,20 +57,20 @@ private void async(Client client, Map params, RestChannel channe
doExecuteWithTimeMeasured(client, params, channel);
} catch (IOException e) {
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
- LOG.warn("[{}] [MCB] async task got an IO/SQL exception: {}", LogUtils.getRequestId(),
+ LOG.warn("[{}] [MCB] async task got an IO/SQL exception: {}", QueryContext.getRequestId(),
e.getMessage());
e.printStackTrace();
channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage()));
} catch (IllegalStateException e) {
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
- LOG.warn("[{}] [MCB] async task got a runtime exception: {}", LogUtils.getRequestId(),
+ LOG.warn("[{}] [MCB] async task got a runtime exception: {}", QueryContext.getRequestId(),
e.getMessage());
e.printStackTrace();
channel.sendResponse(new BytesRestResponse(RestStatus.INSUFFICIENT_STORAGE,
"Memory circuit is broken."));
} catch (Throwable t) {
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
- LOG.warn("[{}] [MCB] async task got an unknown throwable: {}", LogUtils.getRequestId(),
+ LOG.warn("[{}] [MCB] async task got an unknown throwable: {}", QueryContext.getRequestId(),
t.getMessage());
t.printStackTrace();
channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR,
@@ -82,7 +82,7 @@ private void async(Client client, Map params, RestChannel channe
// Preserve context of calling thread to ensure headers of requests are forwarded when running blocking actions
threadPool.schedule(
- LogUtils.withCurrentContext(runnable),
+ QueryContext.withCurrentContext(runnable),
new TimeValue(0L),
SQL_WORKER_THREAD_POOL_NAME
);
@@ -101,7 +101,7 @@ private void doExecuteWithTimeMeasured(Client client,
Duration elapsed = Duration.ofNanos(System.nanoTime() - startTime);
int slowLogThreshold = LocalClusterState.state().getSettingValue(Settings.Key.SQL_SLOWLOG);
if (elapsed.getSeconds() >= slowLogThreshold) {
- LOG.warn("[{}] Slow query: elapsed={} (ms)", LogUtils.getRequestId(), elapsed.toMillis());
+ LOG.warn("[{}] Slow query: elapsed={} (ms)", QueryContext.getRequestId(), elapsed.toMillis());
}
}
}
diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlAction.java b/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlAction.java
index 10d9dab0fa0..e7c40b536a4 100644
--- a/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlAction.java
+++ b/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlAction.java
@@ -35,6 +35,7 @@
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestStatus;
import org.opensearch.sql.common.antlr.SyntaxCheckException;
+import org.opensearch.sql.common.utils.QueryContext;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.legacy.antlr.OpenSearchLegacySqlAnalyzer;
@@ -60,7 +61,6 @@
import org.opensearch.sql.legacy.request.SqlRequestParam;
import org.opensearch.sql.legacy.rewriter.matchtoterm.VerificationException;
import org.opensearch.sql.legacy.utils.JsonPrettyFormatter;
-import org.opensearch.sql.legacy.utils.LogUtils;
import org.opensearch.sql.legacy.utils.QueryDataAnonymizer;
import org.opensearch.sql.sql.domain.SQLQueryRequest;
@@ -123,7 +123,8 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
Metrics.getInstance().getNumericalMetric(MetricName.REQ_TOTAL).increment();
Metrics.getInstance().getNumericalMetric(MetricName.REQ_COUNT_TOTAL).increment();
- LogUtils.addRequestId();
+ QueryContext.addRequestId();
+ QueryContext.recordProcessingStarted();
try {
if (!isSQLFeatureEnabled()) {
@@ -137,12 +138,12 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
if (isExplainRequest(request)) {
throw new IllegalArgumentException("Invalid request. Cannot explain cursor");
} else {
- LOG.info("[{}] Cursor request {}: {}", LogUtils.getRequestId(), request.uri(), sqlRequest.cursor());
+ LOG.info("[{}] Cursor request {}: {}", QueryContext.getRequestId(), request.uri(), sqlRequest.cursor());
return channel -> handleCursorRequest(request, sqlRequest.cursor(), client, channel);
}
}
- LOG.info("[{}] Incoming request {}: {}", LogUtils.getRequestId(), request.uri(),
+ LOG.info("[{}] Incoming request {}: {}", QueryContext.getRequestId(), request.uri(),
QueryDataAnonymizer.anonymizeData(sqlRequest.getSql()));
Format format = SqlRequestParam.getFormat(request.params());
@@ -152,11 +153,11 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
sqlRequest.getSql(), request.path(), request.params());
RestChannelConsumer result = newSqlQueryHandler.prepareRequest(newSqlRequest, client);
if (result != RestSQLQueryAction.NOT_SUPPORTED_YET) {
- LOG.info("[{}] Request is handled by new SQL query engine", LogUtils.getRequestId());
+ LOG.info("[{}] Request is handled by new SQL query engine", QueryContext.getRequestId());
return result;
}
LOG.debug("[{}] Request {} is not supported and falling back to old SQL engine",
- LogUtils.getRequestId(), newSqlRequest);
+ QueryContext.getRequestId(), newSqlRequest);
final QueryAction queryAction = explainRequest(client, sqlRequest, format);
return channel -> executeSqlRequest(request, queryAction, client, channel);
@@ -182,10 +183,10 @@ private void handleCursorRequest(final RestRequest request, final String cursor,
private static void logAndPublishMetrics(final Exception e) {
if (isClientError(e)) {
- LOG.error(LogUtils.getRequestId() + " Client side error during query execution", e);
+ LOG.error(QueryContext.getRequestId() + " Client side error during query execution", e);
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_CUS).increment();
} else {
- LOG.error(LogUtils.getRequestId() + " Server side error during query execution", e);
+ LOG.error(QueryContext.getRequestId() + " Server side error during query execution", e);
Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment();
}
}
diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlStatsAction.java b/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlStatsAction.java
index 70ec21c3fab..5b48ef67104 100644
--- a/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlStatsAction.java
+++ b/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlStatsAction.java
@@ -22,9 +22,9 @@
import org.opensearch.rest.RestController;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestStatus;
+import org.opensearch.sql.common.utils.QueryContext;
import org.opensearch.sql.legacy.executor.format.ErrorMessageFactory;
import org.opensearch.sql.legacy.metrics.Metrics;
-import org.opensearch.sql.legacy.utils.LogUtils;
/**
* Currently this interface is for node level.
@@ -67,7 +67,7 @@ public List replacedRoutes() {
@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
- LogUtils.addRequestId();
+ QueryContext.addRequestId();
try {
return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.OK,
diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/utils/LogUtils.java b/legacy/src/main/java/org/opensearch/sql/legacy/utils/LogUtils.java
deleted file mode 100644
index 4830dd44135..00000000000
--- a/legacy/src/main/java/org/opensearch/sql/legacy/utils/LogUtils.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright OpenSearch Contributors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-
-package org.opensearch.sql.legacy.utils;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-import org.apache.logging.log4j.ThreadContext;
-
-/**
- * Utility class for generating/accessing the request id from logging context.
- */
-public class LogUtils {
-
- /**
- * The key of the request id in the context map
- */
- private static final String REQUEST_ID_KEY = "request_id";
-
- private static final String EMPTY_ID = "ID";
-
- /**
- * Generates a random UUID and adds to the {@link ThreadContext} as the request id.
- *
- * Note: If a request id already present, this method will overwrite it with a new
- * one. This is to pre-vent re-using the same request id for different requests in
- * case the same thread handles both of them. But this also means one should not
- * call this method twice on the same thread within the lifetime of the request.
- *