2222#include < Storages/IStorage.h>
2323#include < Storages/SelectQueryInfo.h>
2424#include < Storages/StorageDictionary.h>
25+ #include < Storages/extractTableFunctionFromSelectQuery.h>
26+ #include < Planner/Utils.h>
27+ #include < Analyzer/QueryTreeBuilder.h>
28+ #include < Analyzer/QueryNode.h>
29+ #include < Analyzer/ColumnNode.h>
30+ #include < Analyzer/InDepthQueryTreeVisitor.h>
2531
2632#include < algorithm>
2733#include < memory>
@@ -39,6 +45,12 @@ namespace Setting
3945 extern const SettingsBool parallel_replicas_local_plan;
4046 extern const SettingsString cluster_for_parallel_replicas;
4147 extern const SettingsNonZeroUInt64 max_parallel_replicas;
48+ extern const SettingsObjectStorageClusterJoinMode object_storage_cluster_join_mode;
49+ }
50+
51+ namespace ErrorCodes
52+ {
53+ extern const int LOGICAL_ERROR;
4254}
4355
4456namespace ErrorCodes
@@ -79,6 +91,175 @@ void ReadFromCluster::createExtension(const ActionsDAG::Node * predicate)
7991 cluster);
8092}
8193
94+ namespace
95+ {
96+
97+ /*
98+ Helping class to find in query tree first node of required type
99+ */
100+ class SearcherVisitor : public InDepthQueryTreeVisitorWithContext <SearcherVisitor>
101+ {
102+ public:
103+ using Base = InDepthQueryTreeVisitorWithContext<SearcherVisitor>;
104+ using Base::Base;
105+
106+ explicit SearcherVisitor (QueryTreeNodeType type_, ContextPtr context) : Base(context), type(type_) {}
107+
108+ bool needChildVisit (QueryTreeNodePtr &, QueryTreeNodePtr & /* child*/ )
109+ {
110+ return !passed_node;
111+ }
112+
113+ void enterImpl (QueryTreeNodePtr & node)
114+ {
115+ if (passed_node)
116+ return ;
117+
118+ auto node_type = node->getNodeType ();
119+
120+ if (node_type == type)
121+ passed_node = node;
122+ }
123+
124+ QueryTreeNodePtr getNode () const { return passed_node; }
125+
126+ private:
127+ QueryTreeNodeType type;
128+ QueryTreeNodePtr passed_node;
129+ };
130+
131+ /*
132+ Helping class to find all used columns with specific source
133+ */
134+ class CollectUsedColumnsForSourceVisitor : public InDepthQueryTreeVisitorWithContext <CollectUsedColumnsForSourceVisitor>
135+ {
136+ public:
137+ using Base = InDepthQueryTreeVisitorWithContext<CollectUsedColumnsForSourceVisitor>;
138+ using Base::Base;
139+
140+ explicit CollectUsedColumnsForSourceVisitor (
141+ QueryTreeNodePtr source_,
142+ ContextPtr context,
143+ bool collect_columns_from_other_sources_ = false )
144+ : Base(context)
145+ , source(source_)
146+ , collect_columns_from_other_sources(collect_columns_from_other_sources_)
147+ {}
148+
149+ void enterImpl (QueryTreeNodePtr & node)
150+ {
151+ auto node_type = node->getNodeType ();
152+
153+ if (node_type != QueryTreeNodeType::COLUMN)
154+ return ;
155+
156+ auto & column_node = node->as <ColumnNode &>();
157+ auto column_source = column_node.getColumnSourceOrNull ();
158+ if (!column_source)
159+ return ;
160+
161+ if ((column_source == source) != collect_columns_from_other_sources)
162+ {
163+ const auto & name = column_node.getColumnName ();
164+ if (!names.count (name))
165+ {
166+ columns.emplace_back (column_node.getColumn ());
167+ names.insert (name);
168+ }
169+ }
170+ }
171+
172+ const NamesAndTypes & getColumns () const { return columns; }
173+
174+ private:
175+ std::unordered_set<std::string> names;
176+ QueryTreeNodePtr source;
177+ NamesAndTypes columns;
178+ bool collect_columns_from_other_sources;
179+ };
180+
181+ };
182+
183+ /*
184+ Try to make subquery to send on nodes
185+ Converts
186+
187+ SELECT s3.c1, s3.c2, t.c3
188+ FROM
189+ s3Cluster(...) AS s3
190+ JOIN
191+ localtable as t
192+ ON s3.key == t.key
193+
194+ to
195+
196+ SELECT s3.c1, s3.c2, s3.key
197+ FROM
198+ s3Cluster(...) AS s3
199+ */
200+ void IStorageCluster::updateQueryWithJoinToSendIfNeeded (
201+ ASTPtr & query_to_send,
202+ QueryTreeNodePtr query_tree,
203+ const ContextPtr & context)
204+ {
205+ auto object_storage_cluster_join_mode = context->getSettingsRef ()[Setting::object_storage_cluster_join_mode];
206+ switch (object_storage_cluster_join_mode)
207+ {
208+ case ObjectStorageClusterJoinMode::LOCAL:
209+ {
210+ auto modified_query_tree = query_tree->clone ();
211+ bool need_modify = false ;
212+
213+ SearcherVisitor table_function_searcher (QueryTreeNodeType::TABLE_FUNCTION, context);
214+ table_function_searcher.visit (query_tree);
215+ auto table_function_node = table_function_searcher.getNode ();
216+ if (!table_function_node)
217+ throw Exception (ErrorCodes::LOGICAL_ERROR, " Can't find table function node" );
218+
219+ if (has_join)
220+ {
221+ auto table_function = extractTableFunctionASTPtrFromSelectQuery (query_to_send);
222+ auto query_tree_distributed = buildTableFunctionQueryTree (table_function, context);
223+ auto & table_function_ast = table_function->as <ASTFunction &>();
224+ query_tree_distributed->setAlias (table_function_ast.alias );
225+
226+ // Find add used columns from table function to make proper projection list
227+ CollectUsedColumnsForSourceVisitor collector (table_function_node, context);
228+ collector.visit (query_tree);
229+ const auto & columns = collector.getColumns ();
230+
231+ auto & query_node = modified_query_tree->as <QueryNode &>();
232+ query_node.resolveProjectionColumns (columns);
233+ auto column_nodes_to_select = std::make_shared<ListNode>();
234+ column_nodes_to_select->getNodes ().reserve (columns.size ());
235+ for (auto & column : columns)
236+ column_nodes_to_select->getNodes ().emplace_back (std::make_shared<ColumnNode>(column, table_function_node));
237+ query_node.getProjectionNode () = column_nodes_to_select;
238+
239+ // Left only table function to send on cluster nodes
240+ modified_query_tree = modified_query_tree->cloneAndReplace (query_node.getJoinTree (), query_tree_distributed);
241+
242+ need_modify = true ;
243+ }
244+
245+ if (has_local_columns_in_where)
246+ {
247+ auto & query_node = modified_query_tree->as <QueryNode &>();
248+ query_node.getWhere () = {};
249+ }
250+
251+ if (need_modify)
252+ query_to_send = queryNodeToDistributedSelectQuery (modified_query_tree);
253+ return ;
254+ }
255+ case ObjectStorageClusterJoinMode::GLOBAL:
256+ // TODO
257+ throw Exception (ErrorCodes::NOT_IMPLEMENTED, " `Global` mode for `object_storage_cluster_join_mode` setting is unimplemented for now" );
258+ case ObjectStorageClusterJoinMode::ALLOW: // Do nothing special
259+ return ;
260+ }
261+ }
262+
82263// / The code executes on initiator
83264void IStorageCluster::read (
84265 QueryPlan & query_plan,
@@ -100,21 +281,23 @@ void IStorageCluster::read(
100281 SharedHeader sample_block;
101282 ASTPtr query_to_send = query_info.query ;
102283
284+ updateQueryWithJoinToSendIfNeeded (query_to_send, query_info.query_tree , context);
285+
103286 if (context->getSettingsRef ()[Setting::allow_experimental_analyzer])
104287 {
105- sample_block = InterpreterSelectQueryAnalyzer::getSampleBlock (query_info. query , context, SelectQueryOptions (processed_stage));
288+ sample_block = InterpreterSelectQueryAnalyzer::getSampleBlock (query_to_send , context, SelectQueryOptions (processed_stage));
106289 }
107290 else
108291 {
109- auto interpreter = InterpreterSelectQuery (query_info. query , context, SelectQueryOptions (processed_stage).analyze ());
292+ auto interpreter = InterpreterSelectQuery (query_to_send , context, SelectQueryOptions (processed_stage).analyze ());
110293 sample_block = interpreter.getSampleBlock ();
111294 query_to_send = interpreter.getQueryInfo ().query ->clone ();
112295 }
113296
114297 updateQueryToSendIfNeeded (query_to_send, storage_snapshot, context);
115298
116299 RestoreQualifiedNamesVisitor::Data data;
117- data.distributed_table = DatabaseAndTableWithAlias (*getTableExpression (query_info. query ->as <ASTSelectQuery &>(), 0 ));
300+ data.distributed_table = DatabaseAndTableWithAlias (*getTableExpression (query_to_send ->as <ASTSelectQuery &>(), 0 ));
118301 data.remote_table .database = context->getCurrentDatabase ();
119302 data.remote_table .table = getName ();
120303 RestoreQualifiedNamesVisitor (data).visit (query_to_send);
@@ -209,8 +392,40 @@ void ReadFromCluster::initializePipeline(QueryPipelineBuilder & pipeline, const
209392}
210393
211394QueryProcessingStage::Enum IStorageCluster::getQueryProcessingStage (
212- ContextPtr context, QueryProcessingStage::Enum to_stage, const StorageSnapshotPtr &, SelectQueryInfo &) const
395+ ContextPtr context, QueryProcessingStage::Enum to_stage, const StorageSnapshotPtr &, SelectQueryInfo & query_info ) const
213396{
397+ auto object_storage_cluster_join_mode = context->getSettingsRef ()[Setting::object_storage_cluster_join_mode];
398+
399+ if (object_storage_cluster_join_mode != ObjectStorageClusterJoinMode::ALLOW)
400+ {
401+ if (!context->getSettingsRef ()[Setting::allow_experimental_analyzer])
402+ throw Exception (ErrorCodes::NOT_IMPLEMENTED,
403+ " object_storage_cluster_join_mode!='allow' is not supported without allow_experimental_analyzer=true" );
404+
405+ SearcherVisitor join_searcher (QueryTreeNodeType::JOIN, context);
406+ join_searcher.visit (query_info.query_tree );
407+ if (join_searcher.getNode ())
408+ has_join = true ;
409+
410+ SearcherVisitor table_function_searcher (QueryTreeNodeType::TABLE_FUNCTION, context);
411+ table_function_searcher.visit (query_info.query_tree );
412+ auto table_function_node = table_function_searcher.getNode ();
413+ if (!table_function_node)
414+ throw Exception (ErrorCodes::LOGICAL_ERROR, " Can't find table function node" );
415+
416+ CollectUsedColumnsForSourceVisitor collector_where (table_function_node, context, true );
417+ auto & query_node = query_info.query_tree ->as <QueryNode &>();
418+ if (query_node.hasWhere ())
419+ collector_where.visit (query_node.getWhere ());
420+
421+ // Can't use 'WHERE' on remote node if it contains columns from other sources
422+ if (!collector_where.getColumns ().empty ())
423+ has_local_columns_in_where = true ;
424+
425+ if (has_join || has_local_columns_in_where)
426+ return QueryProcessingStage::Enum::FetchColumns;
427+ }
428+
214429 // / Initiator executes query on remote node.
215430 if (context->getClientInfo ().query_kind == ClientInfo::QueryKind::INITIAL_QUERY)
216431 if (to_stage >= QueryProcessingStage::Enum::WithMergeableState)
0 commit comments