diff --git a/core/trino-main/src/main/java/io/trino/execution/CreateMaterializedViewTask.java b/core/trino-main/src/main/java/io/trino/execution/CreateMaterializedViewTask.java index 334c3bd2858e..d93efe49eb6d 100644 --- a/core/trino-main/src/main/java/io/trino/execution/CreateMaterializedViewTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/CreateMaterializedViewTask.java @@ -25,6 +25,7 @@ import io.trino.sql.PlannerContext; import io.trino.sql.analyzer.Analysis; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.parser.SqlParser; import io.trino.sql.tree.CreateMaterializedView; import io.trino.sql.tree.Expression; @@ -53,6 +54,7 @@ public class CreateMaterializedViewTask private final SqlParser sqlParser; private final AnalyzerFactory analyzerFactory; private final MaterializedViewPropertyManager materializedViewPropertyManager; + private final QueryAnalyzerFactory queryAnalyzerFactory; @Inject public CreateMaterializedViewTask( @@ -60,13 +62,15 @@ public CreateMaterializedViewTask( AccessControl accessControl, SqlParser sqlParser, AnalyzerFactory analyzerFactory, - MaterializedViewPropertyManager materializedViewPropertyManager) + MaterializedViewPropertyManager materializedViewPropertyManager, + QueryAnalyzerFactory queryAnalyzerFactory) { this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); this.accessControl = requireNonNull(accessControl, "accessControl is null"); this.sqlParser = requireNonNull(sqlParser, "sqlParser is null"); this.analyzerFactory = requireNonNull(analyzerFactory, "analyzerFactory is null"); this.materializedViewPropertyManager = requireNonNull(materializedViewPropertyManager, "materializedViewPropertyManager is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } @Override @@ -88,7 +92,7 @@ public ListenableFuture execute( String sql = getFormattedSql(statement.getQuery(), sqlParser); - Analysis analysis = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, stateMachine.getWarningCollector()) + Analysis analysis = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, stateMachine.getWarningCollector(), queryAnalyzerFactory) .analyze(statement); List columns = analysis.getOutputDescriptor(statement.getQuery()) diff --git a/core/trino-main/src/main/java/io/trino/execution/CreateViewTask.java b/core/trino-main/src/main/java/io/trino/execution/CreateViewTask.java index a55e791d9848..7a53c351cb2a 100644 --- a/core/trino-main/src/main/java/io/trino/execution/CreateViewTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/CreateViewTask.java @@ -24,6 +24,7 @@ import io.trino.spi.security.Identity; import io.trino.sql.analyzer.Analysis; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.parser.SqlParser; import io.trino.sql.tree.CreateView; import io.trino.sql.tree.Expression; @@ -50,14 +51,16 @@ public class CreateViewTask private final AccessControl accessControl; private final SqlParser sqlParser; private final AnalyzerFactory analyzerFactory; + private final QueryAnalyzerFactory queryAnalyzerFactory; @Inject - public CreateViewTask(Metadata metadata, AccessControl accessControl, SqlParser sqlParser, AnalyzerFactory analyzerFactory) + public CreateViewTask(Metadata metadata, AccessControl accessControl, SqlParser sqlParser, AnalyzerFactory analyzerFactory, QueryAnalyzerFactory queryAnalyzerFactory) { this.metadata = requireNonNull(metadata, "metadata is null"); this.accessControl = requireNonNull(accessControl, "accessControl is null"); this.sqlParser = requireNonNull(sqlParser, "sqlParser is null"); this.analyzerFactory = requireNonNull(analyzerFactory, "analyzerFactory is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } @Override @@ -92,7 +95,7 @@ else if (metadata.getTableHandle(session, name).isPresent()) { String sql = getFormattedSql(statement.getQuery(), sqlParser); - Analysis analysis = analyzerFactory.createAnalyzer(session, parameters, parameterExtractor(statement, parameters), stateMachine.getWarningCollector()) + Analysis analysis = analyzerFactory.createAnalyzer(session, parameters, parameterExtractor(statement, parameters), stateMachine.getWarningCollector(), queryAnalyzerFactory) .analyze(statement); List columns = analysis.getOutputDescriptor(statement.getQuery()) diff --git a/core/trino-main/src/main/java/io/trino/execution/SqlQueryExecution.java b/core/trino-main/src/main/java/io/trino/execution/SqlQueryExecution.java index 85b936124c38..a16f10e0e927 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SqlQueryExecution.java +++ b/core/trino-main/src/main/java/io/trino/execution/SqlQueryExecution.java @@ -47,6 +47,7 @@ import io.trino.sql.analyzer.Analysis; import io.trino.sql.analyzer.Analyzer; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.planner.InputExtractor; import io.trino.sql.planner.LogicalPlanner; import io.trino.sql.planner.NodePartitioningManager; @@ -135,6 +136,7 @@ private SqlQueryExecution( Slug slug, PlannerContext plannerContext, AnalyzerFactory analyzerFactory, + QueryAnalyzerFactory queryAnalyzerFactory, SplitSourceFactory splitSourceFactory, NodePartitioningManager nodePartitioningManager, NodeScheduler nodeScheduler, @@ -190,7 +192,7 @@ private SqlQueryExecution( this.stateMachine = requireNonNull(stateMachine, "stateMachine is null"); // analyze query - this.analysis = analyze(preparedQuery, stateMachine, warningCollector, analyzerFactory); + this.analysis = analyze(preparedQuery, stateMachine, warningCollector, analyzerFactory, queryAnalyzerFactory); stateMachine.addStateChangeListener(state -> { if (!state.isDone()) { @@ -254,7 +256,8 @@ private static Analysis analyze( PreparedQuery preparedQuery, QueryStateMachine stateMachine, WarningCollector warningCollector, - AnalyzerFactory analyzerFactory) + AnalyzerFactory analyzerFactory, + QueryAnalyzerFactory queryAnalyzerFactory) { stateMachine.beginAnalysis(); @@ -263,7 +266,8 @@ private static Analysis analyze( stateMachine.getSession(), preparedQuery.getParameters(), parameterExtractor(preparedQuery.getStatement(), preparedQuery.getParameters()), - warningCollector); + warningCollector, + queryAnalyzerFactory); Analysis analysis; try { analysis = analyzer.analyze(preparedQuery.getStatement()); @@ -710,6 +714,7 @@ public static class SqlQueryExecutionFactory private final int scheduleSplitBatchSize; private final PlannerContext plannerContext; private final AnalyzerFactory analyzerFactory; + private final QueryAnalyzerFactory queryAnalyzerFactory; private final SplitSourceFactory splitSourceFactory; private final NodePartitioningManager nodePartitioningManager; private final NodeScheduler nodeScheduler; @@ -739,6 +744,7 @@ public static class SqlQueryExecutionFactory QueryManagerConfig config, PlannerContext plannerContext, AnalyzerFactory analyzerFactory, + QueryAnalyzerFactory queryAnalyzerFactory, SplitSourceFactory splitSourceFactory, NodePartitioningManager nodePartitioningManager, NodeScheduler nodeScheduler, @@ -769,6 +775,7 @@ public static class SqlQueryExecutionFactory this.scheduleSplitBatchSize = config.getScheduleSplitBatchSize(); this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); this.analyzerFactory = requireNonNull(analyzerFactory, "analyzerFactory is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); this.splitSourceFactory = requireNonNull(splitSourceFactory, "splitSourceFactory is null"); this.nodePartitioningManager = requireNonNull(nodePartitioningManager, "nodePartitioningManager is null"); this.nodeScheduler = requireNonNull(nodeScheduler, "nodeScheduler is null"); @@ -811,6 +818,7 @@ public QueryExecution createQueryExecution( slug, plannerContext, analyzerFactory, + queryAnalyzerFactory, splitSourceFactory, nodePartitioningManager, nodeScheduler, diff --git a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java index 48d5e6b68582..077e16f16d72 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java @@ -89,6 +89,8 @@ public interface Metadata Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, Map analyzeProperties); + Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, TupleDomain tupleDomain); + Optional getTableHandleForExecute( Session session, TableHandle tableHandle, diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 537985f73ef3..09c5eea6f26b 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -308,6 +308,29 @@ public Optional getTableHandleForStatisticsCollection(Session sessi return Optional.empty(); } + @Override + public Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, TupleDomain tupleDomain) + { + requireNonNull(tableName, "tableName is null"); + requireNonNull(tupleDomain, "tupleDomain is null"); + + Optional catalog = getOptionalCatalogMetadata(session, tableName.getCatalogName()); + if (catalog.isPresent()) { + CatalogMetadata catalogMetadata = catalog.get(); + CatalogName catalogName = catalogMetadata.getConnectorId(session, tableName); + ConnectorMetadata metadata = catalogMetadata.getMetadataFor(session, catalogName); + + ConnectorTableHandle tableHandle = metadata.getTableHandleForStatisticsCollection(session.toConnectorSession(catalogName), tableName.asSchemaTableName(), tupleDomain); + if (tableHandle != null) { + return Optional.of(new TableHandle( + catalogName, + tableHandle, + catalogMetadata.getTransactionHandleFor(catalogName))); + } + } + return Optional.empty(); + } + @Override public Optional getTableHandleForExecute(Session session, TableHandle tableHandle, String procedure, Map executeProperties) { diff --git a/core/trino-main/src/main/java/io/trino/server/CoordinatorModule.java b/core/trino-main/src/main/java/io/trino/server/CoordinatorModule.java index a2ea52798940..13506dfc51f2 100644 --- a/core/trino-main/src/main/java/io/trino/server/CoordinatorModule.java +++ b/core/trino-main/src/main/java/io/trino/server/CoordinatorModule.java @@ -95,6 +95,7 @@ import io.trino.spi.VersionEmbedder; import io.trino.spi.memory.ClusterMemoryPoolManager; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.analyzer.QueryExplainerFactory; import io.trino.sql.planner.OptimizerStatsMBeanExporter; import io.trino.sql.planner.PlanFragmenter; @@ -275,6 +276,9 @@ protected void setup(Binder binder) // query explainer binder.bind(QueryExplainerFactory.class).in(Scopes.SINGLETON); + // query analyzer + binder.bind(QueryAnalyzerFactory.class).in(Scopes.SINGLETON); + // explain analyze binder.bind(ExplainAnalyzeContext.class).in(Scopes.SINGLETON); 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 8c630b74ced1..760b4a99a43f 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 @@ -211,7 +211,7 @@ public class Analysis private Optional insert = Optional.empty(); private Optional refreshMaterializedView = Optional.empty(); private Optional delegatedRefreshMaterializedView = Optional.empty(); - private Optional analyzeTarget = Optional.empty(); + private Optional> analyzeTargets = Optional.empty(); private Optional> updatedColumns = Optional.empty(); private final QueryType queryType; @@ -611,6 +611,11 @@ public Collection getTables() .collect(toImmutableList()); } + public Collection> getTableNodeRefs() + { + return ImmutableList.copyOf(tables.keySet()); + } + public void registerTable( Table table, Optional handle, @@ -722,14 +727,14 @@ public ColumnHandle getColumn(Field field) return columns.get(field); } - public Optional getAnalyzeTarget() + public Optional> getAnalyzeTargets() { - return analyzeTarget; + return analyzeTargets; } - public void setAnalyzeTarget(TableHandle analyzeTarget) + public void setAnalyzeTargets(List analyzeTargets) { - this.analyzeTarget = Optional.of(analyzeTarget); + this.analyzeTargets = Optional.of(analyzeTargets); } public void setCreate(Create create) diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analyzer.java index 74ea46cdc34d..9bd922cef228 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analyzer.java @@ -47,6 +47,7 @@ public class Analyzer private final Map, Expression> parameterLookup; private final WarningCollector warningCollector; private final StatementRewrite statementRewrite; + private final QueryAnalyzerFactory queryAnalyzerFactory; Analyzer( Session session, @@ -55,7 +56,8 @@ public class Analyzer List parameters, Map, Expression> parameterLookup, WarningCollector warningCollector, - StatementRewrite statementRewrite) + StatementRewrite statementRewrite, + QueryAnalyzerFactory queryAnalyzerFactory) { this.session = requireNonNull(session, "session is null"); this.analyzerFactory = requireNonNull(analyzerFactory, "analyzerFactory is null"); @@ -64,6 +66,7 @@ public class Analyzer this.parameterLookup = parameterLookup; this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); this.statementRewrite = requireNonNull(statementRewrite, "statementRewrite is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } public Analysis analyze(Statement statement) @@ -73,9 +76,9 @@ public Analysis analyze(Statement statement) public Analysis analyze(Statement statement, QueryType queryType) { - Statement rewrittenStatement = statementRewrite.rewrite(analyzerFactory, session, statement, parameters, parameterLookup, warningCollector); + Statement rewrittenStatement = statementRewrite.rewrite(analyzerFactory, session, statement, parameters, parameterLookup, warningCollector, queryAnalyzerFactory); Analysis analysis = new Analysis(rewrittenStatement, parameterLookup, queryType); - StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, Optional.of(queryAnalyzerFactory.createQueryAnalyzer(analyzerFactory, statementAnalyzerFactory)), warningCollector, CorrelationSupport.ALLOWED); analyzer.analyze(rewrittenStatement, Optional.empty()); // check column access permissions for each table diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/AnalyzerFactory.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/AnalyzerFactory.java index e3f7fdc3e9fd..6dd84ef3dc64 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/AnalyzerFactory.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/AnalyzerFactory.java @@ -43,7 +43,8 @@ public Analyzer createAnalyzer( Session session, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { return new Analyzer( session, @@ -52,6 +53,7 @@ public Analyzer createAnalyzer( parameters, parameterLookup, warningCollector, - statementRewrite); + statementRewrite, + queryAnalyzerFactory); } } 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 667067f868f5..2ebd5454e9df 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 @@ -313,6 +313,7 @@ private ExpressionAnalyzer( StatementAnalyzerFactory statementAnalyzerFactory, Analysis analysis, Session session, + Optional queryAnalyzer, TypeProvider types, WarningCollector warningCollector) { @@ -322,6 +323,7 @@ private ExpressionAnalyzer( (node, correlationSupport) -> statementAnalyzerFactory.createStatementAnalyzer( analysis, session, + queryAnalyzer, warningCollector, correlationSupport), session, @@ -2759,10 +2761,11 @@ public static ExpressionAnalysis analyzePatternRecognitionExpression( Scope scope, Analysis analysis, Expression expression, + Optional queryAnalyzer, WarningCollector warningCollector, Set labels) { - ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, TypeProvider.empty(), warningCollector); + ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, queryAnalyzer, TypeProvider.empty(), warningCollector); analyzer.analyze(expression, scope, labels); updateAnalysis(analysis, analyzer, session, accessControl); @@ -2784,6 +2787,7 @@ public static ExpressionAnalysis analyzeExpressions( PlannerContext plannerContext, StatementAnalyzerFactory statementAnalyzerFactory, AccessControl accessControl, + Optional queryAnalyzer, TypeProvider types, Iterable expressions, Map, Expression> parameters, @@ -2791,7 +2795,7 @@ public static ExpressionAnalysis analyzeExpressions( QueryType queryType) { Analysis analysis = new Analysis(null, parameters, queryType); - ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, types, warningCollector); + ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, queryAnalyzer, types, warningCollector); for (Expression expression : expressions) { analyzer.analyze( expression, @@ -2820,10 +2824,11 @@ public static ExpressionAnalysis analyzeExpression( Scope scope, Analysis analysis, Expression expression, + Optional queryAnalyzer, WarningCollector warningCollector, CorrelationSupport correlationSupport) { - ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, TypeProvider.empty(), warningCollector); + ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, queryAnalyzer, TypeProvider.empty(), warningCollector); analyzer.analyze(expression, scope, correlationSupport); updateAnalysis(analysis, analyzer, session, accessControl); @@ -2848,12 +2853,13 @@ public static ExpressionAnalysis analyzeWindow( AccessControl accessControl, Scope scope, Analysis analysis, + Optional queryAnalyzer, WarningCollector warningCollector, CorrelationSupport correlationSupport, ResolvedWindow window, Node originalNode) { - ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, TypeProvider.empty(), warningCollector); + ExpressionAnalyzer analyzer = new ExpressionAnalyzer(plannerContext, accessControl, statementAnalyzerFactory, analysis, session, queryAnalyzer, TypeProvider.empty(), warningCollector); analyzer.analyzeWindow(window, scope, originalNode, correlationSupport); updateAnalysis(analysis, analyzer, session, accessControl); diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryAnalyzer.java new file mode 100644 index 000000000000..45ad47d2229d --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryAnalyzer.java @@ -0,0 +1,86 @@ +/* + * 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.analyzer; + +import io.trino.Session; +import io.trino.cost.CostCalculator; +import io.trino.cost.StatsCalculator; +import io.trino.execution.warnings.WarningCollector; +import io.trino.sql.PlannerContext; +import io.trino.sql.planner.LogicalPlanner; +import io.trino.sql.planner.Plan; +import io.trino.sql.planner.PlanNodeIdAllocator; +import io.trino.sql.planner.PlanOptimizersFactory; +import io.trino.sql.planner.TypeAnalyzer; +import io.trino.sql.planner.optimizations.PlanOptimizer; +import io.trino.sql.tree.Expression; +import io.trino.sql.tree.Statement; + +import java.util.List; + +import static io.trino.sql.ParameterUtils.parameterExtractor; +import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; +import static java.util.Objects.requireNonNull; + +public class QueryAnalyzer +{ + private final QueryAnalyzerFactory queryAnalyzerFactory; + private final List planOptimizers; + private final PlannerContext plannerContext; + private final AnalyzerFactory analyzerFactory; + private final StatementAnalyzerFactory statementAnalyzerFactory; + private final StatsCalculator statsCalculator; + private final CostCalculator costCalculator; + + QueryAnalyzer( + QueryAnalyzerFactory queryAnalyzerFactory, + PlanOptimizersFactory planOptimizersFactory, + PlannerContext plannerContext, + AnalyzerFactory analyzerFactory, + StatementAnalyzerFactory statementAnalyzerFactory, + StatsCalculator statsCalculator, + CostCalculator costCalculator) + { + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); + this.planOptimizers = requireNonNull(planOptimizersFactory.get(), "planOptimizers is null"); + this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); + this.analyzerFactory = requireNonNull(analyzerFactory, "analyzerFactory is null"); + this.statementAnalyzerFactory = requireNonNull(statementAnalyzerFactory, "statementAnalyzerFactory is null"); + this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator is null"); + this.costCalculator = requireNonNull(costCalculator, "costCalculator is null"); + } + + public Analysis analyze(Session session, Statement statement, List parameters, WarningCollector warningCollector) + { + Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterExtractor(statement, parameters), warningCollector, queryAnalyzerFactory); + return analyzer.analyze(statement); + } + + public Plan getLogicalPlan(Session session, Analysis analysis, WarningCollector warningCollector) + { + PlanNodeIdAllocator idAllocator = new PlanNodeIdAllocator(); + + // plan statement + LogicalPlanner logicalPlanner = new LogicalPlanner( + session, + planOptimizers, + idAllocator, + plannerContext, + new TypeAnalyzer(plannerContext, statementAnalyzerFactory), + statsCalculator, + costCalculator, + warningCollector); + return logicalPlanner.plan(analysis, OPTIMIZED_AND_VALIDATED, true); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryAnalyzerFactory.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryAnalyzerFactory.java new file mode 100644 index 000000000000..abee7adf0b80 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryAnalyzerFactory.java @@ -0,0 +1,56 @@ +/* + * 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.analyzer; + +import io.trino.cost.CostCalculator; +import io.trino.cost.StatsCalculator; +import io.trino.sql.PlannerContext; +import io.trino.sql.planner.PlanOptimizersFactory; + +import javax.inject.Inject; + +import static java.util.Objects.requireNonNull; + +public class QueryAnalyzerFactory +{ + private final PlanOptimizersFactory planOptimizersFactory; + private final PlannerContext plannerContext; + private final StatsCalculator statsCalculator; + private final CostCalculator costCalculator; + + @Inject + public QueryAnalyzerFactory( + PlanOptimizersFactory planOptimizersFactory, + PlannerContext plannerContext, + StatsCalculator statsCalculator, + CostCalculator costCalculator) + { + this.planOptimizersFactory = requireNonNull(planOptimizersFactory, "planOptimizersFactory is null"); + this.plannerContext = requireNonNull(plannerContext, "metadata is null"); + this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator is null"); + this.costCalculator = requireNonNull(costCalculator, "costCalculator is null"); + } + + public QueryAnalyzer createQueryAnalyzer(AnalyzerFactory analyzerFactory, StatementAnalyzerFactory statementAnalyzerFactory) + { + return new QueryAnalyzer( + this, + planOptimizersFactory, + plannerContext, + analyzerFactory, + statementAnalyzerFactory, + statsCalculator, + costCalculator); + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainer.java index b093ef07040e..e881bd40cde3 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainer.java @@ -60,6 +60,7 @@ public class QueryExplainer private final StatementAnalyzerFactory statementAnalyzerFactory; private final StatsCalculator statsCalculator; private final CostCalculator costCalculator; + private final QueryAnalyzerFactory queryAnalyzerFactory; QueryExplainer( PlanOptimizersFactory planOptimizersFactory, @@ -68,7 +69,8 @@ public class QueryExplainer AnalyzerFactory analyzerFactory, StatementAnalyzerFactory statementAnalyzerFactory, StatsCalculator statsCalculator, - CostCalculator costCalculator) + CostCalculator costCalculator, + QueryAnalyzerFactory queryAnalyzerFactory) { this.planOptimizers = requireNonNull(planOptimizersFactory.get(), "planOptimizers is null"); this.planFragmenter = requireNonNull(planFragmenter, "planFragmenter is null"); @@ -77,6 +79,7 @@ public class QueryExplainer this.statementAnalyzerFactory = requireNonNull(statementAnalyzerFactory, "statementAnalyzerFactory is null"); this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator is null"); this.costCalculator = requireNonNull(costCalculator, "costCalculator is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } public void validate(Session session, Statement statement, List parameters, WarningCollector warningCollector) @@ -172,7 +175,7 @@ public Plan getLogicalPlan(Session session, Statement statement, List parameters, WarningCollector warningCollector) { - Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterExtractor(statement, parameters), warningCollector); + Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterExtractor(statement, parameters), warningCollector, queryAnalyzerFactory); return analyzer.analyze(statement, EXPLAIN); } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainerFactory.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainerFactory.java index a495a0a518d0..f67fe72fce1c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainerFactory.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/QueryExplainerFactory.java @@ -31,6 +31,7 @@ public class QueryExplainerFactory private final StatementAnalyzerFactory statementAnalyzerFactory; private final StatsCalculator statsCalculator; private final CostCalculator costCalculator; + private final QueryAnalyzerFactory queryAnalyzerFactory; @Inject public QueryExplainerFactory( @@ -39,7 +40,8 @@ public QueryExplainerFactory( PlannerContext plannerContext, StatementAnalyzerFactory statementAnalyzerFactory, StatsCalculator statsCalculator, - CostCalculator costCalculator) + CostCalculator costCalculator, + QueryAnalyzerFactory queryAnalyzerFactory) { this.planOptimizersFactory = requireNonNull(planOptimizersFactory, "planOptimizersFactory is null"); this.planFragmenter = requireNonNull(planFragmenter, "planFragmenter is null"); @@ -47,6 +49,7 @@ public QueryExplainerFactory( this.statementAnalyzerFactory = requireNonNull(statementAnalyzerFactory, "statementAnalyzerFactory is null"); this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator is null"); this.costCalculator = requireNonNull(costCalculator, "costCalculator is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } public QueryExplainer createQueryExplainer(AnalyzerFactory analyzerFactory) @@ -58,6 +61,7 @@ public QueryExplainer createQueryExplainer(AnalyzerFactory analyzerFactory) analyzerFactory, statementAnalyzerFactory, statsCalculator, - costCalculator); + costCalculator, + queryAnalyzerFactory); } } 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 443b8a11710a..e2f555447ae4 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 @@ -67,6 +67,7 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableProcedureMetadata; import io.trino.spi.function.OperatorType; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.ptf.Argument; import io.trino.spi.ptf.ArgumentSpecification; import io.trino.spi.ptf.ConnectorTableFunction; @@ -106,14 +107,17 @@ import io.trino.sql.parser.SqlParser; import io.trino.sql.planner.DeterminismEvaluator; import io.trino.sql.planner.ExpressionInterpreter; +import io.trino.sql.planner.Plan; import io.trino.sql.planner.ScopeAware; import io.trino.sql.planner.SymbolsExtractor; import io.trino.sql.planner.TypeProvider; +import io.trino.sql.planner.plan.TableScanNode; import io.trino.sql.tree.AddColumn; import io.trino.sql.tree.AliasedRelation; import io.trino.sql.tree.AllColumns; import io.trino.sql.tree.AllRows; import io.trino.sql.tree.Analyze; +import io.trino.sql.tree.AnalyzeForQuery; import io.trino.sql.tree.AstVisitor; import io.trino.sql.tree.Call; import io.trino.sql.tree.CallArgument; @@ -327,6 +331,7 @@ import static io.trino.sql.analyzer.SemanticExceptions.semanticException; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.planner.ExpressionInterpreter.evaluateConstantExpression; +import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; import static io.trino.sql.tree.DereferenceExpression.getQualifiedName; import static io.trino.sql.tree.Join.Type.FULL; @@ -362,6 +367,7 @@ class StatementAnalyzer private final TablePropertyManager tablePropertyManager; private final AnalyzePropertyManager analyzePropertyManager; private final TableProceduresPropertyManager tableProceduresPropertyManager; + private final Optional queryAnalyzer; private final WarningCollector warningCollector; private final CorrelationSupport correlationSupport; @@ -381,6 +387,7 @@ class StatementAnalyzer TablePropertyManager tablePropertyManager, AnalyzePropertyManager analyzePropertyManager, TableProceduresPropertyManager tableProceduresPropertyManager, + Optional queryAnalyzer, WarningCollector warningCollector, CorrelationSupport correlationSupport) { @@ -400,6 +407,7 @@ class StatementAnalyzer this.tablePropertyManager = requireNonNull(tablePropertyManager, "tablePropertyManager is null"); this.analyzePropertyManager = requireNonNull(analyzePropertyManager, "analyzePropertyManager is null"); this.tableProceduresPropertyManager = tableProceduresPropertyManager; + this.queryAnalyzer = requireNonNull(queryAnalyzer, "queryAnalyzer is null"); this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); this.correlationSupport = requireNonNull(correlationSupport, "correlationSupport is null"); } @@ -755,7 +763,7 @@ protected Scope visitDelete(Delete node, Optional scope) // TODO: we shouldn't need to create a new analyzer. The access control should be carried in the context object StatementAnalyzer analyzer = statementAnalyzerFactory .withSpecializedAccessControl(new AllowAllAccessControl()) - .createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + .createStatementAnalyzer(analysis, session, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope tableScope = analyzer.analyzeForUpdate(table, scope, UpdateKind.DELETE); node.getWhere().ifPresent(where -> analyzeWhere(node, tableScope, where)); @@ -807,7 +815,87 @@ protected Scope visitAnalyze(Analyze node, Optional scope) throw new AccessDeniedException(format("Cannot ANALYZE (missing insert privilege) table %s", tableName)); } - analysis.setAnalyzeTarget(tableHandle); + analysis.setAnalyzeTargets(ImmutableList.of(tableHandle)); + return createAndAssignScope(node, scope, Field.newUnqualified("rows", BIGINT)); + } + + @Override + protected Scope visitAnalyzeForQuery(AnalyzeForQuery node, Optional scope) + { + analysis.setUpdateType("ANALYZE"); + + checkState(queryAnalyzer.isPresent(), "Query analyzer must be provided for ANALYZE FOR QUERY"); + + Query query = node.getQuery(); + + // analyze statement + Analysis queryAnalysis = queryAnalyzer.get().analyze(session, query, ImmutableList.copyOf(analysis.getParameters().values()), warningCollector); + + ImmutableSet.Builder tableNameBuilder = ImmutableSet.builder(); + ImmutableSet.Builder viewNameBuilder = ImmutableSet.builder(); + queryAnalysis.getTableNodeRefs().stream() + .map(NodeRef::getNode) + .forEach(table -> { + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + if (metadata.getView(session, tableName).isPresent()) { + viewNameBuilder.add(tableName); + } + else { + tableNameBuilder.add(tableName); + } + }); + Set viewNames = viewNameBuilder.build(); + Set tableNames = tableNameBuilder.build(); + + // verify the target tables exists, and it's not all views + if (tableNames.isEmpty()) { + throw semanticException(NOT_SUPPORTED, node, "Analyzing views is not supported"); + } + + // plan statement + Plan plan = queryAnalyzer.get().getLogicalPlan(session, queryAnalysis, warningCollector); + List tableScanNodes = searchFrom(plan.getRoot()) + .where(TableScanNode.class::isInstance) + .findAll(); + + Map> unionTupleDomains = new HashMap<>(); + tableScanNodes.stream() + .map(TableScanNode::getTable) + .forEach(tableHandle -> { + QualifiedObjectName tableName = metadata.getTableMetadata(session, tableHandle).getQualifiedName(); + if (!viewNames.contains(tableName)) { + TupleDomain current = metadata.getTableProperties(session, tableHandle).getPredicate(); + unionTupleDomains.compute(tableName, (ignored, previous) -> { + if (previous == null) { + return current; + } + return TupleDomain.columnWiseUnion(previous, current); + }); + } + }); + + ImmutableList.Builder tableHandles = ImmutableList.builder(); + for (QualifiedObjectName tableName : unionTupleDomains.keySet()) { + TableHandle tableHandle = metadata.getTableHandleForStatisticsCollection(session, tableName, unionTupleDomains.get(tableName)) + .orElseThrow(() -> semanticException(TABLE_NOT_FOUND, node, "Table '%s' does not exist", tableName)); + tableHandles.add(tableHandle); + + // user must have read and insert permission in order to analyze stats of a table + analysis.addTableColumnReferences( + accessControl, + session.getIdentity(), + ImmutableMultimap.builder() + .putAll(tableName, metadata.getColumnHandles(session, tableHandle).keySet()) + .build()); + try { + accessControl.checkCanInsertIntoTable(session.toSecurityContext(), tableName); + } + catch (AccessDeniedException exception) { + throw new AccessDeniedException(format("Cannot ANALYZE (missing insert privilege) table %s", tableName)); + } + } + + analysis.setAnalyzeTargets(tableHandles.build()); return createAndAssignScope(node, scope, Field.newUnqualified("rows", BIGINT)); } @@ -922,7 +1010,7 @@ protected Scope visitCreateView(CreateView node, Optional scope) QualifiedObjectName viewName = createQualifiedObjectName(session, node, node.getName()); // analyze the query that creates the view - StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope queryScope = analyzer.analyze(node.getQuery(), scope); @@ -1269,7 +1357,7 @@ protected Scope visitCreateMaterializedView(CreateMaterializedView node, Optiona } // analyze the query that creates the view - StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope queryScope = analyzer.analyze(node.getQuery(), scope); @@ -1463,7 +1551,7 @@ else if (expressionType instanceof MapType) { @Override protected Scope visitLateral(Lateral node, Optional scope) { - StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope queryScope = analyzer.analyze(node.getQuery(), scope); return createAndAssignScope(node, scope, queryScope.getRelationType()); } @@ -2282,6 +2370,7 @@ private ExpressionAnalysis analyzePatternRecognitionExpression(Expression expres scope, analysis, expression, + queryAnalyzer, warningCollector, labels); } @@ -2338,6 +2427,7 @@ protected Scope visitSampledRelation(SampledRelation relation, Optional s plannerContext, statementAnalyzerFactory, accessControl, + queryAnalyzer, TypeProvider.empty(), ImmutableList.of(samplePercentage), analysis.getParameters(), @@ -2384,7 +2474,7 @@ protected Scope visitSampledRelation(SampledRelation relation, Optional s @Override protected Scope visitTableSubquery(TableSubquery node, Optional scope) { - StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope queryScope = analyzer.analyze(node.getQuery(), scope); return createAndAssignScope(node, scope, queryScope.getRelationType()); } @@ -2706,7 +2796,7 @@ protected Scope visitUpdate(Update update, Optional scope) // Analyzer checks for select permissions but UPDATE has a separate permission, so disable access checks StatementAnalyzer analyzer = statementAnalyzerFactory .withSpecializedAccessControl(new AllowAllAccessControl()) - .createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED); + .createStatementAnalyzer(analysis, session, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope tableScope = analyzer.analyzeForUpdate(table, scope, UpdateKind.UPDATE); update.getWhere().ifPresent(where -> analyzeWhere(update, tableScope, where)); @@ -3029,6 +3119,7 @@ private void analyzeWindow(QuerySpecification querySpecification, ResolvedWindow accessControl, scope, analysis, + queryAnalyzer, WarningCollector.NOOP, correlationSupport, window, @@ -3732,7 +3823,7 @@ private RelationType analyzeView(Query query, QualifiedObjectName name, Optional StatementAnalyzer analyzer = statementAnalyzerFactory .withSpecializedAccessControl(viewAccessControl) - .createStatementAnalyzer(analysis, viewSession, warningCollector, CorrelationSupport.ALLOWED); + .createStatementAnalyzer(analysis, viewSession, queryAnalyzer, warningCollector, CorrelationSupport.ALLOWED); Scope queryScope = analyzer.analyze(query, Scope.create()); return queryScope.getRelationType().withAlias(name.getObjectName(), null); } @@ -3815,6 +3906,7 @@ private ExpressionAnalysis analyzeExpression(Expression expression, Scope scope) scope, analysis, expression, + queryAnalyzer, warningCollector, correlationSupport); } @@ -3829,6 +3921,7 @@ private ExpressionAnalysis analyzeExpression(Expression expression, Scope scope, scope, analysis, expression, + queryAnalyzer, warningCollector, correlationSupport); } @@ -3861,6 +3954,7 @@ private void analyzeRowFilter(String currentIdentity, Table table, QualifiedObje scope, analysis, expression, + queryAnalyzer, warningCollector, correlationSupport); } @@ -3916,6 +4010,7 @@ private void analyzeColumnMask(String currentIdentity, Table table, QualifiedObj scope, analysis, expression, + queryAnalyzer, warningCollector, correlationSupport); } @@ -4346,6 +4441,7 @@ private List analyzeOrderBy(Node node, List sortItems, Sco orderByScope, analysis, expression, + queryAnalyzer, WarningCollector.NOOP, correlationSupport); analysis.recordSubqueries(node, expressionAnalysis); diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzerFactory.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzerFactory.java index eece35f09571..2d23798be085 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzerFactory.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzerFactory.java @@ -31,6 +31,8 @@ import javax.inject.Inject; +import java.util.Optional; + import static java.util.Objects.requireNonNull; public class StatementAnalyzerFactory @@ -93,6 +95,7 @@ public StatementAnalyzerFactory withSpecializedAccessControl(AccessControl acces public StatementAnalyzer createStatementAnalyzer( Analysis analysis, Session session, + Optional queryAnalyzer, WarningCollector warningCollector, CorrelationSupport correlationSupport) { @@ -111,6 +114,7 @@ public StatementAnalyzer createStatementAnalyzer( tablePropertyManager, analyzePropertyManager, tableProceduresPropertyManager, + queryAnalyzer, warningCollector, correlationSupport); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java index ef11d6fcf67a..667e2152ff40 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java @@ -14,7 +14,9 @@ package io.trino.sql.planner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import io.airlift.log.Logger; import io.trino.Session; import io.trino.cost.CachingCostProvider; @@ -67,11 +69,13 @@ import io.trino.sql.planner.plan.TableFinishNode; import io.trino.sql.planner.plan.TableScanNode; import io.trino.sql.planner.plan.TableWriterNode; +import io.trino.sql.planner.plan.UnionNode; import io.trino.sql.planner.plan.UpdateNode; import io.trino.sql.planner.plan.ValuesNode; import io.trino.sql.planner.planprinter.PlanPrinter; import io.trino.sql.planner.sanity.PlanSanityChecker; import io.trino.sql.tree.Analyze; +import io.trino.sql.tree.AnalyzeForQuery; import io.trino.sql.tree.Cast; import io.trino.sql.tree.CoalesceExpression; import io.trino.sql.tree.ComparisonExpression; @@ -291,6 +295,9 @@ private RelationPlan planStatementWithoutOutput(Analysis analysis, Statement sta if (statement instanceof Analyze) { return createAnalyzePlan(analysis, (Analyze) statement); } + if (statement instanceof AnalyzeForQuery) { + return createAnalyzeForQueryPlan(analysis, ((AnalyzeForQuery) statement)); + } if (statement instanceof Insert) { checkState(analysis.getInsert().isPresent(), "Insert handle is missing"); return createInsertPlan(analysis, (Insert) statement); @@ -336,8 +343,57 @@ private RelationPlan createExplainAnalyzePlan(Analysis analysis, ExplainAnalyze private RelationPlan createAnalyzePlan(Analysis analysis, Analyze analyzeStatement) { - TableHandle targetTable = analysis.getAnalyzeTarget().orElseThrow(); + TableHandle targetTable = Iterables.getOnlyElement(analysis.getAnalyzeTargets().orElseThrow()); + PlanNode planNode = createStatisticsWriterNode(targetTable); + return new RelationPlan(planNode, analysis.getScope(analyzeStatement), planNode.getOutputSymbols(), Optional.empty()); + } + + private RelationPlan createAnalyzeForQueryPlan(Analysis analysis, AnalyzeForQuery analyzeForQueryStatement) + { + List targetTables = analysis.getAnalyzeTargets().orElseThrow(); + List statisticsWriterNodes = targetTables.stream() + .map(this::createStatisticsWriterNode) + .collect(toImmutableList()); + + PlanNode planNode; + switch (statisticsWriterNodes.size()) { + case 0: + planNode = new ValuesNode(idAllocator.getNextId(), ImmutableList.of(symbolAllocator.newSymbol("rows", BIGINT)), ImmutableList.of(new Row(ImmutableList.of(new GenericLiteral("BIGINT", "0"))))); + break; + case 1: + planNode = statisticsWriterNodes.get(0); + break; + default: + ImmutableListMultimap.Builder outputToInputs = ImmutableListMultimap.builder(); + Symbol unionRowSymbol = symbolAllocator.newSymbol("rows", BIGINT); + statisticsWriterNodes.forEach(statisticsWriterNode -> outputToInputs.put(unionRowSymbol, statisticsWriterNode.getOutputSymbols().get(0))); + UnionNode unionNode = new UnionNode(idAllocator.getNextId(), statisticsWriterNodes, outputToInputs.build(), ImmutableList.of(unionRowSymbol)); + + Symbol aggregationRowSymbol = symbolAllocator.newSymbol("rows", BIGINT); + AggregationNode.Aggregation aggregation = new AggregationNode.Aggregation( + metadata.resolveFunction(session, QualifiedName.of("sum"), fromTypes(BIGINT)), + ImmutableList.of(unionRowSymbol.toSymbolReference()), + false, + Optional.empty(), + Optional.empty(), + Optional.empty()); + planNode = new AggregationNode( + idAllocator.getNextId(), + unionNode, + ImmutableMap.of(aggregationRowSymbol, aggregation), + singleGroupingSet(ImmutableList.of()), + ImmutableList.of(), + AggregationNode.Step.SINGLE, + Optional.empty(), + Optional.empty()); + } + + return new RelationPlan(planNode, analysis.getScope(analyzeForQueryStatement), planNode.getOutputSymbols(), Optional.empty()); + } + + private StatisticsWriterNode createStatisticsWriterNode(TableHandle targetTable) + { // Plan table scan Map columnHandles = metadata.getColumnHandles(session, targetTable); ImmutableList.Builder tableScanOutputs = ImmutableList.builder(); @@ -360,7 +416,7 @@ private RelationPlan createAnalyzePlan(Analysis analysis, Analyze analyzeStateme StatisticAggregations statisticAggregations = tableStatisticAggregation.getAggregations(); List groupingSymbols = statisticAggregations.getGroupingSymbols(); - PlanNode planNode = new StatisticsWriterNode( + return new StatisticsWriterNode( idAllocator.getNextId(), new AggregationNode( idAllocator.getNextId(), @@ -375,7 +431,6 @@ private RelationPlan createAnalyzePlan(Analysis analysis, Analyze analyzeStateme symbolAllocator.newSymbol("rows", BIGINT), tableStatisticsMetadata.getTableStatistics().contains(ROW_COUNT), tableStatisticAggregation.getDescriptor()); - return new RelationPlan(planNode, analysis.getScope(analyzeStatement), planNode.getOutputSymbols(), Optional.empty()); } private RelationPlan createTableCreationPlan(Analysis analysis, Query query) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/TypeAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/planner/TypeAnalyzer.java index 4e9dd5a63c33..f677abf5c8fd 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/TypeAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/TypeAnalyzer.java @@ -29,6 +29,7 @@ import javax.inject.Inject; import java.util.Map; +import java.util.Optional; import static io.trino.sql.analyzer.ExpressionAnalyzer.analyzeExpressions; import static io.trino.sql.analyzer.QueryType.OTHERS; @@ -59,6 +60,7 @@ public Map, Type> getTypes(Session session, TypeProvider inp plannerContext, statementAnalyzerFactory, new AllowAllAccessControl(), + Optional.empty(), inputTypes, expressions, ImmutableMap.of(), diff --git a/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeInputRewrite.java b/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeInputRewrite.java index 52ae3ad9a630..454deb5ed074 100644 --- a/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeInputRewrite.java +++ b/core/trino-main/src/main/java/io/trino/sql/rewrite/DescribeInputRewrite.java @@ -20,6 +20,7 @@ import io.trino.sql.analyzer.Analysis; import io.trino.sql.analyzer.Analyzer; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.parser.SqlParser; import io.trino.sql.tree.AstVisitor; import io.trino.sql.tree.DescribeInput; @@ -74,9 +75,10 @@ public Statement rewrite( Statement node, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { - return (Statement) new Visitor(session, parser, analyzerFactory, parameters, parameterLookup, warningCollector).process(node, null); + return (Statement) new Visitor(session, parser, analyzerFactory, parameters, parameterLookup, warningCollector, queryAnalyzerFactory).process(node, null); } private static final class Visitor @@ -88,6 +90,7 @@ private static final class Visitor private final List parameters; private final Map, Expression> parameterLookup; private final WarningCollector warningCollector; + private final QueryAnalyzerFactory queryAnalyzerFactory; public Visitor( Session session, @@ -95,7 +98,8 @@ public Visitor( AnalyzerFactory analyzerFactory, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { this.session = requireNonNull(session, "session is null"); this.parser = requireNonNull(parser, "parser is null"); @@ -103,6 +107,7 @@ public Visitor( this.parameters = parameters; this.parameterLookup = parameterLookup; this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } @Override @@ -112,7 +117,7 @@ protected Node visitDescribeInput(DescribeInput node, Void context) Statement statement = parser.createStatement(sqlString, createParsingOptions(session)); // create analysis for the query we are describing. - Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, warningCollector); + Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, warningCollector, queryAnalyzerFactory); Analysis analysis = analyzer.analyze(statement, DESCRIBE); // get all parameters in query 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 46dd3d379384..438bf0bfe57b 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 @@ -22,6 +22,7 @@ import io.trino.sql.analyzer.Analyzer; import io.trino.sql.analyzer.AnalyzerFactory; import io.trino.sql.analyzer.Field; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.parser.SqlParser; import io.trino.sql.tree.AstVisitor; import io.trino.sql.tree.BooleanLiteral; @@ -73,9 +74,10 @@ public Statement rewrite( Statement node, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { - return (Statement) new Visitor(session, parser, analyzerFactory, parameters, parameterLookup, warningCollector).process(node, null); + return (Statement) new Visitor(session, parser, analyzerFactory, parameters, parameterLookup, warningCollector, queryAnalyzerFactory).process(node, null); } private static final class Visitor @@ -87,6 +89,7 @@ private static final class Visitor private final List parameters; private final Map, Expression> parameterLookup; private final WarningCollector warningCollector; + private final QueryAnalyzerFactory queryAnalyzerFactory; public Visitor( Session session, @@ -94,7 +97,8 @@ public Visitor( AnalyzerFactory analyzerFactory, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { this.session = requireNonNull(session, "session is null"); this.parser = requireNonNull(parser, "parser is null"); @@ -102,6 +106,7 @@ public Visitor( this.parameters = parameters; this.parameterLookup = parameterLookup; this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); + this.queryAnalyzerFactory = requireNonNull(queryAnalyzerFactory, "queryAnalyzerFactory is null"); } @Override @@ -110,7 +115,7 @@ protected Node visitDescribeOutput(DescribeOutput node, Void context) String sqlString = session.getPreparedStatement(node.getName().getValue()); Statement statement = parser.createStatement(sqlString, createParsingOptions(session)); - Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, warningCollector); + Analyzer analyzer = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, warningCollector, queryAnalyzerFactory); Analysis analysis = analyzer.analyze(statement, DESCRIBE); Optional limit = Optional.empty(); diff --git a/core/trino-main/src/main/java/io/trino/sql/rewrite/ExplainRewrite.java b/core/trino-main/src/main/java/io/trino/sql/rewrite/ExplainRewrite.java index 5d2b187f3156..abaffb4b8041 100644 --- a/core/trino-main/src/main/java/io/trino/sql/rewrite/ExplainRewrite.java +++ b/core/trino-main/src/main/java/io/trino/sql/rewrite/ExplainRewrite.java @@ -18,6 +18,7 @@ import io.trino.execution.QueryPreparer.PreparedQuery; import io.trino.execution.warnings.WarningCollector; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.analyzer.QueryExplainer; import io.trino.sql.analyzer.QueryExplainerFactory; import io.trino.sql.tree.AstVisitor; @@ -65,7 +66,8 @@ public Statement rewrite( Statement node, List parameter, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { return (Statement) new Visitor(session, queryPreparer, queryExplainerFactory.createQueryExplainer(analyzerFactory), warningCollector).process(node, null); } diff --git a/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowQueriesRewrite.java b/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowQueriesRewrite.java index 6800e87a63f8..bfb1613f4831 100644 --- a/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowQueriesRewrite.java +++ b/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowQueriesRewrite.java @@ -49,6 +49,7 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.session.PropertyMetadata; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.parser.ParsingException; import io.trino.sql.parser.SqlParser; import io.trino.sql.tree.AllColumns; @@ -199,7 +200,8 @@ public Statement rewrite( Statement node, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { Visitor visitor = new Visitor( metadata, diff --git a/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowStatsRewrite.java b/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowStatsRewrite.java index 2d0b1525abca..e65a10b33f92 100644 --- a/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowStatsRewrite.java +++ b/core/trino-main/src/main/java/io/trino/sql/rewrite/ShowStatsRewrite.java @@ -33,6 +33,7 @@ import io.trino.spi.type.Type; import io.trino.sql.QueryUtil; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.analyzer.QueryExplainer; import io.trino.sql.analyzer.QueryExplainerFactory; import io.trino.sql.planner.Plan; @@ -104,7 +105,8 @@ public Statement rewrite( Statement node, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { return (Statement) new Visitor(session, parameters, queryExplainerFactory.createQueryExplainer(analyzerFactory), warningCollector, statsCalculator).process(node, null); } diff --git a/core/trino-main/src/main/java/io/trino/sql/rewrite/StatementRewrite.java b/core/trino-main/src/main/java/io/trino/sql/rewrite/StatementRewrite.java index 80bd79ca5a73..175366d21c8d 100644 --- a/core/trino-main/src/main/java/io/trino/sql/rewrite/StatementRewrite.java +++ b/core/trino-main/src/main/java/io/trino/sql/rewrite/StatementRewrite.java @@ -17,6 +17,7 @@ import io.trino.Session; import io.trino.execution.warnings.WarningCollector; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.tree.Expression; import io.trino.sql.tree.NodeRef; import io.trino.sql.tree.Parameter; @@ -46,7 +47,8 @@ public Statement rewrite( Statement node, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector) + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory) { for (Rewrite rewrite : rewrites) { node = requireNonNull( @@ -56,7 +58,8 @@ public Statement rewrite( node, parameters, parameterLookup, - warningCollector), + warningCollector, + queryAnalyzerFactory), "Statement rewrite returned null"); } return node; @@ -70,6 +73,7 @@ Statement rewrite( Statement node, List parameters, Map, Expression> parameterLookup, - WarningCollector warningCollector); + WarningCollector warningCollector, + QueryAnalyzerFactory queryAnalyzerFactory); } } diff --git a/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java b/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java index d548b3b8e583..1c0c43c9b88e 100644 --- a/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java +++ b/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java @@ -147,6 +147,7 @@ import io.trino.sql.analyzer.Analysis; import io.trino.sql.analyzer.Analyzer; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.analyzer.QueryExplainer; import io.trino.sql.analyzer.QueryExplainerFactory; import io.trino.sql.analyzer.StatementAnalyzerFactory; @@ -610,8 +611,11 @@ public TypeManager getTypeManager() @Override public QueryExplainer getQueryExplainer() { - QueryExplainerFactory queryExplainerFactory = createQueryExplainerFactory(getPlanOptimizers(true)); + List planOptimizers = getPlanOptimizers(true); + QueryAnalyzerFactory queryAnalyzerFactory = createQueryAnalyzerFactory(planOptimizers); + QueryExplainerFactory queryExplainerFactory = createQueryExplainerFactory(planOptimizers, queryAnalyzerFactory); AnalyzerFactory analyzerFactory = createAnalyzerFactory(queryExplainerFactory); + return queryExplainerFactory.createQueryExplainer(analyzerFactory); } @@ -1059,12 +1063,15 @@ public Plan createPlan(Session session, @Language("SQL") String sql, List optimizers) + private QueryExplainerFactory createQueryExplainerFactory(List optimizers, QueryAnalyzerFactory queryAnalyzerFactory) { return new QueryExplainerFactory( () -> optimizers, @@ -1090,7 +1097,8 @@ private QueryExplainerFactory createQueryExplainerFactory(List op plannerContext, statementAnalyzerFactory, statsCalculator, - costCalculator); + costCalculator, + queryAnalyzerFactory); } private AnalyzerFactory createAnalyzerFactory(QueryExplainerFactory queryExplainerFactory) @@ -1114,6 +1122,11 @@ private AnalyzerFactory createAnalyzerFactory(QueryExplainerFactory queryExplain new ExplainRewrite(queryExplainerFactory, new QueryPreparer(sqlParser))))); } + private QueryAnalyzerFactory createQueryAnalyzerFactory(List planOptimizers) + { + return new QueryAnalyzerFactory(() -> planOptimizers, plannerContext, statsCalculator, costCalculator); + } + private static List getNextBatch(SplitSource splitSource) { return getFutureValue(splitSource.getNextBatch(NOT_PARTITIONED, Lifespan.taskWide(), 1000)).getSplits(); diff --git a/core/trino-main/src/main/java/io/trino/util/StatementUtils.java b/core/trino-main/src/main/java/io/trino/util/StatementUtils.java index 26a7a761f85f..504b6b233956 100644 --- a/core/trino-main/src/main/java/io/trino/util/StatementUtils.java +++ b/core/trino-main/src/main/java/io/trino/util/StatementUtils.java @@ -59,6 +59,7 @@ import io.trino.spi.resourcegroups.QueryType; import io.trino.sql.tree.AddColumn; import io.trino.sql.tree.Analyze; +import io.trino.sql.tree.AnalyzeForQuery; import io.trino.sql.tree.Call; import io.trino.sql.tree.Comment; import io.trino.sql.tree.Commit; @@ -177,6 +178,7 @@ private StatementUtils() {} .add(basicStatement(Update.class, UPDATE)) .add(basicStatement(Delete.class, DELETE)) .add(basicStatement(Analyze.class, ANALYZE)) + .add(basicStatement(AnalyzeForQuery.class, ANALYZE)) // DDL .add(dataDefinitionStatement(AddColumn.class, AddColumnTask.class)) .add(dataDefinitionStatement(Call.class, CallTask.class)) diff --git a/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java b/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java index 148e49c7637d..cba6f20b8482 100644 --- a/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java +++ b/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java @@ -92,7 +92,7 @@ public abstract class BaseDataDefinitionTaskTest protected static final String MATERIALIZED_VIEW_PROPERTY_2_NAME = "property2"; protected static final String MATERIALIZED_VIEW_PROPERTY_2_DEFAULT_VALUE = "defaultProperty2Value"; - private LocalQueryRunner queryRunner; + protected LocalQueryRunner queryRunner; protected Session testSession; protected MockMetadata metadata; protected PlannerContext plannerContext; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java index 2bf1f02816fb..89cd2a363d98 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java @@ -46,6 +46,7 @@ import io.trino.spi.session.PropertyMetadata; import io.trino.sql.PlannerContext; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.analyzer.StatementAnalyzerFactory; import io.trino.sql.parser.SqlParser; import io.trino.sql.planner.TestingConnectorTransactionHandle; @@ -117,6 +118,7 @@ public class TestCreateMaterializedViewTask private AnalyzerFactory analyzerFactory; private MaterializedViewPropertyManager materializedViewPropertyManager; private LocalQueryRunner queryRunner; + private QueryAnalyzerFactory queryAnalyzerFactory; @BeforeMethod public void setUp() @@ -141,6 +143,7 @@ public void setUp() parser = queryRunner.getSqlParser(); analyzerFactory = new AnalyzerFactory(createTestingStatementAnalyzerFactory(plannerContext, new AllowAllAccessControl(), new TablePropertyManager(), new AnalyzePropertyManager()), new StatementRewrite(ImmutableSet.of())); queryStateMachine = stateMachine(transactionManager, createTestMetadataManager(), new AllowAllAccessControl()); + queryAnalyzerFactory = new QueryAnalyzerFactory(() -> queryRunner.getPlanOptimizers(true), plannerContext, queryRunner.getStatsCalculator(), queryRunner.getCostCalculator()); } @AfterMethod(alwaysRun = true) @@ -163,7 +166,7 @@ public void testCreateMaterializedViewIfNotExists() ImmutableList.of(), Optional.empty()); - getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager) + getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager, queryAnalyzerFactory) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP)); assertEquals(metadata.getCreateMaterializedViewCallCount(), 1); } @@ -180,7 +183,7 @@ public void testCreateMaterializedViewWithExistingView() ImmutableList.of(), Optional.empty()); - assertTrinoExceptionThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager) + assertTrinoExceptionThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager, queryAnalyzerFactory) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP))) .hasErrorCode(ALREADY_EXISTS) .hasMessage("Materialized view already exists"); @@ -200,7 +203,7 @@ public void testCreateMaterializedViewWithInvalidProperty() ImmutableList.of(new Property(new Identifier("baz"), new StringLiteral("abc"))), Optional.empty()); - assertTrinoExceptionThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager) + assertTrinoExceptionThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager, queryAnalyzerFactory) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP))) .hasErrorCode(INVALID_MATERIALIZED_VIEW_PROPERTY) .hasMessage("Catalog 'catalog' materialized view property 'baz' does not exist"); @@ -223,7 +226,7 @@ public void testCreateMaterializedViewWithDefaultProperties() new Property(new Identifier("bar"))), // set bar to DEFAULT Optional.empty()); getFutureValue( - new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager) + new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager, queryAnalyzerFactory) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP)); Optional definitionOptional = metadata.getMaterializedView(testSession, QualifiedObjectName.valueOf(materializedViewName.toString())); @@ -254,7 +257,7 @@ public void testCreateDenyPermission() new TablePropertyManager(), new AnalyzePropertyManager()); AnalyzerFactory analyzerFactory = new AnalyzerFactory(statementAnalyzerFactory, new StatementRewrite(ImmutableSet.of())); - assertThatThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, accessControl, parser, analyzerFactory, materializedViewPropertyManager) + assertThatThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, accessControl, parser, analyzerFactory, materializedViewPropertyManager, queryAnalyzerFactory) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP))) .isInstanceOf(AccessDeniedException.class) .hasMessageContaining("Cannot create materialized view catalog.schema.test_mv"); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateViewTask.java index 22680e4dcf75..9751642688cb 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateViewTask.java @@ -22,6 +22,7 @@ import io.trino.metadata.TablePropertyManager; import io.trino.security.AllowAllAccessControl; import io.trino.sql.analyzer.AnalyzerFactory; +import io.trino.sql.analyzer.QueryAnalyzerFactory; import io.trino.sql.parser.SqlParser; import io.trino.sql.rewrite.StatementRewrite; import io.trino.sql.tree.AllColumns; @@ -49,6 +50,7 @@ public class TestCreateViewTask private static final String CATALOG_NAME = "catalog"; private SqlParser parser; private AnalyzerFactory analyzerFactory; + private QueryAnalyzerFactory queryAnalyzerFactory; @Override @BeforeMethod @@ -57,6 +59,7 @@ public void setUp() super.setUp(); parser = new SqlParser(); analyzerFactory = new AnalyzerFactory(createTestingStatementAnalyzerFactory(plannerContext, new AllowAllAccessControl(), new TablePropertyManager(), new AnalyzePropertyManager()), new StatementRewrite(ImmutableSet.of())); + queryAnalyzerFactory = new QueryAnalyzerFactory(() -> queryRunner.getPlanOptimizers(true), plannerContext, queryRunner.getStatsCalculator(), queryRunner.getCostCalculator()); QualifiedObjectName tableName = qualifiedObjectName("mock_table"); metadata.createTable(testSession, CATALOG_NAME, someTable(tableName), false); } @@ -132,6 +135,6 @@ private ListenableFuture executeCreateView(QualifiedName viewName, boolean replace, Optional.empty(), Optional.empty()); - return new CreateViewTask(metadata, new AllowAllAccessControl(), parser, analyzerFactory).execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP); + return new CreateViewTask(metadata, new AllowAllAccessControl(), parser, analyzerFactory, queryAnalyzerFactory).execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP); } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java index 5742af206575..364fafc87306 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java @@ -126,6 +126,12 @@ public Optional getTableHandleForStatisticsCollection(Session sessi throw new UnsupportedOperationException(); } + @Override + public Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, TupleDomain tupleDomain) + { + throw new UnsupportedOperationException(); + } + @Override public Optional getTableHandleForExecute(Session session, TableHandle tableHandle, String procedureName, Map executeProperties) { diff --git a/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java index f3ea117e7d87..f178821ab153 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java @@ -138,6 +138,12 @@ public Optional getTableHandleForStatisticsCollection(Session sessi return delegate.getTableHandleForStatisticsCollection(session, tableName, analyzeProperties); } + @Override + public Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, TupleDomain tupleDomain) + { + return delegate.getTableHandleForStatisticsCollection(session, tableName, tupleDomain); + } + @Override public Optional getTableHandleForExecute(Session session, TableHandle tableHandle, String procedureName, Map executeProperties) { 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 2999e81b10b2..61a568b0d447 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 @@ -207,6 +207,7 @@ public class TestAnalyzer private PlannerContext plannerContext; private TablePropertyManager tablePropertyManager; private AnalyzePropertyManager analyzePropertyManager; + private QueryAnalyzerFactory queryAnalyzerFactory; @Test public void testTooManyArguments() @@ -5322,6 +5323,8 @@ public void setup() tablePropertyManager = queryRunner.getTablePropertyManager(); analyzePropertyManager = queryRunner.getAnalyzePropertyManager(); + queryAnalyzerFactory = new QueryAnalyzerFactory(() -> queryRunner.getPlanOptimizers(true), plannerContext, queryRunner.getStatsCalculator(), queryRunner.getCostCalculator()); + queryRunner.createCatalog(SECOND_CATALOG, MockConnectorFactory.create("second"), ImmutableMap.of()); queryRunner.createCatalog(THIRD_CATALOG, MockConnectorFactory.create("third"), ImmutableMap.of()); @@ -5661,7 +5664,8 @@ private Analyzer createAnalyzer(Session session, AccessControl accessControl) session, emptyList(), emptyMap(), - WarningCollector.NOOP); + WarningCollector.NOOP, + queryAnalyzerFactory); } private Analysis analyze(@Language("SQL") String query) diff --git a/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 b/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 index b8b526dce5fa..93c25ba82a6b 100644 --- a/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 +++ b/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 @@ -78,6 +78,7 @@ statement ('(' (callArgument (',' callArgument)*)? ')')? (WHERE where=booleanExpression)? #tableExecute | ANALYZE qualifiedName (WITH properties)? #analyze + | ANALYZE FOR (query | '('query')')? #analyzeForQuery | CREATE (OR REPLACE)? MATERIALIZED VIEW (IF NOT EXISTS)? qualifiedName (COMMENT string)? 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 01b246c2354c..f52aaa037f9d 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 @@ -17,11 +17,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import io.trino.sql.parser.SqlBaseParser.AnalyzeForQueryContext; import io.trino.sql.tree.AddColumn; import io.trino.sql.tree.AliasedRelation; import io.trino.sql.tree.AllColumns; import io.trino.sql.tree.AllRows; import io.trino.sql.tree.Analyze; +import io.trino.sql.tree.AnalyzeForQuery; import io.trino.sql.tree.AnchorPattern; import io.trino.sql.tree.ArithmeticBinaryExpression; import io.trino.sql.tree.ArithmeticUnaryExpression; @@ -645,6 +647,12 @@ public Node visitAnalyze(SqlBaseParser.AnalyzeContext context) properties); } + @Override + public Node visitAnalyzeForQuery(AnalyzeForQueryContext context) + { + return new AnalyzeForQuery(getLocation(context), (Query) visit(context.query())); + } + @Override public Node visitAddColumn(SqlBaseParser.AddColumnContext context) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/AnalyzeForQuery.java b/core/trino-parser/src/main/java/io/trino/sql/tree/AnalyzeForQuery.java new file mode 100644 index 000000000000..0df9820ae02e --- /dev/null +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/AnalyzeForQuery.java @@ -0,0 +1,83 @@ +/* + * 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; +import java.util.Optional; + +import static com.google.common.base.MoreObjects.toStringHelper; + +public class AnalyzeForQuery + extends Statement +{ + private final Query query; + + public AnalyzeForQuery(NodeLocation location, Query query) + { + this(Optional.of(location), query); + } + + private AnalyzeForQuery(Optional location, Query query) + { + super(location); + this.query = query; + } + + public Query getQuery() + { + return query; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitAnalyzeForQuery(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.of(query); + } + + @Override + public int hashCode() + { + return Objects.hash(query); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + AnalyzeForQuery o = (AnalyzeForQuery) obj; + return Objects.equals(query, o.query); + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("query", query) + .toString(); + } +} 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 38da35912c40..0feb0a880f13 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 @@ -682,6 +682,11 @@ protected R visitAnalyze(Analyze node, C context) return visitStatement(node, context); } + protected R visitAnalyzeForQuery(AnalyzeForQuery node, C context) + { + return visitStatement(node, context); + } + protected R visitCreateView(CreateView node, C context) { return visitStatement(node, context); diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/DefaultTraversalVisitor.java b/core/trino-parser/src/main/java/io/trino/sql/tree/DefaultTraversalVisitor.java index a4c89adff8ee..624f438f57ca 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/DefaultTraversalVisitor.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/DefaultTraversalVisitor.java @@ -743,6 +743,14 @@ protected Void visitAnalyze(Analyze node, C context) return null; } + @Override + protected Void visitAnalyzeForQuery(AnalyzeForQuery node, C context) + { + process(node.getQuery(), context); + + return null; + } + @Override protected Void visitCreateView(CreateView node, C context) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java index a0030b64ae84..ef34b1079d05 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java @@ -92,6 +92,16 @@ default ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSess throw new TrinoException(NOT_SUPPORTED, "This connector does not support analyze"); } + /** + * Returns a table handle for the specified table name, or null if the connector does not contain the table. + * The returned table handle can contain information in tupleDomain. + */ + @Nullable + default ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSession session, SchemaTableName tableName, TupleDomain tupleDomain) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support analyze"); + } + /** * Create initial handle for execution of table procedure. The handle will be used through planning process. It will be converted to final * handle used for execution via @{link {@link ConnectorMetadata#beginTableExecute} diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java index 8e20fa03f4df..1694a39532d0 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java @@ -193,6 +193,14 @@ public ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSessi } } + @Override + public ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSession session, SchemaTableName tableName, TupleDomain tupleDomain) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.getTableHandleForStatisticsCollection(session, tableName, tupleDomain); + } + } + @Override public Optional getTableHandleForExecute(ConnectorSession session, ConnectorTableHandle tableHandle, String procedureName, Map executeProperties) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index c56a2697a286..9935562d9217 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -520,6 +520,28 @@ public ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSessi return handle; } + @Override + public ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSession session, SchemaTableName tableName, TupleDomain tupleDomain) + { + HiveTableHandle hiveTable = getTableHandle(session, tableName); + if (hiveTable == null) { + return null; + } + if (hiveTable.getPartitionColumns().isEmpty()) { + return hiveTable; + } + + HivePartitionResult partitions = partitionManager.getPartitions(metastore, hiveTable, new Constraint(tupleDomain)); + hiveTable = partitionManager.applyPartitionResult(hiveTable, partitions, alwaysTrue()); + hiveTable.getPartitionNames(); + + return hiveTable.withAnalyzePartitionValues( + ImmutableList.copyOf(partitions.getPartitions()).stream() + .map(HivePartition::getPartitionId) + .map(HivePartitionManager::extractPartitionValues) + .collect(toImmutableList())); + } + @Override public Optional getSystemTable(ConnectorSession session, SchemaTableName tableName) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestAnalyzeForQuery.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestAnalyzeForQuery.java new file mode 100644 index 000000000000..1ae53c4172f1 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestAnalyzeForQuery.java @@ -0,0 +1,80 @@ +/* + * 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.plugin.hive; + +import com.google.common.collect.ImmutableList; +import io.trino.Session; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static io.trino.tpch.TpchTable.NATION; + +public class TestAnalyzeForQuery + extends AbstractTestQueryFramework +{ + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + return HiveQueryRunner.builder() + // create nation so tpch schema got created + .setInitialTables(ImmutableList.of(NATION)) + .setNodeCount(1) + .build(); + } + + @BeforeClass + public void setUp() + { + assertUpdate("CREATE TABLE nation_partitioned(nationkey BIGINT, name VARCHAR, comment VARCHAR, regionkey BIGINT) WITH (partitioned_by = ARRAY['regionkey'])"); + assertUpdate("INSERT INTO nation_partitioned SELECT nationkey, name, comment, regionkey FROM tpch.tiny.nation", 25); + assertUpdate("CREATE TABLE region AS SELECT * FROM tpch.tiny.region", 5); + } + + @Test + public void testAnalyzeForQuery() + { + // non-partitioned table + assertUpdate("ANALYZE FOR (SELECT * FROM region)", 5); + + // partitioned table + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned)", 25); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey IS NOT NULL)", 25); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey IS NULL)", 0); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey = 1)", 5); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey IN (1, 3))", 10); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey BETWEEN 1 AND 1 + 2)", 15); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey > 3)", 5); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey < 1)", 5); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey > 0 and regionkey < 4)", 15); + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey > 10 or regionkey < 0)", 0); + assertUpdate("ANALYZE FOR (SELECT *, * FROM nation_partitioned WHERE regionkey > 10 or regionkey < 0)", 0); + assertUpdate("ANALYZE FOR (SELECT *, *, regionkey FROM nation_partitioned WHERE regionkey > 10 or regionkey < 0)", 0); + assertUpdate("ANALYZE FOR (SELECT *, regionkey FROM nation_partitioned)", 25); + assertUpdate("ANALYZE FOR (SELECT *, * FROM nation_partitioned)", 25); + assertUpdate("ANALYZE FOR (SELECT regionkey FROM nation_partitioned WHERE regionkey BETWEEN 1 AND 1 + 2)", 15); + assertUpdate("ANALYZE FOR (SELECT regionkey, nationkey FROM nation_partitioned WHERE regionkey BETWEEN 1 AND 1 + 2)", 15); + + // merge same tables and union with different partitions + assertUpdate("ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey = 1 UNION ALL SELECT * FROM nation_partitioned WHERE regionkey = 3)", 10); + + // prepared statement + Session session = Session.builder(getSession()) + .addPreparedStatement("my_query", "ANALYZE FOR (SELECT * FROM nation_partitioned WHERE regionkey = ?)") + .build(); + assertUpdate(session, "EXECUTE my_query USING 1", 5); + } +}