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"
+ )
+ )