Skip to content

Commit 2d5892a

Browse files
Backport #97493 to 25.12: Make convertToFullIfNeeded recursive for compound column types
1 parent 9896f59 commit 2d5892a

3 files changed

Lines changed: 67 additions & 1 deletion

File tree

src/Columns/IColumn.h

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

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

136161
/// 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)