Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
20 changes: 14 additions & 6 deletions core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4
Original file line number Diff line number Diff line change
Expand Up @@ -760,11 +760,7 @@ booleanValue
;

interval
: INTERVAL sign=(PLUS | MINUS)? string from=intervalField (TO to=intervalField)?
;

intervalField
: YEAR | MONTH | DAY | HOUR | MINUTE | SECOND
: INTERVAL sign=(PLUS | MINUS)? string intervalQualifier
;

normalForm
Expand All @@ -773,7 +769,7 @@ normalForm

type
: ROW '(' rowField (',' rowField)* ')' #rowType
| INTERVAL from=intervalField (TO to=intervalField)? #intervalType
| INTERVAL intervalQualifier #intervalType
| base=TIMESTAMP ('(' precision = typeParameter ')')? (WITHOUT TIME ZONE)? #dateTimeType
| base=TIMESTAMP ('(' precision = typeParameter ')')? WITH TIME ZONE #dateTimeType
| base=TIME ('(' precision = typeParameter ')')? (WITHOUT TIME ZONE)? #dateTimeType
Expand All @@ -785,6 +781,18 @@ type
| identifier ('(' typeParameter (',' typeParameter)* ')')? #genericType
;

intervalQualifier
: YEAR ('(' precision=INTEGER_VALUE ')')? TO MONTH #compositeYearToMonthInterval
| field=(YEAR | MONTH) ('(' precision=INTEGER_VALUE ')')? #simpleYearMonthInterval
| start=(DAY | HOUR | MINUTE) ('(' leadingPrecision=INTEGER_VALUE ')')?
TO (
end=HOUR |
end=MINUTE |
end=SECOND ('(' fractionalPrecision=INTEGER_VALUE ')')?) #compositeDayTimeInterval
| field=(MONTH | DAY | HOUR | MINUTE) ('(' precision=INTEGER_VALUE ')')? #simpleDayTimeInterval
| SECOND ('(' leadingPrecision=INTEGER_VALUE (',' fractionalPrecision=INTEGER_VALUE)? ')')? #secondsDayTimeInterval
;

rowField
: type
| identifier type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
import io.trino.sql.tree.DataType;
import io.trino.sql.tree.DateTimeDataType;
import io.trino.sql.tree.GenericDataType;
import io.trino.sql.tree.IntervalDayTimeDataType;
import io.trino.sql.tree.IntervalDataType;
import io.trino.sql.tree.NumericParameter;
import io.trino.sql.tree.RowDataType;
import io.trino.sql.tree.TypeParameter;
Expand Down Expand Up @@ -122,7 +122,7 @@ private static String formatType(DataType type, boolean supportsParametricDateTi
})
.collect(Collectors.joining(", ", dataType.getName().getValue() + "(", ")"));
}
case IntervalDayTimeDataType _ -> ExpressionFormatter.formatExpression(type);
case IntervalDataType _ -> ExpressionFormatter.formatExpression(type);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
import io.trino.sql.tree.Cast;
import io.trino.sql.tree.CoalesceExpression;
import io.trino.sql.tree.ComparisonExpression;
import io.trino.sql.tree.CompositeIntervalQualifier;
import io.trino.sql.tree.CurrentCatalog;
import io.trino.sql.tree.CurrentDate;
import io.trino.sql.tree.CurrentPath;
Expand All @@ -103,7 +104,9 @@
import io.trino.sql.tree.IfExpression;
import io.trino.sql.tree.InListExpression;
import io.trino.sql.tree.InPredicate;
import io.trino.sql.tree.IntervalField;
import io.trino.sql.tree.IntervalLiteral;
import io.trino.sql.tree.IntervalQualifier;
import io.trino.sql.tree.IsNotNullPredicate;
import io.trino.sql.tree.IsNullPredicate;
import io.trino.sql.tree.JsonArray;
Expand Down Expand Up @@ -142,6 +145,7 @@
import io.trino.sql.tree.RowPattern;
import io.trino.sql.tree.SearchedCaseExpression;
import io.trino.sql.tree.SimpleCaseExpression;
import io.trino.sql.tree.SimpleIntervalQualifier;
import io.trino.sql.tree.SkipTo;
import io.trino.sql.tree.SortItem;
import io.trino.sql.tree.SortItem.Ordering;
Expand Down Expand Up @@ -172,6 +176,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand Down Expand Up @@ -225,6 +230,7 @@
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
import static io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;
import static io.trino.spi.StandardErrorCode.OPERATOR_NOT_FOUND;
import static io.trino.spi.StandardErrorCode.SYNTAX_ERROR;
import static io.trino.spi.StandardErrorCode.TOO_MANY_ARGUMENTS;
import static io.trino.spi.StandardErrorCode.TYPE_MISMATCH;
import static io.trino.spi.StandardErrorCode.TYPE_NOT_FOUND;
Expand Down Expand Up @@ -1266,13 +1272,18 @@ private Type processTimestampLiteral(GenericLiteral node)
@Override
protected Type visitIntervalLiteral(IntervalLiteral node, Context context)
{
Type type;
if (node.isYearToMonth()) {
type = INTERVAL_YEAR_MONTH;
}
else {
type = INTERVAL_DAY_TIME;
}
validateIntervalQualifier(node.qualifier());

IntervalField field = switch (node.qualifier()) {
case SimpleIntervalQualifier simple -> simple.getField();
case CompositeIntervalQualifier composite -> composite.getTo(); // we only need to check one. The other one is validated in validateIntervalQualifier
};

Type type = switch (field) {
case IntervalField.Year(), IntervalField.Month() -> INTERVAL_YEAR_MONTH;
case IntervalField.Day(), IntervalField.Hour(), IntervalField.Minute(), IntervalField.Second(OptionalInt _) -> INTERVAL_DAY_TIME;
};

try {
literalInterpreter.evaluate(node, type);
}
Expand Down Expand Up @@ -3428,6 +3439,37 @@ private void addOrReplaceExpressionsCoercion(Collection<NodeRef<Expression>> exp
}
}

private static void validateIntervalQualifier(IntervalQualifier qualifier)
{
if (qualifier instanceof CompositeIntervalQualifier composite) {
boolean valid = (composite.getFrom() instanceof IntervalField.Year() && composite.getTo() instanceof IntervalField.Month()) ||
(composite.getFrom() instanceof IntervalField.Day() && (
composite.getTo() instanceof IntervalField.Hour() ||
composite.getTo() instanceof IntervalField.Minute() ||
composite.getTo() instanceof IntervalField.Second(OptionalInt _))) ||
(composite.getFrom() instanceof IntervalField.Hour() && (
composite.getTo() instanceof IntervalField.Minute() ||
composite.getTo() instanceof IntervalField.Second(OptionalInt _))) ||
(composite.getFrom() instanceof IntervalField.Minute() && composite.getTo() instanceof IntervalField.Second(OptionalInt _));

if (!valid) {
throw semanticException(SYNTAX_ERROR, qualifier, "Invalid INTERVAL qualifier");
}
}

if (qualifier instanceof SimpleIntervalQualifier simple && (
simple.getPrecision().isPresent() ||
(simple.getField() instanceof IntervalField.Second(OptionalInt fractionalPrecision) && fractionalPrecision.isPresent()))) {
throw semanticException(NOT_SUPPORTED, qualifier, "Only INTERVAL literals with default precision are supported");
}

if (qualifier instanceof CompositeIntervalQualifier composite && (
composite.getPrecision().isPresent() ||
(composite.getTo() instanceof IntervalField.Second(OptionalInt fractionalPrecision) && fractionalPrecision.isPresent()))) {
throw semanticException(NOT_SUPPORTED, qualifier, "Only INTERVAL literals with default precision are supported");
}
}

private static class Context
{
private final Scope scope;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,21 @@
import io.trino.sql.tree.AstVisitor;
import io.trino.sql.tree.BinaryLiteral;
import io.trino.sql.tree.BooleanLiteral;
import io.trino.sql.tree.CompositeIntervalQualifier;
import io.trino.sql.tree.DecimalLiteral;
import io.trino.sql.tree.DoubleLiteral;
import io.trino.sql.tree.GenericLiteral;
import io.trino.sql.tree.IntervalField;
import io.trino.sql.tree.IntervalLiteral;
import io.trino.sql.tree.Literal;
import io.trino.sql.tree.LongLiteral;
import io.trino.sql.tree.NullLiteral;
import io.trino.sql.tree.SimpleIntervalQualifier;
import io.trino.sql.tree.StringLiteral;
import io.trino.type.IntervalDayTimeType;
import io.trino.type.IntervalYearMonthType;

import java.util.Optional;
import java.util.function.Function;

import static io.airlift.slice.Slices.utf8Slice;
Expand Down Expand Up @@ -157,10 +163,25 @@ protected Object visitGenericLiteral(GenericLiteral node, Void context)
@Override
protected Long visitIntervalLiteral(IntervalLiteral node, Void context)
{
if (node.isYearToMonth()) {
return node.getSign().multiplier() * parseYearMonthInterval(node.getValue(), node.getStartField(), node.getEndField());
}
return node.getSign().multiplier() * parseDayTimeInterval(node.getValue(), node.getStartField(), node.getEndField());
// TODO: the value should be interpreted according to the analyzed type. However, currently the analyzed type
// is hard-coded to either INTERVAL DAY TO SECOND or INTERVAL YEAR TO MONTH, as arbitrary precision
// invervals are not yet supported in the underlying type system.

IntervalField start = switch (node.qualifier()) {
case SimpleIntervalQualifier simple -> simple.getField();
case CompositeIntervalQualifier composite -> composite.getFrom();
};

Optional<IntervalField> end = switch (node.qualifier()) {
case SimpleIntervalQualifier _ -> Optional.empty();
case CompositeIntervalQualifier composite -> Optional.of(composite.getTo());
};

return switch (type) {
case IntervalDayTimeType _ -> node.getSign().multiplier() * parseDayTimeInterval(node.getValue(), start, end);
case IntervalYearMonthType _ -> node.getSign().multiplier() * parseYearMonthInterval(node.getValue(), start, end);
default -> throw new UnsupportedOperationException("Unhandled interval type: " + type);
};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,23 @@
import io.trino.spi.type.VarcharType;
import io.trino.sql.ReservedIdentifiers;
import io.trino.sql.parser.SqlParser;
import io.trino.sql.tree.CompositeIntervalQualifier;
import io.trino.sql.tree.DataType;
import io.trino.sql.tree.DataTypeParameter;
import io.trino.sql.tree.DateTimeDataType;
import io.trino.sql.tree.GenericDataType;
import io.trino.sql.tree.Identifier;
import io.trino.sql.tree.IntervalDayTimeDataType;
import io.trino.sql.tree.IntervalDataType;
import io.trino.sql.tree.IntervalField;
import io.trino.sql.tree.NodeLocation;
import io.trino.sql.tree.NumericParameter;
import io.trino.sql.tree.RowDataType;
import io.trino.sql.tree.TypeParameter;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.TreeSet;

Expand Down Expand Up @@ -105,7 +109,7 @@ private static TypeSignature toTypeSignature(DataType type, Set<String> typeVari
{
return switch (type) {
case DateTimeDataType dateTimeDataType -> toTypeSignature(dateTimeDataType, typeVariables);
case IntervalDayTimeDataType intervalDayTimeDataType -> toTypeSignature(intervalDayTimeDataType);
case IntervalDataType intervalDataType -> toTypeSignature(intervalDataType);
case RowDataType rowDataType -> toTypeSignature(rowDataType, typeVariables);
case GenericDataType genericDataType -> toTypeSignature(genericDataType, typeVariables);
};
Expand Down Expand Up @@ -175,17 +179,24 @@ private static TypeSignature toTypeSignature(RowDataType type, Set<String> typeV
return new TypeSignature(ROW, parameters);
}

private static TypeSignature toTypeSignature(IntervalDayTimeDataType type)
private static TypeSignature toTypeSignature(IntervalDataType type)
{
if (type.getFrom() == IntervalDayTimeDataType.Field.YEAR && type.getTo() == IntervalDayTimeDataType.Field.MONTH) {
if (type.qualifier() instanceof CompositeIntervalQualifier qualifier &&
qualifier.getFrom() instanceof IntervalField.Year() &&
qualifier.getTo() instanceof IntervalField.Month &&
qualifier.getPrecision().isEmpty()) {
return INTERVAL_YEAR_MONTH.getTypeSignature();
}

if (type.getFrom() == IntervalDayTimeDataType.Field.DAY && type.getTo() == IntervalDayTimeDataType.Field.SECOND) {
if (type.qualifier() instanceof CompositeIntervalQualifier qualifier &&
qualifier.getFrom() instanceof IntervalField.Day() &&
qualifier.getTo() instanceof IntervalField.Second(OptionalInt fractionalPrecision) &&
qualifier.getPrecision().isEmpty() &&
fractionalPrecision.isEmpty()) {
return INTERVAL_DAY_TIME.getTypeSignature();
}

throw new TrinoException(NOT_SUPPORTED, format("INTERVAL %s TO %s type not supported", type.getFrom(), type.getTo()));
throw new TrinoException(NOT_SUPPORTED, format("INTERVAL %s type not supported", type.qualifier()));
}

private static TypeSignature toTypeSignature(DateTimeDataType type, Set<String> typeVariables)
Expand Down Expand Up @@ -235,8 +246,20 @@ private static String canonicalize(Identifier identifier)
static DataType toDataType(TypeSignature typeSignature)
{
return switch (typeSignature.getBase()) {
case INTERVAL_YEAR_TO_MONTH -> new IntervalDayTimeDataType(Optional.empty(), IntervalDayTimeDataType.Field.YEAR, IntervalDayTimeDataType.Field.MONTH);
case INTERVAL_DAY_TO_SECOND -> new IntervalDayTimeDataType(Optional.empty(), IntervalDayTimeDataType.Field.DAY, IntervalDayTimeDataType.Field.SECOND);
case INTERVAL_YEAR_TO_MONTH -> new IntervalDataType(
Optional.empty(),
new CompositeIntervalQualifier(
new NodeLocation(1, 1),
OptionalInt.empty(),
new IntervalField.Year(),
new IntervalField.Month()));
case INTERVAL_DAY_TO_SECOND -> new IntervalDataType(
Optional.empty(),
new CompositeIntervalQualifier(
new NodeLocation(1, 1),
OptionalInt.empty(),
new IntervalField.Day(),
new IntervalField.Second(OptionalInt.empty())));
case TIMESTAMP_WITH_TIME_ZONE -> new DateTimeDataType(
Optional.empty(),
DateTimeDataType.Type.TIMESTAMP,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import io.trino.sql.tree.Cast;
import io.trino.sql.tree.CoalesceExpression;
import io.trino.sql.tree.ComparisonExpression;
import io.trino.sql.tree.CompositeIntervalQualifier;
import io.trino.sql.tree.CurrentCatalog;
import io.trino.sql.tree.CurrentDate;
import io.trino.sql.tree.CurrentPath;
Expand All @@ -83,6 +84,7 @@
import io.trino.sql.tree.IfExpression;
import io.trino.sql.tree.InListExpression;
import io.trino.sql.tree.InPredicate;
import io.trino.sql.tree.IntervalField;
import io.trino.sql.tree.IntervalLiteral;
import io.trino.sql.tree.IsNotNullPredicate;
import io.trino.sql.tree.IsNullPredicate;
Expand All @@ -109,6 +111,7 @@
import io.trino.sql.tree.Row;
import io.trino.sql.tree.SearchedCaseExpression;
import io.trino.sql.tree.SimpleCaseExpression;
import io.trino.sql.tree.SimpleIntervalQualifier;
import io.trino.sql.tree.StringLiteral;
import io.trino.sql.tree.SubscriptExpression;
import io.trino.sql.tree.Trim;
Expand Down Expand Up @@ -396,12 +399,26 @@ private io.trino.sql.ir.Expression translate(IntervalLiteral expression)
{
Type type = analysis.getType(expression);

// TODO: the value should be interpreted according to the analyzed type. However, currently the analyzed type
// is hard-coded to either INTERVAL DAY TO SECOND or INTERVAL YEAR TO MONTH, as arbitrary precision
// invervals are not yet supported in the underlying type system.

IntervalField start = switch (expression.qualifier()) {
case SimpleIntervalQualifier simple -> simple.getField();
case CompositeIntervalQualifier composite -> composite.getFrom();
};

Optional<IntervalField> end = switch (expression.qualifier()) {
case SimpleIntervalQualifier _ -> Optional.empty();
case CompositeIntervalQualifier composite -> Optional.of(composite.getTo());
};

return new Constant(
type,
switch (type) {
case IntervalYearMonthType t -> expression.getSign().multiplier() * parseYearMonthInterval(expression.getValue(), expression.getStartField(), expression.getEndField());
case IntervalDayTimeType t -> expression.getSign().multiplier() * parseDayTimeInterval(expression.getValue(), expression.getStartField(), expression.getEndField());
default -> throw new IllegalArgumentException("Unexpected type for IntervalLiteral: %s" + type);
case IntervalDayTimeType _ -> expression.getSign().multiplier() * parseDayTimeInterval(expression.getValue(), start, end);
case IntervalYearMonthType _ -> expression.getSign().multiplier() * parseYearMonthInterval(expression.getValue(), start, end);
default -> throw new UnsupportedOperationException("Unhandled interval type: " + type);
});
}

Expand Down
Loading