diff --git a/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java b/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java index 991bf3689bbf..7f2c6cac24f2 100644 --- a/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java +++ b/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java @@ -38,6 +38,7 @@ import io.trino.spi.procedure.Procedure; import io.trino.spi.ptf.ArgumentSpecification; import io.trino.spi.ptf.ConnectorTableFunction; +import io.trino.spi.ptf.ReturnTypeSpecification.DescribedTable; import io.trino.spi.ptf.TableArgumentSpecification; import io.trino.spi.session.PropertyMetadata; import io.trino.split.RecordPageSourceProvider; @@ -359,5 +360,9 @@ private static void validateTableFunction(ConnectorTableFunction tableFunction) // The 'keep when empty' or 'prune when empty' property must not be explicitly specified for a table argument with row semantics. // Such a table argument is implicitly 'prune when empty'. The TableArgumentSpecification.Builder enforces the 'prune when empty' property // for a table argument with row semantics. + + if (tableFunction.getReturnTypeSpecification() instanceof DescribedTable describedTable) { + checkArgument(describedTable.getDescriptor().isTyped(), "field types missing in returned type specification"); + } } } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java index f07c574a7dfe..fa88130920d7 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java @@ -239,6 +239,8 @@ public class Analysis // names of tables and aliased relations. All names are resolved case-insensitive. private final Map, QualifiedName> relationNames = new LinkedHashMap<>(); private final Map, TableFunctionInvocationAnalysis> tableFunctionAnalyses = new LinkedHashMap<>(); + private final Set> aliasedRelations = new LinkedHashSet<>(); + private final Set> polymorphicTableFunctions = new LinkedHashSet<>(); public Analysis(@Nullable Statement root, Map, Expression> parameters, QueryType queryType) { @@ -1232,6 +1234,26 @@ public QualifiedName getRelationName(Relation relation) return relationNames.get(NodeRef.of(relation)); } + public void addAliased(Relation relation) + { + aliasedRelations.add(NodeRef.of(relation)); + } + + public boolean isAliased(Relation relation) + { + return aliasedRelations.contains(NodeRef.of(relation)); + } + + public void addPolymorphicTableFunction(TableFunctionInvocation invocation) + { + polymorphicTableFunctions.add(NodeRef.of(invocation)); + } + + public boolean isPolymorphicTableFunction(TableFunctionInvocation invocation) + { + return polymorphicTableFunctions.contains(NodeRef.of(invocation)); + } + private boolean isInputTable(Table table) { return !(isUpdateTarget(table) || isInsertTarget(table)); @@ -2192,6 +2214,7 @@ public static class TableFunctionInvocationAnalysis private final Map arguments; private final List tableArgumentAnalyses; private final List> copartitioningLists; + private final int properColumnsCount; private final ConnectorTableFunctionHandle connectorTableFunctionHandle; private final ConnectorTransactionHandle transactionHandle; @@ -2201,6 +2224,7 @@ public TableFunctionInvocationAnalysis( Map arguments, List tableArgumentAnalyses, List> copartitioningLists, + int properColumnsCount, ConnectorTableFunctionHandle connectorTableFunctionHandle, ConnectorTransactionHandle transactionHandle) { @@ -2209,6 +2233,7 @@ public TableFunctionInvocationAnalysis( this.arguments = ImmutableMap.copyOf(arguments); this.tableArgumentAnalyses = ImmutableList.copyOf(tableArgumentAnalyses); this.copartitioningLists = ImmutableList.copyOf(copartitioningLists); + this.properColumnsCount = properColumnsCount; this.connectorTableFunctionHandle = requireNonNull(connectorTableFunctionHandle, "connectorTableFunctionHandle is null"); this.transactionHandle = requireNonNull(transactionHandle, "transactionHandle is null"); } @@ -2238,6 +2263,16 @@ public List> getCopartitioningLists() return copartitioningLists; } + /** + * Proper columns are the columns produced by the table function, as opposed to pass-through columns from input tables. + * Proper columns should be considered the actual result of the table function. + * @return the number of table function's proper columns + */ + public int getProperColumnsCount() + { + return properColumnsCount; + } + public ConnectorTableFunctionHandle getConnectorTableFunctionHandle() { return connectorTableFunctionHandle; diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index adb27afe02df..54f3e9b3dacb 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -286,6 +286,7 @@ import static io.trino.spi.StandardErrorCode.DUPLICATE_COLUMN_NAME; import static io.trino.spi.StandardErrorCode.DUPLICATE_NAMED_QUERY; import static io.trino.spi.StandardErrorCode.DUPLICATE_PROPERTY; +import static io.trino.spi.StandardErrorCode.DUPLICATE_RANGE_VARIABLE; import static io.trino.spi.StandardErrorCode.DUPLICATE_WINDOW_NAME; import static io.trino.spi.StandardErrorCode.EXPRESSION_NOT_CONSTANT; import static io.trino.spi.StandardErrorCode.EXPRESSION_NOT_IN_DISTINCT; @@ -300,6 +301,7 @@ import static io.trino.spi.StandardErrorCode.INVALID_PARTITION_BY; import static io.trino.spi.StandardErrorCode.INVALID_RECURSIVE_REFERENCE; import static io.trino.spi.StandardErrorCode.INVALID_ROW_FILTER; +import static io.trino.spi.StandardErrorCode.INVALID_TABLE_FUNCTION_INVOCATION; import static io.trino.spi.StandardErrorCode.INVALID_VIEW; import static io.trino.spi.StandardErrorCode.INVALID_WINDOW_FRAME; import static io.trino.spi.StandardErrorCode.INVALID_WINDOW_REFERENCE; @@ -1519,15 +1521,6 @@ protected Scope visitTableFunctionInvocation(TableFunctionInvocation node, Optio List> copartitioningLists = analyzeCopartitioning(node.getCopartitioning(), argumentsAnalysis.getTableArgumentAnalyses()); - analysis.setTableFunctionAnalysis(node, new TableFunctionInvocationAnalysis( - catalogHandle, - function.getName(), - argumentsAnalysis.getPassedArguments(), - argumentsAnalysis.getTableArgumentAnalyses(), - copartitioningLists, - functionAnalysis.getHandle(), - transactionHandle)); - // determine the result relation type. // The result relation type of a table function consists of: // 1. passed columns from input tables: @@ -1535,18 +1528,32 @@ protected Scope visitTableFunctionInvocation(TableFunctionInvocation node, Optio // - for tables without the "pass through columns" option, these are the partitioning columns of the table, if any. // 2. columns created by the table function, called the proper columns. ReturnTypeSpecification returnTypeSpecification = function.getReturnTypeSpecification(); + if (returnTypeSpecification == GENERIC_TABLE || !argumentsAnalysis.getTableArgumentAnalyses().isEmpty()) { + analysis.addPolymorphicTableFunction(node); + } Optional analyzedProperColumnsDescriptor = functionAnalysis.getReturnedType(); Descriptor properColumnsDescriptor; if (returnTypeSpecification == ONLY_PASS_THROUGH) { + if (analysis.isAliased(node)) { + // According to SQL standard ISO/IEC 9075-2, 7.6 , p. 409, + // table alias is prohibited for a table function with ONLY PASS THROUGH returned type. + throw semanticException(INVALID_TABLE_FUNCTION_INVOCATION, node, "Alias specified for table function with ONLY PASS THROUGH return type"); + } // this option is only allowed if there are input tables throw semanticException(NOT_SUPPORTED, node, "Returning only pass through columns is not yet supported for table functions"); } - if (returnTypeSpecification == GENERIC_TABLE) { + else if (returnTypeSpecification == GENERIC_TABLE) { + // According to SQL standard ISO/IEC 9075-2, 7.6
, p. 409, + // table alias is mandatory for a polymorphic table function invocation which produces proper columns. + // We don't enforce this requirement. properColumnsDescriptor = analyzedProperColumnsDescriptor .orElseThrow(() -> semanticException(MISSING_RETURN_TYPE, node, "Cannot determine returned relation type for table function " + node.getName())); } - else { - // returned type is statically declared at function declaration and cannot be overridden + else { // returned type is statically declared at function declaration + // According to SQL standard ISO/IEC 9075-2, 7.6
, p. 409, + // table alias is mandatory for a polymorphic table function invocation which produces proper columns. + // We don't enforce this requirement. + // the declared type cannot be overridden if (analyzedProperColumnsDescriptor.isPresent()) { throw semanticException(AMBIGUOUS_RETURN_TYPE, node, "Returned relation type for table function %s is ambiguous", node.getName()); } @@ -1554,12 +1561,22 @@ protected Scope visitTableFunctionInvocation(TableFunctionInvocation node, Optio } // currently we don't support input tables, so the output consists of proper columns only - // TODO implement SQL standard ISO/IEC 9075-2, 4.33 SQL-invoked routines, p. 123 + // TODO implement SQL standard ISO/IEC 9075-2, 4.33 SQL-invoked routines, p. 123, 413, 414 List fields = properColumnsDescriptor.getFields().stream() // per spec, field names are mandatory .map(field -> Field.newUnqualified(field.getName(), field.getType().orElseThrow(() -> new IllegalStateException("missing returned type for proper field")))) .collect(toImmutableList()); + analysis.setTableFunctionAnalysis(node, new TableFunctionInvocationAnalysis( + catalogHandle, + function.getName(), + argumentsAnalysis.getPassedArguments(), + argumentsAnalysis.getTableArgumentAnalyses(), + copartitioningLists, + properColumnsDescriptor.getFields().size(), + functionAnalysis.getHandle(), + transactionHandle)); + return createAndAssignScope(node, scope, fields); } @@ -2367,6 +2384,9 @@ protected Scope visitPatternRecognitionRelation(PatternRecognitionRelation relat { Scope inputScope = process(relation.getInput(), scope); + // MATCH_RECOGNIZE cannot be applied to a polymorphic table function (SQL standard ISO/IEC 9075-2, 7.6
, p. 409) + validateNoNestedTableFunction(relation.getInput(), "row pattern matching"); + // check that input table column names are not ambiguous // Note: This check is not compliant with SQL identifier semantics. Quoted identifiers should have different comparison rules than unquoted identifiers. // However, field names do not contain the information about quotation, and so every comparison is case-insensitive. For example, if there are fields named @@ -2552,10 +2572,16 @@ private ExpressionAnalysis analyzePatternRecognitionExpression(Expression expres protected Scope visitAliasedRelation(AliasedRelation relation, Optional scope) { analysis.setRelationName(relation, QualifiedName.of(ImmutableList.of(relation.getAlias()))); + analysis.addAliased(relation.getRelation()); Scope relationScope = process(relation.getRelation(), scope); + RelationType relationType = relationScope.getRelationType(); + + // special-handle table function invocation + if (relation.getRelation() instanceof TableFunctionInvocation function) { + return createAndAssignScope(relation, scope, aliasTableFunctionInvocation(relation, relationType, function)); + } // todo this check should be inside of TupleDescriptor.withAlias, but the exception needs the node object - RelationType relationType = relationScope.getRelationType(); if (relation.getColumnNames() != null) { int totalColumns = relationType.getVisibleFieldCount(); if (totalColumns != relation.getColumnNames().size()) { @@ -2588,6 +2614,83 @@ protected Scope visitAliasedRelation(AliasedRelation relation, Optional s return createAndAssignScope(relation, scope, descriptor); } + // As described by the SQL standard ISO/IEC 9075-2, 7.6
, p. 409 + private RelationType aliasTableFunctionInvocation(AliasedRelation relation, RelationType relationType, TableFunctionInvocation function) + { + TableFunctionInvocationAnalysis tableFunctionAnalysis = analysis.getTableFunctionAnalysis(function); + int properColumnsCount = tableFunctionAnalysis.getProperColumnsCount(); + + // check that relation alias is different from range variables of all table arguments + tableFunctionAnalysis.getTableArgumentAnalyses().stream() + .map(TableArgumentAnalysis::getName) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(name -> name.hasSuffix(QualifiedName.of(ImmutableList.of(relation.getAlias())))) + .findFirst() + .ifPresent(name -> { + throw semanticException(DUPLICATE_RANGE_VARIABLE, relation.getAlias(), "Relation alias: %s is a duplicate of input table name: %s", relation.getAlias(), name); + }); + + // build the new relation type. the alias must be applied to the proper columns only, + // and it must not shadow the range variables exposed by the table arguments + ImmutableList.Builder fieldsBuilder = ImmutableList.builder(); + // first, put the table function's proper columns with alias + if (relation.getColumnNames() != null) { + // check that number of column aliases matches number of table function's proper columns + if (properColumnsCount != relation.getColumnNames().size()) { + throw semanticException(MISMATCHED_COLUMN_ALIASES, relation, "Column alias list has %s entries but table function has %s proper columns", relation.getColumnNames().size(), properColumnsCount); + } + for (int i = 0; i < properColumnsCount; i++) { + // proper columns are not hidden, so we don't need to skip hidden fields + Field field = relationType.getFieldByIndex(i); + fieldsBuilder.add(Field.newQualified( + QualifiedName.of(ImmutableList.of(relation.getAlias())), + Optional.of(relation.getColumnNames().get(i).getCanonicalValue()), // although the canonical name is recorded, fields are resolved case-insensitive + field.getType(), + field.isHidden(), + field.getOriginTable(), + field.getOriginColumnName(), + field.isAliased())); + } + } + else { + for (int i = 0; i < properColumnsCount; i++) { + Field field = relationType.getFieldByIndex(i); + fieldsBuilder.add(Field.newQualified( + QualifiedName.of(ImmutableList.of(relation.getAlias())), + field.getName(), + field.getType(), + field.isHidden(), + field.getOriginTable(), + field.getOriginColumnName(), + field.isAliased())); + } + } + + // append remaining fields. They are not being aliased, so hidden fields are included + for (int i = properColumnsCount; i < relationType.getAllFieldCount(); i++) { + fieldsBuilder.add(relationType.getFieldByIndex(i)); + } + + List fields = fieldsBuilder.build(); + + // check that there are no duplicate names within the table function's proper columns + Set names = new HashSet<>(); + fields.subList(0, properColumnsCount).stream() + .map(Field::getName) + .filter(Optional::isPresent) + .map(Optional::get) + // field names are resolved case-insensitive + .map(name -> name.toLowerCase(ENGLISH)) + .forEach(name -> { + if (!names.add(name)) { + throw semanticException(DUPLICATE_COLUMN_NAME, relation.getRelation(), "Duplicate name of table function proper column: " + name); + } + }); + + return new RelationType(fields); + } + @Override protected Scope visitSampledRelation(SampledRelation relation, Optional scope) { @@ -2633,9 +2736,33 @@ protected Scope visitSampledRelation(SampledRelation relation, Optional s analysis.setSampleRatio(relation, samplePercentageValue / 100); Scope relationScope = process(relation.getRelation(), scope); + + // TABLESAMPLE cannot be applied to a polymorphic table function (SQL standard ISO/IEC 9075-2, 7.6
, p. 409) + // Note: the below method finds a table function immediately nested in SampledRelation, or aliased. + // Potentially, a table function could be also nested with intervening PatternRecognitionRelation. + // Such case is handled in visitPatternRecognitionRelation(). + validateNoNestedTableFunction(relation.getRelation(), "sample"); + return createAndAssignScope(relation, scope, relationScope.getRelationType()); } + // this method should run after the `base` relation is processed, so that it is + // determined whether the table function is polymorphic + private void validateNoNestedTableFunction(Relation base, String context) + { + TableFunctionInvocation tableFunctionInvocation = null; + if (base instanceof TableFunctionInvocation invocation) { + tableFunctionInvocation = invocation; + } + else if (base instanceof AliasedRelation aliasedRelation && + aliasedRelation.getRelation() instanceof TableFunctionInvocation invocation) { + tableFunctionInvocation = invocation; + } + if (tableFunctionInvocation != null && analysis.isPolymorphicTableFunction(tableFunctionInvocation)) { + throw semanticException(INVALID_TABLE_FUNCTION_INVOCATION, base, "Cannot apply %s to polymorphic table function invocation", context); + } + } + @Override protected Scope visitTableSubquery(TableSubquery node, Optional scope) { diff --git a/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java b/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java index 3fb954fd9235..87df4e83f455 100644 --- a/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java +++ b/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java @@ -24,6 +24,7 @@ import io.trino.spi.ptf.ConnectorTableFunctionHandle; import io.trino.spi.ptf.Descriptor; import io.trino.spi.ptf.DescriptorArgumentSpecification; +import io.trino.spi.ptf.ReturnTypeSpecification.DescribedTable; import io.trino.spi.ptf.ScalarArgument; import io.trino.spi.ptf.ScalarArgumentSpecification; import io.trino.spi.ptf.TableArgumentSpecification; @@ -35,8 +36,10 @@ import static io.airlift.slice.Slices.utf8Slice; import static io.trino.spi.ptf.ReturnTypeSpecification.GenericTable.GENERIC_TABLE; +import static io.trino.spi.ptf.ReturnTypeSpecification.OnlyPassThrough.ONLY_PASS_THROUGH; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; public class TestingTableFunctions @@ -230,4 +233,76 @@ public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransact return ANALYSIS; } } + + public static class OnlyPassThroughFunction + extends AbstractConnectorTableFunction + { + public OnlyPassThroughFunction() + { + super( + SCHEMA_NAME, + "only_pass_through_function", + ImmutableList.of( + TableArgumentSpecification.builder() + .name("INPUT") + .build()), + ONLY_PASS_THROUGH); + } + + @Override + public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransactionHandle transaction, Map arguments) + { + return TableFunctionAnalysis.builder() + .handle(HANDLE) + .build(); + } + } + + public static class MonomorphicStaticReturnTypeFunction + extends AbstractConnectorTableFunction + { + public MonomorphicStaticReturnTypeFunction() + { + super( + SCHEMA_NAME, + "monomorphic_static_return_type_function", + ImmutableList.of(), + new DescribedTable(Descriptor.descriptor( + ImmutableList.of("a", "b"), + ImmutableList.of(BOOLEAN, INTEGER)))); + } + + @Override + public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransactionHandle transaction, Map arguments) + { + return TableFunctionAnalysis.builder() + .handle(HANDLE) + .build(); + } + } + + public static class PolymorphicStaticReturnTypeFunction + extends AbstractConnectorTableFunction + { + public PolymorphicStaticReturnTypeFunction() + { + super( + SCHEMA_NAME, + "polymorphic_static_return_type_function", + ImmutableList.of(TableArgumentSpecification.builder() + .name("INPUT") + .build()), + new DescribedTable(Descriptor.descriptor( + ImmutableList.of("a", "b"), + ImmutableList.of(BOOLEAN, INTEGER)))); + } + + @Override + public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransactionHandle transaction, Map arguments) + { + return TableFunctionAnalysis.builder() + .handle(HANDLE) + .build(); + } + } } 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 329ad50ca54e..0bbb86e404f3 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 @@ -26,6 +26,9 @@ import io.trino.connector.MockConnectorFactory; import io.trino.connector.StaticConnectorFactory; import io.trino.connector.TestingTableFunctions.DescriptorArgumentFunction; +import io.trino.connector.TestingTableFunctions.MonomorphicStaticReturnTypeFunction; +import io.trino.connector.TestingTableFunctions.OnlyPassThroughFunction; +import io.trino.connector.TestingTableFunctions.PolymorphicStaticReturnTypeFunction; import io.trino.connector.TestingTableFunctions.TableArgumentFunction; import io.trino.connector.TestingTableFunctions.TableArgumentRowSemanticsFunction; import io.trino.connector.TestingTableFunctions.TwoScalarArgumentsFunction; @@ -110,6 +113,7 @@ import static io.trino.spi.StandardErrorCode.DUPLICATE_NAMED_QUERY; import static io.trino.spi.StandardErrorCode.DUPLICATE_PARAMETER_NAME; import static io.trino.spi.StandardErrorCode.DUPLICATE_PROPERTY; +import static io.trino.spi.StandardErrorCode.DUPLICATE_RANGE_VARIABLE; import static io.trino.spi.StandardErrorCode.DUPLICATE_WINDOW_NAME; import static io.trino.spi.StandardErrorCode.EXPRESSION_NOT_AGGREGATE; import static io.trino.spi.StandardErrorCode.EXPRESSION_NOT_CONSTANT; @@ -133,6 +137,7 @@ import static io.trino.spi.StandardErrorCode.INVALID_RANGE; import static io.trino.spi.StandardErrorCode.INVALID_RECURSIVE_REFERENCE; import static io.trino.spi.StandardErrorCode.INVALID_ROW_PATTERN; +import static io.trino.spi.StandardErrorCode.INVALID_TABLE_FUNCTION_INVOCATION; import static io.trino.spi.StandardErrorCode.INVALID_VIEW; import static io.trino.spi.StandardErrorCode.INVALID_WINDOW_FRAME; import static io.trino.spi.StandardErrorCode.INVALID_WINDOW_MEASURE; @@ -6481,6 +6486,134 @@ public void testNullArguments() analyze("SELECT * FROM TABLE(system.two_arguments_function('a'))"); } + @Test + public void testTableFunctionInvocationContext() + { + // cannot specify relation alias for table function with ONLY PASS THROUGH return type + assertFails("SELECT * FROM TABLE(system.only_pass_through_function(TABLE(t1))) f(x)") + .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) + .hasMessage("line 1:21: Alias specified for table function with ONLY PASS THROUGH return type"); + + // per SQL standard, relation alias is required for table function with GENERIC TABLE return type. We don't require it. + analyze("SELECT * FROM TABLE(system.two_arguments_function('a', 1)) f(x)"); + analyze("SELECT * FROM TABLE(system.two_arguments_function('a', 1))"); + + // per SQL standard, relation alias is required for table function with statically declared return type, only if the function is polymorphic. + // We don't require aliasing polymorphic functions. + analyze("SELECT * FROM TABLE(system.monomorphic_static_return_type_function())"); + analyze("SELECT * FROM TABLE(system.monomorphic_static_return_type_function()) f(x, y)"); + analyze("SELECT * FROM TABLE(system.polymorphic_static_return_type_function(input => TABLE(t1)))"); + analyze("SELECT * FROM TABLE(system.polymorphic_static_return_type_function(input => TABLE(t1))) f(x, y)"); + + // TODO enable this test when ONLY PASS THROUGH functions are fully analyzed (currently they fail with NOT_SUPPORTED). + // An ONLY PASS THROUGH function had to be used here, because it's the only kind which does not take an alias. +// // sampled +// assertFails("SELECT * FROM TABLE(system.only_pass_through_function(TABLE(t1))) TABLESAMPLE BERNOULLI (10)") +// .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) +// .hasMessage("line 1:21: Cannot apply sample to polymorphic table function invocation"); + + // TODO enable this test when ONLY PASS THROUGH functions are fully analyzed (currently they fail with NOT_SUPPORTED) + // An ONLY PASS THROUGH function had to be used here, because it's the only kind which does not take an alias. +// // row pattern matching +// assertFails(""" +// SELECT * +// FROM TABLE(system.only_pass_through_function(TABLE(t1))) +// MATCH_RECOGNIZE( +// PATTERN (a*) +// DEFINE a AS true) +// """) +// .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) +// .hasMessage("line 2:12: Cannot apply row pattern matching to polymorphic table function invocation"); + + // aliased + sampled + assertFails("SELECT * FROM TABLE(system.two_arguments_function('a', 1)) f(x) TABLESAMPLE BERNOULLI (10)") + .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) + .hasMessage("line 1:15: Cannot apply sample to polymorphic table function invocation"); + + // aliased + row pattern matching + assertFails(""" + SELECT * + FROM TABLE(system.two_arguments_function('a', 1)) f(x) + MATCH_RECOGNIZE( + PATTERN (a*) + DEFINE a AS true + ) t(y) + """) + .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) + .hasMessage("line 2:6: Cannot apply row pattern matching to polymorphic table function invocation"); + + // TODO enable this test when ONLY PASS THROUGH functions are fully analyzed (currently they fail with NOT_SUPPORTED) + // An ONLY PASS THROUGH function had to be used here, because it's the only kind which does not take an alias. +// // row pattern matching + sampled +// assertFails(""" +// SELECT * +// FROM TABLE(system.only_pass_through_function(TABLE(t1))) +// MATCH_RECOGNIZE( +// PATTERN (a*) +// DEFINE a AS true) +// TABLESAMPLE BERNOULLI (10) +// """) +// .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) +// .hasMessage("line 2:12: Cannot apply row pattern matching to polymorphic table function invocation"); + + // aliased + row pattern matching + sampled + assertFails(""" + SELECT * + FROM TABLE(system.two_arguments_function('a', 1)) f(x) + MATCH_RECOGNIZE( + PATTERN (a*) + DEFINE a AS true + ) t(y) + TABLESAMPLE BERNOULLI (10) + """) + .hasErrorCode(INVALID_TABLE_FUNCTION_INVOCATION) + .hasMessage("line 2:6: Cannot apply row pattern matching to polymorphic table function invocation"); + } + + @Test + public void testTableFunctionAliasing() + { + // case-insensitive name matching + assertFails("SELECT * FROM TABLE(system.table_argument_function(TABLE(t1))) T1(x)") + .hasErrorCode(DUPLICATE_RANGE_VARIABLE) + .hasMessage("line 1:64: Relation alias: T1 is a duplicate of input table name: tpch.s1.t1"); + + assertFails("SELECT * FROM TABLE(system.table_argument_function(TABLE(SELECT 1) T1(a))) t1(x)") + .hasErrorCode(DUPLICATE_RANGE_VARIABLE) + .hasMessage("line 1:76: Relation alias: t1 is a duplicate of input table name: t1"); + + analyze("SELECT * FROM TABLE(system.table_argument_function(TABLE(t1) t2)) T1(x)"); + + // the original returned relation type is ("column" : BOOLEAN) + analyze("SELECT column FROM TABLE(system.two_arguments_function('a', 1)) table_alias"); + + analyze("SELECT column_alias FROM TABLE(system.two_arguments_function('a', 1)) table_alias(column_alias)"); + + analyze("SELECT table_alias.column_alias FROM TABLE(system.two_arguments_function('a', 1)) table_alias(column_alias)"); + + assertFails("SELECT column FROM TABLE(system.two_arguments_function('a', 1)) table_alias(column_alias)") + .hasErrorCode(COLUMN_NOT_FOUND) + .hasMessage("line 1:8: Column 'column' cannot be resolved"); + + assertFails("SELECT column FROM TABLE(system.two_arguments_function('a', 1)) table_alias(col1, col2, col3)") + .hasErrorCode(MISMATCHED_COLUMN_ALIASES) + .hasMessage("line 1:20: Column alias list has 3 entries but table function has 1 proper columns"); + + // the original returned relation type is ("a" : BOOLEAN, "b" : INTEGER) + analyze("SELECT column_alias_1, column_alias_2 FROM TABLE(system.monomorphic_static_return_type_function()) table_alias(column_alias_1, column_alias_2)"); + + assertFails("SELECT * FROM TABLE(system.monomorphic_static_return_type_function()) table_alias(col, col)") + .hasErrorCode(DUPLICATE_COLUMN_NAME) + .hasMessage("line 1:21: Duplicate name of table function proper column: col"); + + // case-insensitive name matching + assertFails("SELECT * FROM TABLE(system.monomorphic_static_return_type_function()) table_alias(col, COL)") + .hasErrorCode(DUPLICATE_COLUMN_NAME) + .hasMessage("line 1:21: Duplicate name of table function proper column: col"); + + // TODO test pass-through columns wen we support them: they mustn't be aliased, and must be referenced by the original range variables of their corresponding table arguments + } + @BeforeClass public void setup() { @@ -6859,7 +6992,10 @@ public ConnectorTransactionHandle getConnectorTransaction(TransactionId transact new TableArgumentFunction(), new TableArgumentRowSemanticsFunction(), new DescriptorArgumentFunction(), - new TwoTableArgumentsFunction()))), + new TwoTableArgumentsFunction(), + new OnlyPassThroughFunction(), + new MonomorphicStaticReturnTypeFunction(), + new PolymorphicStaticReturnTypeFunction()))), new SessionPropertyManager(), tablePropertyManager, analyzePropertyManager, diff --git a/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java b/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java index 1f78f61f38f3..cf5892e56ca9 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java +++ b/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java @@ -142,6 +142,8 @@ public enum StandardErrorCode JSON_VALUE_RESULT_ERROR(118, USER_ERROR), MERGE_TARGET_ROW_MULTIPLE_MATCHES(119, USER_ERROR), INVALID_COPARTITIONING(120, USER_ERROR), + INVALID_TABLE_FUNCTION_INVOCATION(121, USER_ERROR), + DUPLICATE_RANGE_VARIABLE(122, USER_ERROR), GENERIC_INTERNAL_ERROR(65536, INTERNAL_ERROR), TOO_MANY_REQUESTS_FAILED(65537, INTERNAL_ERROR),