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 1fa023c7fe2d..75a5d2d9cd0b 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 @@ -586,58 +586,62 @@ valueExpression ; primaryExpression - : literal #literals - | QUESTION_MARK #parameter - | POSITION '(' valueExpression IN valueExpression ')' #position - | '(' expression (',' expression)+ ')' #rowConstructor - | ROW '(' expression (',' expression)* ')' #rowConstructor + : literal #literals + | QUESTION_MARK #parameter + | POSITION '(' valueExpression IN valueExpression ')' #position + | '(' expression (',' expression)+ ')' #rowConstructor + | ROW '(' expression (',' expression)* ')' #rowConstructor | name=LISTAGG '(' setQuantifier? expression (',' string)? (ON OVERFLOW listAggOverflowBehavior)? ')' (WITHIN GROUP '(' orderBy ')') - filter? over? #listagg + filter? over? #listagg | processingMode? qualifiedName '(' (label=identifier '.')? ASTERISK ')' - filter? over? #functionCall + filter? over? #functionCall | processingMode? qualifiedName '(' (setQuantifier? expression (',' expression)*)? - orderBy? ')' filter? (nullTreatment? over)? #functionCall - | identifier over #measure - | identifier '->' expression #lambda - | '(' (identifier (',' identifier)*)? ')' '->' expression #lambda - | '(' query ')' #subqueryExpression + orderBy? ')' filter? (nullTreatment? over)? #functionCall + | identifier over #measure + | identifier '->' expression #lambda + | '(' (identifier (',' identifier)*)? ')' '->' expression #lambda + | '(' query ')' #subqueryExpression // This is an extension to ANSI SQL, which considers EXISTS to be a - | EXISTS '(' query ')' #exists - | CASE operand=expression whenClause+ (ELSE elseExpression=expression)? END #simpleCase - | CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase - | CAST '(' expression AS type ')' #cast - | TRY_CAST '(' expression AS type ')' #cast - | ARRAY '[' (expression (',' expression)*)? ']' #arrayConstructor - | '[' (expression (',' expression)*)? ']' #arrayConstructor - | value=primaryExpression '[' index=valueExpression ']' #subscript - | identifier #columnReference - | base=primaryExpression '.' fieldName=identifier #dereference - | name=CURRENT_DATE #currentDate - | name=CURRENT_TIME ('(' precision=INTEGER_VALUE ')')? #currentTime - | name=CURRENT_TIMESTAMP ('(' precision=INTEGER_VALUE ')')? #currentTimestamp - | name=LOCALTIME ('(' precision=INTEGER_VALUE ')')? #localTime - | name=LOCALTIMESTAMP ('(' precision=INTEGER_VALUE ')')? #localTimestamp - | name=CURRENT_USER #currentUser - | name=CURRENT_CATALOG #currentCatalog - | name=CURRENT_SCHEMA #currentSchema - | name=CURRENT_PATH #currentPath - | TRIM '(' (trimsSpecification? trimChar=valueExpression? FROM)? - trimSource=valueExpression ')' #trim - | TRIM '(' trimSource=valueExpression ',' trimChar=valueExpression ')' #trim - | SUBSTRING '(' valueExpression FROM valueExpression (FOR valueExpression)? ')' #substring - | NORMALIZE '(' valueExpression (',' normalForm)? ')' #normalize - | EXTRACT '(' identifier FROM valueExpression ')' #extract - | '(' expression ')' #parenthesizedExpression - | GROUPING '(' (qualifiedName (',' qualifiedName)*)? ')' #groupingOperation - | JSON_EXISTS '(' jsonPathInvocation (jsonExistsErrorBehavior ON ERROR)? ')' #jsonExists + | EXISTS '(' query ')' #exists + | CASE operand=expression whenClause+ (ELSE elseExpression=expression)? END #simpleCase + | CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase + | CAST '(' expression AS type ')' #cast + | TRY_CAST '(' expression AS type ')' #cast + // the target is a primaryExpression to support PostgreSQL-style casts + // of the form ::, which are syntactically ambiguous with + // static method calls defined by the SQL spec (and we reserve it for future use) + | primaryExpression DOUBLE_COLON identifier ('(' (expression (',' expression)*)? ')')? #staticMethodCall + | ARRAY '[' (expression (',' expression)*)? ']' #arrayConstructor + | '[' (expression (',' expression)*)? ']' #arrayConstructor + | value=primaryExpression '[' index=valueExpression ']' #subscript + | identifier #columnReference + | base=primaryExpression '.' fieldName=identifier #dereference + | name=CURRENT_DATE #currentDate + | name=CURRENT_TIME ('(' precision=INTEGER_VALUE ')')? #currentTime + | name=CURRENT_TIMESTAMP ('(' precision=INTEGER_VALUE ')')? #currentTimestamp + | name=LOCALTIME ('(' precision=INTEGER_VALUE ')')? #localTime + | name=LOCALTIMESTAMP ('(' precision=INTEGER_VALUE ')')? #localTimestamp + | name=CURRENT_USER #currentUser + | name=CURRENT_CATALOG #currentCatalog + | name=CURRENT_SCHEMA #currentSchema + | name=CURRENT_PATH #currentPath + | TRIM '(' (trimsSpecification? trimChar=valueExpression? FROM)? + trimSource=valueExpression ')' #trim + | TRIM '(' trimSource=valueExpression ',' trimChar=valueExpression ')' #trim + | SUBSTRING '(' valueExpression FROM valueExpression (FOR valueExpression)? ')' #substring + | NORMALIZE '(' valueExpression (',' normalForm)? ')' #normalize + | EXTRACT '(' identifier FROM valueExpression ')' #extract + | '(' expression ')' #parenthesizedExpression + | GROUPING '(' (qualifiedName (',' qualifiedName)*)? ')' #groupingOperation + | JSON_EXISTS '(' jsonPathInvocation (jsonExistsErrorBehavior ON ERROR)? ')' #jsonExists | JSON_VALUE '(' jsonPathInvocation (RETURNING type)? (emptyBehavior=jsonValueBehavior ON EMPTY)? (errorBehavior=jsonValueBehavior ON ERROR)? - ')' #jsonValue + ')' #jsonValue | JSON_QUERY '(' jsonPathInvocation (RETURNING type (FORMAT jsonRepresentation)?)? @@ -645,7 +649,7 @@ primaryExpression ((KEEP | OMIT) QUOTES (ON SCALAR TEXT_STRING)?)? (emptyBehavior=jsonQueryBehavior ON EMPTY)? (errorBehavior=jsonQueryBehavior ON ERROR)? - ')' #jsonQuery + ')' #jsonQuery | JSON_OBJECT '(' ( jsonObjectMember (',' jsonObjectMember)* @@ -1123,6 +1127,7 @@ DISTINCT: 'DISTINCT'; DISTRIBUTED: 'DISTRIBUTED'; DO: 'DO'; DOUBLE: 'DOUBLE'; +DOUBLE_COLON: '::'; DROP: 'DROP'; ELSE: 'ELSE'; EMPTY: 'EMPTY'; diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java index c7011b1829a0..7a31b344a327 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java @@ -145,6 +145,7 @@ import io.trino.sql.tree.SkipTo; import io.trino.sql.tree.SortItem; import io.trino.sql.tree.SortItem.Ordering; +import io.trino.sql.tree.StaticMethodCall; import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SubqueryExpression; import io.trino.sql.tree.SubscriptExpression; @@ -1704,6 +1705,43 @@ private void analyzeFrameRangeOffset(Expression offsetValue, FrameBound.Type bou frameBoundCalculations.put(NodeRef.of(offsetValue), function); } + @Override + protected Type visitStaticMethodCall(StaticMethodCall node, Context context) + { + // PostgreSQL-style casts are syntactically ambiguous with static method calls. So, static method call semantics take precendence. + // A static method call is characterized by the target being an expression whose type is "type". This not yet supported + // as a first-class concept, so we fake it by analyzing the expression normally. If the analysis succeeds, we treat it as + // the target of a cast. + + // Trino allows resolving column names that match type names, so we need to check explicitly + // if this is a type reference in the context of a static method call + if (node.getTarget() instanceof Identifier target) { + try { + plannerContext.getTypeManager().fromSqlType(target.getValue()); + throw semanticException(NOT_SUPPORTED, node, "Static method calls are not supported"); + } + catch (TypeNotFoundException typeException) { + // since the type is not found, this must be a normal value-producing expression. Treat it as a candidate for + // resolving the PostgreSQL-style cast, as explained above. + } + } + + if (!node.getArguments().isEmpty()) { + throw semanticException(NOT_SUPPORTED, node, "Static method calls are not supported"); + } + + process(node.getTarget(), context); + + // assume it's a PostgreSQL-style cast unless result type is not a known type + try { + Type type = plannerContext.getTypeManager().fromSqlType(node.getMethod().getValue()); + return setExpressionType(node, type); + } + catch (Exception e) { + throw semanticException(NOT_SUPPORTED, node, "Static method calls are not supported"); + } + } + @Override protected Type visitWindowOperation(WindowOperation node, Context context) { diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/TranslationMap.java b/core/trino-main/src/main/java/io/trino/sql/planner/TranslationMap.java index dd5701af9ba0..2a3ec1adbb69 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/TranslationMap.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/TranslationMap.java @@ -109,6 +109,7 @@ import io.trino.sql.tree.Row; import io.trino.sql.tree.SearchedCaseExpression; import io.trino.sql.tree.SimpleCaseExpression; +import io.trino.sql.tree.StaticMethodCall; import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SubscriptExpression; import io.trino.sql.tree.Trim; @@ -316,6 +317,7 @@ private io.trino.sql.ir.Expression translate(Expression expr, boolean isRoot) case io.trino.sql.tree.FieldReference expression -> translate(expression); case Identifier expression -> translate(expression); case FunctionCall expression -> translate(expression); + case StaticMethodCall expression -> translate(expression); case DereferenceExpression expression -> translate(expression); case Array expression -> translate(expression); case CurrentCatalog expression -> translate(expression); @@ -663,6 +665,14 @@ private io.trino.sql.ir.Expression translate(FunctionCall expression) .collect(toImmutableList())); } + private io.trino.sql.ir.Expression translate(StaticMethodCall expression) + { + // Currently, only PostgreSQL-style cast shorthand expressions are supported + return new io.trino.sql.ir.Cast( + translateExpression(expression.getTarget()), + analysis.getType(expression)); + } + private io.trino.sql.ir.Expression translate(DereferenceExpression expression) { if (analysis.isColumnReference(expression)) { diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestStaticMethodCall.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestStaticMethodCall.java new file mode 100644 index 000000000000..e5a6b7381dec --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestStaticMethodCall.java @@ -0,0 +1,92 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.operator.scalar; + +import io.trino.spi.type.DoubleType; +import io.trino.spi.type.VarcharType; +import io.trino.sql.query.QueryAssertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) +public class TestStaticMethodCall +{ + private QueryAssertions assertions; + + @BeforeAll + public void init() + { + assertions = new QueryAssertions(); + } + + @AfterAll + public void teardown() + { + assertions.close(); + assertions = null; + } + + @Test + void testPostgreSqlStyleCast() + { + assertThat(assertions.expression("1::double")) + .hasType(DoubleType.DOUBLE) + .isEqualTo(1.0); + + assertThat(assertions.expression("1::varchar")) + .hasType(VarcharType.VARCHAR) + .isEqualTo("1"); + + assertThatThrownBy(() -> assertions.expression("1::varchar(100)").evaluate()) + .hasMessage("line 1:13: Static method calls are not supported"); + + assertThat(assertions.expression("(a + b)::double") + .binding("a", "1") + .binding("b", "2")) + .hasType(DoubleType.DOUBLE) + .isEqualTo(3.0); + + assertThatThrownBy(() -> assertions.expression("1::decimal(3, 2)").evaluate()) + .hasMessage("line 1:13: Static method calls are not supported"); + } + + @Test + void testCall() + { + assertThatThrownBy(() -> assertions.expression("1::double(2)").evaluate()) + .hasMessage("line 1:13: Static method calls are not supported"); + + assertThatThrownBy(() -> assertions.expression("1::foo").evaluate()) + .hasMessage("line 1:13: Static method calls are not supported"); + + assertThatThrownBy(() -> assertions.expression("integer::foo").evaluate()) + .hasMessage("line 1:19: Static method calls are not supported"); + + assertThatThrownBy(() -> assertions.expression("integer::foo(1, 2)").evaluate()) + .hasMessage("line 1:19: Static method calls are not supported"); + + assertThat(assertions.query("SELECT bigint::real FROM (VALUES 1) AS t(bigint)")) + .failure() + .hasMessage("line 1:14: Static method calls are not supported"); + } +} diff --git a/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java index d049cbb55fb6..ace8be991c12 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java @@ -91,6 +91,7 @@ import io.trino.sql.tree.SimpleGroupBy; import io.trino.sql.tree.SkipTo; import io.trino.sql.tree.SortItem; +import io.trino.sql.tree.StaticMethodCall; import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SubqueryExpression; import io.trino.sql.tree.SubscriptExpression; @@ -467,6 +468,24 @@ protected String visitFunctionCall(FunctionCall node, Void context) return builder.toString(); } + @Override + protected String visitStaticMethodCall(StaticMethodCall node, Void context) + { + StringBuilder builder = new StringBuilder(); + + builder.append(process(node.getTarget(), context)) + .append("::") + .append(process(node.getMethod(), context)); + + if (!node.getArguments().isEmpty()) { + builder.append('(') + .append(joinExpressions(node.getArguments())) + .append(')'); + } + + return builder.toString(); + } + @Override protected String visitWindowOperation(WindowOperation node, Void context) { 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 6c65ef8bb6f4..30b1c2b20a29 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 @@ -278,6 +278,7 @@ import io.trino.sql.tree.SortItem; import io.trino.sql.tree.StartTransaction; import io.trino.sql.tree.Statement; +import io.trino.sql.tree.StaticMethodCall; import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SubqueryExpression; import io.trino.sql.tree.SubscriptExpression; @@ -3104,6 +3105,16 @@ else if (processingMode.FINAL() != null) { arguments); } + @Override + public Node visitStaticMethodCall(SqlBaseParser.StaticMethodCallContext context) + { + return new StaticMethodCall( + getLocation(context.DOUBLE_COLON()), + (Expression) visit(context.primaryExpression()), + (Identifier) visit(context.identifier()), + visit(context.expression(), Expression.class)); + } + @Override public Node visitMeasure(SqlBaseParser.MeasureContext context) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java b/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java index 2bb0ce8c75ef..79b0871e821d 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java @@ -322,6 +322,11 @@ protected R visitFunctionCall(FunctionCall node, C context) return visitExpression(node, context); } + protected R visitStaticMethodCall(StaticMethodCall node, C context) + { + return visitExpression(node, context); + } + protected R visitProcessingMode(ProcessingMode node, C context) { return visitNode(node, context); diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/StaticMethodCall.java b/core/trino-parser/src/main/java/io/trino/sql/tree/StaticMethodCall.java new file mode 100644 index 000000000000..8866c1b7c30a --- /dev/null +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/StaticMethodCall.java @@ -0,0 +1,91 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.tree; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; + +public class StaticMethodCall + extends Expression +{ + private final Expression target; + private final Identifier method; + private final List arguments; + + public StaticMethodCall(NodeLocation location, Expression target, Identifier method, List arguments) + { + super(location); + this.target = target; + this.method = method; + this.arguments = arguments; + } + + public Expression getTarget() + { + return target; + } + + public Identifier getMethod() + { + return method; + } + + public List getArguments() + { + return arguments; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitStaticMethodCall(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.builder() + .add(target) + .add(method) + .addAll(arguments) + .build(); + } + + @Override + public boolean shallowEquals(Node other) + { + if (!sameClass(this, other)) { + return false; + } + + return true; + } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof StaticMethodCall that)) { + return false; + } + return Objects.equals(target, that.target) && Objects.equals(method, that.method) && Objects.equals(arguments, that.arguments); + } + + @Override + public int hashCode() + { + return Objects.hash(target, method, arguments); + } +} diff --git a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java index 6ad375c5b3a7..c57edee125ec 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java +++ b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java @@ -212,6 +212,7 @@ import io.trino.sql.tree.SortItem; import io.trino.sql.tree.StartTransaction; import io.trino.sql.tree.Statement; +import io.trino.sql.tree.StaticMethodCall; import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SubqueryExpression; import io.trino.sql.tree.SubscriptExpression; @@ -823,6 +824,46 @@ public void testIf() assertInvalidExpression("IF(true, 1, 0) OVER()", "OVER clause not valid for 'if' function"); } + @Test + void testStaticMethodCall() + { + assertThat(expression("integer::parse('123')")) + .isEqualTo(new StaticMethodCall( + location(1, 8), + new Identifier(location(1, 1), "integer", false), + new Identifier(location(1, 10), "parse", false), + ImmutableList.of(new StringLiteral(location(1, 16), "123")))); + + assertThat(expression("integer::max")) + .isEqualTo(new StaticMethodCall( + location(1, 8), + new Identifier(location(1, 1), "integer", false), + new Identifier(location(1, 10), "max", false), + emptyList() + )); + + assertThat(expression("(a + b)::double")) + .isEqualTo(new StaticMethodCall( + location(1, 8), + new ArithmeticBinaryExpression( + location(1, 4), + ArithmeticBinaryExpression.Operator.ADD, + new Identifier(location(1, 2), "a", false), + new Identifier(location(1, 6), "b", false)), + new Identifier(location(1, 10), "double", false), + emptyList())); + + + assertThatThrownBy(() -> SQL_PARSER.createExpression("1::double precision")) + .hasMessageMatching(".*mismatched input.*"); + + assertThatThrownBy(() -> SQL_PARSER.createExpression("'abc'::timestamp with time zone")) + .hasMessageMatching(".*mismatched input.*"); + + assertThatThrownBy(() -> SQL_PARSER.createExpression("'abc'::interval year to month")) + .hasMessageMatching(".*mismatched input.*"); + } + @Test public void testNullIf() { diff --git a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParserErrorHandling.java b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParserErrorHandling.java index 92e9ec4db3a9..6d77e4700ee4 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParserErrorHandling.java +++ b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParserErrorHandling.java @@ -33,7 +33,7 @@ private static Stream expressions() { return Stream.of( Arguments.of("", "line 1:1: mismatched input ''. Expecting: "), - Arguments.of("1 + 1 x", "line 1:7: mismatched input 'x'. Expecting: '%', '*', '+', '-', '.', '/', 'AND', 'AT', 'OR', '[', '||', , ")); + Arguments.of("1 + 1 x", "line 1:7: mismatched input 'x'. Expecting: '%', '*', '+', '-', '.', '/', '::', 'AND', 'AT', 'OR', '[', '||', , ")); } private static Stream statements() @@ -67,7 +67,7 @@ private static Stream statements() Arguments.of("select 1x from dual", "line 1:8: identifiers must not start with a digit; surround the identifier with double quotes"), Arguments.of("select fuu from dual order by fuu order by fuu", - "line 1:35: mismatched input 'order'. Expecting: '%', '*', '+', ',', '-', '.', '/', 'AND', 'ASC', 'AT', 'DESC', 'FETCH', 'LIMIT', 'NULLS', 'OFFSET', 'OR', '[', '||', , "), + "line 1:35: mismatched input 'order'. Expecting: '%', '*', '+', ',', '-', '.', '/', '::', 'AND', 'ASC', 'AT', 'DESC', 'FETCH', 'LIMIT', 'NULLS', 'OFFSET', 'OR', '[', '||', , "), Arguments.of("select fuu from dual limit 10 order by fuu", "line 1:31: mismatched input 'order'. Expecting: "), Arguments.of("select CAST(12223222232535343423232435343 AS BIGINT)", @@ -99,7 +99,7 @@ private static Stream statements() Arguments.of("SELECT x() over (ROWS select) FROM t", "line 1:23: mismatched input 'select'. Expecting: ')', 'BETWEEN', 'CURRENT', 'GROUPS', 'MEASURES', 'ORDER', 'PARTITION', 'RANGE', 'ROWS', 'UNBOUNDED', "), Arguments.of("SELECT X() OVER (ROWS UNBOUNDED) FROM T", - "line 1:32: mismatched input ')'. Expecting: '%', '(', '*', '+', '-', '->', '.', '/', 'AND', 'AT', 'FOLLOWING', 'OR', 'OVER', 'PRECEDING', '[', '||', , "), + "line 1:32: mismatched input ')'. Expecting: '%', '(', '*', '+', '-', '->', '.', '/', '::', 'AND', 'AT', 'FOLLOWING', 'OR', 'OVER', 'PRECEDING', '[', '||', , "), Arguments.of("SELECT a FROM x ORDER BY (SELECT b FROM t WHERE ", "line 1:49: mismatched input ''. Expecting: "), Arguments.of("SELECT a FROM a AS x TABLESAMPLE x ", @@ -134,7 +134,7 @@ private static Stream statements() Arguments.of("SELECT a FROM \"\".s.t", "line 1:15: Zero-length delimited identifier not allowed"), Arguments.of("WITH t AS (SELECT 1 SELECT t.* FROM t", - "line 1:21: mismatched input 'SELECT'. Expecting: '%', ')', '*', '+', ',', '-', '.', '/', 'AND', 'AS', 'AT', 'EXCEPT', 'FETCH', 'FROM', " + + "line 1:21: mismatched input 'SELECT'. Expecting: '%', ')', '*', '+', ',', '-', '.', '/', '::', 'AND', 'AS', 'AT', 'EXCEPT', 'FETCH', 'FROM', " + "'GROUP', 'HAVING', 'INTERSECT', 'LIMIT', 'OFFSET', 'OR', 'ORDER', 'UNION', 'WHERE', 'WINDOW', '[', '||', " + ", "), Arguments.of("SHOW CATALOGS LIKE '%$_%' ESCAPE", @@ -160,9 +160,9 @@ private static Stream statements() Arguments.of("SELECT * FROM t FOR VERSION AS OF TIMESTAMP WHERE", "line 1:50: mismatched input ''. Expecting: "), Arguments.of("SELECT ROW(DATE '2022-10-10', DOUBLE 12.0)", - "line 1:38: mismatched input '12.0'. Expecting: '%', '(', ')', '*', '+', ',', '-', '->', '.', '/', 'AND', 'AT', 'OR', 'ORDER', 'OVER', 'PRECISION', '[', '||', , "), + "line 1:38: mismatched input '12.0'. Expecting: '%', '(', ')', '*', '+', ',', '-', '->', '.', '/', '::', 'AND', 'AT', 'OR', 'ORDER', 'OVER', 'PRECISION', '[', '||', , "), Arguments.of("VALUES(DATE 2)", - "line 1:13: mismatched input '2'. Expecting: '%', '(', ')', '*', '+', ',', '-', '->', '.', '/', 'AND', 'AT', 'OR', 'OVER', '[', '||', , "), + "line 1:13: mismatched input '2'. Expecting: '%', '(', ')', '*', '+', ',', '-', '->', '.', '/', '::', 'AND', 'AT', 'OR', 'OVER', '[', '||', , "), Arguments.of("SELECT count(DISTINCT *) FROM (VALUES 1)", "line 1:23: mismatched input '*'. Expecting: ")); } @@ -182,7 +182,7 @@ public void testPossibleExponentialBacktracking() "1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * " + "1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * " + "1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9", - "line 1:375: mismatched input ''. Expecting: '%', '*', '+', '-', '.', '/', 'AND', 'AT', 'OR', 'THEN', '[', '||', "); + "line 1:375: mismatched input ''. Expecting: '%', '*', '+', '-', '.', '/', '::', 'AND', 'AT', 'OR', 'THEN', '[', '||', "); } @Test @@ -212,7 +212,7 @@ public void testPossibleExponentialBacktracking2() "OR (f()\n" + "OR (f()\n" + "GROUP BY id", - "line 24:1: mismatched input 'GROUP'. Expecting: '%', ')', '*', '+', ',', '-', '.', '/', 'AND', 'AT', 'FILTER', 'IGNORE', 'OR', 'OVER', 'RESPECT', '[', '||', "); + "line 24:1: mismatched input 'GROUP'. Expecting: '%', ')', '*', '+', ',', '-', '.', '/', '::', 'AND', 'AT', 'FILTER', 'IGNORE', 'OR', 'OVER', 'RESPECT', '[', '||', "); } @ParameterizedTest