Skip to content

Commit eabb71d

Browse files
authored
Merge pull request #542 from ClickHouse/polyglot/fix-simpleaggregatefunction-nonterminal-inner-type
Fix CreateColumnByType for SimpleAggregateFunction with non-terminal inner type
2 parents a3eb255 + 62991a1 commit eabb71d

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

clickhouse/columns/factory.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ static ColumnRef CreateColumnFromAst(const TypeAst& ast, CreateColumnByTypeSetti
264264
}
265265
}
266266
case TypeAst::SimpleAggregateFunction: {
267-
return CreateTerminalColumn(GetASTChildElement(ast, -1));
267+
return CreateColumnFromAst(GetASTChildElement(ast, -1), settings);
268268
}
269269

270270
case TypeAst::Map: {

ut/CreateColumnByType_ut.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include <gtest/gtest.h>
1111

12+
#include <tuple>
13+
1214
namespace {
1315
using namespace clickhouse;
1416
}
@@ -21,6 +23,33 @@ TEST(CreateColumnByType, CreateSimpleAggregateFunction) {
2123
ASSERT_NE(nullptr, col->As<ColumnInt32>());
2224
}
2325

26+
// SimpleAggregateFunction is transparent on the wire: the created column must
27+
// match its value (inner) type. The inner type may itself be a wrapper such as
28+
// LowCardinality, Nullable, Array or Map, which previously produced a nullptr
29+
// because only terminal inner types were handled (issue #540).
30+
class CreateColumnBySimpleAggregateFunctionType
31+
: public ::testing::TestWithParam<std::tuple<const char* /*type*/, const char* /*expected inner type name*/>>
32+
{};
33+
34+
TEST_P(CreateColumnBySimpleAggregateFunctionType, CreateColumnByType) {
35+
const auto & [type_name, expected_inner_name] = GetParam();
36+
const auto col = CreateColumnByType(type_name);
37+
ASSERT_NE(nullptr, col) << "CreateColumnByType returned nullptr for " << type_name;
38+
EXPECT_EQ(expected_inner_name, col->GetType().GetName());
39+
}
40+
41+
INSTANTIATE_TEST_SUITE_P(InnerType, CreateColumnBySimpleAggregateFunctionType, ::testing::Values(
42+
// Terminal inner type — handled before the fix; must stay unchanged.
43+
std::make_tuple("SimpleAggregateFunction(sum, UInt64)", "UInt64"),
44+
// Non-terminal (wrapper) inner types — returned nullptr before the fix.
45+
std::make_tuple("SimpleAggregateFunction(anyLast, LowCardinality(String))", "LowCardinality(String)"),
46+
std::make_tuple("SimpleAggregateFunction(anyLast, Nullable(String))", "Nullable(String)"),
47+
std::make_tuple("SimpleAggregateFunction(groupArrayArray, Array(UInt64))", "Array(UInt64)"),
48+
std::make_tuple("SimpleAggregateFunction(sumMap, Map(String, UInt64))", "Map(String, UInt64)"),
49+
std::make_tuple("SimpleAggregateFunction(anyLast, Enum8('a' = 1, 'b' = 2))", "Enum8('a' = 1, 'b' = 2)"),
50+
std::make_tuple("SimpleAggregateFunction(anyLast, Tuple(UInt64, String))", "Tuple(UInt64, String)")
51+
));
52+
2453
TEST(CreateColumnByType, UnmatchedBrackets) {
2554
// When type string has unmatched brackets, CreateColumnByType must return nullptr.
2655
ASSERT_EQ(nullptr, CreateColumnByType("FixedString(10"));

ut/client_ut.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,44 @@ TEST_P(ClientCase, SimpleAggregateFunction) {
728728
EXPECT_EQ(EXPECTED_ROWS, total_rows);
729729
}
730730

731+
TEST_P(ClientCase, SimpleAggregateFunctionLowCardinality) {
732+
const auto & server_info = client_->GetServerInfo();
733+
if (versionNumber(server_info) < versionNumber(19, 9)) {
734+
GTEST_SKIP() << "Test is skipped since server '" << server_info << "' does not support SimpleAggregateFunction" << std::endl;
735+
}
736+
737+
// A SimpleAggregateFunction column whose value type is a non-terminal
738+
// wrapper (here LowCardinality(String)) must be readable: the column
739+
// factory previously returned nullptr for such a type, so reading any
740+
// block that contained it failed (#540).
741+
client_->Execute("DROP TEMPORARY TABLE IF EXISTS test_clickhouse_cpp_saf_lc");
742+
client_->Execute(
743+
"CREATE TEMPORARY TABLE IF NOT EXISTS test_clickhouse_cpp_saf_lc "
744+
"(saf SimpleAggregateFunction(anyLast, LowCardinality(String)))");
745+
746+
const std::vector<std::string> data{"foo", "bar", "foo", "baz"};
747+
client_->Execute(
748+
"INSERT INTO test_clickhouse_cpp_saf_lc (saf) VALUES ('foo'),('bar'),('foo'),('baz')");
749+
750+
size_t total_rows = 0;
751+
client_->Select("SELECT saf FROM test_clickhouse_cpp_saf_lc", [&total_rows, &data](const Block & block) {
752+
if (block.GetRowCount() == 0)
753+
return;
754+
755+
total_rows += block.GetRowCount();
756+
ASSERT_EQ(1U, block.GetColumnCount());
757+
758+
auto col = block[0]->As<ColumnLowCardinalityT<ColumnString>>();
759+
ASSERT_NE(nullptr, col);
760+
ASSERT_EQ(data.size(), col->Size());
761+
for (size_t r = 0; r < col->Size(); ++r) {
762+
EXPECT_EQ(data[r], (*col)[r]) << " at index: " << r;
763+
}
764+
});
765+
766+
EXPECT_EQ(data.size(), total_rows);
767+
}
768+
731769
TEST_P(ClientCase, Cancellable) {
732770
/// Create a table.
733771
client_->Execute(

0 commit comments

Comments
 (0)