Skip to content

Commit feb0f8e

Browse files
committed
Create Nested(...) columns as Array(Tuple(...))
The type parser had no entry for Nested, so a column typed Array(Nested(key String, value String)) was created as Array(Void) and the native block stream fell out of sync ("Can't cast from Void", "some data was not read"). A top-level Nested column, which the server sends with flatten_nested = 0, was rejected as unsupported. The server defines Nested as a custom name over Array(Tuple(...)) (DataTypeNested.cpp). The parser now recognises Nested as its own node, like Map and SimpleAggregateFunction, and the column factory builds it as its Tuple wrapped in an Array. Closes #571
1 parent fc5d98f commit feb0f8e

8 files changed

Lines changed: 170 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ still might break.
185185
* String
186186
* LowCardinality(String) or LowCardinality(FixedString(N))
187187
* Tuple
188+
* Nested, read and written as Array(Tuple(...))
188189
* UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64
189190
* UInt128, Int128
190191
* UUID

‎clickhouse/columns/factory.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ static ColumnRef CreateColumnFromAst(const TypeAst& ast, CreateColumnByTypeSetti
191191
return std::make_shared<ColumnTuple>(columns);
192192
}
193193

194+
case TypeAst::Nested: {
195+
TypeAst tuple = ast;
196+
tuple.meta = TypeAst::Tuple;
197+
if (auto column = CreateColumnFromAst(tuple, settings)) {
198+
return std::make_shared<ColumnArray>(column);
199+
}
200+
return nullptr;
201+
}
202+
194203
case TypeAst::Enum: {
195204
std::vector<Type::EnumItem> enum_items;
196205
//ast.elements.size() minimum is 1.

‎clickhouse/types/type_parser.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ static TypeAst::Meta GetTypeMeta(const StringView& name) {
146146
return TypeAst::Map;
147147
}
148148

149+
if (name == "Nested") {
150+
return TypeAst::Nested;
151+
}
152+
149153
return TypeAst::Terminal;
150154
}
151155

‎clickhouse/types/type_parser.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ struct TypeAst {
2222
Enum,
2323
LowCardinality,
2424
SimpleAggregateFunction,
25-
Map
25+
Map,
26+
Nested
2627
};
2728

2829
/// Type's category.

‎docs/index.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ client.Select(
164164
- `Nullable(T)`
165165
- `Array(T)`
166166
- `Tuple`
167+
- `Nested`, read and written as `Array(Tuple(...))`
167168
- `Map`
168169
- `IPv4`, `IPv6`
169170
- `Point`, `Ring`, `Polygon`, `MultiPolygon`

‎ut/CreateColumnByType_ut.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,19 @@ INSTANTIATE_TEST_SUITE_P(Nested, CreateColumnByTypeWithName, ::testing::Values(
280280
"Array(Nullable(LowCardinality(FixedString(10000))))",
281281
"Array(Enum8('ONE' = 1, 'TWO' = 2))"
282282
));
283+
284+
class CreateColumnByNestedType : public ::testing::TestWithParam<std::tuple<const char*, const char*>> {};
285+
286+
TEST_P(CreateColumnByNestedType, CreateColumnByType) {
287+
const auto & [type_name, expected_name] = GetParam();
288+
const auto col = CreateColumnByType(type_name);
289+
ASSERT_NE(nullptr, col) << "CreateColumnByType returned nullptr for " << type_name;
290+
EXPECT_EQ(expected_name, col->GetType().GetName());
291+
}
292+
293+
INSTANTIATE_TEST_SUITE_P(Nested, CreateColumnByNestedType, ::testing::Values(
294+
std::make_tuple("Nested(key String, value UInt8)", "Array(Tuple(key String, value UInt8))"),
295+
std::make_tuple("Array(Nested(key String, value UInt8))", "Array(Array(Tuple(key String, value UInt8)))"),
296+
std::make_tuple("Map(String, Nested(key String, value UInt8))", "Map(String, Array(Tuple(key String, value UInt8)))"),
297+
std::make_tuple("Nested(a Nested(b String))", "Array(Tuple(a Array(Tuple(b String))))")
298+
));

‎ut/client_ut.cpp‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,117 @@ const auto LocalHostEndpoint = ClientOptions()
18231823
.SetPassword( getEnvOrDefault("CLICKHOUSE_PASSWORD", ""))
18241824
.SetDefaultDatabase(getEnvOrDefault("CLICKHOUSE_DB", "default"));
18251825

1826+
namespace {
1827+
1828+
using KeyValuePairs = std::vector<std::pair<std::string, uint8_t>>;
1829+
1830+
KeyValuePairs KeyValuePairsOf(const ColumnRef& column) {
1831+
auto tuples = column->AsStrict<ColumnTuple>();
1832+
auto keys = (*tuples)[0]->AsStrict<ColumnString>();
1833+
auto values = (*tuples)[1]->AsStrict<ColumnUInt8>();
1834+
KeyValuePairs pairs;
1835+
for (size_t i = 0; i < tuples->Size(); ++i) {
1836+
pairs.emplace_back(std::string(keys->At(i)), values->At(i));
1837+
}
1838+
return pairs;
1839+
}
1840+
1841+
std::vector<KeyValuePairs> ArraysOfKeyValuePairsOf(const ColumnRef& column) {
1842+
auto arrays = column->AsStrict<ColumnArray>();
1843+
std::vector<KeyValuePairs> arrays_of_pairs;
1844+
for (size_t i = 0; i < arrays->Size(); ++i) {
1845+
arrays_of_pairs.push_back(KeyValuePairsOf(arrays->GetAsColumn(i)));
1846+
}
1847+
return arrays_of_pairs;
1848+
}
1849+
1850+
}
1851+
1852+
TEST_P(ClientCase, ArrayOfNested_SelectServerTypedColumn) {
1853+
client_->Execute("DROP TEMPORARY TABLE IF EXISTS test_clickhouse_cpp_array_of_nested");
1854+
client_->Execute("CREATE TEMPORARY TABLE test_clickhouse_cpp_array_of_nested "
1855+
"(id UInt8, arrays_of_pairs Array(Nested(key String, value UInt8)))");
1856+
client_->Execute("INSERT INTO test_clickhouse_cpp_array_of_nested VALUES "
1857+
"(1, [[('a', 1), ('b', 2)], []]), (2, []), (3, [[('c', 3)]])");
1858+
1859+
std::vector<std::vector<KeyValuePairs>> rows;
1860+
std::string type_name;
1861+
client_->Select("SELECT id, arrays_of_pairs FROM test_clickhouse_cpp_array_of_nested ORDER BY id", [&](const Block& block) {
1862+
if (block.GetRowCount() == 0) {
1863+
return;
1864+
}
1865+
type_name = block[1]->GetType().GetName();
1866+
auto arrays_of_pairs = block[1]->AsStrict<ColumnArray>();
1867+
for (size_t row = 0; row < block.GetRowCount(); ++row) {
1868+
rows.push_back(ArraysOfKeyValuePairsOf(arrays_of_pairs->GetAsColumn(row)));
1869+
}
1870+
});
1871+
1872+
EXPECT_EQ("Array(Array(Tuple(key String, value UInt8)))", type_name);
1873+
const std::vector<std::vector<KeyValuePairs>> expected = {
1874+
{{{"a", 1}, {"b", 2}}, {}},
1875+
{},
1876+
{{{"c", 3}}},
1877+
};
1878+
EXPECT_EQ(expected, rows);
1879+
}
1880+
1881+
TEST_P(ClientCase, ArrayOfNested_ReinsertSelectedRows) {
1882+
client_->Execute("DROP TEMPORARY TABLE IF EXISTS test_clickhouse_cpp_array_of_nested_source");
1883+
client_->Execute("DROP TEMPORARY TABLE IF EXISTS test_clickhouse_cpp_array_of_nested_copy");
1884+
client_->Execute("CREATE TEMPORARY TABLE test_clickhouse_cpp_array_of_nested_source "
1885+
"(id UInt8, arrays_of_pairs Array(Nested(key String, value UInt8)))");
1886+
client_->Execute("CREATE TEMPORARY TABLE test_clickhouse_cpp_array_of_nested_copy "
1887+
"(id UInt8, arrays_of_pairs Array(Nested(key String, value UInt8)))");
1888+
client_->Execute("INSERT INTO test_clickhouse_cpp_array_of_nested_source VALUES (1, [[('a', 1), ('b', 2)], []]), (2, [])");
1889+
1890+
std::vector<Block> selected_blocks;
1891+
client_->Select("SELECT id, arrays_of_pairs FROM test_clickhouse_cpp_array_of_nested_source ORDER BY id", [&](const Block& block) {
1892+
if (block.GetRowCount() > 0) {
1893+
selected_blocks.push_back(block);
1894+
}
1895+
});
1896+
for (const auto& block : selected_blocks) {
1897+
client_->Insert("test_clickhouse_cpp_array_of_nested_copy", block);
1898+
}
1899+
1900+
std::vector<std::string> rendered_rows;
1901+
client_->Select("SELECT toString(arrays_of_pairs) FROM test_clickhouse_cpp_array_of_nested_copy ORDER BY id",
1902+
[&](const Block& block) {
1903+
for (size_t row = 0; row < block.GetRowCount(); ++row) {
1904+
rendered_rows.emplace_back(block[0]->AsStrict<ColumnString>()->At(row));
1905+
}
1906+
});
1907+
1908+
const std::vector<std::string> expected = {"[[('a',1),('b',2)],[]]", "[]"};
1909+
EXPECT_EQ(expected, rendered_rows);
1910+
}
1911+
1912+
TEST_P(ClientCase, Nested_UnflattenedColumn) {
1913+
client_->Execute("SET flatten_nested = 0");
1914+
client_->Execute("DROP TEMPORARY TABLE IF EXISTS test_clickhouse_cpp_nested_unflattened");
1915+
client_->Execute("CREATE TEMPORARY TABLE test_clickhouse_cpp_nested_unflattened "
1916+
"(id UInt8, pairs Nested(key String, value UInt8))");
1917+
client_->Execute("INSERT INTO test_clickhouse_cpp_nested_unflattened VALUES (1, [('a', 1), ('b', 2)]), (2, [])");
1918+
1919+
std::vector<KeyValuePairs> rows;
1920+
std::string type_name;
1921+
client_->Select("SELECT id, pairs FROM test_clickhouse_cpp_nested_unflattened ORDER BY id", [&](const Block& block) {
1922+
if (block.GetRowCount() == 0) {
1923+
return;
1924+
}
1925+
type_name = block[1]->GetType().GetName();
1926+
auto pairs = block[1]->AsStrict<ColumnArray>();
1927+
for (size_t row = 0; row < block.GetRowCount(); ++row) {
1928+
rows.push_back(KeyValuePairsOf(pairs->GetAsColumn(row)));
1929+
}
1930+
});
1931+
1932+
EXPECT_EQ("Array(Tuple(key String, value UInt8))", type_name);
1933+
const std::vector<KeyValuePairs> expected = {{{"a", 1}, {"b", 2}}, {}};
1934+
EXPECT_EQ(expected, rows);
1935+
}
1936+
18261937
INSTANTIATE_TEST_SUITE_P(
18271938
Client, ClientCase,
18281939
::testing::Values(

‎ut/type_parser_ut.cpp‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,3 +552,29 @@ TEST(TypeParser, AggregateFunction) {
552552
EXPECT_FALSE(TypeParser("AggregateFunction(argMax, LowCardinality(Nullable(FixedString(4))), DateTime(3, 'UTC'))").Parse(&ast));
553553
}
554554
}
555+
556+
TEST(TypeParserCase, ParseNested) {
557+
TypeAst ast;
558+
ASSERT_TRUE(TypeParser("Nested(key String, value UInt8)").Parse(&ast));
559+
ASSERT_EQ(ast.meta, TypeAst::Nested);
560+
ASSERT_EQ(ast.name, "Nested");
561+
ASSERT_EQ(ast.elements.size(), 2u);
562+
563+
ASSERT_EQ(ast.elements[0].element_name, "key");
564+
ASSERT_EQ(ast.elements[0].name, "String");
565+
ASSERT_EQ(ast.elements[0].code, Type::String);
566+
567+
ASSERT_EQ(ast.elements[1].element_name, "value");
568+
ASSERT_EQ(ast.elements[1].name, "UInt8");
569+
ASSERT_EQ(ast.elements[1].code, Type::UInt8);
570+
}
571+
572+
TEST(TypeParserCase, ParseNested_FieldNamedNested) {
573+
TypeAst ast;
574+
ASSERT_TRUE(TypeParser("Nested(Nested String)").Parse(&ast));
575+
ASSERT_EQ(ast.meta, TypeAst::Nested);
576+
ASSERT_EQ(ast.elements.size(), 1u);
577+
ASSERT_EQ(ast.elements[0].meta, TypeAst::Terminal);
578+
ASSERT_EQ(ast.elements[0].element_name, "Nested");
579+
ASSERT_EQ(ast.elements[0].name, "String");
580+
}

0 commit comments

Comments
 (0)