diff --git a/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 b/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 index 4e9328c3bc6f..d9be3c5648bd 100644 --- a/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 +++ b/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 @@ -720,11 +720,12 @@ booleanValue ; interval - : INTERVAL sign=(PLUS | MINUS)? string from=intervalField (TO to=intervalField)? - ; - -intervalField - : YEAR | MONTH | DAY | HOUR | MINUTE | SECOND + : INTERVAL sign=(PLUS | MINUS)? string from=YEAR (TO to=MONTH)? + | INTERVAL sign=(PLUS | MINUS)? string from=MONTH + | INTERVAL sign=(PLUS | MINUS)? string from=DAY (TO to=(HOUR | MINUTE | SECOND))? + | INTERVAL sign=(PLUS | MINUS)? string from=HOUR (TO to=(MINUTE | SECOND))? + | INTERVAL sign=(PLUS | MINUS)? string from=MINUTE (TO to=SECOND)? + | INTERVAL sign=(PLUS | MINUS)? string from=SECOND ; normalForm @@ -733,7 +734,12 @@ normalForm type : ROW '(' rowField (',' rowField)* ')' #rowType - | INTERVAL from=intervalField (TO to=intervalField)? #intervalType + | INTERVAL from=YEAR (TO to=MONTH)? #yearMonthIntervalDataType + | INTERVAL from=MONTH #yearMonthIntervalDataType + | INTERVAL from=DAY (TO to=(HOUR | MINUTE | SECOND))? #dayTimeIntervalDataType + | INTERVAL from=HOUR (TO to=(MINUTE | SECOND))? #dayTimeIntervalDataType + | INTERVAL from=MINUTE (TO to=MINUTE)? #dayTimeIntervalDataType + | INTERVAL from=SECOND #dayTimeIntervalDataType | base=TIMESTAMP ('(' precision = typeParameter ')')? (WITHOUT TIME ZONE)? #dateTimeType | base=TIMESTAMP ('(' precision = typeParameter ')')? WITH TIME ZONE #dateTimeType | base=TIME ('(' precision = typeParameter ')')? (WITHOUT TIME ZONE)? #dateTimeType diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java index 15ae5b26bce0..13ff4f4e8119 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java @@ -152,6 +152,24 @@ public void testLiterals() assertThatThrownBy(assertions.expression("INTERVAL '--12 -10' DAY TO HOUR")::evaluate) .hasMessage("line 1:12: '--12 -10' is not a valid INTERVAL literal"); + + assertThatThrownBy(assertions.expression("INTERVAL '12' DAY TO YEAR")::evaluate) + .hasMessage("line 1:33: mismatched input 'YEAR'. Expecting: 'HOUR', 'MINUTE', 'SECOND'"); + + assertThatThrownBy(assertions.expression("INTERVAL '12' DAY TO MONTH")::evaluate) + .hasMessage("line 1:33: mismatched input 'MONTH'. Expecting: 'HOUR', 'MINUTE', 'SECOND'"); + + assertThatThrownBy(assertions.expression("INTERVAL '12' DAY TO DAY")::evaluate) + .hasMessage("line 1:33: mismatched input 'DAY'. Expecting: 'HOUR', 'MINUTE', 'SECOND'"); + + assertThatThrownBy(assertions.expression("INTERVAL '12' HOUR TO HOUR")::evaluate) + .hasMessage("line 1:34: mismatched input 'HOUR'. Expecting: 'MINUTE', 'SECOND'"); + + assertThatThrownBy(assertions.expression("INTERVAL '12' MINUTE TO MINUTE")::evaluate) + .hasMessage("line 1:36: mismatched input 'MINUTE'. Expecting: 'SECOND'"); + + assertThatThrownBy(assertions.expression("INTERVAL '12' SECOND TO SECOND")::evaluate) + .hasMessageContaining("line 1:33: mismatched input 'TO'."); } private static SqlIntervalDayTime interval(int day, int hour, int minute, int second, int milliseconds) diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java index e0fe9c19bd6d..31e60a2ca3bc 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java @@ -80,6 +80,12 @@ public void testLiterals() assertThatThrownBy(assertions.expression("INTERVAL '--124--30' YEAR TO MONTH")::evaluate) .hasMessage("line 1:12: '--124--30' is not a valid INTERVAL literal"); + + assertThatThrownBy(assertions.expression("INTERVAL '124' YEAR TO YEAR"):: evaluate) + .hasMessage("line 1:35: mismatched input 'YEAR'. Expecting: 'MONTH'"); + + assertThatThrownBy(assertions.expression("INTERVAL '124' MONTH TO MONTH"):: evaluate) + .hasMessageContaining("line 1:33: mismatched input 'TO'."); } private static SqlIntervalYearMonth interval(int year, int month) diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index ff5170289d75..b283d1ee768b 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -3727,10 +3727,6 @@ public void testLiteral() .hasErrorCode(INVALID_LITERAL); assertFails("SELECT INTERVAL '12.1' DAY TO SECOND") .hasErrorCode(INVALID_LITERAL); - assertFails("SELECT INTERVAL '12' YEAR TO DAY") - .hasErrorCode(INVALID_LITERAL); - assertFails("SELECT INTERVAL '12' SECOND TO MINUTE") - .hasErrorCode(INVALID_LITERAL); // json assertFails("SELECT JSON ''") diff --git a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java index e92a6beb1705..0fa2b09ac3cb 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java +++ b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java @@ -306,7 +306,6 @@ import io.trino.sql.tree.ZeroOrOneQuantifier; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import java.util.ArrayDeque; @@ -3308,9 +3307,8 @@ public Node visitInterval(SqlBaseParser.IntervalContext context) Optional.ofNullable(context.sign) .map(AstBuilder::getIntervalSign) .orElse(IntervalLiteral.Sign.POSITIVE), - getIntervalFieldType((Token) context.from.getChild(0).getPayload()), + getIntervalFieldType(context.from), Optional.ofNullable(context.to) - .map((x) -> x.getChild(0).getPayload()) .map(Token.class::cast) .map(AstBuilder::getIntervalFieldType)); } @@ -3397,11 +3395,25 @@ public Node visitTypeParameter(SqlBaseParser.TypeParameterContext context) } @Override - public Node visitIntervalType(SqlBaseParser.IntervalTypeContext context) + public Node visitYearMonthIntervalDataType(SqlBaseParser.YearMonthIntervalDataTypeContext context) { String from = context.from.getText(); - String to = Optional.ofNullable((ParserRuleContext) context.to) - .map(ParseTree::getText) + String to = Optional.ofNullable(context.to) + .map(Token::getText) + .orElse(from); + + return new IntervalDayTimeDataType( + getLocation(context), + IntervalDayTimeDataType.Field.valueOf(from.toUpperCase(ENGLISH)), + IntervalDayTimeDataType.Field.valueOf(to.toUpperCase(ENGLISH))); + } + + @Override + public Node visitDayTimeIntervalDataType(SqlBaseParser.DayTimeIntervalDataTypeContext context) + { + String from = context.from.getText(); + String to = Optional.ofNullable(context.to) + .map(Token::getText) .orElse(from); return new IntervalDayTimeDataType(