Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@

package org.opensearch.sql.calcite;

import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_DATE;
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_TIME;
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_TIMESTAMP;

import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
Expand All @@ -19,6 +24,8 @@
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.opensearch.sql.ast.expression.SpanUnit;
import org.opensearch.sql.calcite.type.ExprSqlType;
import org.opensearch.sql.expression.function.PPLBuiltinOperators;

public class ExtendedRexBuilder extends RexBuilder {

Expand Down Expand Up @@ -117,6 +124,16 @@ public RexNode makeCast(
// SqlStdOperatorTable.NOT_EQUALS,
// ImmutableList.of(exp, makeZeroLiteral(exp.getType())));
}
} else if (type instanceof ExprSqlType exprSqlType
&& Set.of(EXPR_DATE, EXPR_TIME, EXPR_TIMESTAMP).contains(exprSqlType.getUdt())) {
switch (exprSqlType.getUdt()) {
case EXPR_DATE:
return makeCall(type, PPLBuiltinOperators.DATE, List.of(exp));
case EXPR_TIME:
return makeCall(type, PPLBuiltinOperators.TIME, List.of(exp));
case EXPR_TIMESTAMP:
return makeCall(type, PPLBuiltinOperators.TIMESTAMP, List.of(exp));
}
}
return super.makeCast(pos, type, exp, matchNullability, safe, format);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.apache.calcite.avatica.util.TimeUnit;
import org.opensearch.sql.data.model.*;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.expression.function.FunctionProperties;

public final class DateTimeConversionUtils {
Expand All @@ -36,7 +36,7 @@ public static ExprTimestampValue forceConvertToTimestampValue(
ExprValueUtils.timestampValue(timeValue.timestampValue(properties));
case ExprStringValue stringValue -> new ExprTimestampValue(
DateTimeParser.parse(stringValue.stringValue()));
default -> throw new SemanticCheckException(
default -> throw new ExpressionEvaluationException(
String.format(
"Cannot convert %s to timestamp, only STRING, DATE, TIME and TIMESTAMP are supported",
value.type()));
Expand All @@ -63,8 +63,8 @@ public static ExprTimestampValue convertToTimestampValue(
} else {
try {
return new ExprTimestampValue(value.timestampValue());
} catch (SemanticCheckException e) {
throw new SemanticCheckException(
} catch (ExpressionEvaluationException e) {
throw new ExpressionEvaluationException(
String.format(
"Cannot convert %s to timestamp, only STRING, DATE, TIME and TIMESTAMP are"
+ " supported",
Expand Down Expand Up @@ -98,7 +98,7 @@ public static ExprDateValue convertToDateValue(ExprValue value, FunctionProperti
return new ExprDateValue(value.stringValue());
}
default -> {
throw new SemanticCheckException(
throw new ExpressionEvaluationException(
String.format(
"Cannot convert %s to date, only STRING, DATE, TIME and TIMESTAMP are supported",
value.type()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,127 +5,38 @@

package org.opensearch.sql.calcite.utils.datetime;

import com.google.common.collect.ImmutableList;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.opensearch.sql.exception.SemanticCheckException;
import java.time.format.DateTimeParseException;
import java.util.Locale;
import org.opensearch.sql.utils.DateTimeFormatters;

public interface DateTimeParser {
/**
* Parse a string into a LocalDateTime If only date is found, time is set to 00:00:00. If only
* time is found, date is set to today.
* Parse a string into a LocalDateTime. If only date is found, time is set to 00:00:00. If only
* time is found, date is set to today at UTC.
*
* @param input A date/time/timestamp string
* @return A LocalDateTime
* @throws IllegalArgumentException if parsing fails
*/
static LocalDateTime parse(String input) {

if (input == null || input.trim().isEmpty()) {
throw new SemanticCheckException("Cannot parse a null/empty date-time string.");
}

if (input.contains(":")) {
try {
return parseTimestamp(input);
} catch (Exception ignored) {
}

try {
LocalTime t = parseTime(input);
return LocalDateTime.of(LocalDate.now(ZoneId.of("UTC")), t);
} catch (Exception ignored) {
}
} else {
try {
LocalDate d = parseDate(input);
return d.atStartOfDay();
} catch (Exception ignored) {
}
}
throw new SemanticCheckException(String.format("Unable to parse %s as datetime", input));
}

static LocalDateTime parseTimeOrTimestamp(String input) {
if (input == null || input.trim().isEmpty()) {
throw new SemanticCheckException("Cannot parse a null/empty date-time string.");
}

try {
return parseTime(input).atDate(LocalDate.now(ZoneId.of("UTC")));
} catch (Exception ignored) {
return LocalDateTime.parse(input, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER);
} catch (DateTimeParseException ignored) {
}

try {
return parseTimestamp(input);
LocalTime t = LocalTime.parse(input, DateTimeFormatters.TIME_TIMESTAMP_FORMATTER);
return LocalDateTime.of(LocalDate.now(ZoneId.of("UTC")), t);
} catch (Exception ignored) {
throw new IllegalArgumentException(
String.format(
Locale.ROOT,
"timestamp:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
input));
}

throw new SemanticCheckException(
String.format("time:%s in unsupported format, please use 'HH:mm:ss[.SSSSSSSSS]'", input));
}

static LocalDateTime parseDateOrTimestamp(String input) {
if (input == null || input.trim().isEmpty()) {
throw new SemanticCheckException("Cannot parse a null/empty date-time string.");
}

try {
return parseDate(input).atStartOfDay();
} catch (Exception ignored) {
}

try {
return parseTimestamp(input);
} catch (Exception ignored) {
}

throw new SemanticCheckException(
String.format("date:%s in unsupported format, please use 'yyyy-MM-dd'", input));
}

static LocalDateTime parseTimestamp(String input) {
List<DateTimeFormatter> dateTimeFormatters =
ImmutableList.of(DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL);

for (DateTimeFormatter fmt : dateTimeFormatters) {
try {
return LocalDateTime.parse(input, fmt);
} catch (Exception ignored) {
}
}
throw new SemanticCheckException(
String.format(
"timestamp:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
input));
}

static LocalTime parseTime(String input) {
List<DateTimeFormatter> timeFormatters = ImmutableList.of(DateTimeFormatter.ISO_TIME);
for (DateTimeFormatter fmt : timeFormatters) {
try {
return LocalTime.parse(input, fmt);
} catch (Exception ignored) {
}
}
throw new SemanticCheckException(
String.format("time:%s in unsupported format, please use 'HH:mm:ss[.SSSSSSSSS]'", input));
}

static LocalDate parseDate(String input) {
List<DateTimeFormatter> dateFormatters = ImmutableList.of(DateTimeFormatter.ISO_DATE);
for (DateTimeFormatter fmt : dateFormatters) {
try {
return LocalDate.parse(input, fmt);
} catch (Exception ignored) {
}
}
throw new SemanticCheckException(
String.format("date:%s in unsupported format, please use 'yyyy-MM-dd'", input));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

package org.opensearch.sql.data.model;

import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL;

import com.google.common.base.Objects;
import java.time.Instant;
import java.time.LocalDate;
Expand All @@ -18,20 +16,25 @@
import lombok.RequiredArgsConstructor;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.data.type.ExprType;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.utils.DateTimeFormatters;

/** Expression Date Value. */
@RequiredArgsConstructor
public class ExprDateValue extends AbstractExprValue {

private final LocalDate date;

/** Constructor of ExprDateValue. */
/**
* Constructor with date string.
*
* @param date a date or timestamp string (does not accept time string)
*/
public ExprDateValue(String date) {
try {
this.date = LocalDate.parse(date, DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL);
this.date = LocalDate.parse(date, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER);
} catch (DateTimeParseException e) {
throw new SemanticCheckException(
throw new ExpressionEvaluationException(
String.format("date:%s in unsupported format, please use 'yyyy-MM-dd'", date));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import lombok.RequiredArgsConstructor;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.data.type.ExprType;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.exception.ExpressionEvaluationException;

/** Expression String Value. */
@RequiredArgsConstructor
Expand All @@ -39,7 +39,7 @@ public String stringValue() {
public Instant timestampValue() {
try {
return new ExprTimestampValue(value).timestampValue();
} catch (SemanticCheckException e) {
} catch (ExpressionEvaluationException e) {
return new ExprTimestampValue(
LocalDateTime.of(new ExprDateValue(value).dateValue(), LocalTime.of(0, 0, 0)))
.timestampValue();
Expand All @@ -50,7 +50,7 @@ public Instant timestampValue() {
public LocalDate dateValue() {
try {
return new ExprTimestampValue(value).dateValue();
} catch (SemanticCheckException e) {
} catch (ExpressionEvaluationException e) {
return new ExprDateValue(value).dateValue();
}
}
Expand All @@ -59,7 +59,7 @@ public LocalDate dateValue() {
public LocalTime timeValue() {
try {
return new ExprTimestampValue(value).timeValue();
} catch (SemanticCheckException e) {
} catch (ExpressionEvaluationException e) {
return new ExprTimeValue(value).timeValue();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package org.opensearch.sql.data.model;

import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL;

import java.time.Instant;
import java.time.LocalDate;
Expand All @@ -18,21 +17,26 @@
import lombok.RequiredArgsConstructor;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.data.type.ExprType;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.expression.function.FunctionProperties;
import org.opensearch.sql.utils.DateTimeFormatters;

/** Expression Time Value. */
@RequiredArgsConstructor
public class ExprTimeValue extends AbstractExprValue {

private final LocalTime time;

/** Constructor of ExprTimeValue. */
/**
* Constructor with time string.
*
* @param time a time or timestamp string (does not accept date string)
*/
public ExprTimeValue(String time) {
try {
this.time = LocalTime.parse(time, DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL);
this.time = LocalTime.parse(time, DateTimeFormatters.TIME_TIMESTAMP_FORMATTER);
} catch (DateTimeParseException e) {
throw new SemanticCheckException(
throw new ExpressionEvaluationException(
String.format("time:%s in unsupported format, please use 'HH:mm:ss[.SSSSSSSSS]'", time));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,27 @@
import lombok.RequiredArgsConstructor;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.data.type.ExprType;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.utils.DateTimeFormatters;

/** Expression Timestamp Value. */
@RequiredArgsConstructor
public class ExprTimestampValue extends AbstractExprValue {

private final Instant timestamp;

/** Constructor. */
/**
* Constructor with timestamp string.
*
* @param timestamp a date or timestamp string (does not accept time string)
*/
public ExprTimestampValue(String timestamp) {
try {
this.timestamp =
LocalDateTime.parse(timestamp, DATE_TIME_FORMATTER_VARIABLE_NANOS)
.atZone(ZoneOffset.UTC)
.toInstant();
LocalDateTime.parse(timestamp, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER)
.toInstant(ZoneOffset.UTC);
} catch (DateTimeParseException e) {
throw new SemanticCheckException(
throw new ExpressionEvaluationException(
String.format(
"timestamp:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
timestamp));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ public class DateTimeFormatters {
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT);

public static final DateTimeFormatter DATE_TIMESTAMP_FORMATTER =
new DateTimeFormatterBuilder()
.appendPattern("[uuuu-MM-dd HH:mm:ss][uuuu-MM-dd HH:mm][uuuu-MM-dd]")
.appendFraction(
ChronoField.NANO_OF_SECOND, MIN_FRACTION_SECONDS, MAX_FRACTION_SECONDS, true)
.parseDefaulting(HOUR_OF_DAY, 0)
.parseDefaulting(MINUTE_OF_HOUR, 0)
.parseDefaulting(SECOND_OF_MINUTE, 0)
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT);

public static final DateTimeFormatter TIME_TIMESTAMP_FORMATTER =
new DateTimeFormatterBuilder()
.appendPattern("[uuuu-MM-dd HH:mm:ss][uuuu-MM-dd HH:mm][HH:mm:ss][HH:mm]")
.appendFraction(
ChronoField.NANO_OF_SECOND, MIN_FRACTION_SECONDS, MAX_FRACTION_SECONDS, true)
.parseDefaulting(SECOND_OF_MINUTE, 0)
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.STRICT);

// MDD
public static final DateTimeFormatter DATE_FORMATTER_SINGLE_DIGIT_MONTH =
new DateTimeFormatterBuilder()
Expand Down
Loading
Loading