From 425a99813ba8cd44ffa762869ffc3ad1624a5968 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Wed, 14 Sep 2022 19:20:42 -0700 Subject: [PATCH] Rework on `now` function implementation (#113) Signed-off-by: Yury-Fridlyand --- .../sql/common/utils/QueryContext.java | 21 ---- .../sql/analysis/AnalysisContext.java | 16 +++ .../sql/analysis/ExpressionAnalyzer.java | 14 +++ .../sql/ast/AbstractNodeVisitor.java | 5 + .../org/opensearch/sql/ast/dsl/AstDSL.java | 5 + .../sql/ast/expression/ConstantFunction.java | 28 +++++ .../expression/datetime/DateTimeFunction.java | 37 +++--- .../sql/analysis/ExpressionAnalyzerTest.java | 89 +++++++------- .../datetime/NowLikeFunctionTest.java | 12 +- docs/user/dql/functions.rst | 115 +++++++----------- docs/user/ppl/functions/datetime.rst | 115 +++++++----------- .../sql/ppl/DateTimeFunctionIT.java | 12 +- .../sql/sql/DateTimeFunctionIT.java | 12 +- .../sql/legacy/plugin/RestSqlAction.java | 1 - .../transport/TransportPPLQueryAction.java | 1 - ppl/src/main/antlr/OpenSearchPPLParser.g4 | 18 ++- .../sql/ppl/parser/AstExpressionBuilder.java | 24 +++- .../ppl/parser/AstExpressionBuilderTest.java | 20 +++ .../ppl/parser/AstNowLikeFunctionTest.java | 31 +++-- sql/src/main/antlr/OpenSearchSQLParser.g4 | 21 +++- .../sql/sql/parser/AstExpressionBuilder.java | 25 +++- .../sql/sql/parser/AstBuilderTest.java | 27 ++-- 22 files changed, 360 insertions(+), 289 deletions(-) create mode 100644 core/src/main/java/org/opensearch/sql/ast/expression/ConstantFunction.java diff --git a/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java b/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java index b399123054..ab11029d73 100644 --- a/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java +++ b/common/src/main/java/org/opensearch/sql/common/utils/QueryContext.java @@ -24,11 +24,6 @@ public class QueryContext { */ 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. *

@@ -56,22 +51,6 @@ public static String getRequestId() { 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(); - } - /** * Wraps a given instance of {@link Runnable} into a new one which gets all the * entries from current ThreadContext map. diff --git a/core/src/main/java/org/opensearch/sql/analysis/AnalysisContext.java b/core/src/main/java/org/opensearch/sql/analysis/AnalysisContext.java index 2d3ee1a52c..f3fd623371 100644 --- a/core/src/main/java/org/opensearch/sql/analysis/AnalysisContext.java +++ b/core/src/main/java/org/opensearch/sql/analysis/AnalysisContext.java @@ -7,9 +7,12 @@ package org.opensearch.sql.analysis; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import lombok.Getter; +import org.opensearch.sql.expression.Expression; import org.opensearch.sql.expression.NamedExpression; /** @@ -23,13 +26,26 @@ public class AnalysisContext { @Getter private final List namedParseExpressions; + /** + * Storage for values of functions which return a constant value. + * We are storing the values there to use it in sequential calls to those functions. + * For example, `now` function should the same value during processing a query. + */ + @Getter + private final Map constantFunctionValues; + public AnalysisContext() { this(new TypeEnvironment(null)); } + /** + * Class CTOR. + * @param environment Env to set to a new instance. + */ public AnalysisContext(TypeEnvironment environment) { this.environment = environment; this.namedParseExpressions = new ArrayList<>(); + this.constantFunctionValues = new HashMap<>(); } /** diff --git a/core/src/main/java/org/opensearch/sql/analysis/ExpressionAnalyzer.java b/core/src/main/java/org/opensearch/sql/analysis/ExpressionAnalyzer.java index 670da5c85c..ef9d73b7f5 100644 --- a/core/src/main/java/org/opensearch/sql/analysis/ExpressionAnalyzer.java +++ b/core/src/main/java/org/opensearch/sql/analysis/ExpressionAnalyzer.java @@ -24,6 +24,7 @@ import org.opensearch.sql.ast.expression.Case; import org.opensearch.sql.ast.expression.Cast; import org.opensearch.sql.ast.expression.Compare; +import org.opensearch.sql.ast.expression.ConstantFunction; import org.opensearch.sql.ast.expression.EqualTo; import org.opensearch.sql.ast.expression.Field; import org.opensearch.sql.ast.expression.Function; @@ -169,6 +170,19 @@ public Expression visitRelevanceFieldList(RelevanceFieldList node, AnalysisConte ImmutableMap.copyOf(node.getFieldList()))); } + @Override + public Expression visitConstantFunction(ConstantFunction node, AnalysisContext context) { + var valueName = node.getFuncName(); + if (context.getConstantFunctionValues().containsKey(valueName)) { + return context.getConstantFunctionValues().get(valueName); + } + + var value = visitFunction(node, context); + value = DSL.literal(value.valueOf(null)); + context.getConstantFunctionValues().put(valueName, value); + return value; + } + @Override public Expression visitFunction(Function node, AnalysisContext context) { FunctionName functionName = FunctionName.of(node.getFuncName()); diff --git a/core/src/main/java/org/opensearch/sql/ast/AbstractNodeVisitor.java b/core/src/main/java/org/opensearch/sql/ast/AbstractNodeVisitor.java index 17321bc473..e75f8f4ce5 100644 --- a/core/src/main/java/org/opensearch/sql/ast/AbstractNodeVisitor.java +++ b/core/src/main/java/org/opensearch/sql/ast/AbstractNodeVisitor.java @@ -15,6 +15,7 @@ import org.opensearch.sql.ast.expression.Case; import org.opensearch.sql.ast.expression.Cast; import org.opensearch.sql.ast.expression.Compare; +import org.opensearch.sql.ast.expression.ConstantFunction; import org.opensearch.sql.ast.expression.EqualTo; import org.opensearch.sql.ast.expression.Field; import org.opensearch.sql.ast.expression.Function; @@ -116,6 +117,10 @@ public T visitRelevanceFieldList(RelevanceFieldList node, C context) { return visitChildren(node, context); } + public T visitConstantFunction(ConstantFunction node, C context) { + return visitChildren(node, context); + } + public T visitUnresolvedAttribute(UnresolvedAttribute node, C context) { return visitChildren(node, context); } diff --git a/core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java b/core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java index 99d8aaa882..c13dc53ea3 100644 --- a/core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java +++ b/core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java @@ -19,6 +19,7 @@ import org.opensearch.sql.ast.expression.Case; import org.opensearch.sql.ast.expression.Cast; import org.opensearch.sql.ast.expression.Compare; +import org.opensearch.sql.ast.expression.ConstantFunction; import org.opensearch.sql.ast.expression.DataType; import org.opensearch.sql.ast.expression.EqualTo; import org.opensearch.sql.ast.expression.Field; @@ -234,6 +235,10 @@ public static Function function(String funcName, UnresolvedExpression... funcArg return new Function(funcName, Arrays.asList(funcArgs)); } + public static Function constantFunction(String funcName, UnresolvedExpression... funcArgs) { + return new ConstantFunction(funcName, Arrays.asList(funcArgs)); + } + /** * CASE * WHEN search_condition THEN result_expr diff --git a/core/src/main/java/org/opensearch/sql/ast/expression/ConstantFunction.java b/core/src/main/java/org/opensearch/sql/ast/expression/ConstantFunction.java new file mode 100644 index 0000000000..f14e65eeb2 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/ast/expression/ConstantFunction.java @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + +package org.opensearch.sql.ast.expression; + +import java.util.List; +import lombok.EqualsAndHashCode; +import org.opensearch.sql.ast.AbstractNodeVisitor; + +/** + * Expression node that holds a function which should be replaced by its constant[1] value. + * [1] Constant at execution time. + */ +@EqualsAndHashCode(callSuper = false) +public class ConstantFunction extends Function { + + public ConstantFunction(String funcName, List funcArgs) { + super(funcName, funcArgs); + } + + @Override + public R accept(AbstractNodeVisitor nodeVisitor, C context) { + return nodeVisitor.visitConstantFunction(this, context); + } +} 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 228fd5ed71..d8dc7fc85f 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 @@ -28,10 +28,8 @@ 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; @@ -105,15 +103,12 @@ public void register(BuiltinFunctionRepository repository) { /** * NOW() returns a constant time that indicates the time at which the statement began to execute. + * `fsp` argument support is removed until refactoring to avoid bug where `now()`, `now(x)` and + * `now(y) return different values. */ - 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) + impl(() -> new ExprDatetimeValue(formatNow(null)), DATETIME) ); } @@ -136,21 +131,19 @@ private FunctionResolver localtime() { /** * 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) + impl(() -> new ExprDatetimeValue(formatNow(null)), DATETIME), + impl((v) -> new ExprDatetimeValue(formatNow(v.integerValue())), DATETIME, INTEGER) ); } + /** + * Synonym for @see `now`. + */ 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) + impl(() -> new ExprTimeValue(formatNow(null).toLocalTime()), TIME) ); } @@ -164,7 +157,7 @@ private FunctionResolver current_time() { private FunctionResolver curdate(FunctionName functionName) { return define(functionName, - impl(() -> new ExprDateValue(sysDate(null).toLocalDate()), DATE) + impl(() -> new ExprDateValue(formatNow(null).toLocalDate()), DATE) ); } @@ -832,17 +825,15 @@ private ExprValue exprYear(ExprValue date) { } /** - * Prepare LocalDateTime value. - * @param supplier A function which returns LocalDateTime to format. + * Prepare LocalDateTime value. Truncate fractional second part according to the argument. * @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(); + private LocalDateTime formatNow(@Nullable Integer fsp) { + var res = LocalDateTime.now(); if (fsp == null) { - return res; + fsp = 0; } 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] 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 5a9052f3e4..e3b1ac7e6a 100644 --- a/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java +++ b/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java @@ -8,7 +8,9 @@ import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opensearch.sql.ast.dsl.AstDSL.field; import static org.opensearch.sql.ast.dsl.AstDSL.floatLiteral; import static org.opensearch.sql.ast.dsl.AstDSL.function; @@ -27,14 +29,10 @@ import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; 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; @@ -53,6 +51,7 @@ import org.opensearch.sql.expression.Expression; import org.opensearch.sql.expression.FunctionExpression; import org.opensearch.sql.expression.HighlightExpression; +import org.opensearch.sql.expression.LiteralExpression; import org.opensearch.sql.expression.config.ExpressionConfig; import org.opensearch.sql.expression.window.aggregation.AggregateWindowFunction; import org.springframework.context.annotation.Configuration; @@ -552,47 +551,45 @@ 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 + public void constant_function_is_calculated_on_analyze() { + // Actually, we can call any function as ConstantFunction to be calculated on analyze stage + assertTrue(analyze(AstDSL.constantFunction("now")) instanceof LiteralExpression); + assertTrue(analyze(AstDSL.constantFunction("localtime")) instanceof LiteralExpression); + } + + @Test + public void function_isnt_calculated_on_analyze() { + assertTrue(analyze(function("now")) instanceof FunctionExpression); + assertTrue(analyze(AstDSL.function("localtime")) instanceof FunctionExpression); + } + + @Test + public void constant_function_returns_constant_cached_value() { + var values = List.of(analyze(AstDSL.constantFunction("now")), + analyze(AstDSL.constantFunction("now")), analyze(AstDSL.constantFunction("now"))); + assertTrue(values.stream().allMatch(v -> + v.valueOf(null) == analyze(AstDSL.constantFunction("now")).valueOf(null))); + } + + @Test + public void function_returns_non_constant_value() { + // Even a function returns the same values - they are calculated on each call + // `sysdate()` which returns `LocalDateTime.now()` shouldn't be cached and should return always + // different values + var values = List.of(analyze(function("sysdate")), analyze(function("sysdate")), + analyze(function("sysdate")), analyze(function("sysdate"))); + var referenceValue = analyze(function("sysdate")).valueOf(null); + assertTrue(values.stream().noneMatch(v -> v.valueOf(null) == referenceValue)); + } + + @Test + public void now_as_a_function_not_cached() { + // // We can call `now()` as a function, in that case nothing should be cached + var values = List.of(analyze(function("now")), analyze(function("now")), + analyze(function("now")), analyze(function("now"))); + var referenceValue = analyze(function("now")).valueOf(null); + assertTrue(values.stream().noneMatch(v -> v.valueOf(null) == referenceValue)); } @Test 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 index 76a7e4be46..e8f5c16025 100644 --- a/core/src/test/java/org/opensearch/sql/expression/datetime/NowLikeFunctionTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/NowLikeFunctionTest.java @@ -39,19 +39,19 @@ 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), + "now", DATETIME, false, (Supplier)LocalDateTime::now), Arguments.of((Function)dsl::current_timestamp, - "current_timestamp", DATETIME, true, (Supplier)LocalDateTime::now), + "current_timestamp", DATETIME, false, (Supplier)LocalDateTime::now), Arguments.of((Function)dsl::localtimestamp, - "localtimestamp", DATETIME, true, (Supplier)LocalDateTime::now), + "localtimestamp", DATETIME, false, (Supplier)LocalDateTime::now), Arguments.of((Function)dsl::localtime, - "localtime", DATETIME, true, (Supplier)LocalDateTime::now), + "localtime", DATETIME, false, (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), + "curtime", TIME, false, (Supplier)LocalTime::now), Arguments.of((Function)dsl::current_time, - "current_time", TIME, true, (Supplier)LocalTime::now), + "current_time", TIME, false, (Supplier)LocalTime::now), Arguments.of((Function)dsl::curdate, "curdate", DATE, false, (Supplier)LocalDate::now), Arguments.of((Function)dsl::current_date, diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index c566b10463..e914803243 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1385,26 +1385,20 @@ 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 +Specification: NOW() -> DATETIME Example:: - > SELECT NOW(), NOW(0); + > SELECT NOW() as value_1, NOW() as value_2; fetched rows / total rows = 1/1 - +----------------------------+---------------------+ - | NOW() | NOW(0) | - |----------------------------+---------------------| - | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 | - +----------------------------+---------------------+ + +----------------------------+----------------------------+ + | value_1 | value_2 | + |----------------------------+----------------------------| + | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05.173069 | + +----------------------------+----------------------------+ CURRENT_TIMESTAMP @@ -1417,13 +1411,13 @@ Description Example:: - > SELECT CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(0), CURRENT_TIMESTAMP; + > SELECT CURRENT_TIMESTAMP(), 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 | - +----------------------------+------------------------+----------------------------+ + +----------------------------+----------------------------+ + | CURRENT_TIMESTAMP() | CURRENT_TIMESTAMP | + |----------------------------+----------------------------| + | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19.209361 | + +----------------------------+----------------------------+ LOCALTIMESTAMP @@ -1436,13 +1430,13 @@ Description Example:: - > SELECT LOCALTIMESTAMP(), LOCALTIMESTAMP(0), LOCALTIMESTAMP; + > SELECT LOCALTIMESTAMP(), 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 | - +----------------------------+---------------------+----------------------------+ + +----------------------------+----------------------------+ + | LOCALTIMESTAMP() | LOCALTIMESTAMP | + |----------------------------+----------------------------| + | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19.209361 | + +----------------------------+----------------------------+ LOCALTIME @@ -1455,13 +1449,13 @@ Description Example:: - > SELECT LOCALTIME(), LOCALTIME(0), LOCALTIME; + > SELECT LOCALTIME(), 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 | - +----------------------------+---------------------+----------------------------+ + +----------------------------+----------------------------+ + | LOCALTIME() | LOCALTIME | + |----------------------------+----------------------------| + | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19.209361 | + +----------------------------+----------------------------+ SYSDATE @@ -1472,26 +1466,20 @@ 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 +Specification: SYSDATE() -> DATETIME Example:: - > SELECT SYSDATE(), SYSDATE(0); + > SELECT SYSDATE() as value_1, SYSDATE() as value_2; fetched rows / total rows = 1/1 - +----------------------------+---------------------+ - | SYSDATE() | SYSDATE(0) | - |----------------------------+---------------------| - | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 | - +----------------------------+---------------------+ + +----------------------------+----------------------------+ + | value_1 | value_2 | + |----------------------------+----------------------------| + | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05.173142 | + +----------------------------+----------------------------+ CURTIME @@ -1501,27 +1489,21 @@ 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 +CURTIME() returns the time at which the statement began to execute as `NOW() <#now>`_ does. Return type: TIME -Specifications: - -1. CURTIME() -> TIME -2. CURTIME(INTEGER) -> TIME +Specification: CURTIME() -> TIME Example:: - > SELECT CURTIME(), CURTIME(0); + > SELECT CURTIME() as value_1, CURTIME() as value_2; fetched rows / total rows = 1/1 - +-----------------+--------------+ - | CURTIME() | CURTIME(0) | - |-----------------+--------------| - | 15:39:05.173069 | 15:39:05 | - +-----------------+--------------+ + +-----------------+-----------------+ + | value_1 | value_2 | + |-----------------+-----------------| + | 15:39:05.173069 | 15:39:05.173069 | + +-----------------+-----------------+ CURRENT_TIME @@ -1534,13 +1516,13 @@ Description Example:: - > SELECT CURRENT_TIME(), CURRENT_TIME(0), CURRENT_TIME; + > SELECT CURRENT_TIME(), 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 | - +------------------+-------------------+-----------------+ + +-----------------+-----------------+ + | CURRENT_TIME() | CURRENT_TIME | + |-----------------+-----------------| + | 15:39:05.173069 | 15:39:05.173069 | + +-----------------+-----------------+ CURDATE @@ -1551,13 +1533,10 @@ 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 +Specification: CURDATE() -> DATE Example:: diff --git a/docs/user/ppl/functions/datetime.rst b/docs/user/ppl/functions/datetime.rst index e7eeb32e8e..dbbc2abe21 100644 --- a/docs/user/ppl/functions/datetime.rst +++ b/docs/user/ppl/functions/datetime.rst @@ -554,26 +554,20 @@ 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 +Specification: NOW() -> DATETIME Example:: - > source=people | eval `NOW()` = NOW(), `NOW(0)` = NOW(0) | fields `NOW()`, `NOW(0)` + > source=people | eval `value_1` = NOW(), `value_2` = NOW() | fields `value_1`, `value_2` fetched rows / total rows = 1/1 - +----------------------------+---------------------+ - | NOW() | NOW(0) | - |----------------------------+---------------------| - | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 | - +----------------------------+---------------------+ + +----------------------------+----------------------------+ + | value_1 | value_2 | + |----------------------------+----------------------------| + | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05.173069 | + +----------------------------+----------------------------+ CURRENT_TIMESTAMP @@ -586,13 +580,13 @@ Description 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` + > source=people | eval `CURRENT_TIMESTAMP()` = CURRENT_TIMESTAMP(), `CURRENT_TIMESTAMP` = CURRENT_TIMESTAMP | fields `CURRENT_TIMESTAMP()`, `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 | - +----------------------------+------------------------+----------------------------+ + +----------------------------+----------------------------+ + | CURRENT_TIMESTAMP() | CURRENT_TIMESTAMP | + |----------------------------+----------------------------| + | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19.209361 | + +----------------------------+----------------------------+ LOCALTIMESTAMP @@ -605,13 +599,13 @@ Description Example:: - > source=people | eval `LOCALTIMESTAMP()` = LOCALTIMESTAMP(), `LOCALTIMESTAMP(0)` = LOCALTIMESTAMP(0), `LOCALTIMESTAMP` = LOCALTIMESTAMP | fields `LOCALTIMESTAMP()`, `LOCALTIMESTAMP(0)`, `LOCALTIMESTAMP` + > source=people | eval `LOCALTIMESTAMP()` = LOCALTIMESTAMP(), `LOCALTIMESTAMP` = LOCALTIMESTAMP | fields `LOCALTIMESTAMP()`, `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 | - +----------------------------+---------------------+----------------------------+ + +----------------------------+----------------------------+ + | LOCALTIMESTAMP() | LOCALTIMESTAMP | + |----------------------------+----------------------------| + | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19.209361 | + +----------------------------+----------------------------+ LOCALTIME @@ -624,13 +618,13 @@ Description Example:: - > source=people | eval `LOCALTIME()` = LOCALTIME(), `LOCALTIME(0)` = LOCALTIME(0), `LOCALTIME` = LOCALTIME | fields `LOCALTIME()`, `LOCALTIME(0)`, `LOCALTIME` + > source=people | eval `LOCALTIME()` = LOCALTIME(), `LOCALTIME` = LOCALTIME | fields `LOCALTIME()`, `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 | - +----------------------------+---------------------+----------------------------+ + +----------------------------+----------------------------+ + | LOCALTIME() | LOCALTIME | + |----------------------------+----------------------------| + | 2022-08-02 15:54:19.209361 | 2022-08-02 15:54:19.209361 | + +----------------------------+----------------------------+ SYSDATE @@ -641,26 +635,20 @@ 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 +Specification: SYSDATE() -> DATETIME Example:: - > source=people | eval `SYSDATE()` = SYSDATE(), `SYSDATE(0)` = SYSDATE(0) | fields `SYSDATE()`, `SYSDATE(0)` + > source=people | eval `value_1` = SYSDATE(), `value_2` = SYSDATE() | fields `value_1`, `value_2` fetched rows / total rows = 1/1 - +----------------------------+---------------------+ - | SYSDATE() | SYSDATE(0) | - |----------------------------+---------------------| - | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05 | - +----------------------------+---------------------+ + +----------------------------+----------------------------+ + | value_1 | value_2 | + |----------------------------+----------------------------| + | 2022-08-02 15:39:05.173069 | 2022-08-02 15:39:05.173142 | + +----------------------------+----------------------------+ CURTIME @@ -670,27 +658,21 @@ 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 +CURTIME() returns the time at which the statement began to execute as `NOW() <#now>`_ does. Return type: TIME -Specifications: - -1. CURTIME() -> TIME -2. CURTIME(INTEGER) -> TIME +Specification: CURTIME() -> TIME Example:: - > source=people | eval `CURTIME()` = CURTIME(), `CURTIME(0)` = CURTIME(0) | fields `CURTIME()`, `CURTIME(0)` + > source=people | eval `value_1` = CURTIME(), `value_2` = CURTIME() | fields `value_1`, `value_2` fetched rows / total rows = 1/1 - +-----------------+--------------+ - | CURTIME() | CURTIME(0) | - |-----------------+--------------| - | 15:39:05.173069 | 15:39:05 | - +-----------------+--------------+ + +-----------------+-----------------+ + | value_1 | value_2 | + |-----------------+-----------------| + | 15:39:05.173069 | 15:39:05.173069 | + +-----------------+-----------------+ CURRENT_TIME @@ -703,13 +685,13 @@ Description 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` + > source=people | eval `CURRENT_TIME()` = CURRENT_TIME(), `CURRENT_TIME` = CURRENT_TIME | fields `CURRENT_TIME()`, `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 | - +------------------+-------------------+-----------------+ + +------------------+-----------------+ + | CURRENT_TIME() | CURRENT_TIME | + |------------------+-----------------| + | 15:39:05.173069 | 15:39:05.173069 | + +------------------+-----------------+ CURDATE @@ -720,13 +702,10 @@ 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 +Specification: CURDATE() -> DATE Example:: 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 9ba83b63bf..a0b0e8673b 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 @@ -504,7 +504,7 @@ private List> nowLikeFunctionsData() { return List.of( ImmutableMap.builder() .put("name", "now") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", false) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -513,7 +513,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "current_timestamp") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -522,7 +522,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "localtimestamp") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -531,7 +531,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "localtime") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -549,7 +549,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "curtime") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", false) .put("constValue", false) .put("referenceGetter", (Supplier) LocalTime::now) @@ -558,7 +558,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "current_time") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", false) .put("referenceGetter", (Supplier) LocalTime::now) 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 7b5b1c70f8..db4bec2540 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 @@ -493,7 +493,7 @@ private List> nowLikeFunctionsData() { return List.of( ImmutableMap.builder() .put("name", "now") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", false) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -502,7 +502,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "current_timestamp") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -511,7 +511,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "localtimestamp") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -520,7 +520,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "localtime") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", true) .put("referenceGetter", (Supplier) LocalDateTime::now) @@ -538,7 +538,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "curtime") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", false) .put("constValue", false) .put("referenceGetter", (Supplier) LocalTime::now) @@ -547,7 +547,7 @@ private List> nowLikeFunctionsData() { .build(), ImmutableMap.builder() .put("name", "current_time") - .put("hasFsp", true) + .put("hasFsp", false) .put("hasShortcut", true) .put("constValue", false) .put("referenceGetter", (Supplier) LocalTime::now) 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 8c68dbd416..ab146404f8 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 @@ -127,7 +127,6 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli Metrics.getInstance().getNumericalMetric(MetricName.REQ_COUNT_TOTAL).increment(); QueryContext.addRequestId(); - QueryContext.recordProcessingStarted(); try { if (!isSQLFeatureEnabled()) { diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java index 9f0f57fe3b..eaad009216 100644 --- a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java +++ b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java @@ -81,7 +81,6 @@ protected void doExecute( Metrics.getInstance().getNumericalMetric(MetricName.PPL_REQ_COUNT_TOTAL).increment(); QueryContext.addRequestId(); - QueryContext.recordProcessingStarted(); PPLService pplService = createPPLService(client); TransportPPLQueryRequest transportRequest = TransportPPLQueryRequest.fromActionRequest(request); diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index fc15318b65..1bd1140d8a 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -221,6 +221,11 @@ primaryExpression | dataTypeFunctionCall | fieldExpression | literalValue + | constantFunction + ; + +constantFunction + : constantFunctionName LT_PRTHS functionArgs? RT_PRTHS ; booleanExpression @@ -373,11 +378,14 @@ trigonometricFunctionName ; dateAndTimeFunctionBase - : ADDDATE | DATE | DATE_ADD | DATE_SUB | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK | DAYOFYEAR | FROM_DAYS - | HOUR | MICROSECOND | MINUTE | MONTH | MONTHNAME | QUARTER | SECOND | SUBDATE | TIME | TIME_TO_SEC - | TIMESTAMP | TO_DAYS | YEAR | WEEK | DATE_FORMAT | NOW | CURDATE | CURRENT_DATE | CURTIME | CURRENT_TIME - | LOCALTIME | CURRENT_TIMESTAMP | LOCALTIMESTAMP | SYSDATE | UTC_TIMESTAMP | UTC_DATE | UTC_TIME - | MAKETIME | MAKEDATE + : ADDDATE | DATE | DATE_ADD | DATE_FORMAT | DATE_SUB | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK + | DAYOFYEAR | FROM_DAYS | HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE | MONTH | MONTHNAME + | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC| TIMESTAMP | TO_DAYS | WEEK | YEAR + ; + +constantFunctionName + : datetimeConstantLiteral + | CURDATE | CURTIME | NOW ; /** condition function return boolean value */ diff --git a/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java b/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java index c8424aecb4..5df1c4ec56 100644 --- a/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java +++ b/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java @@ -14,6 +14,7 @@ import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.BooleanLiteralContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.BySpanClauseContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.CompareExprContext; +import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.ConstantFunctionContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.ConvertedDataTypeContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.CountAllFunctionCallContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.DataTypeFunctionCallContext; @@ -61,6 +62,7 @@ import org.opensearch.sql.ast.expression.Argument; import org.opensearch.sql.ast.expression.Cast; import org.opensearch.sql.ast.expression.Compare; +import org.opensearch.sql.ast.expression.ConstantFunction; import org.opensearch.sql.ast.expression.DataType; import org.opensearch.sql.ast.expression.Field; import org.opensearch.sql.ast.expression.Function; @@ -245,15 +247,29 @@ public UnresolvedExpression visitConvertedDataType(ConvertedDataTypeContext ctx) @Override public UnresolvedExpression visitDatetimeConstantLiteral(DatetimeConstantLiteralContext ctx) { - return visitFunction(ctx.getText(), null); + return visitConstantFunction(ctx.getText(), null); } - private Function visitFunction(String functionName, FunctionArgsContext args) { - return new Function( - functionName, + public UnresolvedExpression visitConstantFunction(ConstantFunctionContext ctx) { + return visitConstantFunction(ctx.constantFunctionName().getText(), + ctx.functionArgs()); + } + + private UnresolvedExpression visitConstantFunction(String functionName, + FunctionArgsContext args) { + return new ConstantFunction(functionName, args == null ? Collections.emptyList() : args.functionArg() + .stream() + .map(this::visitFunctionArg) + .collect(Collectors.toList())); + } + + private Function visitFunction(String functionName, FunctionArgsContext args) { + return new Function( + functionName, + args.functionArg() .stream() .map(this::visitFunctionArg) .collect(Collectors.toList()) diff --git a/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java b/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java index bb3315d5c8..1becf086ac 100644 --- a/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java +++ b/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java @@ -7,6 +7,7 @@ package org.opensearch.sql.ppl.parser; import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; import static org.opensearch.sql.ast.dsl.AstDSL.agg; import static org.opensearch.sql.ast.dsl.AstDSL.aggregate; import static org.opensearch.sql.ast.dsl.AstDSL.alias; @@ -47,11 +48,13 @@ import java.util.Collections; import org.junit.Ignore; import org.junit.Test; +import org.opensearch.sql.ast.Node; import org.opensearch.sql.ast.expression.AllFields; import org.opensearch.sql.ast.expression.Argument; import org.opensearch.sql.ast.expression.DataType; import org.opensearch.sql.ast.expression.Literal; import org.opensearch.sql.ast.expression.RelevanceFieldList; +import org.opensearch.sql.ppl.antlr.PPLSyntaxParser; public class AstExpressionBuilderTest extends AstBuilderTest { @@ -168,6 +171,18 @@ public void testEvalFunctionExpr() { )); } + @Test + public void testEvalFunctionExprNoArgs() { + assertEqual("source=t | eval f=PI()", + eval( + relation("t"), + let( + field("f"), + function("PI") + ) + )); + } + @Test public void testEvalBinaryOperationExpr() { assertEqual("source=t | eval f=a+b", @@ -715,4 +730,9 @@ public void canBuildQuery_stringRelevanceFunctionWithArguments() { ) ); } + + private Node buildExprAst(String query) { + AstBuilder astBuilder = new AstBuilder(new AstExpressionBuilder(), query); + return astBuilder.visit(new PPLSyntaxParser().parse(query)); + } } diff --git a/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstNowLikeFunctionTest.java b/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstNowLikeFunctionTest.java index feb06b996b..6c6233a17f 100644 --- a/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstNowLikeFunctionTest.java +++ b/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstNowLikeFunctionTest.java @@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals; import static org.opensearch.sql.ast.dsl.AstDSL.compare; +import static org.opensearch.sql.ast.dsl.AstDSL.constantFunction; import static org.opensearch.sql.ast.dsl.AstDSL.eval; import static org.opensearch.sql.ast.dsl.AstDSL.field; import static org.opensearch.sql.ast.dsl.AstDSL.filter; @@ -33,11 +34,14 @@ public class AstNowLikeFunctionTest { * @param name Function name * @param hasFsp Whether function has fsp argument * @param hasShortcut Whether function has shortcut (call without `()`) + * @param isConstantFunction Whether function has constant value */ - public AstNowLikeFunctionTest(String name, Boolean hasFsp, Boolean hasShortcut) { + public AstNowLikeFunctionTest(String name, Boolean hasFsp, Boolean hasShortcut, + Boolean isConstantFunction) { this.name = name; this.hasFsp = hasFsp; this.hasShortcut = hasShortcut; + this.isConstantFunction = isConstantFunction; } /** @@ -47,21 +51,22 @@ public AstNowLikeFunctionTest(String name, Boolean hasFsp, Boolean hasShortcut) @Parameterized.Parameters(name = "{0}") public static Iterable functionNames() { return List.of(new Object[][]{ - {"now", true, false}, - {"current_timestamp", true, true}, - {"localtimestamp", true, true}, - {"localtime", true, true}, - {"sysdate", true, false}, - {"curtime", true, false}, - {"current_time", true, true}, - {"curdate", false, false}, - {"current_date", false, true} + {"now", false, false, true}, + {"current_timestamp", false, true, true}, + {"localtimestamp", false, true, true}, + {"localtime", false, true, true}, + {"sysdate", true, false, false}, + {"curtime", false, false, true}, + {"current_time", false, true, true}, + {"curdate", false, false, true}, + {"current_date", false, true, true} }); } private final String name; private final Boolean hasFsp; private final Boolean hasShortcut; + private final Boolean isConstantFunction; @Test public void test_now_like_functions() { @@ -71,17 +76,19 @@ public void test_now_like_functions() { relation("t"), let( field("r"), - function(name) + (isConstantFunction ? constantFunction(name) : function(name)) ) )); assertEqual("search source=t | where a=" + call, filter( relation("t"), - compare("=", field("a"), function(name)) + compare("=", field("a"), + (isConstantFunction ? constantFunction(name) : function(name))) ) ); } + // Unfortunately, only real functions (not ConstantFunction) might have `fsp` now. if (hasFsp) { assertEqual("search source=t | where a=" + name + "(0)", filter( diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index ef4f99bd1a..60b24ddcd2 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -307,6 +307,11 @@ functionCall | aggregateFunction (orderByClause)? filterClause #filteredAggregationFunctionCall | relevanceFunction #relevanceFunctionCall | highlightFunction #highlightFunctionCall + | constantFunction #constantFunctionCall + ; + +constantFunction + : constantFunctionName LR_BRACKET functionArgs? RR_BRACKET ; highlightFunction @@ -389,11 +394,15 @@ trigonometricFunctionName ; dateTimeFunctionName - : ADDDATE | DATE | DATE_ADD | DATE_SUB | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK | DAYOFYEAR | FROM_DAYS - | HOUR | MICROSECOND | MINUTE | MONTH | MONTHNAME | QUARTER | SECOND | SUBDATE | TIME | TIME_TO_SEC - | TIMESTAMP | TO_DAYS | YEAR | WEEK | DATE_FORMAT | NOW | CURDATE | CURRENT_DATE | CURTIME | CURRENT_TIME - | LOCALTIME | CURRENT_TIMESTAMP | LOCALTIMESTAMP | SYSDATE | UTC_TIMESTAMP | UTC_DATE | UTC_TIME - | MAKETIME | MAKEDATE + : ADDDATE | DATE | DATE_ADD | DATE_FORMAT | DATE_SUB | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK + | DAYOFYEAR | FROM_DAYS | HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE | MONTH | MONTHNAME + | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC| TIMESTAMP | TO_DAYS | WEEK | YEAR + ; + +// Functions which value could be cached in scope of a single query +constantFunctionName + : datetimeConstantLiteral + | CURDATE | CURTIME | NOW ; textFunctionName @@ -422,7 +431,7 @@ legacyRelevanceFunctionName ; functionArgs - : functionArg (COMMA functionArg)* + : (functionArg (COMMA functionArg)*)? ; functionArg diff --git a/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java b/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java index 1714b608ad..006ed5fba2 100644 --- a/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java +++ b/sql/src/main/java/org/opensearch/sql/sql/parser/AstExpressionBuilder.java @@ -18,6 +18,7 @@ import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.CaseFuncAlternativeContext; import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.CaseFunctionCallContext; import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.ColumnFilterContext; +import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.ConstantFunctionContext; import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.ConvertedDataTypeContext; import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.CountStarFunctionCallContext; import static org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.DataTypeFunctionCallContext; @@ -62,6 +63,7 @@ import org.opensearch.sql.ast.expression.And; import org.opensearch.sql.ast.expression.Case; import org.opensearch.sql.ast.expression.Cast; +import org.opensearch.sql.ast.expression.ConstantFunction; import org.opensearch.sql.ast.expression.DataType; import org.opensearch.sql.ast.expression.Function; import org.opensearch.sql.ast.expression.HighlightFunction; @@ -389,9 +391,7 @@ public UnresolvedExpression visitMultiFieldRelevanceFunction( private Function visitFunction(String functionName, FunctionArgsContext args) { return new Function( functionName, - args == null - ? Collections.emptyList() - : args.functionArg() + args.functionArg() .stream() .map(this::visitFunctionArg) .collect(Collectors.toList()) @@ -400,7 +400,24 @@ private Function visitFunction(String functionName, FunctionArgsContext args) { @Override public UnresolvedExpression visitDatetimeConstantLiteral(DatetimeConstantLiteralContext ctx) { - return visitFunction(ctx.getText(), null); + return visitConstantFunction(ctx.getText(), null); + } + + @Override + public UnresolvedExpression visitConstantFunction(ConstantFunctionContext ctx) { + return visitConstantFunction(ctx.constantFunctionName().getText(), + ctx.functionArgs()); + } + + private UnresolvedExpression visitConstantFunction(String functionName, + FunctionArgsContext args) { + return new ConstantFunction(functionName, + args == null + ? Collections.emptyList() + : args.functionArg() + .stream() + .map(this::visitFunctionArg) + .collect(Collectors.toList())); } private QualifiedName visitIdentifiers(List identifiers) { diff --git a/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java b/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java index b3fbffae6e..c3b9ed245a 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/parser/AstBuilderTest.java @@ -14,6 +14,7 @@ import static org.opensearch.sql.ast.dsl.AstDSL.alias; import static org.opensearch.sql.ast.dsl.AstDSL.argument; import static org.opensearch.sql.ast.dsl.AstDSL.booleanLiteral; +import static org.opensearch.sql.ast.dsl.AstDSL.constantFunction; import static org.opensearch.sql.ast.dsl.AstDSL.doubleLiteral; import static org.opensearch.sql.ast.dsl.AstDSL.field; import static org.opensearch.sql.ast.dsl.AstDSL.filter; @@ -676,26 +677,27 @@ public void can_build_limit_clause_with_offset() { private static Stream nowLikeFunctionsData() { return Stream.of( - Arguments.of("now", true, false), - Arguments.of("current_timestamp", true, true), - Arguments.of("localtimestamp", true, true), - Arguments.of("localtime", true, true), - Arguments.of("sysdate", true, false), - Arguments.of("curtime", true, false), - Arguments.of("current_time", true, true), - Arguments.of("curdate", false, false), - Arguments.of("current_date", false, true) + Arguments.of("now", false, false, true), + Arguments.of("current_timestamp", false, true, true), + Arguments.of("localtimestamp", false, true, true), + Arguments.of("localtime", false, true, true), + Arguments.of("sysdate", true, false, false), + Arguments.of("curtime", false, false, true), + Arguments.of("current_time", false, true, true), + Arguments.of("curdate", false, false, true), + Arguments.of("current_date", false, true, true) ); } @ParameterizedTest(name = "{0}") @MethodSource("nowLikeFunctionsData") - public void test_now_like_functions(String name, Boolean hasFsp, Boolean hasShortcut) { + public void test_now_like_functions(String name, Boolean hasFsp, Boolean hasShortcut, + Boolean isConstantFunction) { for (var call : hasShortcut ? List.of(name, name + "()") : List.of(name + "()")) { assertEquals( project( values(emptyList()), - alias(call, function(name)) + alias(call, (isConstantFunction ? constantFunction(name) : function(name))) ), buildAST("SELECT " + call) ); @@ -707,7 +709,7 @@ public void test_now_like_functions(String name, Boolean hasFsp, Boolean hasShor function( "=", qualifiedName("data"), - function(name)) + (isConstantFunction ? constantFunction(name) : function(name))) ), AllFields.of() ), @@ -715,6 +717,7 @@ public void test_now_like_functions(String name, Boolean hasFsp, Boolean hasShor ); } + // Unfortunately, only real functions (not ConstantFunction) might have `fsp` now. if (hasFsp) { assertEquals( project(