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 d895994be627..4ab5d52ca3b0 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 @@ -14,7 +14,6 @@ package io.trino.sql.analyzer; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -38,6 +37,7 @@ import io.trino.spi.security.Identity; import io.trino.spi.security.ViewExpression; import io.trino.spi.type.Type; +import io.trino.sql.analyzer.Field.OriginColumnDetail; import io.trino.sql.tree.AllColumns; import io.trino.sql.tree.ExistsPredicate; import io.trino.sql.tree.Expression; @@ -121,9 +121,6 @@ public class Analysis // a map of users to the columns per table that they access private final Map>> tableColumnReferences = new LinkedHashMap<>(); - // Track referenced fields from source relation node - private final Multimap, Field> referencedFields = HashMultimap.create(); - private final Map, List> aggregates = new LinkedHashMap<>(); private final Map, List> orderByAggregates = new LinkedHashMap<>(); private final Map, GroupingSetAnalysis> groupingSets = new LinkedHashMap<>(); @@ -194,6 +191,7 @@ public class Analysis // row id field for update/delete queries private final Map, FieldReference> rowIdField = new LinkedHashMap<>(); + private final Map, List> originColumnDetails = new LinkedHashMap<>(); public Analysis(@Nullable Statement root, Map, Expression> parameters, boolean isDescribe) { @@ -839,9 +837,9 @@ public void addEmptyColumnReferencesForTable(AccessControl accessControl, Identi tableColumnReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>()).computeIfAbsent(table, k -> new HashSet<>()); } - public void addReferencedFields(Multimap, Field> references) + public void addReferencedFields(Expression expression, Multimap, Field> references) { - referencedFields.putAll(references); + originColumnDetails.put(NodeRef.of(expression), references.values().stream().flatMap(x -> x.getOriginColumnDetails().stream()).collect(toImmutableList())); } public Map>> getTableColumnReferences() @@ -885,6 +883,11 @@ public List getRowFilters(Table node) return rowFilters.getOrDefault(NodeRef.of(node), ImmutableList.of()); } + public List getColumnOriginDetails(Expression expression) + { + return originColumnDetails.get(NodeRef.of(expression)); + } + public boolean hasColumnMask(QualifiedObjectName table, String column, String identity) { return columnMaskScopes.contains(new ColumnMaskScopeEntry(table, column, identity)); 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 db9688107560..73fbdf59bd48 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 @@ -503,9 +503,7 @@ private Type handleResolvedField(Expression node, ResolvedField resolvedField, S } } - if (field.getOriginTable().isPresent() && field.getOriginColumnName().isPresent()) { - tableColumnReferences.put(field.getOriginTable().get(), field.getOriginColumnName().get()); - } + field.getOriginColumnDetails().forEach(columnDetail -> tableColumnReferences.put(columnDetail.getTableName(), columnDetail.getColumnName())); fieldId.getRelationId() .getSourceNode() @@ -1973,6 +1971,7 @@ public static ExpressionAnalysis analyzeExpression( analyzer.analyze(expression, scope); updateAnalysis(analysis, analyzer, session, accessControl); + analysis.addReferencedFields(expression, analyzer.getReferencedFields()); return new ExpressionAnalysis( analyzer.getExpressionTypes(), @@ -2030,7 +2029,6 @@ private static void updateAnalysis(Analysis analysis, ExpressionAnalyzer analyze analysis.addColumnReferences(analyzer.getColumnReferences()); analysis.addLambdaArgumentReferences(analyzer.getLambdaArgumentReferences()); analysis.addTableColumnReferences(accessControl, session.getIdentity(), analyzer.getTableColumnReferences()); - analysis.addReferencedFields(analyzer.getReferencedFields()); } public static ExpressionAnalyzer create( diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Field.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Field.java index 6f4cf158e2f0..b04f5a1e633c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Field.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Field.java @@ -13,18 +13,19 @@ */ package io.trino.sql.analyzer; +import com.google.common.collect.ImmutableList; import io.trino.metadata.QualifiedObjectName; import io.trino.spi.type.Type; import io.trino.sql.tree.QualifiedName; +import java.util.List; import java.util.Optional; import static java.util.Objects.requireNonNull; public class Field { - private final Optional originTable; - private final Optional originColumnName; + private final List originColumnDetails; private final Optional relationAlias; private final Optional name; private final Type type; @@ -36,7 +37,7 @@ public static Field newUnqualified(String name, Type type) requireNonNull(name, "name is null"); requireNonNull(type, "type is null"); - return new Field(Optional.empty(), Optional.of(name), type, false, Optional.empty(), Optional.empty(), false); + return new Field(Optional.empty(), Optional.of(name), type, false, ImmutableList.of(), false); } public static Field newUnqualified(Optional name, Type type) @@ -44,53 +45,46 @@ public static Field newUnqualified(Optional name, Type type) requireNonNull(name, "name is null"); requireNonNull(type, "type is null"); - return new Field(Optional.empty(), name, type, false, Optional.empty(), Optional.empty(), false); + return new Field(Optional.empty(), name, type, false, ImmutableList.of(), false); } - public static Field newUnqualified(Optional name, Type type, Optional originTable, Optional originColumn, boolean aliased) + public static Field newUnqualified(Optional name, Type type, List originColumnDetails, boolean aliased) { requireNonNull(name, "name is null"); requireNonNull(type, "type is null"); - requireNonNull(originTable, "originTable is null"); + requireNonNull(originColumnDetails, "originColumnDetails is null"); - return new Field(Optional.empty(), name, type, false, originTable, originColumn, aliased); + return new Field(Optional.empty(), name, type, false, originColumnDetails, aliased); } - public static Field newQualified(QualifiedName relationAlias, Optional name, Type type, boolean hidden, Optional originTable, Optional originColumn, boolean aliased) + public static Field newQualified(QualifiedName relationAlias, Optional name, Type type, boolean hidden, List originColumnDetails, boolean aliased) { requireNonNull(relationAlias, "relationAlias is null"); requireNonNull(name, "name is null"); requireNonNull(type, "type is null"); - requireNonNull(originTable, "originTable is null"); + requireNonNull(originColumnDetails, "originColumnDetails is null"); - return new Field(Optional.of(relationAlias), name, type, hidden, originTable, originColumn, aliased); + return new Field(Optional.of(relationAlias), name, type, hidden, originColumnDetails, aliased); } - public Field(Optional relationAlias, Optional name, Type type, boolean hidden, Optional originTable, Optional originColumnName, boolean aliased) + public Field(Optional relationAlias, Optional name, Type type, boolean hidden, List originColumnDetails, boolean aliased) { requireNonNull(relationAlias, "relationAlias is null"); requireNonNull(name, "name is null"); requireNonNull(type, "type is null"); - requireNonNull(originTable, "originTable is null"); - requireNonNull(originColumnName, "originColumnName is null"); + requireNonNull(originColumnDetails, "originColumnDetails is null"); this.relationAlias = relationAlias; this.name = name; this.type = type; this.hidden = hidden; - this.originTable = originTable; - this.originColumnName = originColumnName; + this.originColumnDetails = ImmutableList.copyOf(originColumnDetails); this.aliased = aliased; } - public Optional getOriginTable() + public List getOriginColumnDetails() { - return originTable; - } - - public Optional getOriginColumnName() - { - return originColumnName; + return originColumnDetails; } public Optional getRelationAlias() @@ -169,4 +163,26 @@ public String toString() return result.toString(); } + + public static class OriginColumnDetail + { + private final QualifiedObjectName tableName; + private final String columnName; + + public OriginColumnDetail(QualifiedObjectName tableName, String columnName) + { + this.tableName = requireNonNull(tableName, "tableName is null"); + this.columnName = requireNonNull(columnName, "columnName is null"); + } + + public QualifiedObjectName getTableName() + { + return tableName; + } + + public String getColumnName() + { + return columnName; + } + } } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/RelationType.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/RelationType.java index 682594881a93..9a87f93cbd53 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/RelationType.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/RelationType.java @@ -179,8 +179,7 @@ public RelationType withAlias(String relationAlias, List columnAliases) columnAlias, field.getType(), field.isHidden(), - field.getOriginTable(), - field.getOriginColumnName(), + field.getOriginColumnDetails(), field.isAliased())); } else if (!field.isHidden()) { @@ -192,8 +191,7 @@ else if (!field.isHidden()) { columnAlias, field.getType(), false, - field.getOriginTable(), - field.getOriginColumnName(), + field.getOriginColumnDetails(), field.isAliased())); } } 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 c2a8f412da21..19c74bad482b 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 @@ -62,6 +62,7 @@ import io.trino.sql.analyzer.Analysis.ResolvedWindow; import io.trino.sql.analyzer.Analysis.SelectExpression; import io.trino.sql.analyzer.Analysis.UnnestAnalysis; +import io.trino.sql.analyzer.Field.OriginColumnDetail; import io.trino.sql.analyzer.Scope.AsteriskedIdentifierChainBasis; import io.trino.sql.parser.ParsingException; import io.trino.sql.parser.SqlParser; @@ -191,6 +192,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getLast; @@ -1253,8 +1255,7 @@ protected Scope visitTable(Table table, Optional scope) Optional.of(column.getName()), column.getType(), column.isHidden(), - Optional.of(name), - Optional.of(column.getName()), + ImmutableList.of(new OriginColumnDetail(name, column.getName())), false); fields.add(field); ColumnHandle columnHandle = columnHandles.get(column.getName()); @@ -1363,8 +1364,7 @@ private Scope createScopeForCommonTableExpression(Table table, Optional s Optional.of(aliases.next().getValue()), inputField.getType(), false, - inputField.getOriginTable(), - inputField.getOriginColumnName(), + inputField.getOriginColumnDetails(), inputField.isAliased())); } } @@ -1380,8 +1380,7 @@ private Scope createScopeForCommonTableExpression(Table table, Optional s inputField.getName(), inputField.getType(), false, - inputField.getOriginTable(), - inputField.getOriginColumnName(), + inputField.getOriginColumnDetails(), inputField.isAliased())); } } @@ -1423,8 +1422,7 @@ private Scope createScopeForView(Table table, QualifiedObjectName name, Optional Optional.of(column.getName()), getViewColumnType(column, name, table), false, - Optional.of(name), - Optional.of(column.getName()), + ImmutableList.of(new OriginColumnDetail(name, column.getName())), false)) .collect(toImmutableList()); @@ -1477,8 +1475,7 @@ private Scope createScopeForMaterializedView(Table table, QualifiedObjectName na Optional.of(column.getName()), getViewColumnType(column, name, table), false, - Optional.of(name), - Optional.of(column.getName()), + ImmutableList.of(new OriginColumnDetail(name, column.getName())), false)) .collect(toImmutableList()); @@ -1597,7 +1594,7 @@ protected Scope visitQuerySpecification(QuerySpecification node, Optional GroupingSetAnalysis groupByAnalysis = analyzeGroupBy(node, sourceScope, outputExpressions); analyzeHaving(node, sourceScope); - Scope outputScope = computeAndAssignOutputScope(node, scope, sourceScope); + Scope outputScope = computeAndAssignOutputScope(node, scope); List orderByExpressions = emptyList(); Optional orderByScope = Optional.empty(); @@ -1737,8 +1734,7 @@ protected Scope visitSetOperation(SetOperation node, Optional scope) oldField.getName(), outputFieldTypes[i], oldField.isHidden(), - oldField.getOriginTable(), - oldField.getOriginColumnName(), + oldField.getOriginColumnDetails(), oldField.isAliased()); } @@ -2006,12 +2002,11 @@ private Scope analyzeJoinUsing(Join node, List columns, Optional scope, Scope sourceScope) + private Scope computeAndAssignOutputScope(QuerySpecification node, Optional scope) { ImmutableList.Builder outputFields = ImmutableList.builder(); @@ -2495,7 +2490,7 @@ private Scope computeAndAssignOutputScope(QuerySpecification node, Optional field = column.getAlias(); - Optional originTable = Optional.empty(); - Optional originColumn = Optional.empty(); + ImmutableList.Builder builder = ImmutableList.builder(); QualifiedName name = null; if (expression instanceof Identifier) { @@ -2515,13 +2509,7 @@ else if (expression instanceof DereferenceExpression) { name = DereferenceExpression.getQualifiedName((DereferenceExpression) expression); } - if (name != null) { - List matchingFields = sourceScope.getRelationType().resolveFields(name); - if (!matchingFields.isEmpty()) { - originTable = matchingFields.get(0).getOriginTable(); - originColumn = matchingFields.get(0).getOriginColumnName(); - } - } + builder.addAll(analysis.getColumnOriginDetails(expression)); if (field.isEmpty()) { if (name != null) { @@ -2529,7 +2517,7 @@ else if (expression instanceof DereferenceExpression) { } } - outputFields.add(Field.newUnqualified(field.map(Identifier::getValue), analysis.getType(expression), originTable, originColumn, column.getAlias().isPresent())); // TODO don't use analysis as a side-channel. Use outputExpressions to look up the type + outputFields.add(Field.newUnqualified(field.map(Identifier::getValue), analysis.getType(expression), builder.build(), column.getAlias().isPresent())); // TODO don't use analysis as a side-channel. Use outputExpressions to look up the type } else { throw new IllegalArgumentException("Unsupported SelectItem type: " + item.getClass().getName()); @@ -2673,8 +2661,7 @@ private void analyzeAllColumnsFromTable( alias, field.getType(), false, - field.getOriginTable(), - field.getOriginColumnName(), + field.getOriginColumnDetails(), !allColumns.getAliases().isEmpty() || field.isAliased())); Type type = field.getType(); diff --git a/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeOutputRewrite.java b/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeOutputRewrite.java index 5ee44a10e24a..4fbb79608cab 100644 --- a/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeOutputRewrite.java +++ b/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeOutputRewrite.java @@ -25,6 +25,7 @@ import io.trino.sql.analyzer.Analysis; import io.trino.sql.analyzer.Analyzer; import io.trino.sql.analyzer.Field; +import io.trino.sql.analyzer.Field.OriginColumnDetail; import io.trino.sql.analyzer.QueryExplainer; import io.trino.sql.parser.SqlParser; import io.trino.sql.tree.AstVisitor; @@ -45,6 +46,8 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.SystemSessionProperties.isOmitDateTimeTypePrecision; import static io.trino.sql.ParsingUtil.createParsingOptions; import static io.trino.sql.QueryUtil.aliased; @@ -166,14 +169,15 @@ private Row createDescribeOutputRow(Field field, Analysis analysis) int columnIndex = ImmutableList.copyOf(analysis.getOutputDescriptor().getVisibleFields()).indexOf(field); columnName = "_col" + columnIndex; } - - Optional originTable = field.getOriginTable(); + List originTableDetails = field.getOriginColumnDetails().stream() + .map(OriginColumnDetail::getTableName) + .collect(toImmutableList()); return row( new StringLiteral(columnName), - new StringLiteral(originTable.map(QualifiedObjectName::getCatalogName).orElse("")), - new StringLiteral(originTable.map(QualifiedObjectName::getSchemaName).orElse("")), - new StringLiteral(originTable.map(QualifiedObjectName::getObjectName).orElse("")), + new StringLiteral(originTableDetails.size() == 1 ? getOnlyElement(originTableDetails).getCatalogName() : ""), + new StringLiteral(originTableDetails.size() == 1 ? getOnlyElement(originTableDetails).getSchemaName() : ""), + new StringLiteral(originTableDetails.size() == 1 ? getOnlyElement(originTableDetails).getObjectName() : ""), new StringLiteral(getDisplayLabel(field.getType(), isOmitDateTimeTypePrecision(session))), typeSize, new BooleanLiteral(String.valueOf(field.isAliased()))); diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java index cddf31c95e4a..6f5fc73c3b98 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java @@ -13,6 +13,7 @@ */ package io.trino.sql.analyzer; +import com.google.common.collect.ImmutableList; import io.trino.sql.tree.DereferenceExpression; import io.trino.sql.tree.Expression; import io.trino.sql.tree.QualifiedName; @@ -32,12 +33,12 @@ public void test() { Scope root = Scope.create(); - Field outerColumn1 = Field.newQualified(QualifiedName.of("outer", "column1"), Optional.of("c1"), BIGINT, false, Optional.empty(), Optional.empty(), false); - Field outerColumn2 = Field.newQualified(QualifiedName.of("outer", "column2"), Optional.of("c2"), BIGINT, false, Optional.empty(), Optional.empty(), false); + Field outerColumn1 = Field.newQualified(QualifiedName.of("outer", "column1"), Optional.of("c1"), BIGINT, false, ImmutableList.of(), false); + Field outerColumn2 = Field.newQualified(QualifiedName.of("outer", "column2"), Optional.of("c2"), BIGINT, false, ImmutableList.of(), false); Scope outer = Scope.builder().withParent(root).withRelationType(RelationId.anonymous(), new RelationType(outerColumn1, outerColumn2)).build(); - Field innerColumn2 = Field.newQualified(QualifiedName.of("inner", "column2"), Optional.of("c2"), BIGINT, false, Optional.empty(), Optional.empty(), false); - Field innerColumn3 = Field.newQualified(QualifiedName.of("inner", "column3"), Optional.of("c3"), BIGINT, false, Optional.empty(), Optional.empty(), false); + Field innerColumn2 = Field.newQualified(QualifiedName.of("inner", "column2"), Optional.of("c2"), BIGINT, false, ImmutableList.of(), false); + Field innerColumn3 = Field.newQualified(QualifiedName.of("inner", "column3"), Optional.of("c3"), BIGINT, false, ImmutableList.of(), false); Scope inner = Scope.builder().withOuterQueryParent(outer).withRelationType(RelationId.anonymous(), new RelationType(innerColumn2, innerColumn3)).build(); Expression c1 = name("c1");