diff --git a/src/Analyzer/Resolve/QueryAnalyzer.cpp b/src/Analyzer/Resolve/QueryAnalyzer.cpp index 560fea916996..369f6884c3cf 100644 --- a/src/Analyzer/Resolve/QueryAnalyzer.cpp +++ b/src/Analyzer/Resolve/QueryAnalyzer.cpp @@ -4629,7 +4629,26 @@ void QueryAnalyzer::resolveTableFunction(QueryTreeNodePtr & table_function_node, table_function_node_to_resolve_typed->getArgumentsNode() = table_function_argument_function->getArgumentsNode(); QueryTreeNodePtr table_function_node_to_resolve = std::move(table_function_node_to_resolve_typed); - resolveTableFunction(table_function_node_to_resolve, scope, expressions_visitor, true /*nested_table_function*/); + if (table_function_argument_function_name == "view") + { + /// Subquery in view() table function can reference tables that don't exist on the initiator. + /// In the following example `users` table may be not available on the initiator: + /// SELECT * + /// FROM remoteSecure(
, view( + /// SELECT + /// t1.age, + /// t1.name, + /// t2.name + /// FROM users AS t1 + /// INNER JOIN users AS t2 ON t1.uid = t2.uid + /// ), , ) + /// SETTINGS prefer_localhost_replica = 0 + skip_analysis_arguments_indexes.push_back(table_function_argument_index); + } + else + { + resolveTableFunction(table_function_node_to_resolve, scope, expressions_visitor, true /*nested_table_function*/); + } result_table_function_arguments.push_back(std::move(table_function_node_to_resolve)); continue; diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index 6a72dfa28942..98d4cc7a6bcb 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -759,9 +759,28 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info, if (table_expression_modifiers) table_function_node->setTableExpressionModifiers(*table_expression_modifiers); - QueryAnalysisPass query_analysis_pass; - QueryTreeNodePtr node = table_function_node; - query_analysis_pass.run(node, query_context); + /// Subquery in table function `view` may reference tables that don't exist on the initiator. + if (table_function_node->getTableFunctionName() == "view") + { + auto get_column_options = GetColumnsOptions(GetColumnsOptions::All).withExtendedObjects().withVirtuals(); + auto column_names_and_types = distributed_storage_snapshot->getColumns(get_column_options); + + StorageID fake_storage_id = StorageID::createEmpty(); + if (auto * table_node = query_info.table_expression->as()) + fake_storage_id = table_node->getStorage()->getStorageID(); + else if (auto * original_table_function_node = query_info.table_expression->as()) + fake_storage_id = original_table_function_node->getStorage()->getStorageID(); + + auto storage = std::make_shared(fake_storage_id, ColumnsDescription{column_names_and_types}); + + table_function_node->resolve({}, std::move(storage), query_context, /*unresolved_arguments_indexes_=*/{ 0 }); + } + else + { + QueryAnalysisPass query_analysis_pass; + QueryTreeNodePtr node = table_function_node; + query_analysis_pass.run(node, query_context); + } replacement_table_expression = std::move(table_function_node); } @@ -809,9 +828,7 @@ void StorageDistributed::read( if (settings.allow_experimental_analyzer) { - StorageID remote_storage_id = StorageID::createEmpty(); - if (!remote_table_function_ptr) - remote_storage_id = StorageID{remote_database, remote_table}; + StorageID remote_storage_id = StorageID{remote_database, remote_table}; auto query_tree_distributed = buildQueryTreeDistributed(modified_query_info, query_info.merge_storage_snapshot ? query_info.merge_storage_snapshot : storage_snapshot, diff --git a/tests/integration/test_remote_function_view/__init__.py b/tests/integration/test_remote_function_view/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/integration/test_remote_function_view/configs/clusters.xml b/tests/integration/test_remote_function_view/configs/clusters.xml new file mode 100644 index 000000000000..2209484f8ac8 --- /dev/null +++ b/tests/integration/test_remote_function_view/configs/clusters.xml @@ -0,0 +1,16 @@ + + + + + + node1 + 9000 + + + node2 + 9000 + + + + + \ No newline at end of file diff --git a/tests/integration/test_remote_function_view/test.py b/tests/integration/test_remote_function_view/test.py new file mode 100644 index 000000000000..e999e2ff3ed0 --- /dev/null +++ b/tests/integration/test_remote_function_view/test.py @@ -0,0 +1,48 @@ +import pytest + +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) +node1 = cluster.add_instance("node1", main_configs=["configs/clusters.xml"]) +node2 = cluster.add_instance("node2", main_configs=["configs/clusters.xml"]) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + + node2.query( + """ + CREATE TABLE test_table( + APIKey UInt32, + CustomAttributeId UInt64, + ProfileIDHash UInt64, + DeviceIDHash UInt64, + Data String) + ENGINE = SummingMergeTree() + ORDER BY (APIKey, CustomAttributeId, ProfileIDHash, DeviceIDHash, intHash32(DeviceIDHash)) + """ + ) + yield cluster + + finally: + cluster.shutdown() + + +def test_remote(start_cluster): + assert ( + node1.query( + "SELECT 1 FROM remote('node2', view(SELECT * FROM default.test_table)) WHERE (APIKey = 137715) AND (CustomAttributeId IN (45, 66)) AND (ProfileIDHash != 0) LIMIT 1" + ) + == "" + ) + + +def test_remote_fail(start_cluster): + assert ( + "Unknown table expression identifier 'default.table_not_exists'" + in node1.query_and_get_error( + "SELECT 1 FROM remote('node2', view(SELECT * FROM default.table_not_exists)) WHERE (APIKey = 137715) AND (CustomAttributeId IN (45, 66)) AND (ProfileIDHash != 0) LIMIT 1" + ) + )