From 4493cf99ec6bb344e42e1313140dc764c4599907 Mon Sep 17 00:00:00 2001 From: Mikhail Koviazin Date: Tue, 20 Oct 2026 09:48:57 +0200 Subject: [PATCH] Merge pull request #2565 from Altinity/bugfix/antalya-26.6/empty-string-low-cardinality Treat `LowCardinality(String)` like `String` in empty-string comparison rewrite --- ...ertEmptyStringComparisonToFunctionPass.cpp | 3 +- ...sons_distributed_low_cardinality.reference | 8 ++ ...comparisons_distributed_low_cardinality.sh | 82 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.reference create mode 100755 tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.sh diff --git a/src/Analyzer/Passes/ConvertEmptyStringComparisonToFunctionPass.cpp b/src/Analyzer/Passes/ConvertEmptyStringComparisonToFunctionPass.cpp index be8a0c2501eed..586cbe3bed205 100644 --- a/src/Analyzer/Passes/ConvertEmptyStringComparisonToFunctionPass.cpp +++ b/src/Analyzer/Passes/ConvertEmptyStringComparisonToFunctionPass.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +73,7 @@ class ConvertEmptyStringComparisonToFunctionVisitor const auto & expr_node = args[expr_idx]; const auto expr_type = expr_node->getResultType(); - if (!expr_type || !isStringOrFixedString(expr_type)) + if (!expr_type || !isStringOrFixedString(removeLowCardinality(expr_type))) return; const String replacement_func = (func_name == "equals") ? "empty" : "notEmpty"; diff --git a/tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.reference b/tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.reference new file mode 100644 index 0000000000000..5b6db5d13eb73 --- /dev/null +++ b/tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.reference @@ -0,0 +1,8 @@ +2 +2 +2 +2 +2 +2 +2 +2 diff --git a/tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.sh b/tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.sh new file mode 100755 index 0000000000000..346462cb10994 --- /dev/null +++ b/tests/queries/0_stateless/05303_optimize_empty_string_comparisons_distributed_low_cardinality.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-replicated-database +# - no-fasttest: requires `IcebergLocal` (USE_AVRO). +# - no-replicated-database: `IcebergLocal` is non-replicated. + +# A `Distributed` table may declare `LowCardinality(String)` over a plain `String` column. +# With the analyzer and `optimize_empty_string_comparisons`, `s != ''` is rewritten to `notEmpty` +# only for `String`. `object_storage_cluster` re-analyzes that expression against the underlying +# `IcebergLocal` column, so the initiator header and the remote block disagree +# (`NOT_FOUND_COLUMN_IN_BLOCK`). Two of the four values are non-empty, so every query must return 2. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +TABLE_PATH="${CLICKHOUSE_USER_FILES_UNIQUE}/t_ice/" +rm -rf "${CLICKHOUSE_USER_FILES_UNIQUE}" +mkdir -p "${TABLE_PATH}" +trap 'rm -rf "${CLICKHOUSE_USER_FILES_UNIQUE}"' EXIT + +${CLICKHOUSE_CLIENT} --query " +DROP TABLE IF EXISTS t_dist; +DROP TABLE IF EXISTS t_dist_str; +DROP TABLE IF EXISTS t; + +CREATE TABLE t (id UInt64, s String) +ENGINE = IcebergLocal('${TABLE_PATH}'); +" + +${CLICKHOUSE_CLIENT} --allow_insert_into_iceberg=1 --async_insert=0 --query " +INSERT INTO t VALUES (1, 'abc'), (2, ''), (3, 'def'), (4, ''); +" + +${CLICKHOUSE_CLIENT} --query " +CREATE TABLE t_dist (id UInt64, s LowCardinality(String)) +ENGINE = Distributed('test_shard_localhost', currentDatabase(), t); + +CREATE TABLE t_dist_str (id UInt64, s String) +ENGINE = Distributed('test_shard_localhost', currentDatabase(), t); +" + +# Declared `LowCardinality(String)`, real column `String`. Only analyzer + the optimization + cluster dispatch fails today. +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t_dist +SETTINGS enable_analyzer = 1, optimize_empty_string_comparisons = 1, object_storage_cluster = 'test_shard_localhost' +" + +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t_dist +SETTINGS enable_analyzer = 0, optimize_empty_string_comparisons = 1, object_storage_cluster = 'test_shard_localhost' +" + +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t_dist +SETTINGS enable_analyzer = 1, optimize_empty_string_comparisons = 0, object_storage_cluster = 'test_shard_localhost' +" + +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t_dist +SETTINGS enable_analyzer = 0, optimize_empty_string_comparisons = 0, object_storage_cluster = 'test_shard_localhost' +" + +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s = '') FROM t_dist +SETTINGS enable_analyzer = 1, optimize_empty_string_comparisons = 1, object_storage_cluster = 'test_shard_localhost' +" + +# Same declared type, and the underlying table itself, stay correct with the optimization enabled. +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t_dist +SETTINGS enable_analyzer = 1, optimize_empty_string_comparisons = 1 +" + +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t +SETTINGS enable_analyzer = 1, optimize_empty_string_comparisons = 1, object_storage_cluster = 'test_shard_localhost' +" + +${CLICKHOUSE_CLIENT} --query " +SELECT countIf(s != '') FROM t_dist_str +SETTINGS enable_analyzer = 1, optimize_empty_string_comparisons = 1, object_storage_cluster = 'test_shard_localhost' +"