-
Notifications
You must be signed in to change notification settings - Fork 13
25.8 Antalya: Fix joins with Iceberg tables #1082
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
38a45e4
43825ba
4e9ac7e
3dd9b46
f5215f2
023e38d
bff8bba
8dcdd67
e143307
70805a8
39882f5
9ce6b71
454bafd
239cf4c
154e5fe
e1a8cd1
06e48e4
6ceecc4
2513c29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,9 @@ | |
| #include <Analyzer/QueryTreeBuilder.h> | ||
| #include <Analyzer/QueryNode.h> | ||
| #include <Analyzer/ColumnNode.h> | ||
| #include <Analyzer/JoinNode.h> | ||
| #include <Analyzer/InDepthQueryTreeVisitor.h> | ||
| #include <Analyzer/Utils.h> | ||
| #include <Storages/StorageDistributed.h> | ||
| #include <TableFunctions/TableFunctionFactory.h> | ||
|
|
||
|
|
@@ -112,7 +114,7 @@ class SearcherVisitor : public InDepthQueryTreeVisitorWithContext<SearcherVisito | |
| using Base = InDepthQueryTreeVisitorWithContext<SearcherVisitor>; | ||
| using Base::Base; | ||
|
|
||
| explicit SearcherVisitor(QueryTreeNodeType type_, ContextPtr context) : Base(context), type(type_) {} | ||
| explicit SearcherVisitor(std::unordered_set<QueryTreeNodeType> types_, ContextPtr context) : Base(context), types(types_) {} | ||
|
|
||
| bool needChildVisit(QueryTreeNodePtr &, QueryTreeNodePtr & /*child*/) | ||
| { | ||
|
|
@@ -126,15 +128,20 @@ class SearcherVisitor : public InDepthQueryTreeVisitorWithContext<SearcherVisito | |
|
|
||
| auto node_type = node->getNodeType(); | ||
|
|
||
| if (node_type == type) | ||
| if (types.contains(node_type)) | ||
| { | ||
| passed_node = node; | ||
| passed_type = node_type; | ||
| } | ||
| } | ||
|
|
||
| QueryTreeNodePtr getNode() const { return passed_node; } | ||
| std::optional<QueryTreeNodeType> getType() const { return passed_type; } | ||
|
|
||
| private: | ||
| QueryTreeNodeType type; | ||
| std::unordered_set<QueryTreeNodeType> types; | ||
| QueryTreeNodePtr passed_node; | ||
| std::optional<QueryTreeNodeType> passed_type; | ||
| }; | ||
|
|
||
| /* | ||
|
|
@@ -216,49 +223,80 @@ void IStorageCluster::updateQueryWithJoinToSendIfNeeded( | |
| { | ||
| case ObjectStorageClusterJoinMode::LOCAL: | ||
| { | ||
| auto modified_query_tree = query_tree->clone(); | ||
| bool need_modify = false; | ||
|
|
||
| SearcherVisitor table_function_searcher(QueryTreeNodeType::TABLE_FUNCTION, context); | ||
| table_function_searcher.visit(query_tree); | ||
| auto table_function_node = table_function_searcher.getNode(); | ||
| if (!table_function_node) | ||
| throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't find table function node"); | ||
| auto info = getQueryTreeInfo(query_tree, context); | ||
|
|
||
| if (has_join) | ||
| if (info.has_join || info.has_cross_join || info.has_local_columns_in_where) | ||
| { | ||
| auto table_function = extractTableFunctionASTPtrFromSelectQuery(query_to_send); | ||
| auto query_tree_distributed = buildTableFunctionQueryTree(table_function, context); | ||
| auto & table_function_ast = table_function->as<ASTFunction &>(); | ||
| query_tree_distributed->setAlias(table_function_ast.alias); | ||
| auto modified_query_tree = query_tree->clone(); | ||
|
|
||
| SearcherVisitor table_function_searcher({QueryTreeNodeType::TABLE, QueryTreeNodeType::TABLE_FUNCTION}, context); | ||
| table_function_searcher.visit(modified_query_tree); | ||
| auto table_function_node = table_function_searcher.getNode(); | ||
| if (!table_function_node) | ||
| throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't find table function node"); | ||
|
|
||
| QueryTreeNodePtr query_tree_distributed; | ||
|
|
||
| auto & query_node = modified_query_tree->as<QueryNode &>(); | ||
|
|
||
| if (info.has_join || info.has_cross_join) | ||
| { | ||
| if (table_function_searcher.getType().value() == QueryTreeNodeType::TABLE_FUNCTION) | ||
| { | ||
| auto table_function = extractTableFunctionASTPtrFromSelectQuery(query_to_send); | ||
| query_tree_distributed = buildTableFunctionQueryTree(table_function, context); | ||
| auto & table_function_ast = table_function->as<ASTFunction &>(); | ||
| query_tree_distributed->setAlias(table_function_ast.alias); | ||
| } | ||
| else if (info.has_join) | ||
|
||
| { | ||
| auto join_node = query_node.getJoinTree(); | ||
| query_tree_distributed = join_node->as<JoinNode>()->getLeftTableExpression()->clone(); | ||
| } | ||
| else | ||
| { | ||
| SearcherVisitor join_searcher({QueryTreeNodeType::CROSS_JOIN}, context); | ||
|
||
| join_searcher.visit(modified_query_tree); | ||
| auto cross_join_node = join_searcher.getNode(); | ||
| if (!cross_join_node) | ||
| throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't find CROSS JOIN node"); | ||
| query_tree_distributed = cross_join_node->as<CrossJoinNode>()->getTableExpressions()[0]->clone(); | ||
|
||
| } | ||
| } | ||
|
|
||
| // Find add used columns from table function to make proper projection list | ||
| // Need to do before changing WHERE condition | ||
| CollectUsedColumnsForSourceVisitor collector(table_function_node, context); | ||
| collector.visit(query_tree); | ||
| collector.visit(modified_query_tree); | ||
| const auto & columns = collector.getColumns(); | ||
|
|
||
| auto & query_node = modified_query_tree->as<QueryNode &>(); | ||
| query_node.resolveProjectionColumns(columns); | ||
| auto column_nodes_to_select = std::make_shared<ListNode>(); | ||
| column_nodes_to_select->getNodes().reserve(columns.size()); | ||
| for (auto & column : columns) | ||
| column_nodes_to_select->getNodes().emplace_back(std::make_shared<ColumnNode>(column, table_function_node)); | ||
| query_node.getProjectionNode() = column_nodes_to_select; | ||
|
|
||
| // Left only table function to send on cluster nodes | ||
| modified_query_tree = modified_query_tree->cloneAndReplace(query_node.getJoinTree(), query_tree_distributed); | ||
| if (info.has_local_columns_in_where) | ||
| { | ||
| if (query_node.getPrewhere()) | ||
| removeExpressionsThatDoNotDependOnTableIdentifiers(query_node.getPrewhere(), table_function_node, context); | ||
| if (query_node.getWhere()) | ||
| removeExpressionsThatDoNotDependOnTableIdentifiers(query_node.getWhere(), table_function_node, context); | ||
| } | ||
|
|
||
| need_modify = true; | ||
| } | ||
| query_node.getOrderByNode() = std::make_shared<ListNode>(); | ||
| query_node.getGroupByNode() = std::make_shared<ListNode>(); | ||
|
|
||
| if (has_local_columns_in_where) | ||
| { | ||
| auto & query_node = modified_query_tree->as<QueryNode &>(); | ||
| query_node.getWhere() = {}; | ||
| } | ||
| if (query_tree_distributed) | ||
| { | ||
| // Left only table function to send on cluster nodes | ||
| modified_query_tree = modified_query_tree->cloneAndReplace(query_node.getJoinTree(), query_tree_distributed); | ||
| } | ||
|
|
||
| if (need_modify) | ||
| query_to_send = queryNodeToDistributedSelectQuery(modified_query_tree); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
| case ObjectStorageClusterJoinMode::GLOBAL: | ||
|
|
@@ -492,38 +530,58 @@ void ReadFromCluster::initializePipeline(QueryPipelineBuilder & pipeline, const | |
| pipeline.init(std::move(pipe)); | ||
| } | ||
|
|
||
| QueryProcessingStage::Enum IStorageCluster::getQueryProcessingStage( | ||
| ContextPtr context, QueryProcessingStage::Enum to_stage, const StorageSnapshotPtr &, SelectQueryInfo & query_info) const | ||
| IStorageCluster::QueryTreeInfo IStorageCluster::getQueryTreeInfo(QueryTreeNodePtr query_tree, ContextPtr context) | ||
| { | ||
| auto object_storage_cluster_join_mode = context->getSettingsRef()[Setting::object_storage_cluster_join_mode]; | ||
| QueryTreeInfo info; | ||
|
|
||
| if (object_storage_cluster_join_mode != ObjectStorageClusterJoinMode::ALLOW) | ||
| { | ||
| if (!context->getSettingsRef()[Setting::allow_experimental_analyzer]) | ||
| throw Exception(ErrorCodes::NOT_IMPLEMENTED, | ||
| "object_storage_cluster_join_mode!='allow' is not supported without allow_experimental_analyzer=true"); | ||
| SearcherVisitor join_searcher({QueryTreeNodeType::JOIN, QueryTreeNodeType::CROSS_JOIN}, context); | ||
| join_searcher.visit(query_tree); | ||
|
|
||
| SearcherVisitor join_searcher(QueryTreeNodeType::JOIN, context); | ||
| join_searcher.visit(query_info.query_tree); | ||
| if (join_searcher.getNode()) | ||
| has_join = true; | ||
| if (join_searcher.getNode()) | ||
| { | ||
| if (join_searcher.getType() == QueryTreeNodeType::JOIN) | ||
| info.has_join = true; | ||
| else | ||
| info.has_cross_join = true; | ||
| } | ||
|
|
||
| SearcherVisitor table_function_searcher(QueryTreeNodeType::TABLE_FUNCTION, context); | ||
| table_function_searcher.visit(query_info.query_tree); | ||
| auto table_function_node = table_function_searcher.getNode(); | ||
| if (!table_function_node) | ||
| throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't find table function node"); | ||
| SearcherVisitor table_function_searcher({QueryTreeNodeType::TABLE, QueryTreeNodeType::TABLE_FUNCTION}, context); | ||
|
||
| table_function_searcher.visit(query_tree); | ||
| auto table_function_node = table_function_searcher.getNode(); | ||
| if (!table_function_node) | ||
| throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't find table or table function node"); | ||
|
|
||
| auto & query_node = query_tree->as<QueryNode &>(); | ||
| if (query_node.hasWhere() || query_node.hasPrewhere()) | ||
| { | ||
| CollectUsedColumnsForSourceVisitor collector_where(table_function_node, context, true); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and collector traverses the tree and collect all cases in single list.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the columns can be in different places, but let's say there are no local columns. Wouldn't
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually |
||
| auto & query_node = query_info.query_tree->as<QueryNode &>(); | ||
| if (query_node.hasPrewhere()) | ||
| collector_where.visit(query_node.getPrewhere()); | ||
| if (query_node.hasWhere()) | ||
| collector_where.visit(query_node.getWhere()); | ||
|
|
||
| // Can't use 'WHERE' on remote node if it contains columns from other sources | ||
| // SELECT x FROM datalake.table WHERE x IN local.table | ||
| // Need to modify 'WHERE' on remote node if it contains columns from other sources | ||
arthurpassos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (!collector_where.getColumns().empty()) | ||
| has_local_columns_in_where = true; | ||
| info.has_local_columns_in_where = true; | ||
| } | ||
|
|
||
| return info; | ||
| } | ||
|
|
||
| QueryProcessingStage::Enum IStorageCluster::getQueryProcessingStage( | ||
| ContextPtr context, QueryProcessingStage::Enum to_stage, const StorageSnapshotPtr &, SelectQueryInfo & query_info) const | ||
| { | ||
| auto object_storage_cluster_join_mode = context->getSettingsRef()[Setting::object_storage_cluster_join_mode]; | ||
|
|
||
| if (object_storage_cluster_join_mode != ObjectStorageClusterJoinMode::ALLOW) | ||
| { | ||
| if (!context->getSettingsRef()[Setting::allow_experimental_analyzer]) | ||
| throw Exception(ErrorCodes::NOT_IMPLEMENTED, | ||
| "object_storage_cluster_join_mode!='allow' is not supported without allow_experimental_analyzer=true"); | ||
|
|
||
| if (has_join || has_local_columns_in_where) | ||
| auto info = getQueryTreeInfo(query_info.query_tree, context); | ||
| if (info.has_join || info.has_cross_join || info.has_local_columns_in_where) | ||
| return QueryProcessingStage::Enum::FetchColumns; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <clickhouse> | ||
| <remote_servers> | ||
| <cluster_simple> | ||
| <shard> | ||
| <replica> | ||
| <host>node1</host> | ||
| <port>9000</port> | ||
| </replica> | ||
| </shard> | ||
| </cluster_simple> | ||
| </remote_servers> | ||
| </clickhouse> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I get it right that you are extracting ONLY the table function from the original AST? Why doesn't it have to be done for tables as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, I understand it is being done for tables when grabbing the left side. But why is it different. Is the query tree different for table function vs regular tables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Looks like lats two blocks work for table function too!
But now need to retest everything.