Skip to content

Commit e640cb6

Browse files
Backport #97493 to 25.11: Make convertToFullIfNeeded recursive for compound column types
1 parent 22e33c8 commit e640cb6

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

src/Columns/IColumn.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,32 @@ class IColumn : public COW<IColumn>
132132

133133
[[nodiscard]] virtual Ptr convertToFullIfNeeded() const
134134
{
135-
return convertToFullColumnIfConst()->convertToFullColumnIfReplicated()->convertToFullColumnIfSparse()->convertToFullColumnIfLowCardinality();
135+
Ptr converted = convertToFullColumnIfConst()
136+
->convertToFullColumnIfReplicated()
137+
->convertToFullColumnIfSparse()
138+
->convertToFullColumnIfLowCardinality();
139+
140+
Columns new_subcolumns;
141+
bool any_changed = false;
142+
143+
converted->forEachSubcolumn([&](const WrappedPtr & subcolumn)
144+
{
145+
auto new_sub = subcolumn->convertToFullIfNeeded();
146+
any_changed |= (new_sub.get() != subcolumn.get());
147+
new_subcolumns.push_back(std::move(new_sub));
148+
});
149+
150+
if (!any_changed)
151+
return converted;
152+
153+
auto mutable_column = IColumn::mutate(std::move(converted));
154+
size_t i = 0;
155+
mutable_column->forEachMutableSubcolumn([&](WrappedPtr & subcolumn)
156+
{
157+
subcolumn = std::move(new_subcolumns[i++]);
158+
});
159+
160+
return std::move(mutable_column);
136161
}
137162

138163
/// Creates empty column with the same type.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
200
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- Tags: no-random-merge-tree-settings
2+
3+
-- Reproduces assertion failure in Set::appendSetElements when ColumnTuple has
4+
-- inner sparse columns from one MergeTree part and non-sparse from another.
5+
-- The fix: IColumn::convertToFullIfNeeded now recursively converts subcolumns.
6+
--
7+
-- The bug path: Set::appendSetElements is called only when fill_set_elements is
8+
-- true, which happens when KeyCondition calls buildOrderedSetInplace for index
9+
-- evaluation. So the IN column must be part of the ORDER BY key.
10+
--
11+
-- The assertion fires when the non-sparse chunk is read first (into set_elements)
12+
-- and the sparse chunk is inserted second, because ColumnVector::insertRangeFrom
13+
-- asserts typeid equality with the source.
14+
15+
SET optimize_on_insert = 0;
16+
17+
DROP TABLE IF EXISTS t_sparse_tuple;
18+
19+
-- ORDER BY val is essential: it makes KeyCondition call buildOrderedSetInplace
20+
-- for the IN expression, which triggers fillSetElements and appendSetElements.
21+
CREATE TABLE t_sparse_tuple (key UInt64, val Tuple(a UInt64, b UInt64))
22+
ENGINE = MergeTree ORDER BY val
23+
SETTINGS ratio_of_defaults_for_sparse_serialization = 0.5;
24+
25+
SYSTEM STOP MERGES t_sparse_tuple;
26+
27+
-- Part 1 (all_1_1_0): non-sparse inner columns (no defaults in second element).
28+
-- This part is read first into set_elements, so set_elements uses ColumnVector.
29+
INSERT INTO t_sparse_tuple SELECT number, (number + 1, number + 1) FROM numbers(100);
30+
31+
-- Part 2 (all_2_2_0): second tuple element is all zeros → sparse serialization.
32+
-- When this chunk is inserted into the Set, insertRangeFrom sees ColumnSparse
33+
-- source vs ColumnVector destination, triggering the assertion.
34+
INSERT INTO t_sparse_tuple SELECT number + 200, (number + 200, 0) FROM numbers(100);
35+
36+
-- Building a Set from a subquery that reads both parts triggers the bug.
37+
-- val must be in ORDER BY for KeyCondition to use buildOrderedSetInplace.
38+
SELECT count() FROM t_sparse_tuple WHERE val IN (SELECT val FROM t_sparse_tuple);
39+
40+
DROP TABLE t_sparse_tuple;

0 commit comments

Comments
 (0)