Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add implementation of now, sysdate, localtime and similar functions #754

Merged
merged 3 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@

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 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 QueryContext {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -23,13 +26,26 @@ public class AnalysisContext {
@Getter
private final List<NamedExpression> 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<String, Expression> 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<>();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
dai-chen marked this conversation as resolved.
Show resolved Hide resolved
return value;
}

@Override
public Expression visitFunction(Function node, AnalysisContext context) {
FunctionName functionName = FunctionName.of(node.getFuncName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<UnresolvedExpression> funcArgs) {
super(funcName, funcArgs);
}

@Override
public <R, C> R accept(AbstractNodeVisitor<R, C> nodeVisitor, C context) {
return nodeVisitor.visitConstantFunction(this, context);
}
}
36 changes: 36 additions & 0 deletions core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
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.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import lombok.experimental.UtilityClass;
import org.opensearch.sql.data.model.ExprDateValue;
import org.opensearch.sql.data.model.ExprDatetimeValue;
Expand Down Expand Up @@ -85,6 +89,84 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(to_days());
repository.register(week());
repository.register(year());

repository.register(now());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should have inserted these alphabetically into the above list

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update in next PR

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.
* `fsp` argument support is removed until refactoring to avoid bug where `now()`, `now(x)` and
* `now(y) return different values.
*/
private FunctionResolver now(FunctionName functionName) {
return define(functionName,
impl(() -> new ExprDatetimeValue(formatNow(null)), DATETIME)
);
}

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 FunctionResolver sysdate() {
return define(BuiltinFunctionName.SYSDATE.getName(),
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(formatNow(null).toLocalTime()), TIME)
);
}

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(formatNow(null).toLocalDate()), DATE)
);
}

private FunctionResolver curdate() {
return curdate(BuiltinFunctionName.CURDATE.getName());
}

private FunctionResolver current_date() {
return curdate(BuiltinFunctionName.CURRENT_DATE.getName());
}

/**
Expand Down Expand Up @@ -742,4 +824,24 @@ private ExprValue exprYear(ExprValue date) {
return new ExprIntegerValue(date.dateValue().getYear());
}

/**
* 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 formatNow(@Nullable Integer fsp) {
var res = LocalDateTime.now();
if (fsp == null) {
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]
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,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.
*/
Expand Down
Loading