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 d93137b9f13 [fix](be) Ignore negative integers in bitmap aggregates
(#68219)
d93137b9f13 is described below
commit d93137b9f134b1526dc6ec6aa13dc1709e627180
Author: HappenLee <[email protected]>
AuthorDate: Mon Sep 21 12:00:07 2026 +0800
[fix](be) Ignore negative integers in bitmap aggregates (#68219)
`bitmap_agg` and `bitmap_union_int` currently pass signed negative
integers into `BitmapValue`, which converts them to unsigned bitmap
members. For example, `bitmap_agg(-1)` can contain
`18446744073709551615` and `bitmap_union_int(-1)` returns 1, while
`to_bitmap(-1)` produces an empty bitmap.
Filter negative integers before insertion in both aggregates. Cover
scalar insertion, nullable and non-nullable `bitmap_agg` batches, and
the integer-only branch of the shared bitmap-count aggregate. Preserve
the allocation-free batch path for all-nonnegative input and use a
tracked temporary buffer for filtered batches. Generic bitmap operations
and the full uint64 domain of `bitmap_union_count` are unchanged.
With this change:
- `bitmap_count(bitmap_agg(-1))` and `bitmap_union_int(-1)` return 0.
- Input `[-1, 0, 1, 1, NULL]` contributes exactly the members `{0, 1}`.
- All-negative/all-NULL groups produce an empty bitmap or a count of 0.
### Release note
`bitmap_agg` and `bitmap_union_int` now ignore negative integers,
consistently with `to_bitmap`. In particular, `bitmap_union_int` counts
distinct nonnegative integers rather than all distinct integers. Queries
containing negative inputs can return smaller counts after upgrading.
### Check List (For Author)
- Test: add BE unit tests covering TINYINT/SMALLINT/INT/BIGINT,
nullable/non-nullable input, negative minima, positive maxima, zero,
duplicates, all-negative/all-NULL groups, dense batches, reset,
streaming serialization and column merge paths. Also protect UINT64_MAX
in `bitmap_union_count`.
- Passed: clang-format 16 check, BE build-hygiene checks and `git diff
--check`.
- Passed: compiler syntax checks of both aggregate
registration/instantiation translation units and the modified test
translation unit, using the generated ASAN-UT compilation settings.
- Attempted: `run-be-ut.sh -j48 --run
--filter='BitmapIntegerAggregateTest.*:AggBitmapTest.*'`. The build
stops in unchanged `common/cpp/aws_common.cpp` because the local
thirdparty installation lacks
`aws/core/auth/GeneralHTTPCredentialsProvider.h`; tests have not
executed locally.
- Attempted: repository clang-tidy script. Analysis is blocked by an
existing unmatched NOLINTEND in `core/types.h` and existing warnings in
the touched files outside the new logic. No clean clang-tidy run is
claimed.
- Behavior changed: Yes, negative integers no longer contribute bitmap
members or counts. NULL handling, result types, function signatures and
intermediate/storage formats remain unchanged.
- Does this need documentation: Yes:
https://github.com/apache/doris-website/pull/4154 updates the
English/Chinese development-version documentation and clarifies the
changed `bitmap_union_int` semantics.
### Review notes
The existing aggregate test helper exercises the batch, streaming and
merge paths without adding a new framework. Grouped additions use the
same scalar `add` entry point. No new locks, configuration, persistence
changes or FE/BE fields are introduced. Filtering is applied only to
integer inputs; already-materialized bitmaps are preserved. During a
rolling upgrade, old BEs can still contribute old partial states, so
uniform negative-input semantics require all executing BEs to contain
this fix.
---
be/src/exprs/aggregate/aggregate_function_bitmap.h | 22 +++--
.../aggregate/aggregate_function_bitmap_agg.h | 27 +++++--
be/test/exprs/aggregate/agg_bitmap_test.cpp | 93 ++++++++++++++++++++++
3 files changed, 129 insertions(+), 13 deletions(-)
diff --git a/be/src/exprs/aggregate/aggregate_function_bitmap.h
b/be/src/exprs/aggregate/aggregate_function_bitmap.h
index 296433df8b6..e60ab9769d4 100644
--- a/be/src/exprs/aggregate/aggregate_function_bitmap.h
+++ b/be/src/exprs/aggregate/aggregate_function_bitmap.h
@@ -351,19 +351,25 @@ public:
void add(AggregateDataPtr __restrict place, const IColumn** columns,
ssize_t row_num,
Arena&) const override {
+ const IColumn* data_column = columns[0];
if constexpr (arg_is_nullable) {
const auto& nullable_column =
assert_cast<const ColumnNullable&,
TypeCheckOnRelease::DISABLE>(*columns[0]);
- if (!nullable_column.is_null_at(row_num)) {
- const auto& column = assert_cast<const ColVecType&,
TypeCheckOnRelease::DISABLE>(
- nullable_column.get_nested_column());
- this->data(place).add(column.get_data()[row_num]);
+ if (nullable_column.is_null_at(row_num)) {
+ return;
+ }
+ data_column = &nullable_column.get_nested_column();
+ }
+ const auto& value =
+ assert_cast<const ColVecType&,
TypeCheckOnRelease::DISABLE>(*data_column)
+ .get_data()[row_num];
+ if constexpr (!std::is_same_v<ColVecType, ColumnBitmap>) {
+ // Match bitmap_agg and to_bitmap before conversion to uint64_t.
+ if (value < 0) {
+ return;
}
- } else {
- const auto& column =
- assert_cast<const ColVecType&,
TypeCheckOnRelease::DISABLE>(*columns[0]);
- this->data(place).add(column.get_data()[row_num]);
}
+ this->data(place).add(value);
}
void add_many(AggregateDataPtr __restrict place, const IColumn** columns,
diff --git a/be/src/exprs/aggregate/aggregate_function_bitmap_agg.h
b/be/src/exprs/aggregate/aggregate_function_bitmap_agg.h
index 212698f7dbd..8d6af091066 100644
--- a/be/src/exprs/aggregate/aggregate_function_bitmap_agg.h
+++ b/be/src/exprs/aggregate/aggregate_function_bitmap_agg.h
@@ -27,6 +27,7 @@
#include "core/assert_cast.h"
#include "core/data_type/data_type_bitmap.h"
+#include "core/pod_array.h"
#include "core/value/bitmap_value.h"
#include "exprs/aggregate/aggregate_function.h"
@@ -43,7 +44,11 @@ template <PrimitiveType T>
struct AggregateFunctionBitmapAggData {
BitmapValue value;
- void add(const typename PrimitiveTypeTraits<T>::CppType& value_) {
value.add(value_); }
+ void add(const typename PrimitiveTypeTraits<T>::CppType& value_) {
+ if (value_ >= 0) {
+ value.add(value_);
+ }
+ }
void reset() { value.reset(); }
@@ -96,9 +101,9 @@ public:
assert_cast<const ColumnNullable&,
TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& column = assert_cast<const ColVecType&,
TypeCheckOnRelease::DISABLE>(
nullable_column.get_nested_column());
- std::vector<typename PrimitiveTypeTraits<T>::CppType> values;
- for (int i = 0; i < batch_size; ++i) {
- if (!nullable_column.is_null_at(i)) {
+ PaddedPODArray<typename PrimitiveTypeTraits<T>::CppType> values;
+ for (size_t i = 0; i < batch_size; ++i) {
+ if (!nullable_column.is_null_at(i) && column.get_data()[i] >=
0) {
values.push_back(column.get_data()[i]);
}
}
@@ -106,7 +111,19 @@ public:
} else {
const auto& column =
assert_cast<const ColVecType&,
TypeCheckOnRelease::DISABLE>(*columns[0]);
- this->data(place).value.add_many(column.get_data().data(),
column.size());
+ const auto* data = column.get_data().data();
+ // Keep the allocation-free batch path for nonnegative input.
+ if (std::all_of(data, data + batch_size, [](auto value) { return
value >= 0; })) {
+ this->data(place).value.add_many(data, batch_size);
+ } else {
+ PaddedPODArray<typename PrimitiveTypeTraits<T>::CppType>
values;
+ for (size_t i = 0; i < batch_size; ++i) {
+ if (data[i] >= 0) {
+ values.push_back(data[i]);
+ }
+ }
+ this->data(place).value.add_many(values.data(), values.size());
+ }
}
}
diff --git a/be/test/exprs/aggregate/agg_bitmap_test.cpp
b/be/test/exprs/aggregate/agg_bitmap_test.cpp
index f0b4addcabd..e2ebb5b211b 100644
--- a/be/test/exprs/aggregate/agg_bitmap_test.cpp
+++ b/be/test/exprs/aggregate/agg_bitmap_test.cpp
@@ -16,6 +16,7 @@
// under the License.
#include <cstddef>
+#include <limits>
#include <memory>
#include <string>
#include <vector>
@@ -24,11 +25,13 @@
#include "core/column/column_complex.h"
#include "core/data_type/data_type_bitmap.h"
#include "core/data_type/data_type_decimal.h"
+#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
#include "core/field.h"
#include "core/types.h"
#include "core/value/bitmap_value.h"
+#include "exprs/aggregate/agg_function_test.h"
#include "exprs/aggregate/aggregate_function.h"
#include "exprs/aggregate/aggregate_function_simple_factory.h"
#include "gtest/gtest_pred_impl.h"
@@ -299,4 +302,94 @@ TEST(AggBitmapTest, bitmap_union_int_test) {
validate_bitmap_union_int_test<TYPE_BIGINT>();
}
+class BitmapIntegerAggregateTest : public AggregateFunctiontest {
+protected:
+ template <PrimitiveType T>
+ void check_input(const std::vector<typename
PrimitiveTypeTraits<T>::CppType>& values,
+ const BitmapValue& expected, bool nullable) {
+ DataTypePtr type = std::make_shared<typename
PrimitiveTypeTraits<T>::DataType>();
+ if (nullable) {
+ type = make_nullable(type);
+ }
+ auto input = type->create_column();
+ for (auto value : values) {
+ input->insert(Field::create_field<T>(value));
+ }
+ if (nullable) {
+ input->insert_default();
+ }
+ Block block({{std::move(input), type, "input"}});
+
+ // The shared helper covers single-place batches, streaming
aggregation,
+ // reset, serialization and all column-based merge paths.
+ create_agg("bitmap_agg", false, {type},
std::make_shared<DataTypeBitMap>());
+ execute(block,
ColumnHelper::create_column_with_name<DataTypeBitMap>({expected}));
+ create_agg("bitmap_union_int", false, {type},
std::make_shared<DataTypeInt64>());
+ execute(block, ColumnHelper::create_column_with_name<DataTypeInt64>(
+ {static_cast<Int64>(expected.cardinality())}));
+ }
+
+ template <PrimitiveType T>
+ void check_negative_inputs() {
+ using CppType = typename PrimitiveTypeTraits<T>::CppType;
+ constexpr auto min = std::numeric_limits<CppType>::min();
+ constexpr auto max = std::numeric_limits<CppType>::max();
+ for (bool nullable : {false, true}) {
+ SCOPED_TRACE(nullable);
+ check_input<T>({-1}, BitmapValue(), nullable);
+ check_input<T>({min, -2, -1, -1}, BitmapValue(), nullable);
+ BitmapValue expected(std::vector<uint64_t> {0, 1,
static_cast<uint64_t>(max)});
+ check_input<T>({min, -2, -1, 0, 1, 1, max}, expected, nullable);
+ check_input<T>({0, 1, 1, max}, expected, nullable);
+
+ // Cross the small-set threshold and exercise repeated negative
values.
+ std::vector<CppType> dense_values;
+ BitmapValue dense_expected;
+ for (int i = 0; i < 100; ++i) {
+ dense_values.push_back(-1);
+ dense_values.push_back(i);
+ dense_expected.add(i);
+ }
+ check_input<T>(dense_values, dense_expected, nullable);
+ }
+ // One NULL and no non-NULL values.
+ check_input<T>({}, BitmapValue(), true);
+ }
+};
+
+TEST_F(BitmapIntegerAggregateTest, IgnoreNegativeTinyInt) {
+ check_negative_inputs<TYPE_TINYINT>();
+}
+
+TEST_F(BitmapIntegerAggregateTest, IgnoreNegativeSmallInt) {
+ check_negative_inputs<TYPE_SMALLINT>();
+}
+
+TEST_F(BitmapIntegerAggregateTest, IgnoreNegativeInt) {
+ check_negative_inputs<TYPE_INT>();
+}
+
+TEST_F(BitmapIntegerAggregateTest, IgnoreNegativeBigInt) {
+ check_negative_inputs<TYPE_BIGINT>();
+}
+
+TEST_F(BitmapIntegerAggregateTest, PreserveUnsignedBitmapValues) {
+ const BitmapValue bitmap(std::vector<uint64_t> {0,
std::numeric_limits<uint64_t>::max()});
+ for (bool nullable : {false, true}) {
+ DataTypePtr type = std::make_shared<DataTypeBitMap>();
+ if (nullable) {
+ type = make_nullable(type);
+ }
+ auto input = type->create_column();
+ input->insert(Field::create_field<TYPE_BITMAP>(bitmap));
+ input->insert(Field::create_field<TYPE_BITMAP>(bitmap));
+ if (nullable) {
+ input->insert_default();
+ }
+ create_agg("bitmap_union_count", false, {type},
std::make_shared<DataTypeInt64>());
+ execute(Block({{std::move(input), type, "input"}}),
+ ColumnHelper::create_column_with_name<DataTypeInt64>({2}));
+ }
+}
+
} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]