Skip to content

Commit 62991a1

Browse files
Fix CreateColumnByType for SimpleAggregateFunction with non-terminal inner type
CreateColumnByType returned nullptr for a SimpleAggregateFunction whose value type is a non-terminal wrapper such as LowCardinality, Nullable, Array, Tuple, Map or Enum (e.g. SimpleAggregateFunction(anyLast, LowCardinality(String))), which made reading any block containing such a column fail with "unsupported column type". The factory handled the SimpleAggregateFunction meta by calling CreateTerminalColumn on the inner type, but that only covers terminal type codes and falls through to nullptr for wrapper metas. Every other wrapper (Array, Nullable, Tuple, Map, LowCardinality) recurses through CreateColumnFromAst; SimpleAggregateFunction was the only one restricted to terminals. Recurse through CreateColumnFromAst instead (passing settings so low_cardinality_as_wrapped_column is honored). A SimpleAggregateFunction is transparent on the wire, so the created column matches its inner type. Fixes: #540
1 parent 737145d commit 62991a1

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
@@ -252,7 +252,7 @@ static ColumnRef CreateColumnFromAst(const TypeAst& ast, CreateColumnByTypeSetti
252252
}
253253
}
254254
case TypeAst::SimpleAggregateFunction: {
255-
return CreateTerminalColumn(GetASTChildElement(ast, -1));
255+
return CreateColumnFromAst(GetASTChildElement(ast, -1), settings);
256256
}
257257

258258
case TypeAst::Map: {

ut/CreateColumnByType_ut.cpp

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

99
#include <gtest/gtest.h>
1010

11+
#include <tuple>
12+
1113
namespace {
1214
using namespace clickhouse;
1315
}
@@ -20,6 +22,33 @@ TEST(CreateColumnByType, CreateSimpleAggregateFunction) {
2022
ASSERT_NE(nullptr, col->As<ColumnInt32>());
2123
}
2224

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