Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/Analyzer/Resolve/QueryAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(<address>, view(
/// SELECT
/// t1.age,
/// t1.name,
/// t2.name
/// FROM users AS t1
/// INNER JOIN users AS t2 ON t1.uid = t2.uid
/// ), <user>, <password>)
/// 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;
Expand Down
29 changes: 23 additions & 6 deletions src/Storages/StorageDistributed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TableNode>())
fake_storage_id = table_node->getStorage()->getStorageID();
else if (auto * original_table_function_node = query_info.table_expression->as<TableFunctionNode>())
fake_storage_id = original_table_function_node->getStorage()->getStorageID();

auto storage = std::make_shared<StorageDummy>(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);
}
Expand Down Expand Up @@ -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,
Expand Down
Empty file.
16 changes: 16 additions & 0 deletions tests/integration/test_remote_function_view/configs/clusters.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<clickhouse>
<remote_servers>
<cluster>
<shard>
<replica>
<host>node1</host>
<port>9000</port>
</replica>
<replica>
<host>node2</host>
<port>9000</port>
</replica>
</shard>
</cluster>
</remote_servers>
</clickhouse>
48 changes: 48 additions & 0 deletions tests/integration/test_remote_function_view/test.py
Original file line number Diff line number Diff line change
@@ -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"
)
)
Loading