Skip to content

Commit b8bb3b2

Browse files
authored
[GLUTEN-12174][CH] Fix flatten nullable inner array row handling (#12175)
SparkArrayFlatten handled Array(Nullable(Array(T))) by scanning all nested inner arrays and returning a fully-null result column as soon as any inner array was null. That made unrelated rows null, even though Spark flatten semantics only null the outer row that contains a null inner array. Build a result null map per outer row, mark only rows containing null inner arrays as null, and keep non-null rows using the flattened array offsets. Add a ClickHouse backend regression test where the first row contains a null inner array and the second row remains non-null.
1 parent ed8af63 commit b8bb3b2

2 files changed

Lines changed: 32 additions & 7 deletions

File tree

backends-clickhouse/src/test/scala/org/apache/gluten/execution/GlutenFunctionValidateSuite.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,22 @@ class GlutenFunctionValidateSuite extends GlutenClickHouseWholeStageTransformerS
682682
}
683683
}
684684

685+
test("test flatten with nullable inner arrays") {
686+
val sql =
687+
"""
688+
|select id, flatten(arr)
689+
|from (
690+
| select id,
691+
| if(id = 0,
692+
| array(array(cast(id + 1 as int)), cast(null as array<int>)),
693+
| array(array(cast(id + 1 as int)))) as arr
694+
| from range(2)
695+
|)
696+
|order by id
697+
|""".stripMargin
698+
runQueryAndCompare(sql)(checkGlutenPlan[ProjectExecTransformer])
699+
}
700+
685701
test("test common subexpression eliminate") {
686702
def checkOperatorCount[T <: TransformSupport](count: Int)(df: DataFrame)(implicit
687703
tag: ClassTag[T]): Unit = {

cpp-ch/local-engine/Functions/SparkArrayFlatten.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
#include <Columns/ColumnArray.h>
1818
#include <Columns/ColumnNullable.h>
19+
#include <Columns/ColumnVector.h>
1920
#include <DataTypes/DataTypeArray.h>
2021
#include <DataTypes/DataTypeNullable.h>
2122
#include <Functions/FunctionFactory.h>
@@ -107,19 +108,27 @@ result: Row 1: [1, 2, 3], Row2: [4]
107108
const IColumn::Offsets * prev_offsets = &src_offsets;
108109
const IColumn * prev_data = &src_col->getData();
109110
bool nullable = prev_data->isNullable();
110-
// when array has null element, return null
111+
ColumnUInt8::MutablePtr result_null_map;
112+
// When an inner array is null, only the corresponding outer row is null.
111113
if (nullable)
112114
{
113115
const ColumnNullable * nullable_column = checkAndGetColumn<ColumnNullable>(prev_data);
114116
prev_data = nullable_column->getNestedColumnPtr().get();
115-
for (size_t i = 0; i < nullable_column->size(); i++)
117+
result_null_map = ColumnUInt8::create(input_rows_count, 0);
118+
auto & result_null_map_data = result_null_map->getData();
119+
size_t prev_offset = 0;
120+
for (size_t row = 0; row < input_rows_count; ++row)
116121
{
117-
if (nullable_column->isNullAt(i))
122+
const auto current_offset = src_offsets[row];
123+
for (size_t i = prev_offset; i < current_offset; ++i)
118124
{
119-
auto res= nullable_column->cloneEmpty();
120-
res->insertManyDefaults(input_rows_count);
121-
return res;
125+
if (nullable_column->isNullAt(i))
126+
{
127+
result_null_map_data[row] = 1;
128+
break;
129+
}
122130
}
131+
prev_offset = current_offset;
123132
}
124133
}
125134
if (isNothing(prev_data->getDataType()))
@@ -142,7 +151,7 @@ result: Row 1: [1, 2, 3], Row2: [4]
142151
prev_data->getPtr(),
143152
result_offsets_column ? std::move(result_offsets_column) : src_col->getOffsetsPtr());
144153
if (nullable)
145-
return makeNullable(res);
154+
return ColumnNullable::create(std::move(res), std::move(result_null_map));
146155
return res;
147156
}
148157

0 commit comments

Comments
 (0)