This is an automated email from the ASF dual-hosted git repository.
HappenLee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new a4423dc2a02 [opt](column) reuse nullable column metadata during
function execution (#66031)
a4423dc2a02 is described below
commit a4423dc2a02cb60fd45c4f31343f768715b0100d
Author: Mryange <[email protected]>
AuthorDate: Mon Aug 3 14:24:53 2026 +0800
[opt](column) reuse nullable column metadata during function execution
(#66031)
The default nullable function path independently inspected the same null
maps when checking for all-NULL arguments, unnesting nullable inputs,
and wrapping function results. This caused redundant full null-map scans
and made type-level nullable checks difficult to distinguish from
runtime NULL-value checks.
This change introduces `NullableColumnInfo` to collect the nested
column, typed null-map column, constness, and non-NULL count once per
input column. The information is reused across nullable unnesting, CAST
handling, and result wrapping. It also renames the type-level helper to
`has_nullable_argument_type` and preserves the existing copy-on-write
behavior by copying nested data only when NULL payloads must be replaced
with defaults.
---
be/src/core/block/column_with_type_and_name.cpp | 83 +++++++-----
be/src/core/block/column_with_type_and_name.h | 20 ++-
be/src/core/column/column.h | 11 ++
be/src/core/column/column_const.h | 7 +
be/src/core/column/column_nullable.cpp | 23 ++++
be/src/core/column/column_nullable.h | 8 ++
be/src/exprs/function/cast/function_cast.cpp | 27 +++-
be/src/exprs/function/function.cpp | 59 ++++++---
be/src/exprs/function/function.h | 3 +
.../core/block/column_with_type_and_name_test.cpp | 142 ++++++++++++++++++++-
.../exprs/function/function_arithmetic_test.cpp | 8 ++
11 files changed, 331 insertions(+), 60 deletions(-)
diff --git a/be/src/core/block/column_with_type_and_name.cpp
b/be/src/core/block/column_with_type_and_name.cpp
index ec3f4a61386..917bfd3d142 100644
--- a/be/src/core/block/column_with_type_and_name.cpp
+++ b/be/src/core/block/column_with_type_and_name.cpp
@@ -30,10 +30,10 @@
#include "core/column/column.h"
#include "core/column/column_const.h"
#include "core/column/column_nothing.h"
+#include "core/column/column_nullable.h"
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_nullable.h"
#include "core/types.h"
-#include "util/simd/bits.h"
namespace doris {
@@ -105,41 +105,58 @@ void
ColumnWithTypeAndName::to_pb_column_meta(PColumnMeta* col_meta) const {
type->to_pb_column_meta(col_meta);
}
+const ColumnNullable& ColumnWithTypeAndName::get_nullable_column() const {
+ DCHECK(type->is_nullable());
+ DCHECK(column);
+ const auto& [physical_column, _] = unpack_if_const(column);
+ return assert_cast<const ColumnNullable&,
TypeCheckOnRelease::DISABLE>(*physical_column);
+}
+
+const ColumnUInt8::Ptr& ColumnWithTypeAndName::get_nullable_null_map_column()
const {
+ return get_nullable_column().get_null_map_column_ptr();
+}
+
+NullableColumnInfo ColumnWithTypeAndName::get_nullable_column_info() const {
+ DCHECK(type->is_nullable());
+ DCHECK(column);
+
+ const auto [has_null, only_null] =
get_nullable_column().get_null_map_state();
+ return {.has_null = has_null,
+ .only_null = only_null,
+ .is_const = is_column_const(*column),
+ .is_nullable = true};
+}
+
ColumnWithTypeAndName ColumnWithTypeAndName::unnest_nullable(
- bool replace_null_data_to_default) const {
- if (type->is_nullable()) {
- auto nested_type =
- assert_cast<const DataTypeNullable*,
TypeCheckOnRelease::DISABLE>(type.get())
- ->get_nested_type();
- ColumnPtr nested_column = column;
- if (column) {
- // A column_ptr is needed here to ensure that the column in
convert_to_full_column_if_const is not released.
- auto [column_ptr, is_const] = unpack_if_const(column);
- const auto* source_column =
- assert_cast<const ColumnNullable*,
TypeCheckOnRelease::DISABLE>(
- column_ptr.get());
- if (is_const) {
- nested_column =
-
ColumnConst::create(source_column->get_nested_column_ptr(), column->size());
- } else {
- nested_column = source_column->get_nested_column_ptr();
- }
-
- if (replace_null_data_to_default) {
- const auto& null_map = source_column->get_null_map_data();
- // only need to mutate nested column, avoid to copy nullmap
- auto mutable_nested_col = (*std::move(nested_column)).mutate();
- if (simd::contain_one(null_map.data(), null_map.size())) {
-
mutable_nested_col->replace_column_null_data(null_map.data());
- }
-
- return {std::move(mutable_nested_col), nested_type, ""};
- }
- }
- return {nested_column, nested_type, ""};
- } else {
+ const NullableColumnInfo& info, bool replace_null_data_to_default)
const {
+ if (!type->is_nullable()) {
return {column, type, ""};
}
+ DCHECK(info.is_nullable);
+
+ const auto& nullable_column = get_nullable_column();
+ const auto get_nested_column = [&]() -> ColumnPtr {
+ const auto& nested_column = nullable_column.get_nested_column_ptr();
+ if (info.is_const) {
+ return ColumnConst::create(nested_column, column->size());
+ }
+ return nested_column;
+ };
+
+ auto nested_type = assert_cast<const DataTypeNullable*,
TypeCheckOnRelease::DISABLE>(type.get())
+ ->get_nested_type();
+ if (replace_null_data_to_default && info.has_null) {
+ if (column->try_replace_null_payload_with_default_without_cow()) {
+ return {get_nested_column(), nested_type, ""};
+ }
+
+ // Only copy the nested column because the original nullable column
must remain unchanged.
+ const auto nested_column = get_nested_column();
+ auto mutable_nested_col =
nested_column->clone_resized(nested_column->size());
+
mutable_nested_col->replace_column_null_data(nullable_column.get_null_map_data().data());
+ return {std::move(mutable_nested_col), nested_type, ""};
+ }
+ return {get_nested_column(), nested_type, ""};
}
Status ColumnWithTypeAndName::check_type_and_column_match() const {
diff --git a/be/src/core/block/column_with_type_and_name.h
b/be/src/core/block/column_with_type_and_name.h
index 9b1c357ac66..d6a1954101b 100644
--- a/be/src/core/block/column_with_type_and_name.h
+++ b/be/src/core/block/column_with_type_and_name.h
@@ -26,17 +26,29 @@
#include <memory>
#include <string>
#include <utility>
+#include <vector>
+#include "core/column/column_vector.h"
#include "core/data_type/data_type.h"
#include "core/data_type_serde/data_type_serde.h"
#include "core/types.h"
namespace doris {
+class ColumnNullable;
class PColumnMeta;
} // namespace doris
namespace doris {
+struct NullableColumnInfo {
+ bool has_null = false;
+ bool only_null = false;
+ bool is_const = false;
+ bool is_nullable = false;
+};
+
+using NullableColumnInfos = std::vector<NullableColumnInfo>;
+
// class WriteBuffer;
/** Column data along with its data type and name.
@@ -69,9 +81,15 @@ struct ColumnWithTypeAndName {
void to_pb_column_meta(PColumnMeta* col_meta) const;
- ColumnWithTypeAndName unnest_nullable(bool replace_null_data_to_default =
false) const;
+ const ColumnUInt8::Ptr& get_nullable_null_map_column() const;
+ NullableColumnInfo get_nullable_column_info() const;
+ ColumnWithTypeAndName unnest_nullable(const NullableColumnInfo& info,
+ bool replace_null_data_to_default)
const;
Status check_type_and_column_match() const;
+
+private:
+ const ColumnNullable& get_nullable_column() const;
};
} // namespace doris
diff --git a/be/src/core/column/column.h b/be/src/core/column/column.h
index d09263a9ae6..9a6f3d05d02 100644
--- a/be/src/core/column/column.h
+++ b/be/src/core/column/column.h
@@ -764,6 +764,17 @@ public:
// column_vector and column_decimal override this method to return true
virtual bool support_replace_column_null_data() const { return false; }
+ /**
+ * Try to replace the payload of NULL rows with the nested column's
default value without
+ * going through COW. Implementations must return false without modifying
data unless the
+ * complete column ownership chain is exclusive. This is only safe because
payloads of rows
+ * that are already NULL are not observable through the nullable column.
In particular, a
+ * shared nested column may belong to another nullable column with a
different null map.
+ *
+ * This bypasses the normal COW mutation path. Do not use it for general
column mutation.
+ */
+ virtual bool try_replace_null_payload_with_default_without_cow() const {
return false; }
+
// For float/double types, replace -0.0 with 0.0, set NaN to quiet NaN,
// used to ensure data hash equality for -0.0 and +0.0, e.g. aggregate and
join
virtual void replace_float_special_values() {}
diff --git a/be/src/core/column/column_const.h
b/be/src/core/column/column_const.h
index f4a373f3178..7c38fd2cbbe 100644
--- a/be/src/core/column/column_const.h
+++ b/be/src/core/column/column_const.h
@@ -310,6 +310,13 @@ public:
return data->support_replace_column_null_data();
}
+ bool try_replace_null_payload_with_default_without_cow() const override {
+ if (!IColumn::is_exclusive()) {
+ return false;
+ }
+ return data->try_replace_null_payload_with_default_without_cow();
+ }
+
void finalize() override { data->finalize(); }
void erase(size_t start, size_t length) override {
diff --git a/be/src/core/column/column_nullable.cpp
b/be/src/core/column/column_nullable.cpp
index ed51d4e7a42..a3857c2af04 100644
--- a/be/src/core/column/column_nullable.cpp
+++ b/be/src/core/column/column_nullable.cpp
@@ -676,6 +676,29 @@ bool ColumnNullable::only_null() const {
return !simd::contain_zero(get_null_map_data().data(), size());
}
+ColumnNullable::NullMapState ColumnNullable::get_null_map_state() const {
+ const auto& null_map = get_null_map_data();
+ if (null_map.empty()) {
+ return {.has_null = false, .only_null = true};
+ }
+
+ if (null_map[0]) {
+ return {.has_null = true,
+ .only_null = !simd::contain_zero(null_map.data() + 1,
null_map.size() - 1)};
+ }
+ return {.has_null = simd::contain_one(null_map.data() + 1, null_map.size()
- 1),
+ .only_null = false};
+}
+
+bool ColumnNullable::try_replace_null_payload_with_default_without_cow() const
{
+ if (!is_exclusive()) {
+ return false;
+ }
+
+
const_cast<IColumn&>(get_nested_column()).replace_column_null_data(get_null_map_data().data());
+ return true;
+}
+
bool ColumnNullable::has_null(size_t begin, size_t end) const {
return simd::contain_one(get_null_map_data().data() + begin, end - begin);
}
diff --git a/be/src/core/column/column_nullable.h
b/be/src/core/column/column_nullable.h
index de0e6b64e5c..c4ec70fb69a 100644
--- a/be/src/core/column/column_nullable.h
+++ b/be/src/core/column/column_nullable.h
@@ -62,6 +62,11 @@ private:
ColumnNullable(const ColumnNullable&) = default;
public:
+ struct NullMapState {
+ bool has_null;
+ bool only_null;
+ };
+
/** Create a column from immutable/shared subcolumns without cloning them.
* Call IColumn::mutate before modifying the returned column tree.
*/
@@ -275,7 +280,10 @@ public:
get_null_map_column().is_exclusive();
}
+ bool try_replace_null_payload_with_default_without_cow() const override;
+
bool only_null() const override;
+ NullMapState get_null_map_state() const;
// used in schema change
void change_nested_column(ColumnPtr& other) { ((ColumnPtr&)_nested_column)
= other; }
diff --git a/be/src/exprs/function/cast/function_cast.cpp
b/be/src/exprs/function/cast/function_cast.cpp
index febf94eb336..5561a3a76de 100644
--- a/be/src/exprs/function/cast/function_cast.cpp
+++ b/be/src/exprs/function/cast/function_cast.cpp
@@ -195,24 +195,37 @@ WrapperType prepare_remove_nullable(FunctionContext*
context, const DataTypePtr&
bool replace_null_data_to_default =
need_replace_null_data_to_default(
context, from_type_not_nullable, to_type_not_nullable);
+ NullableColumnInfo source_info;
+ if (block.get_by_position(arguments[0]).type->is_nullable()) {
+ source_info =
block.get_by_position(arguments[0]).get_nullable_column_info();
+ }
auto nested_result_index = block.columns();
- block.insert(block.get_by_position(result).unnest_nullable());
+ const auto& result_column = block.get_by_position(result);
+ block.insert({nullptr, to_type_not_nullable, result_column.name});
auto nested_source_index = block.columns();
- block.insert(block.get_by_position(arguments[0])
-
.unnest_nullable(replace_null_data_to_default));
+ if (source_info.is_nullable) {
+ block.insert(block.get_by_position(arguments[0])
+ .unnest_nullable(source_info,
replace_null_data_to_default));
+ } else {
+ block.insert(block.get_by_position(arguments[0]));
+ }
- const auto& arg_col = block.get_by_position(arguments[0]);
const NullMap::value_type* arg_null_map = nullptr;
- if (const auto* nullable =
check_and_get_column<ColumnNullable>(*arg_col.column)) {
- arg_null_map = nullable->get_null_map_data().data();
+ if (source_info.is_nullable) {
+ arg_null_map = block.get_by_position(arguments[0])
+ .get_nullable_null_map_column()
+ ->get_data()
+ .data();
}
RETURN_IF_ERROR(prepare_impl(context, from_type_not_nullable,
to_type_not_nullable)(
context, block, {nested_source_index},
nested_result_index, input_rows_count,
arg_null_map));
+ NullableColumnInfos nullable_column_infos(block.columns());
+ nullable_column_infos[arguments[0]] = std::move(source_info);
block.get_by_position(result).column =
wrap_in_nullable(block.get_by_position(nested_result_index).column, block,
- arguments, input_rows_count);
+ arguments, nullable_column_infos,
input_rows_count);
block.erase(nested_source_index);
block.erase(nested_result_index);
diff --git a/be/src/exprs/function/function.cpp
b/be/src/exprs/function/function.cpp
index 1d2b9347a52..4f8c4e25059 100644
--- a/be/src/exprs/function/function.cpp
+++ b/be/src/exprs/function/function.cpp
@@ -43,6 +43,7 @@
namespace doris {
ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const
ColumnNumbers& args,
+ const NullableColumnInfos& nullable_column_infos,
size_t input_rows_count) {
ColumnPtr result_null_map_column;
/// If result is already nullable.
@@ -55,13 +56,13 @@ ColumnPtr wrap_in_nullable(const ColumnPtr& src, const
Block& block, const Colum
}
for (const auto& arg : args) {
- const ColumnWithTypeAndName& elem = block.get_by_position(arg);
- if (!elem.type->is_nullable() || is_column_const(*elem.column)) {
+ const auto& info = nullable_column_infos[arg];
+ if (!info.is_nullable || info.is_const) {
continue;
}
- if (auto nullable = cast_to_column<ColumnNullable>(elem.column);
nullable->has_null()) {
- const ColumnPtr& null_map_column =
nullable->get_null_map_column_ptr();
+ if (info.has_null) {
+ const auto& null_map_column =
block.get_by_position(arg).get_nullable_null_map_column();
if (!result_null_map_column) { // NOLINT(bugprone-use-after-move)
result_null_map_column = null_map_column;
continue;
@@ -73,8 +74,7 @@ ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block&
block, const Colum
NullMap& result_null_map =
assert_cast<ColumnUInt8&>(*mutable_result_null_map_column).get_data();
- const NullMap& src_null_map =
- assert_cast<const
ColumnUInt8&>(*null_map_column).get_data();
+ const NullMap& src_null_map = null_map_column->get_data();
VectorizedUtils::update_null_map(result_null_map, src_null_map);
}
@@ -99,6 +99,18 @@ ColumnPtr wrap_in_nullable(const ColumnPtr& src, const
Block& block, const Colum
return ColumnNullable::create(src_not_nullable, result_null_map_column);
}
+ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const
ColumnNumbers& args,
+ size_t input_rows_count) {
+ NullableColumnInfos nullable_column_infos(block.columns());
+ for (const auto arg : args) {
+ const auto& column = block.get_by_position(arg);
+ if (column.type->is_nullable()) {
+ nullable_column_infos[arg] = column.get_nullable_column_info();
+ }
+ }
+ return wrap_in_nullable(src, block, args, nullable_column_infos,
input_rows_count);
+}
+
bool have_null_column(const Block& block, const ColumnNumbers& args) {
return std::ranges::any_of(args, [&block](const auto& elem) {
return block.get_by_position(elem).type->is_nullable();
@@ -195,16 +207,25 @@ Status
PreparedFunctionImpl::default_implementation_for_nulls(
return Status::OK();
}
- if (std::ranges::any_of(args, [&block](const auto& elem) {
- return block.get_by_position(elem).column->only_null();
- })) {
- block.get_by_position(result).column =
-
block.get_by_position(result).type->create_column_const(input_rows_count,
Field());
- *executed = true;
- return Status::OK();
- }
-
if (have_null_column(block, args)) {
+ NullableColumnInfos nullable_column_infos(block.columns());
+ for (const auto arg : args) {
+ const auto& argument = block.get_by_position(arg);
+ if (!argument.type->is_nullable()) {
+ continue;
+ }
+
+ auto info = argument.get_nullable_column_info();
+ if (info.only_null) {
+ auto& result_column = block.get_by_position(result);
+ result_column.column =
+
result_column.type->create_column_const(input_rows_count, Field());
+ *executed = true;
+ return Status::OK();
+ }
+ nullable_column_infos[arg] = info;
+ }
+
bool need_to_default = need_replace_null_data_to_default();
// extract nested column from nulls
ColumnNumbers new_args;
@@ -213,7 +234,8 @@ Status
PreparedFunctionImpl::default_implementation_for_nulls(
for (int i = 0; i < args.size(); ++i) {
uint32_t arg = args[i];
new_args.push_back(i);
-
new_block.insert(block.get_by_position(arg).unnest_nullable(need_to_default));
+
new_block.insert(block.get_by_position(arg).unnest_nullable(nullable_column_infos[arg],
+
need_to_default));
}
new_block.insert(block.get_by_position(result));
int new_result = new_block.columns() - 1;
@@ -222,8 +244,9 @@ Status
PreparedFunctionImpl::default_implementation_for_nulls(
// After run with nested, wrap them in null. Before this,
block.get_by_position(result).type
// is not compatible with get_by_position(result).column
- block.get_by_position(result).column = wrap_in_nullable(
- new_block.get_by_position(new_result).column, block, args,
input_rows_count);
+ block.get_by_position(result).column =
+ wrap_in_nullable(new_block.get_by_position(new_result).column,
block, args,
+ nullable_column_infos, input_rows_count);
*executed = true;
return Status::OK();
diff --git a/be/src/exprs/function/function.h b/be/src/exprs/function/function.h
index d26cab4d783..8307a6a1c7c 100644
--- a/be/src/exprs/function/function.h
+++ b/be/src/exprs/function/function.h
@@ -681,5 +681,8 @@ using FunctionPtr = std::shared_ptr<IFunction>;
*/
ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const
ColumnNumbers& args,
size_t input_rows_count);
+ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const
ColumnNumbers& args,
+ const NullableColumnInfos& nullable_column_infos,
+ size_t input_rows_count);
} // namespace doris
diff --git a/be/test/core/block/column_with_type_and_name_test.cpp
b/be/test/core/block/column_with_type_and_name_test.cpp
index 8a5fd999d42..bafce0c4350 100644
--- a/be/test/core/block/column_with_type_and_name_test.cpp
+++ b/be/test/core/block/column_with_type_and_name_test.cpp
@@ -35,9 +35,149 @@ TEST(ColumnWithTypeAndNameTest, get_nested_test) {
column_with_type_and_name.type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
column_with_type_and_name.name = "column_with_type_and_name";
- auto result = column_with_type_and_name.unnest_nullable(true);
+ auto result = column_with_type_and_name.unnest_nullable(
+ column_with_type_and_name.get_nullable_column_info(), true);
EXPECT_TRUE(is_column_const(*result.column));
EXPECT_EQ(result.column->size(), 3);
+ EXPECT_EQ(result.column->get_int(0), 0);
+}
+
+TEST(ColumnWithTypeAndNameTest, get_nullable_column_info_for_const_column) {
+ auto nullable_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
+
+ auto null_column =
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>({1}),
+
ColumnHelper::create_column<DataTypeUInt8>({true}));
+ ColumnWithTypeAndName const_null
{ColumnConst::create(std::move(null_column), 3), nullable_type,
+ "const_null"};
+ auto null_info = const_null.get_nullable_column_info();
+ EXPECT_TRUE(null_info.is_const);
+ EXPECT_TRUE(null_info.has_null);
+ EXPECT_TRUE(null_info.only_null);
+ EXPECT_EQ(const_null.get_nullable_null_map_column()->size(), 1);
+
+ auto non_null_column =
+
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>({1}),
+
ColumnHelper::create_column<DataTypeUInt8>({false}));
+ ColumnWithTypeAndName const_non_null
{ColumnConst::create(std::move(non_null_column), 3),
+ nullable_type, "const_non_null"};
+ auto non_null_info = const_non_null.get_nullable_column_info();
+ EXPECT_TRUE(non_null_info.is_const);
+ EXPECT_FALSE(non_null_info.has_null);
+ EXPECT_FALSE(non_null_info.only_null);
+ EXPECT_EQ(const_non_null.get_nullable_null_map_column()->size(), 1);
+}
+
+TEST(ColumnWithTypeAndNameTest, get_nullable_column_info_null_map_states) {
+ auto nullable_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
+
+ const auto check_state = [&](std::initializer_list<int32_t> values,
+ std::initializer_list<uint8_t> null_map, bool
has_null,
+ bool only_null) {
+ ColumnWithTypeAndName column {
+
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>(values),
+
ColumnHelper::create_column<DataTypeUInt8>(null_map)),
+ nullable_type, "nullable"};
+ const auto info = column.get_nullable_column_info();
+ EXPECT_EQ(info.has_null, has_null);
+ EXPECT_EQ(info.only_null, only_null);
+ };
+
+ check_state({}, {}, false, true);
+ check_state({1, 2, 3}, {false, false, false}, false, false);
+ check_state({1, 2, 3}, {true, true, true}, true, true);
+ check_state({1, 2, 3}, {false, true, false}, true, false);
+ check_state({1, 2, 3}, {true, false, true}, true, false);
+}
+
+TEST(ColumnWithTypeAndNameTest,
unnest_nullable_without_null_reuses_nested_column) {
+ auto nested_column = ColumnHelper::create_column<DataTypeInt32>({1, 2, 3});
+ auto nullable_column = ColumnNullable::create(
+ nested_column, ColumnHelper::create_column<DataTypeUInt8>({false,
false, false}));
+ ColumnWithTypeAndName column_with_type_and_name {
+ std::move(nullable_column),
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()),
"nullable"};
+
+ auto result = column_with_type_and_name.unnest_nullable(
+ column_with_type_and_name.get_nullable_column_info(), true);
+
+ EXPECT_EQ(result.column.get(), nested_column.get());
+}
+
+TEST(ColumnWithTypeAndNameTest,
unnest_nullable_with_unique_nested_replaces_data_in_place) {
+ auto nullable_column = ColumnNullable::create(
+ ColumnHelper::create_column<DataTypeInt32>({1, 2, 3}),
+ ColumnHelper::create_column<DataTypeUInt8>({false, true, false}));
+ const auto* original_nested_column =
+ static_cast<const
ColumnNullable&>(*nullable_column).get_nested_column_ptr().get();
+ ColumnWithTypeAndName column_with_type_and_name {
+ std::move(nullable_column),
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()),
"nullable"};
+
+ const auto info = column_with_type_and_name.get_nullable_column_info();
+ auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+ EXPECT_EQ(result.column.get(), original_nested_column);
+ EXPECT_EQ(assert_cast<const ColumnInt32&>(*result.column).get_data()[1],
0);
+}
+
+TEST(ColumnWithTypeAndNameTest,
unnest_nullable_with_shared_nested_preserves_visible_alias) {
+ auto nested_column = ColumnHelper::create_column<DataTypeInt32>({1, 2, 3});
+ auto nullable_column = ColumnNullable::create(
+ nested_column, ColumnHelper::create_column<DataTypeUInt8>({false,
true, false}));
+ auto visible_alias = ColumnNullable::create(
+ nested_column, ColumnHelper::create_column<DataTypeUInt8>({false,
false, false}));
+ ColumnWithTypeAndName column_with_type_and_name {
+ std::move(nullable_column),
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()),
"nullable"};
+
+ const auto info = column_with_type_and_name.get_nullable_column_info();
+ auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+ EXPECT_NE(result.column.get(), nested_column.get());
+ EXPECT_EQ(assert_cast<const ColumnInt32&>(*result.column).get_data()[1],
0);
+ EXPECT_FALSE(visible_alias->is_null_at(1));
+ const ColumnNullable& visible_alias_column = *visible_alias;
+ EXPECT_EQ(
+ assert_cast<const
ColumnInt32&>(visible_alias_column.get_nested_column()).get_data()[1],
+ 2);
+}
+
+TEST(ColumnWithTypeAndNameTest,
unnest_nullable_with_shared_source_replaces_data_on_copy) {
+ auto nullable_column = ColumnNullable::create(
+ ColumnHelper::create_column<DataTypeInt32>({1, 2, 3}),
+ ColumnHelper::create_column<DataTypeUInt8>({false, true, false}));
+ ColumnWithTypeAndName column_with_type_and_name {
+ std::move(nullable_column),
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()),
"nullable"};
+ ColumnPtr source_alias = column_with_type_and_name.column;
+ const auto& original_nested_column =
+ assert_cast<const
ColumnNullable&>(*source_alias).get_nested_column();
+
+ const auto info = column_with_type_and_name.get_nullable_column_info();
+ auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+ EXPECT_NE(result.column.get(), &original_nested_column);
+ EXPECT_EQ(assert_cast<const ColumnInt32&>(*result.column).get_data()[1],
0);
+ EXPECT_EQ(assert_cast<const
ColumnInt32&>(original_nested_column).get_data()[1], 2);
+}
+
+TEST(ColumnWithTypeAndNameTest,
unnest_const_nullable_with_shared_source_replaces_data_on_copy) {
+ auto nullable_column =
+
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>({1}),
+
ColumnHelper::create_column<DataTypeUInt8>({true}));
+ ColumnWithTypeAndName column_with_type_and_name {
+ ColumnConst::create(std::move(nullable_column), 3),
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()),
"nullable"};
+ ColumnPtr source_alias = column_with_type_and_name.column;
+ const auto& original_nullable_column = assert_cast<const ColumnNullable&>(
+ assert_cast<const ColumnConst&>(*source_alias).get_data_column());
+
+ const auto info = column_with_type_and_name.get_nullable_column_info();
+ auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+ EXPECT_TRUE(is_column_const(*result.column));
+ EXPECT_EQ(result.column->get_int(0), 0);
+ EXPECT_EQ(original_nullable_column.get_nested_column().get_int(0), 1);
}
} // namespace doris
diff --git a/be/test/exprs/function/function_arithmetic_test.cpp
b/be/test/exprs/function/function_arithmetic_test.cpp
index 09c66ba9bf8..4d3829bf30a 100644
--- a/be/test/exprs/function/function_arithmetic_test.cpp
+++ b/be/test/exprs/function/function_arithmetic_test.cpp
@@ -32,6 +32,14 @@
namespace doris {
+TEST(function_arithmetic_test, add_mixed_nullable_arguments_test) {
+ InputTypeSet input_types = {Nullable {PrimitiveType::TYPE_INT},
+ Notnull {PrimitiveType::TYPE_INT}};
+ DataSet data_set = {{{int32_t {1}, int32_t {2}}, int32_t {3}}, {{Null(),
int32_t {4}}, Null()}};
+
+ static_cast<void>(check_function<DataTypeInt32, true>("add", input_types,
data_set));
+}
+
TEST(function_arithmetic_test, function_arithmetic_mod_test) {
std::string func_name = "mod";
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]