Try to expand an ordinary (non-materialized, non-parameterized) VIEW into an inline subquery. * This replaces the TableNode wrapping a StorageView with a QueryNode/UnionNode built from the * view's inner query AST, making the view transparent to the analyzer and all optimization passes. */
| 5425 | * view's inner query AST, making the view transparent to the analyzer and all optimization passes. |
| 5426 | */ |
| 5427 | void QueryAnalyzer::inlineViewSubqueryIfNeeded(QueryTreeNodePtr & join_tree_node, IdentifierResolveScope & scope) const |
| 5428 | { |
| 5429 | if (!scope.context->getSettingsRef()[Setting::analyzer_inline_views]) |
| 5430 | return; |
| 5431 | |
| 5432 | auto * table_node = join_tree_node->as<TableNode>(); |
| 5433 | if (!table_node) |
| 5434 | return; |
| 5435 | |
| 5436 | const auto & storage = table_node->getStorage(); |
| 5437 | const auto * view = typeid_cast<const StorageView *>(storage.get()); |
| 5438 | if (!view || view->isParameterizedView()) |
| 5439 | return; |
| 5440 | |
| 5441 | /// Do not inline views with FINAL/SAMPLE modifiers for now. |
| 5442 | if (table_node->hasTableExpressionModifiers()) |
| 5443 | return; |
| 5444 | |
| 5445 | /// Get the view's inner query AST. |
| 5446 | const auto & storage_snapshot = table_node->getStorageSnapshot(); |
| 5447 | |
| 5448 | auto view_context = StorageView::getViewSubqueryContext(scope.context, storage_snapshot); |
| 5449 | |
| 5450 | /// Check for row policies on the view itself. |
| 5451 | auto storage_id = storage->getStorageID(); |
| 5452 | auto row_policy_filter = scope.context->getRowPolicyFilter( |
| 5453 | storage_id.getDatabaseName(), storage_id.getTableName(), RowPolicyFilterType::SELECT_FILTER); |
| 5454 | bool has_row_policy = row_policy_filter && !row_policy_filter->isAlwaysTrue(); |
| 5455 | |
| 5456 | /// Build the query tree from the view's inner query AST. |
| 5457 | ASTPtr view_ast = storage_snapshot->metadata->getSelectQuery().inner_query->clone(); |
| 5458 | auto view_query_tree = buildQueryTree(view_ast, view_context); |
| 5459 | QueryAnalyzer view_analyzer(this->only_analyze); |
| 5460 | view_analyzer.resolve(view_query_tree, {}, view_context); |
| 5461 | |
| 5462 | /// Mark the inlined query as a subquery so it is wrapped in parentheses when serialized to AST |
| 5463 | /// (e.g. for sending to parallel replicas). Without this, it produces `FROM SELECT ...` instead |
| 5464 | /// of `FROM (SELECT ...)`. |
| 5465 | if (auto * query_node = view_query_tree->as<QueryNode>()) |
| 5466 | query_node->setIsSubquery(true); |
| 5467 | else if (auto * union_node = view_query_tree->as<UnionNode>()) |
| 5468 | union_node->setIsSubquery(true); |
| 5469 | |
| 5470 | /// The VIEW's declared column types may differ from what the inner subquery actually produces |
| 5471 | /// (e.g. the view was created with explicit column types, or implicit conversions apply). |
| 5472 | /// Normally StorageView adds a converting step in the query pipeline. When inlining, we must |
| 5473 | /// reproduce that by wrapping the subquery in SELECT CAST(col, 'ViewType') ... FROM (subquery). |
| 5474 | auto view_columns_list = storage_snapshot->metadata->getColumns().getOrdinary(); |
| 5475 | NamesAndTypes view_columns(view_columns_list.begin(), view_columns_list.end()); |
| 5476 | |
| 5477 | NamesAndTypes subquery_columns; |
| 5478 | if (const auto * query_node = view_query_tree->as<QueryNode>()) |
| 5479 | subquery_columns = query_node->getProjectionColumns(); |
| 5480 | else if (const auto * union_node = view_query_tree->as<UnionNode>()) |
| 5481 | subquery_columns = union_node->computeProjectionColumns(); |
| 5482 | |
| 5483 | bool needs_type_conversion = false; |
| 5484 | if (view_columns.size() == subquery_columns.size()) |
nothing calls this directly
no test coverage detected