This is an automated email from the ASF dual-hosted git repository.
yiguolei 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 f2d8e65c6c7 [fix](be) Own values in single-value aggregate states
(#68210)
f2d8e65c6c7 is described below
commit f2d8e65c6c7d1be19bf6d3f7179cc88f6547ccc4
Author: Mryange <[email protected]>
AuthorDate: Thu Sep 24 14:19:58 2026 +0800
[fix](be) Own values in single-value aggregate states (#68210)
Single-value aggregate state implementations were duplicated across
aggregate functions, and complex values were not handled consistently.
In particular, window functions such as `first_value`, `last_value`,
`nth_value`, `lead`, and `lag` retained a raw input column pointer and
row offset. The input column can be cleared while the window state is
still alive, leaving the state with an invalid reference.
Root cause: the shared state abstraction did not own complex values, so
aggregate functions either maintained separate implementations or
referenced input columns directly.
This change introduces reusable fixed-width, string, and owned-column
single-value states. The column-backed state materializes exactly one
selected row and is reused by complex `min`, `max`, `min_by`, `max_by`,
and window value functions. Unsupported complex comparison types use
non-incremental window evaluation. Existing fixed-width and string state
serialization formats are preserved.
Tests cover value ownership after source-column reclamation, comparison
and serialization, and verify that retained column allocation does not
scale with the number of source rows.
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
.../exprs/aggregate/aggregate_function_min_max.h | 734 ++-------------------
.../aggregate/aggregate_function_min_max_by.cpp | 11 +-
.../aggregate/aggregate_function_min_max_by.h | 146 ++--
.../aggregate/aggregate_function_min_max_impl.h | 12 +-
.../aggregate_function_reader_first_last.h | 128 +---
be/src/exprs/aggregate/aggregate_function_window.h | 36 +-
be/src/exprs/aggregate/single_value_data.h | 417 ++++++++++++
be/test/exprs/aggregate/agg_replace_test.cpp | 120 ----
.../exprs/aggregate/agg_window_nth_value_test.cpp | 47 ++
.../aggregate/aggregate_function_min_max_test.cpp | 197 ++++--
10 files changed, 776 insertions(+), 1072 deletions(-)
diff --git a/be/src/exprs/aggregate/aggregate_function_min_max.h
b/be/src/exprs/aggregate/aggregate_function_min_max.h
index 9a766ddb96a..416bf226196 100644
--- a/be/src/exprs/aggregate/aggregate_function_min_max.h
+++ b/be/src/exprs/aggregate/aggregate_function_min_max.h
@@ -48,6 +48,7 @@
#include "core/type_limit.h"
#include "core/types.h"
#include "exprs/aggregate/aggregate_function.h"
+#include "exprs/aggregate/single_value_data.h"
namespace doris {
class Arena;
@@ -59,650 +60,23 @@ class ColumnVector;
namespace doris {
-/// For numeric values.
-template <PrimitiveType T>
-struct SingleValueDataFixed {
-private:
- using Self = SingleValueDataFixed;
-
- bool has_value =
- false; /// We need to remember if at least one value has been
passed. This is necessary for AggregateFunctionIf.
- typename PrimitiveTypeTraits<T>::CppType value;
-
-public:
- using ColVecType = typename PrimitiveTypeTraits<T>::ColumnType;
- static constexpr bool NeedCheckColumnType = true;
-
- SingleValueDataFixed() = default;
- SingleValueDataFixed(bool has_value_, typename
PrimitiveTypeTraits<T>::CppType value_)
- : has_value(has_value_), value(value_) {}
- bool has() const { return has_value; }
-
- constexpr static bool IsFixedLength = true;
-
- void set_to_min_max(bool max) {
- value = max ? Compare::max_value<typename
PrimitiveTypeTraits<T>::CppType>()
- : Compare::min_value<typename
PrimitiveTypeTraits<T>::CppType>();
- }
-
- void change_if(const IColumn& column, size_t row_num, bool less) {
- has_value = true;
- value = less ? Compare::min(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
-
TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value)
- : Compare::max(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
-
TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value);
- }
-
- void insert_result_into(IColumn& to) const {
- if (has()) {
- assert_cast<typename PrimitiveTypeTraits<T>::ColumnType&,
TypeCheckOnRelease::DISABLE>(
- to)
- .get_data()
- .push_back(value);
- } else {
- assert_cast<typename PrimitiveTypeTraits<T>::ColumnType&,
TypeCheckOnRelease::DISABLE>(
- to)
- .insert_default();
- }
- }
-
- void reset() {
- if (has()) {
- has_value = false;
- }
- }
-
- void write(BufferWritable& buf) const {
- buf.write_binary(has());
- if (has()) {
- buf.write_binary(value);
- }
- }
-
- void read(BufferReadable& buf, Arena&) {
- buf.read_binary(has_value);
- if (has()) {
- buf.read_binary(value);
- }
- }
-
- void change(const IColumn& column, size_t row_num, Arena&) {
- has_value = true;
- value = assert_cast<const typename PrimitiveTypeTraits<T>::ColumnType&,
- TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num];
- }
-
- /// Assuming to.has()
- void change(const Self& to, Arena&) {
- has_value = true;
- value = to.value;
- }
-
- bool change_if_less(const IColumn& column, size_t row_num, Arena& arena) {
- if (!has() || Compare::less(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
-
TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value)) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_less(const Self& to, Arena& arena) {
- if (to.has() && (!has() || Compare::less(to.value, value))) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const IColumn& column, size_t row_num, Arena&
arena) {
- if (!has() ||
- Compare::greater(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
- TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value)) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool check_if_equal(const IColumn& column, size_t row_num) const {
- if (!has()) {
- return false;
- }
- return Compare::equal(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
- TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value);
- }
-
- bool change_if_greater(const Self& to, Arena& arena) {
- if (to.has() && (!has() || Compare::greater(to.value, value))) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- void change_first_time(const IColumn& column, size_t row_num, Arena&
arena) {
- if (UNLIKELY(!has())) {
- change(column, row_num, arena);
- }
- }
-
- void change_first_time(const Self& to, Arena& arena) {
- if (UNLIKELY(!has() && to.has())) {
- change(to, arena);
- }
- }
-};
-
-/// For decimal values.
-template <PrimitiveType T>
-struct SingleValueDataDecimal {
-private:
- using Self = SingleValueDataDecimal;
-
- bool has_value =
- false; /// We need to remember if at least one value has been
passed. This is necessary for AggregateFunctionIf.
- typename PrimitiveTypeTraits<T>::CppType value;
-
-public:
- using ColVecType = typename PrimitiveTypeTraits<T>::ColumnType;
- static constexpr bool NeedCheckColumnType = true;
-
- SingleValueDataDecimal() = default;
- SingleValueDataDecimal(bool has_value_, typename
PrimitiveTypeTraits<T>::CppType value_)
- : has_value(has_value_), value(value_) {}
- bool has() const { return has_value; }
-
- constexpr static bool IsFixedLength = true;
-
- void set_to_min_max(bool max) {
- value = max ? Compare::max_value<typename
PrimitiveTypeTraits<T>::CppType>()
- : Compare::min_value<typename
PrimitiveTypeTraits<T>::CppType>();
- }
-
- void change_if(const IColumn& column, size_t row_num, bool less) {
- has_value = true;
- value = less ? Compare::min(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
-
TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value)
- : Compare::max(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
-
TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value);
- }
-
- void insert_result_into(IColumn& to) const {
- if (has()) {
- assert_cast<typename PrimitiveTypeTraits<T>::ColumnType&,
TypeCheckOnRelease::DISABLE>(
- to)
- .insert_data((const char*)&value, 0);
- } else {
- assert_cast<typename PrimitiveTypeTraits<T>::ColumnType&,
TypeCheckOnRelease::DISABLE>(
- to)
- .insert_default();
- }
- }
-
- void reset() {
- if (has()) {
- has_value = false;
- }
- }
-
- void write(BufferWritable& buf) const {
- buf.write_binary(has());
- if (has()) {
- buf.write_binary(value);
- }
- }
-
- void read(BufferReadable& buf, Arena&) {
- buf.read_binary(has_value);
- if (has()) {
- buf.read_binary(value);
- }
- }
-
- void change(const IColumn& column, size_t row_num, Arena&) {
- has_value = true;
- value = assert_cast<const typename PrimitiveTypeTraits<T>::ColumnType&,
- TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num];
- }
-
- /// Assuming to.has()
- void change(const Self& to, Arena&) {
- has_value = true;
- value = to.value;
- }
-
- bool change_if_less(const IColumn& column, size_t row_num, Arena& arena) {
- if (!has() || Compare::less(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
-
TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value)) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_less(const Self& to, Arena& arena) {
- if (to.has() && (!has() || Compare::less(to.value, value))) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const IColumn& column, size_t row_num, Arena&
arena) {
- if (!has() ||
- Compare::greater(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
- TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value)) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const Self& to, Arena& arena) {
- if (to.has() && (!has() || Compare::greater(to.value, value))) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool check_if_equal(const IColumn& column, size_t row_num) const {
- if (!has()) {
- return false;
- }
- return Compare::equal(assert_cast<const typename
PrimitiveTypeTraits<T>::ColumnType&,
- TypeCheckOnRelease::DISABLE>(column)
- .get_data()[row_num],
- value);
- }
-
- void change_first_time(const IColumn& column, size_t row_num, Arena&
arena) {
- if (UNLIKELY(!has())) {
- change(column, row_num, arena);
- }
- }
-
- void change_first_time(const Self& to, Arena& arena) {
- if (UNLIKELY(!has() && to.has())) {
- change(to, arena);
- }
- }
-};
-
-/** For strings. Short strings are stored in the object itself, and long
strings are allocated separately.
- * NOTE It could also be suitable for arrays of numbers.
- */
-struct SingleValueDataString {
-private:
- using Self = SingleValueDataString;
- // This function uses int32 for storage, which triggers a 64-bit to 32-bit
conversion warning.
- // However, considering compatibility with future upgrades, no changes
will be made here.
- Int32 size = -1; /// -1 indicates that there is no value.
- Int32 capacity = 0; /// power of two or zero
- DorisUniqueBufferPtr<char> large_data;
-
-public:
- static constexpr Int32 AUTOMATIC_STORAGE_SIZE = 64;
- static constexpr Int32 MAX_SMALL_STRING_SIZE =
- AUTOMATIC_STORAGE_SIZE - sizeof(size) - sizeof(capacity) -
sizeof(large_data);
-
-private:
- char small_data[MAX_SMALL_STRING_SIZE]; /// Including the terminating zero.
-
-public:
- using ColVecType = ColumnString;
- static constexpr bool NeedCheckColumnType = true;
-
- ~SingleValueDataString() = default;
-
- constexpr static bool IsFixedLength = false;
-
- bool has() const { return size >= 0; }
-
- const char* get_data() const {
- return size <= MAX_SMALL_STRING_SIZE ? small_data : large_data.get();
- }
-
- void insert_result_into(IColumn& to) const {
- if (has()) {
- assert_cast<ColumnString&,
TypeCheckOnRelease::DISABLE>(to).insert_data(get_data(),
-
size);
- } else {
- assert_cast<ColumnString&,
TypeCheckOnRelease::DISABLE>(to).insert_default();
- }
- }
-
- void reset() {
- if (size != -1) {
- size = -1;
- capacity = 0;
- large_data.reset();
- }
- }
-
- void write(BufferWritable& buf) const {
- buf.write_binary(size);
- if (has()) {
- buf.write(get_data(), size);
- }
- }
-
- void read(BufferReadable& buf, Arena&) {
- Int32 rhs_size;
- buf.read_binary(rhs_size);
-
- if (rhs_size >= 0) {
- if (rhs_size <= MAX_SMALL_STRING_SIZE) {
- /// Don't free large_data here.
-
- size = rhs_size;
-
- if (size > 0) {
- buf.read(small_data, size);
- }
- } else {
- if (capacity < rhs_size) {
- capacity =
(Int32)round_up_to_power_of_two_or_zero(rhs_size);
- large_data = DorisUniqueBufferPtr<char>(capacity);
- }
-
- size = rhs_size;
- buf.read(large_data.get(), size);
- }
- } else {
- /// Don't free large_data here.
- size = rhs_size;
- }
- }
-
- StringRef get_string_ref() const { return StringRef(get_data(), size); }
-
- /// Assuming to.has()
- void change_impl(StringRef value, Arena&) {
- Int32 value_size = cast_set<Int32>(value.size);
- if (value_size <= MAX_SMALL_STRING_SIZE) {
- /// Don't free large_data here.
- size = value_size;
-
- if (size > 0) {
- memcpy(small_data, value.data, size);
- }
- } else {
- if (capacity < value_size) {
- /// Don't free large_data here.
- capacity = (Int32)round_up_to_power_of_two_or_zero(value_size);
- large_data = DorisUniqueBufferPtr<char>(capacity);
- }
-
- size = value_size;
- memcpy(large_data.get(), value.data, size);
- }
- }
-
- void change(const IColumn& column, size_t row_num, Arena& arena) {
- change_impl(
- assert_cast<const ColumnString&,
TypeCheckOnRelease::DISABLE>(column).get_data_at(
- row_num),
- arena);
- }
-
- void change(const Self& to, Arena& arena) {
change_impl(to.get_string_ref(), arena); }
-
- bool change_if_less(const IColumn& column, size_t row_num, Arena& arena) {
- if (!has() ||
- assert_cast<const ColumnString&,
TypeCheckOnRelease::DISABLE>(column).get_data_at(
- row_num) < get_string_ref()) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const IColumn& column, size_t row_num, Arena&
arena) {
- if (!has() ||
- assert_cast<const ColumnString&,
TypeCheckOnRelease::DISABLE>(column).get_data_at(
- row_num) > get_string_ref()) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_less(const Self& to, Arena& arena) {
- if (to.has() && (!has() || to.get_string_ref() < get_string_ref())) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const Self& to, Arena& arena) {
- if (to.has() && (!has() || to.get_string_ref() > get_string_ref())) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool check_if_equal(const IColumn& column, size_t row_num) const {
- if (!has()) {
- return false;
- }
- return assert_cast<const ColumnString&,
TypeCheckOnRelease::DISABLE>(column).get_data_at(
- row_num) == get_string_ref();
- }
-
- void change_first_time(const IColumn& column, size_t row_num, Arena&
arena) {
- if (UNLIKELY(!has())) {
- change(column, row_num, arena);
- }
- }
-
- void change_first_time(const Self& to, Arena& arena) {
- if (UNLIKELY(!has() && to.has())) {
- change(to, arena);
- }
- }
-};
-
-static_assert(sizeof(SingleValueDataString) ==
SingleValueDataString::AUTOMATIC_STORAGE_SIZE);
-
-struct SingleValueDataComplexType {
-private:
- using Self = SingleValueDataComplexType;
-
- DataTypePtr column_type;
- bool has_value = false;
- MutableColumnPtr column_data; // a column ptr only save a single value
- int be_exec_version = -1;
-
-public:
- static constexpr bool NeedCheckColumnType = false;
-
- SingleValueDataComplexType() = default;
- SingleValueDataComplexType(const DataTypes& argument_types, int
be_version) {
- column_type = argument_types[0];
- column_data = column_type->create_column();
- be_exec_version = be_version;
- }
-
- bool has() const { return has_value; }
-
- constexpr static bool IsFixedLength = false;
-
- void insert_result_into(IColumn& to) const {
- if (has()) {
- to.insert_from(*column_data, 0);
- } else {
- to.insert_default();
- }
- }
-
- void reset() {
- has_value = false;
- column_data->clear();
- }
-
- void write(BufferWritable& buf) const {
- buf.write_binary(has_value);
- if (!has()) {
- return;
- }
- auto size_bytes =
- column_type->get_uncompressed_serialized_bytes(*column_data,
be_exec_version);
- buf.write_binary(size_bytes);
- buf.resize(size_bytes);
- auto* p = column_type->serialize(*column_data, buf.data(),
be_exec_version);
- DCHECK_EQ(p, buf.data() + size_bytes);
- buf.add_offset(size_bytes);
- }
-
- void read(BufferReadable& buf, Arena& arena) {
- buf.read_binary(has_value);
- if (!has()) {
- return;
- }
- int64_t size;
- buf.read_binary(size);
- const auto* p = column_type->deserialize(buf.data(), &column_data,
be_exec_version);
- DCHECK_EQ(p, buf.data() + size);
- buf.add_offset(size);
- }
-
- void change(const IColumn& column, size_t row_num, Arena&) {
- has_value = true;
- column_data->clear();
- column_data->insert_from(column, row_num);
- }
-
- /// Assuming to.has()
- void change(const Self& to, Arena&) {
- has_value = true;
- column_data->clear();
- column_data->insert_from(*to.column_data, 0);
- }
-
- bool change_if_less(const IColumn& column, size_t row_num, Arena& arena) {
- if (!has() || column_data->compare_at(0, row_num, column, 1) == 1) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_less(const Self& to, Arena& arena) {
- if (to.has() && (!has() || column_data->compare_at(0, 0,
*to.column_data, 1) == 1)) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const IColumn& column, size_t row_num, Arena&
arena) {
- if (!has() || column_data->compare_at(0, row_num, column, 1) == -1) {
- change(column, row_num, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool change_if_greater(const Self& to, Arena& arena) {
- if (to.has() && (!has() || column_data->compare_at(0, 0,
*to.column_data, 1) == -1)) {
- change(to, arena);
- return true;
- } else {
- return false;
- }
- }
-
- bool check_if_equal(const IColumn& column, size_t row_num) const {
- if (!has()) {
- return false;
- }
- auto type = column_type->get_primitive_type();
- if (type == TYPE_BITMAP || type == TYPE_HLL || type ==
TYPE_QUANTILE_STATE ||
- type == TYPE_AGG_STATE) {
- return false;
- } else {
- return !column_data->compare_at(0, row_num, column, 1);
- }
- }
-
- void change_first_time(const IColumn& column, size_t row_num, Arena&
arena) {
- if (UNLIKELY(!has())) {
- change(column, row_num, arena);
- }
- }
-
- void change_first_time(const Self& to, Arena& arena) {
- if (UNLIKELY(!has() && to.has())) {
- change(to, arena);
- }
- }
-};
-
template <typename Data>
struct AggregateFunctionMaxData : public Data {
using Self = AggregateFunctionMaxData;
- using Data::IsFixedLength;
+ using Data::UsesFixedLengthStateSerialization;
constexpr static bool IS_ANY = false;
- AggregateFunctionMaxData(const DataTypes& argument_types, int be_version)
- : Data(argument_types, be_version) {
- this->reset();
- }
-
AggregateFunctionMaxData() { reset(); }
void change_if_better(const IColumn& column, size_t row_num, Arena& arena)
{
- if constexpr (Data::IsFixedLength) {
- this->change_if(column, row_num, false);
- } else {
- this->change_if_greater(column, row_num, arena);
- }
+ this->set_if_greater(column, row_num, arena);
}
- void change_if_better(const Self& to, Arena& arena) {
this->change_if_greater(to, arena); }
+ void change_if_better(const Self& to, Arena& arena) {
this->set_if_greater(to, arena); }
void reset() {
- if constexpr (Data::IsFixedLength) {
- this->set_to_min_max(false);
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
+ this->set_value_to_min();
}
Data::reset();
}
@@ -713,28 +87,19 @@ struct AggregateFunctionMaxData : public Data {
template <typename Data>
struct AggregateFunctionMinData : Data {
using Self = AggregateFunctionMinData;
- using Data::IsFixedLength;
+ using Data::UsesFixedLengthStateSerialization;
constexpr static bool IS_ANY = false;
- AggregateFunctionMinData(const DataTypes& argument_types, int be_version)
- : Data(argument_types, be_version) {
- this->reset();
- }
-
AggregateFunctionMinData() { reset(); }
void change_if_better(const IColumn& column, size_t row_num, Arena& arena)
{
- if constexpr (Data::IsFixedLength) {
- this->change_if(column, row_num, true);
- } else {
- this->change_if_less(column, row_num, arena);
- }
+ this->set_if_smaller(column, row_num, arena);
}
- void change_if_better(const Self& to, Arena& arena) {
this->change_if_less(to, arena); }
+ void change_if_better(const Self& to, Arena& arena) {
this->set_if_smaller(to, arena); }
void reset() {
- if constexpr (Data::IsFixedLength) {
- this->set_to_min_max(true);
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
+ this->set_value_to_max();
}
Data::reset();
}
@@ -746,53 +111,47 @@ struct AggregateFunctionMinData : Data {
template <typename Data>
struct AggregateFunctionAnyData : Data {
using Self = AggregateFunctionAnyData;
- using Data::IsFixedLength;
+ using Data::UsesFixedLengthStateSerialization;
static const char* name() { return "any"; }
constexpr static bool IS_ANY = true;
- AggregateFunctionAnyData(const DataTypes& argument_types, int be_version)
- : Data(argument_types, be_version) {};
-
AggregateFunctionAnyData() {};
void change_if_better(const IColumn& column, size_t row_num, Arena& arena)
{
- this->change_first_time(column, row_num, arena);
+ if (UNLIKELY(!this->has())) {
+ this->set(column, row_num, arena);
+ }
}
- void change_if_better(const Self& to, Arena& arena) {
this->change_first_time(to, arena); }
+ void change_if_better(const Self& to, Arena& arena) {
+ if (UNLIKELY(!this->has() && to.has())) {
+ this->set(to, arena);
+ }
+ }
};
template <typename Data>
class AggregateFunctionsSingleValue final
: public IAggregateFunctionDataHelper<Data,
AggregateFunctionsSingleValue<Data>> {
private:
- DataTypePtr& type;
+ const DataTypePtr& _data_type;
using Base = IAggregateFunctionDataHelper<Data,
AggregateFunctionsSingleValue<Data>>;
using IAggregateFunction::argument_types;
public:
AggregateFunctionsSingleValue(const DataTypes& arguments)
: IAggregateFunctionDataHelper<Data,
AggregateFunctionsSingleValue<Data>>(arguments),
- type(this->argument_types[0]) {}
+ _data_type(this->argument_types[0]) {}
- void create(AggregateDataPtr __restrict place) const override {
- if constexpr (std::is_same_v<Data,
AggregateFunctionMaxData<SingleValueDataComplexType>> ||
- std::is_same_v<Data,
AggregateFunctionMinData<SingleValueDataComplexType>> ||
- std::is_same_v<Data,
AggregateFunctionAnyData<SingleValueDataComplexType>>) {
- new (place) Data(argument_types, IAggregateFunction::version);
- } else {
- new (place) Data;
- }
- }
+ void create(AggregateDataPtr __restrict place) const override { new
(place) Data; }
String get_name() const override { return Data::name(); }
- DataTypePtr get_return_type() const override { return type; }
+ DataTypePtr get_return_type() const override { return _data_type; }
- // min/max require sentinel-initialized state (MAX_VALUE for min,
MIN_VALUE for max) via
- // create(), so they cannot use zero-init and must return false. any_value
is safe with
- // zero-init because it checks has_value before comparing
(change_first_time).
- bool is_trivial() const override { return Data::IsFixedLength &&
Data::IS_ANY; }
+ bool is_trivial() const override {
+ return Data::UsesFixedLengthStateSerialization && Data::IS_ANY;
+ }
void add(AggregateDataPtr __restrict place, const IColumn** columns,
ssize_t row_num,
Arena& arena) const override {
@@ -817,12 +176,12 @@ public:
}
void serialize(ConstAggregateDataPtr __restrict place, BufferWritable&
buf) const override {
- this->data(place).write(buf);
+ this->data(place).write(buf, _data_type, IAggregateFunction::version);
}
void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf,
Arena& arena) const override {
- this->data(place).read(buf, arena);
+ this->data(place).read(buf, _data_type, IAggregateFunction::version,
arena);
}
void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn&
to) const override {
@@ -845,7 +204,7 @@ public:
void serialize_to_column(const std::vector<AggregateDataPtr>& places,
size_t offset,
MutableColumnPtr& dst, const size_t num_rows)
const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
auto& dst_column = assert_cast<ColumnFixedLengthObject&>(*dst);
dst_column.resize(num_rows);
auto* dst_data =
reinterpret_cast<Data*>(dst_column.get_data().data());
@@ -859,12 +218,12 @@ public:
void streaming_agg_serialize_to_column(const IColumn** columns,
MutableColumnPtr& dst,
const size_t num_rows, Arena&
arena) const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
auto& dst_column = assert_cast<ColumnFixedLengthObject&>(*dst);
dst_column.resize(num_rows);
auto* dst_data =
reinterpret_cast<Data*>(dst_column.get_data().data());
for (size_t i = 0; i != num_rows; ++i) {
- dst_data[i].change(*columns[0], i, arena);
+ dst_data[i].set(*columns[0], i, arena);
}
} else {
Base::streaming_agg_serialize_to_column(columns, dst, num_rows,
arena);
@@ -874,7 +233,7 @@ public:
void deserialize_and_merge_from_column_range(AggregateDataPtr __restrict
place,
const IColumn& column, size_t
begin, size_t end,
Arena& arena) const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
DCHECK(end <= column.size() && begin <= end) << ", begin:" <<
begin << ", end:" << end
<< ", column.size():"
<< column.size();
auto& col = assert_cast<const ColumnFixedLengthObject&>(column);
@@ -890,7 +249,7 @@ public:
void deserialize_and_merge_vec(const AggregateDataPtr* places, size_t
offset,
AggregateDataPtr rhs, const IColumn*
column, Arena& arena,
const size_t num_rows) const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
const auto& col = assert_cast<const
ColumnFixedLengthObject&>(*column);
const auto* data = col.get_data().data();
this->merge_vec(places, offset, AggregateDataPtr(data), arena,
num_rows);
@@ -904,7 +263,7 @@ public:
void deserialize_and_merge_vec_selected(const AggregateDataPtr* places,
size_t offset,
AggregateDataPtr rhs, const
IColumn* column,
Arena& arena, const size_t
num_rows) const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
const auto& col = assert_cast<const
ColumnFixedLengthObject&>(*column);
const auto* data = col.get_data().data();
this->merge_vec_selected(places, offset, AggregateDataPtr(data),
arena, num_rows);
@@ -917,7 +276,7 @@ public:
void serialize_without_key_to_column(ConstAggregateDataPtr __restrict
place,
IColumn& to) const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
auto& col = assert_cast<ColumnFixedLengthObject&>(to);
size_t old_size = col.size();
col.resize(old_size + 1);
@@ -928,7 +287,7 @@ public:
}
MutableColumnPtr create_serialize_column() const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
return ColumnFixedLengthObject::create(sizeof(Data));
} else {
return ColumnString::create();
@@ -936,14 +295,27 @@ public:
}
DataTypePtr get_serialized_type() const override {
- if constexpr (Data::IsFixedLength) {
+ if constexpr (Data::UsesFixedLengthStateSerialization) {
return std::make_shared<DataTypeFixedLengthObject>();
} else {
return std::make_shared<DataTypeString>();
}
}
- bool supported_incremental_mode() const override { return !(Data::IS_ANY);
}
+ bool supported_incremental_mode() const override {
+ if constexpr (Data::IS_ANY) {
+ return false;
+ }
+ switch (_data_type->get_primitive_type()) {
+ case TYPE_BITMAP:
+ case TYPE_HLL:
+ case TYPE_QUANTILE_STATE:
+ case TYPE_AGG_STATE:
+ return false;
+ default:
+ return true;
+ }
+ }
void execute_function_with_incremental(int64_t partition_start, int64_t
partition_end,
int64_t frame_start, int64_t
frame_end,
@@ -962,7 +334,7 @@ public:
auto incoming_pos = frame_end - 1;
if (!previous_is_nul && outcoming_pos >= partition_start &&
outcoming_pos < partition_end) {
- if (this->data(place).check_if_equal(*columns[0],
outcoming_pos)) {
+ if (this->data(place).is_equal_to(*columns[0], outcoming_pos))
{
this->data(place).reset();
if (has_null) {
const auto& null_map_data =
diff --git a/be/src/exprs/aggregate/aggregate_function_min_max_by.cpp
b/be/src/exprs/aggregate/aggregate_function_min_max_by.cpp
index 70e5508069e..f0136d78701 100644
--- a/be/src/exprs/aggregate/aggregate_function_min_max_by.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_min_max_by.cpp
@@ -22,16 +22,12 @@
#include "exprs/aggregate/aggregate_function_simple_factory.h"
namespace doris {
-std::unique_ptr<MaxMinValueBase> create_max_min_value(const DataTypePtr& type,
int be_version) {
+std::unique_ptr<MaxMinValueBase> create_max_min_value(const DataTypePtr& type)
{
std::unique_ptr<MaxMinValueBase> result;
auto call = [&](const auto& dispatch_type) -> bool {
using DispatchType = std::decay_t<decltype(dispatch_type)>;
constexpr auto PT = DispatchType::PType;
- if constexpr (is_decimal(PT)) {
- result =
std::make_unique<MaxMinValue<SingleValueDataDecimal<PT>>>();
- } else {
- result = std::make_unique<MaxMinValue<SingleValueDataFixed<PT>>>();
- }
+ result = std::make_unique<MaxMinValue<SingleValueDataFixed<PT>>>();
return true;
};
if (type->get_primitive_type() == TYPE_TIMESTAMP_NS) {
@@ -51,8 +47,7 @@ std::unique_ptr<MaxMinValueBase> create_max_min_value(const
DataTypePtr& type, i
case PrimitiveType::TYPE_ARRAY:
case PrimitiveType::TYPE_MAP:
case PrimitiveType::TYPE_STRUCT:
- return
std::make_unique<MaxMinValue<SingleValueDataComplexType>>(DataTypes {type},
-
be_version);
+ return std::make_unique<MaxMinValue<SingleValueDataColumn>>();
default:
throw doris::Exception(ErrorCode::INTERNAL_ERROR,
"Illegal type {} of argument of aggregate
function min/max_by",
diff --git a/be/src/exprs/aggregate/aggregate_function_min_max_by.h
b/be/src/exprs/aggregate/aggregate_function_min_max_by.h
index dfba3fbcd25..a692aca65c0 100644
--- a/be/src/exprs/aggregate/aggregate_function_min_max_by.h
+++ b/be/src/exprs/aggregate/aggregate_function_min_max_by.h
@@ -34,12 +34,14 @@ namespace doris {
struct MaxMinValueBase {
virtual ~MaxMinValueBase() = default;
- virtual void write(BufferWritable& buf) const = 0;
- virtual void read(BufferReadable& buf, Arena& arena) = 0;
+ virtual void write(BufferWritable& buf, const DataTypePtr& data_type,
+ int be_exec_version) const = 0;
+ virtual void read(BufferReadable& buf, const DataTypePtr& data_type, int
be_exec_version,
+ Arena& arena) = 0;
virtual void insert_result_into(IColumn& to) const = 0;
virtual void reset() = 0;
- virtual void change(const IColumn& column, size_t row_num, Arena& arena) =
0;
- virtual void change(const MaxMinValueBase& to, Arena& arena) = 0;
+ virtual void set(const IColumn& column, size_t row_num, Arena& arena) = 0;
+ virtual void set(const MaxMinValueBase& to, Arena& arena) = 0;
};
template <typename VT>
@@ -48,30 +50,33 @@ struct MaxMinValue : public MaxMinValueBase {
MaxMinValue() = default;
- MaxMinValue(const DataTypes& argument_types, int be_version)
- : value(argument_types, be_version) {}
-
~MaxMinValue() override = default;
- void write(BufferWritable& buf) const override { value.write(buf); }
+ void write(BufferWritable& buf, const DataTypePtr& data_type,
+ int be_exec_version) const override {
+ value.write(buf, data_type, be_exec_version);
+ }
- void read(BufferReadable& buf, Arena& arena) override { value.read(buf,
arena); }
+ void read(BufferReadable& buf, const DataTypePtr& data_type, int
be_exec_version,
+ Arena& arena) override {
+ value.read(buf, data_type, be_exec_version, arena);
+ }
void insert_result_into(IColumn& to) const override {
value.insert_result_into(to); }
void reset() override { value.reset(); }
- void change(const IColumn& column, size_t row_num, Arena& arena) override {
- value.change(column, row_num, arena);
+ void set(const IColumn& column, size_t row_num, Arena& arena) override {
+ value.set(column, row_num, arena);
}
- void change(const MaxMinValueBase& to, Arena& arena) override {
+ void set(const MaxMinValueBase& to, Arena& arena) override {
const auto& derived = assert_cast<const MaxMinValue&>(to);
- value.change(derived.value, arena);
+ value.set(derived.value, arena);
}
};
-std::unique_ptr<MaxMinValueBase> create_max_min_value(const DataTypePtr& type,
int be_version);
+std::unique_ptr<MaxMinValueBase> create_max_min_value(const DataTypePtr& type);
/// For bitmap value
struct BitmapValueData {
@@ -98,27 +103,28 @@ public:
}
}
- void write(BufferWritable& buf) const {
+ void write(BufferWritable& buf, const DataTypePtr&, int) const {
buf.write_binary(has());
if (has()) {
DataTypeBitMap::serialize_as_stream(value, buf);
}
}
- void read(BufferReadable& buf, Arena&) {
+ void read(BufferReadable& buf, const DataTypePtr&, int, Arena&) {
buf.read_binary(has_value);
if (has()) {
DataTypeBitMap::deserialize_as_stream(value, buf);
}
}
- void change(const IColumn& column, size_t row_num, Arena&) {
+ void set(const IColumn& column, size_t row_num, Arena&) {
has_value = true;
value = assert_cast<const ColumnBitmap&,
TypeCheckOnRelease::DISABLE>(column)
.get_data()[row_num];
}
- void change(const Self& to, Arena&) {
+ void set(const Self& to, Arena&) {
+ DORIS_CHECK(to.has());
has_value = true;
value = to.value;
}
@@ -139,7 +145,7 @@ public:
* In contrast, the value type VT is intentionally not made a template
parameter.
* On one hand, templating both key and value types would lead to an n × n
* explosion in template instantiations, increasing compile time and code size.
- * On the other hand, value objects typically only invoke the change method;
for
+ * On the other hand, value objects typically only invoke the set method; for
* random data, this method is called approximately log(x) times (where x is
the
* data size), making the overhead acceptable.
*/
@@ -152,16 +158,8 @@ protected:
public:
AggregateFunctionMinMaxByBaseData() {}
- AggregateFunctionMinMaxByBaseData(const DataTypes argument_types, int
be_version)
- requires(std::is_same_v<KT, SingleValueDataComplexType>)
- : key(SingleValueDataComplexType(DataTypes {argument_types[1]},
be_version)) {
- value = create_max_min_value(argument_types[0], be_version);
- }
-
- AggregateFunctionMinMaxByBaseData(const DataTypes argument_types, int
be_version)
- requires(!std::is_same_v<KT, SingleValueDataComplexType>)
- {
- value = create_max_min_value(argument_types[0], be_version);
+ explicit AggregateFunctionMinMaxByBaseData(const DataTypes&
argument_types) {
+ value = create_max_min_value(argument_types[0]);
}
void insert_result_into(IColumn& to) const {
value->insert_result_into(to); }
@@ -170,14 +168,16 @@ public:
value->reset();
key.reset();
}
- void write(BufferWritable& buf) const {
- value->write(buf);
- key.write(buf);
+ void write(BufferWritable& buf, const DataTypePtr& value_type, const
DataTypePtr& key_type,
+ int be_exec_version) const {
+ value->write(buf, value_type, be_exec_version);
+ key.write(buf, key_type, be_exec_version);
}
- void read(BufferReadable& buf, Arena& arena) {
- value->read(buf, arena);
- key.read(buf, arena);
+ void read(BufferReadable& buf, const DataTypePtr& value_type, const
DataTypePtr& key_type,
+ int be_exec_version, Arena& arena) {
+ value->read(buf, value_type, be_exec_version, arena);
+ key.read(buf, key_type, be_exec_version, arena);
}
};
@@ -187,32 +187,32 @@ struct AggregateFunctionMaxByData : public
AggregateFunctionMinMaxByBaseData<KT>
AggregateFunctionMaxByData() {}
- AggregateFunctionMaxByData(const DataTypes argument_types, int be_version)
- : AggregateFunctionMinMaxByBaseData<KT>(argument_types,
be_version) {}
+ explicit AggregateFunctionMaxByData(const DataTypes& argument_types)
+ : AggregateFunctionMinMaxByBaseData<KT>(argument_types) {}
void change_if_better(const IColumn& value_column, const IColumn&
key_column, size_t row_num,
Arena& arena) {
- if (this->key.change_if_greater(key_column, row_num, arena)) {
- this->value->change(value_column, row_num, arena);
+ if (this->key.set_if_greater(key_column, row_num, arena)) {
+ this->value->set(value_column, row_num, arena);
}
}
void change_if_better_batch(const IColumn& value_column, const IColumn&
key_column,
size_t batch_size, Arena& arena) {
- size_t max_pos = -1;
+ size_t best_pos = batch_size;
for (size_t i = 0; i < batch_size; ++i) {
- if (this->key.change_if_greater(key_column, i, arena)) {
- max_pos = i;
+ if (this->key.set_if_greater(key_column, i, arena)) {
+ best_pos = i;
}
}
- if (max_pos != static_cast<size_t>(-1)) {
- this->value->change(value_column, max_pos, arena);
+ if (best_pos < batch_size) {
+ this->value->set(value_column, best_pos, arena);
}
}
void change_if_better(const Self& to, Arena& arena) {
- if (this->key.change_if_greater(to.key, arena)) {
- this->value->change(*to.value, arena);
+ if (this->key.set_if_greater(to.key, arena)) {
+ this->value->set(*to.value, arena);
}
}
@@ -225,32 +225,32 @@ struct AggregateFunctionMinByData : public
AggregateFunctionMinMaxByBaseData<KT>
AggregateFunctionMinByData() {}
- AggregateFunctionMinByData(const DataTypes argument_types, int be_version)
- : AggregateFunctionMinMaxByBaseData<KT>(argument_types,
be_version) {}
+ explicit AggregateFunctionMinByData(const DataTypes& argument_types)
+ : AggregateFunctionMinMaxByBaseData<KT>(argument_types) {}
void change_if_better(const IColumn& value_column, const IColumn&
key_column, size_t row_num,
Arena& arena) {
- if (this->key.change_if_less(key_column, row_num, arena)) {
- this->value->change(value_column, row_num, arena);
+ if (this->key.set_if_smaller(key_column, row_num, arena)) {
+ this->value->set(value_column, row_num, arena);
}
}
void change_if_better_batch(const IColumn& value_column, const IColumn&
key_column,
size_t batch_size, Arena& arena) {
- size_t min_pos = -1;
+ size_t best_pos = batch_size;
for (size_t i = 0; i < batch_size; ++i) {
- if (this->key.change_if_less(key_column, i, arena)) {
- min_pos = i;
+ if (this->key.set_if_smaller(key_column, i, arena)) {
+ best_pos = i;
}
}
- if (min_pos != static_cast<size_t>(-1)) {
- this->value->change(value_column, min_pos, arena);
+ if (best_pos < batch_size) {
+ this->value->set(value_column, best_pos, arena);
}
}
void change_if_better(const Self& to, Arena& arena) {
- if (this->key.change_if_less(to.key, arena)) {
- this->value->change(*to.value, arena);
+ if (this->key.set_if_smaller(to.key, arena)) {
+ this->value->set(*to.value, arena);
}
}
@@ -263,23 +263,23 @@ class AggregateFunctionsMinMaxBy final
MultiExpression,
NullableAggregateFunction {
private:
- DataTypePtr& value_type;
- DataTypePtr& key_type;
+ const DataTypePtr& _value_type;
+ const DataTypePtr& _key_type;
public:
AggregateFunctionsMinMaxBy(const DataTypes& arguments)
: IAggregateFunctionDataHelper<Data,
AggregateFunctionsMinMaxBy<Data>>(
{arguments[0], arguments[1]}),
- value_type(this->argument_types[0]),
- key_type(this->argument_types[1]) {}
+ _value_type(this->argument_types[0]),
+ _key_type(this->argument_types[1]) {}
void create(AggregateDataPtr __restrict place) const override {
- new (place) Data(IAggregateFunction::argument_types,
IAggregateFunction::version);
+ new (place) Data(IAggregateFunction::argument_types);
}
String get_name() const override { return Data::name(); }
- DataTypePtr get_return_type() const override { return value_type; }
+ DataTypePtr get_return_type() const override { return _value_type; }
void add(AggregateDataPtr __restrict place, const IColumn** columns,
ssize_t row_num,
Arena& arena) const override {
@@ -299,12 +299,12 @@ public:
}
void serialize(ConstAggregateDataPtr __restrict place, BufferWritable&
buf) const override {
- this->data(place).write(buf);
+ this->data(place).write(buf, _value_type, _key_type,
IAggregateFunction::version);
}
void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf,
Arena& arena) const override {
- this->data(place).read(buf, arena);
+ this->data(place).read(buf, _value_type, _key_type,
IAggregateFunction::version, arena);
}
void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn&
to) const override {
@@ -326,15 +326,9 @@ AggregateFunctionPtr
create_aggregate_function_min_max_by(const String& name,
auto call = [&](const auto& dispatch_type) -> bool {
using DispatchType = std::decay_t<decltype(dispatch_type)>;
constexpr auto PT = DispatchType::PType;
- if constexpr (is_decimal(PT)) {
- result = creator_without_type::create_multi_arguments<
-
AggregateFunctionsMinMaxBy<Data<SingleValueDataDecimal<PT>>>>(
- argument_types, result_is_nullable, attr);
- } else {
- result = creator_without_type::create_multi_arguments<
-
AggregateFunctionsMinMaxBy<Data<SingleValueDataFixed<PT>>>>(
- argument_types, result_is_nullable, attr);
- }
+ result = creator_without_type::create_multi_arguments<
+ AggregateFunctionsMinMaxBy<Data<SingleValueDataFixed<PT>>>>(
+ argument_types, result_is_nullable, attr);
return true;
};
// Keep nano dispatch local because the shared scalar dispatcher is also
used by templates
@@ -356,8 +350,8 @@ AggregateFunctionPtr
create_aggregate_function_min_max_by(const String& name,
result_is_nullable, attr);
case PrimitiveType::TYPE_ARRAY:
return creator_without_type::create_multi_arguments<
- AggregateFunctionsMinMaxBy<Data<SingleValueDataComplexType>>>(
- argument_types, result_is_nullable, attr);
+
AggregateFunctionsMinMaxBy<Data<SingleValueDataColumn>>>(argument_types,
+
result_is_nullable, attr);
default:
return nullptr;
}
diff --git a/be/src/exprs/aggregate/aggregate_function_min_max_impl.h
b/be/src/exprs/aggregate/aggregate_function_min_max_impl.h
index 0c49c79aa8a..61753683ac4 100644
--- a/be/src/exprs/aggregate/aggregate_function_min_max_impl.h
+++ b/be/src/exprs/aggregate/aggregate_function_min_max_impl.h
@@ -113,23 +113,23 @@ AggregateFunctionPtr
create_aggregate_function_single_value(const String& name,
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_DECIMAL32:
return creator_without_type::create_unary_arguments<
-
AggregateFunctionsSingleValue<Data<SingleValueDataDecimal<TYPE_DECIMAL32>>>>(
+
AggregateFunctionsSingleValue<Data<SingleValueDataFixed<TYPE_DECIMAL32>>>>(
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_DECIMAL64:
return creator_without_type::create_unary_arguments<
-
AggregateFunctionsSingleValue<Data<SingleValueDataDecimal<TYPE_DECIMAL64>>>>(
+
AggregateFunctionsSingleValue<Data<SingleValueDataFixed<TYPE_DECIMAL64>>>>(
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_DECIMALV2:
return creator_without_type::create_unary_arguments<
-
AggregateFunctionsSingleValue<Data<SingleValueDataDecimal<TYPE_DECIMALV2>>>>(
+
AggregateFunctionsSingleValue<Data<SingleValueDataFixed<TYPE_DECIMALV2>>>>(
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_DECIMAL128I:
return creator_without_type::create_unary_arguments<
-
AggregateFunctionsSingleValue<Data<SingleValueDataDecimal<TYPE_DECIMAL128I>>>>(
+
AggregateFunctionsSingleValue<Data<SingleValueDataFixed<TYPE_DECIMAL128I>>>>(
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_DECIMAL256:
return creator_without_type::create_unary_arguments<
-
AggregateFunctionsSingleValue<Data<SingleValueDataDecimal<TYPE_DECIMAL256>>>>(
+
AggregateFunctionsSingleValue<Data<SingleValueDataFixed<TYPE_DECIMAL256>>>>(
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_ARRAY:
case PrimitiveType::TYPE_MAP:
@@ -139,7 +139,7 @@ AggregateFunctionPtr
create_aggregate_function_single_value(const String& name,
case PrimitiveType::TYPE_HLL:
case PrimitiveType::TYPE_QUANTILE_STATE:
return creator_without_type::create_unary_arguments<
-
AggregateFunctionsSingleValue<Data<SingleValueDataComplexType>>>(
+ AggregateFunctionsSingleValue<Data<SingleValueDataColumn>>>(
argument_types, result_is_nullable, attr);
case PrimitiveType::TYPE_VARBINARY:
// Owning binary values for IO must not implicitly enable single-value
aggregates.
diff --git a/be/src/exprs/aggregate/aggregate_function_reader_first_last.h
b/be/src/exprs/aggregate/aggregate_function_reader_first_last.h
index ff693eec98e..b2ba4225ff0 100644
--- a/be/src/exprs/aggregate/aggregate_function_reader_first_last.h
+++ b/be/src/exprs/aggregate/aggregate_function_reader_first_last.h
@@ -17,104 +17,14 @@
#pragma once
-#include <type_traits>
-
#include "core/column/column_nullable.h"
+#include "exprs/aggregate/single_value_data.h"
namespace doris {
-template <bool arg_is_nullable>
-struct Value {
-public:
- bool is_null() const {
- if (_ptr == nullptr) {
- return true;
- }
- if constexpr (arg_is_nullable) {
- return assert_cast<const ColumnNullable*,
TypeCheckOnRelease::DISABLE>(_ptr)
- ->is_null_at(_offset);
- }
- return false;
- }
-
- template <typename ColVecType>
- void insert_into(IColumn& to) const {
- if constexpr (arg_is_nullable) {
- const auto* col = assert_cast<const ColumnNullable*,
TypeCheckOnRelease::DISABLE>(_ptr);
- assert_cast<ColVecType&,
TypeCheckOnRelease::DISABLE>(to).insert_from(
- col->get_nested_column(), _offset);
- } else {
- assert_cast<ColVecType&,
TypeCheckOnRelease::DISABLE>(to).insert_from(*_ptr, _offset);
- }
- }
-
- // Non-template version: virtual dispatch on IColumn::insert_from.
- // Used by window path where devirtualization is not beneficial.
- void insert_into(IColumn& to) const {
- if constexpr (arg_is_nullable) {
- const auto* col = assert_cast<const ColumnNullable*,
TypeCheckOnRelease::DISABLE>(_ptr);
- to.insert_from(col->get_nested_column(), _offset);
- } else {
- to.insert_from(*_ptr, _offset);
- }
- }
-
- void set_value(const IColumn* column, size_t row) {
- _ptr = column;
- _offset = row;
- }
-
- void reset() {
- _ptr = nullptr;
- _offset = 0;
- }
-
-protected:
- const IColumn* _ptr = nullptr;
- size_t _offset = 0;
-};
-
-template <bool arg_is_nullable>
-struct CopiedValue : public Value<arg_is_nullable> {
-public:
- template <typename ColVecType>
- void insert_into(IColumn& to) const {
- assert_cast<ColVecType&,
TypeCheckOnRelease::DISABLE>(to).insert(_copied_value);
- }
-
- bool is_null() const { return this->_ptr == nullptr; }
-
- template <typename ColVecType>
- void set_value(const IColumn* column, size_t row) {
- // here _ptr, maybe null at row, so call reset to set nullptr
- // But we will use is_null() check first, others have set _ptr column
to a meaningless address
- // because the address have meaningless, only need it to check is
nullptr
- this->_ptr = (IColumn*)0x00000001;
- if constexpr (arg_is_nullable) {
- const auto* col =
- assert_cast<const ColumnNullable*,
TypeCheckOnRelease::DISABLE>(column);
- if (col->is_null_at(row)) {
- this->reset();
- return;
- } else {
- auto& nested_col = assert_cast<const ColVecType&,
TypeCheckOnRelease::DISABLE>(
- col->get_nested_column());
- nested_col.get(row, _copied_value);
- }
- } else {
- column->get(row, _copied_value);
- }
- }
-
-private:
- Field _copied_value;
-};
-
-template <typename ColVecType, bool result_is_nullable, bool arg_is_nullable,
bool is_copy>
+template <bool result_is_nullable, bool arg_is_nullable>
struct ReaderFirstAndLastData {
public:
- using StoreType =
- std::conditional_t<is_copy, CopiedValue<arg_is_nullable>,
Value<arg_is_nullable>>;
static constexpr bool nullable = arg_is_nullable;
static constexpr bool result_nullable = result_is_nullable;
@@ -125,44 +35,40 @@ public:
void insert_result_into(IColumn& to) const {
if constexpr (result_is_nullable) {
- if (_data_value.is_null()) { //_ptr == nullptr || null data at row
+ if (!_data_value.has()) {
auto& col = assert_cast<ColumnNullable&,
TypeCheckOnRelease::DISABLE>(to);
col.insert_default();
} else {
auto& col = assert_cast<ColumnNullable&,
TypeCheckOnRelease::DISABLE>(to);
col.get_null_map_data().push_back(0);
- if constexpr (!std::is_same_v<ColVecType, void>) {
- _data_value.template
insert_into<ColVecType>(col.get_nested_column());
- } else {
- _data_value.insert_into(col.get_nested_column());
- }
+ _data_value.insert_result_into(col.get_nested_column());
}
} else {
- if constexpr (!std::is_same_v<ColVecType, void>) {
- _data_value.template insert_into<ColVecType>(to);
- } else {
- _data_value.insert_into(to);
- }
+ _data_value.insert_result_into(to);
}
}
- // here not check the columns[0] is null at the row,
- // but it is need to check in other
void set_value(const IColumn** columns, size_t pos) {
- if constexpr (is_copy) {
- _data_value.template set_value<ColVecType>(columns[0], pos);
+ if constexpr (arg_is_nullable) {
+ const auto& nullable_column =
+ assert_cast<const ColumnNullable&,
TypeCheckOnRelease::DISABLE>(*columns[0]);
+ if (nullable_column.is_null_at(pos)) {
+ _data_value.reset();
+ } else {
+ _data_value.set(nullable_column.get_nested_column(), pos);
+ }
} else {
- _data_value.set_value(columns[0], pos);
+ _data_value.set(*columns[0], pos);
}
_has_value = true;
}
- bool has_set_value() { return _has_value; }
+ bool has_set_value() const { return _has_value; }
- bool is_null() { return _data_value.is_null(); }
+ bool is_null() const { return !_data_value.has(); }
protected:
- StoreType _data_value;
+ SingleValueDataColumn _data_value;
bool _has_value = false;
};
diff --git a/be/src/exprs/aggregate/aggregate_function_window.h
b/be/src/exprs/aggregate/aggregate_function_window.h
index 56eb337f97c..1a491ef96ca 100644
--- a/be/src/exprs/aggregate/aggregate_function_window.h
+++ b/be/src/exprs/aggregate/aggregate_function_window.h
@@ -397,8 +397,7 @@ public:
};
template <bool result_is_nullable, bool arg_is_nullable>
-struct FirstLastData
- : public ReaderFirstAndLastData<void, result_is_nullable,
arg_is_nullable, false> {
+struct FirstLastData : public ReaderFirstAndLastData<result_is_nullable,
arg_is_nullable> {
public:
void set_is_null() { this->_data_value.reset(); }
};
@@ -417,19 +416,11 @@ public:
int64_t _frame_total_rows = 0;
};
-template <bool arg_is_nullable>
-struct BaseValue : public Value<arg_is_nullable> {
-public:
- bool is_null() const { return this->_ptr == nullptr; }
- // because _ptr pointer to first_argument or third argument, so it's
difficult to cast ptr
- // so here will call virtual function
- StringRef get_value() const { return
this->_ptr->get_data_at(this->_offset); }
-};
-
template <bool result_is_nullable, bool arg_is_nullable>
struct LeadLagData {
public:
static constexpr bool result_nullable = result_is_nullable;
+
void reset() {
_data_value.reset();
_is_inited = false;
@@ -438,17 +429,16 @@ public:
void insert_result_into(IColumn& to) const {
if constexpr (result_is_nullable) {
- if (_data_value.is_null()) {
+ if (!_data_value.has()) {
auto& col = assert_cast<ColumnNullable&,
TypeCheckOnRelease::DISABLE>(to);
col.insert_default();
} else {
auto& col = assert_cast<ColumnNullable&,
TypeCheckOnRelease::DISABLE>(to);
- StringRef value = _data_value.get_value();
- col.insert_data(value.data, value.size);
+ col.get_null_map_data().push_back(0);
+ _data_value.insert_result_into(col.get_nested_column());
}
} else {
- StringRef value = _data_value.get_value();
- to.insert_data(value.data, value.size);
+ _data_value.insert_result_into(to);
}
}
@@ -456,13 +446,15 @@ public:
if constexpr (arg_is_nullable) {
if (assert_cast<const ColumnNullable*,
TypeCheckOnRelease::DISABLE>(columns[0])
->is_null_at(pos)) {
- // ptr == nullptr means nullable
_data_value.reset();
return;
}
+ const auto& nullable =
+ assert_cast<const ColumnNullable&,
TypeCheckOnRelease::DISABLE>(*columns[0]);
+ _data_value.set(nullable.get_nested_column(), pos);
+ } else {
+ _data_value.set(*columns[0], pos);
}
- // here ptr is pointer to nullable column or not null column from first
- _data_value.set_value(columns[0], pos);
}
void set_value_from_default(const IColumn* column, size_t pos) {
@@ -473,10 +465,10 @@ public:
if (nullable_column->is_null_at(pos)) {
this->_data_value.reset();
} else {
-
this->_data_value.set_value(nullable_column->get_nested_column_ptr().get(),
pos);
+ this->_data_value.set(nullable_column->get_nested_column(),
pos);
}
} else {
- this->_data_value.set_value(column, pos);
+ this->_data_value.set(*column, pos);
}
}
@@ -492,7 +484,7 @@ public:
int64_t get_offset_value() const { return _offset_value; }
private:
- BaseValue<arg_is_nullable> _data_value;
+ SingleValueDataColumn _data_value;
bool _is_inited = false;
int64_t _offset_value = 0;
};
diff --git a/be/src/exprs/aggregate/single_value_data.h
b/be/src/exprs/aggregate/single_value_data.h
new file mode 100644
index 00000000000..ff947577d5d
--- /dev/null
+++ b/be/src/exprs/aggregate/single_value_data.h
@@ -0,0 +1,417 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <cstring>
+
+#include "common/cast_set.h"
+#include "common/compare.h"
+#include "common/logging.h"
+#include "core/assert_cast.h"
+#include "core/column/column.h"
+#include "core/column/column_decimal.h"
+#include "core/column/column_string.h"
+#include "core/custom_allocator.h"
+#include "core/data_type/data_type.h"
+#include "core/data_type/primitive_type.h"
+#include "core/string_buffer.hpp"
+#include "core/string_ref.h"
+#include "core/types.h"
+
+namespace doris {
+
+class Arena;
+
+/// Stores one fixed-size scalar value directly in the aggregate state.
+template <PrimitiveType T>
+struct SingleValueDataFixed {
+private:
+ using Self = SingleValueDataFixed;
+ using ValueType = typename PrimitiveTypeTraits<T>::CppType;
+
+ static const ValueType& _value_at(const IColumn& column, size_t row_num) {
+ return assert_cast<const typename PrimitiveTypeTraits<T>::ColumnType&,
+ TypeCheckOnRelease::DISABLE>(column)
+ .get_data()[row_num];
+ }
+
+ // AggregateFunctionIf and state merging must distinguish an empty state
from a default value.
+ bool has_value = false;
+ ValueType value {};
+
+public:
+ using ColVecType = typename PrimitiveTypeTraits<T>::ColumnType;
+ static constexpr bool NeedCheckColumnType = true;
+
+ SingleValueDataFixed() = default;
+ bool has() const { return has_value; }
+
+ static constexpr bool UsesFixedLengthStateSerialization = true;
+
+ void set_value_to_min() { value = Compare::min_value<ValueType>(); }
+
+ void set_value_to_max() { value = Compare::max_value<ValueType>(); }
+
+ void insert_result_into(IColumn& to) const {
+ if (has()) {
+ assert_cast<typename PrimitiveTypeTraits<T>::ColumnType&,
TypeCheckOnRelease::DISABLE>(
+ to)
+ .insert_value(value);
+ } else {
+ assert_cast<typename PrimitiveTypeTraits<T>::ColumnType&,
TypeCheckOnRelease::DISABLE>(
+ to)
+ .insert_default();
+ }
+ }
+
+ void reset() { has_value = false; }
+
+ void write(BufferWritable& buf, const DataTypePtr&, int) const {
+ buf.write_binary(has());
+ if (has()) {
+ buf.write_binary(value);
+ }
+ }
+
+ void read(BufferReadable& buf, const DataTypePtr&, int, Arena&) {
+ buf.read_binary(has_value);
+ if (has()) {
+ buf.read_binary(value);
+ }
+ }
+
+ void set(const IColumn& column, size_t row_num, Arena&) {
+ has_value = true;
+ value = _value_at(column, row_num);
+ }
+
+ void set(const Self& to, Arena&) {
+ DORIS_CHECK(to.has());
+ has_value = true;
+ value = to.value;
+ }
+
+ bool set_if_smaller(const IColumn& column, size_t row_num, Arena& arena) {
+ if (!has() || Compare::less(_value_at(column, row_num), value)) {
+ set(column, row_num, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_smaller(const Self& to, Arena& arena) {
+ if (to.has() && (!has() || Compare::less(to.value, value))) {
+ set(to, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_greater(const IColumn& column, size_t row_num, Arena& arena) {
+ if (!has() || Compare::greater(_value_at(column, row_num), value)) {
+ set(column, row_num, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool is_equal_to(const IColumn& column, size_t row_num) const {
+ if (!has()) {
+ return false;
+ }
+ return Compare::equal(_value_at(column, row_num), value);
+ }
+
+ bool set_if_greater(const Self& to, Arena& arena) {
+ if (to.has() && (!has() || Compare::greater(to.value, value))) {
+ set(to, arena);
+ return true;
+ }
+ return false;
+ }
+};
+
+/// Stores short strings inline and allocates a separate buffer for long
strings.
+struct SingleValueDataString {
+private:
+ using Self = SingleValueDataString;
+ // Keep the signed 32-bit size because -1 represents an empty state in the
serialized format.
+ Int32 size = -1; /// -1 indicates that there is no value.
+ Int32 capacity = 0; /// power of two or zero
+ DorisUniqueBufferPtr<char> large_data;
+
+public:
+ static constexpr Int32 AUTOMATIC_STORAGE_SIZE = 64;
+ static constexpr Int32 MAX_SMALL_STRING_SIZE =
+ AUTOMATIC_STORAGE_SIZE - sizeof(size) - sizeof(capacity) -
sizeof(large_data);
+
+private:
+ char small_data[MAX_SMALL_STRING_SIZE];
+
+public:
+ using ColVecType = ColumnString;
+ static constexpr bool NeedCheckColumnType = true;
+
+ ~SingleValueDataString() = default;
+
+ static constexpr bool UsesFixedLengthStateSerialization = false;
+
+ bool has() const { return size >= 0; }
+
+private:
+ static StringRef _value_at(const IColumn& column, size_t row_num) {
+ return assert_cast<const ColumnString&,
TypeCheckOnRelease::DISABLE>(column).get_data_at(
+ row_num);
+ }
+
+ const char* _data() const {
+ return size <= MAX_SMALL_STRING_SIZE ? small_data : large_data.get();
+ }
+
+ StringRef _value() const { return StringRef(_data(), size); }
+
+ void _set(StringRef source) {
+ Int32 value_size = cast_set<Int32>(source.size);
+ if (value_size <= MAX_SMALL_STRING_SIZE) {
+ /// Don't free large_data here.
+ size = value_size;
+
+ if (size > 0) {
+ memcpy(small_data, source.data, size);
+ }
+ } else {
+ if (capacity < value_size) {
+ /// Don't free large_data here.
+ capacity = (Int32)round_up_to_power_of_two_or_zero(value_size);
+ large_data = DorisUniqueBufferPtr<char>(capacity);
+ }
+
+ size = value_size;
+ memcpy(large_data.get(), source.data, size);
+ }
+ }
+
+public:
+ void insert_result_into(IColumn& to) const {
+ if (has()) {
+ assert_cast<ColumnString&,
TypeCheckOnRelease::DISABLE>(to).insert_data(_data(), size);
+ } else {
+ assert_cast<ColumnString&,
TypeCheckOnRelease::DISABLE>(to).insert_default();
+ }
+ }
+
+ void reset() {
+ size = -1;
+ capacity = 0;
+ large_data.reset();
+ }
+
+ void write(BufferWritable& buf, const DataTypePtr&, int) const {
+ buf.write_binary(size);
+ if (has()) {
+ buf.write(_data(), size);
+ }
+ }
+
+ void read(BufferReadable& buf, const DataTypePtr&, int, Arena&) {
+ Int32 rhs_size;
+ buf.read_binary(rhs_size);
+
+ if (rhs_size >= 0) {
+ if (rhs_size <= MAX_SMALL_STRING_SIZE) {
+ /// Don't free large_data here.
+
+ size = rhs_size;
+
+ if (size > 0) {
+ buf.read(small_data, size);
+ }
+ } else {
+ if (capacity < rhs_size) {
+ capacity =
(Int32)round_up_to_power_of_two_or_zero(rhs_size);
+ large_data = DorisUniqueBufferPtr<char>(capacity);
+ }
+
+ size = rhs_size;
+ buf.read(large_data.get(), size);
+ }
+ } else {
+ /// Don't free large_data here.
+ size = rhs_size;
+ }
+ }
+
+ void set(const IColumn& column, size_t row_num, Arena&) {
_set(_value_at(column, row_num)); }
+
+ void set(const Self& to, Arena&) {
+ DORIS_CHECK(to.has());
+ _set(to._value());
+ }
+
+ bool set_if_smaller(const IColumn& column, size_t row_num, Arena& arena) {
+ if (!has() || _value_at(column, row_num) < _value()) {
+ set(column, row_num, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_greater(const IColumn& column, size_t row_num, Arena& arena) {
+ if (!has() || _value_at(column, row_num) > _value()) {
+ set(column, row_num, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_smaller(const Self& to, Arena& arena) {
+ if (to.has() && (!has() || to._value() < _value())) {
+ set(to, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_greater(const Self& to, Arena& arena) {
+ if (to.has() && (!has() || to._value() > _value())) {
+ set(to, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool is_equal_to(const IColumn& column, size_t row_num) const {
+ if (!has()) {
+ return false;
+ }
+ return _value_at(column, row_num) == _value();
+ }
+};
+
+static_assert(sizeof(SingleValueDataString) ==
SingleValueDataString::AUTOMATIC_STORAGE_SIZE);
+
+/// Owns a materialized one-row column for values without a dedicated
representation above.
+class SingleValueDataColumn {
+private:
+ using Self = SingleValueDataColumn;
+
+ ColumnPtr _value;
+
+public:
+ static constexpr bool NeedCheckColumnType = false;
+ static constexpr bool UsesFixedLengthStateSerialization = false;
+
+ bool has() const { return _value.get() != nullptr; }
+
+ size_t allocated_bytes() const { return has() ? _value->allocated_bytes()
: 0; }
+
+ void set(const IColumn& column, size_t row_num) {
+ auto value = column.clone_empty();
+ DCHECK(value->empty());
+ value->reserve(1);
+ value->insert_from(column, row_num);
+ DCHECK_EQ(value->size(), 1);
+ _value = std::move(value);
+ }
+
+ void insert_result_into(IColumn& to) const {
+ if (has()) {
+ to.insert_from(*_value, 0);
+ } else {
+ to.insert_default();
+ }
+ }
+
+ void reset() { _value.reset(); }
+
+ void write(BufferWritable& buf, const DataTypePtr& data_type, int
be_exec_version) const {
+ buf.write_binary(has());
+ if (!has()) {
+ return;
+ }
+ auto size_bytes =
data_type->get_uncompressed_serialized_bytes(*_value, be_exec_version);
+ buf.write_binary(size_bytes);
+ buf.resize(size_bytes);
+ auto* p = data_type->serialize(*_value, buf.data(), be_exec_version);
+ DCHECK_EQ(p, buf.data() + size_bytes);
+ buf.add_offset(size_bytes);
+ }
+
+ void read(BufferReadable& buf, const DataTypePtr& data_type, int
be_exec_version, Arena&) {
+ bool has_value = false;
+ buf.read_binary(has_value);
+ if (!has_value) {
+ reset();
+ return;
+ }
+ int64_t size = 0;
+ buf.read_binary(size);
+ auto value = data_type->create_column();
+ value->reserve(1);
+ const auto* p = data_type->deserialize(buf.data(), &value,
be_exec_version);
+ DCHECK_EQ(p, buf.data() + size);
+ buf.add_offset(size);
+ _value = std::move(value);
+ }
+
+ void set(const IColumn& column, size_t row_num, Arena&) { set(column,
row_num); }
+
+ void set(const Self& to, Arena&) {
+ DORIS_CHECK(to.has());
+ // Stored one-row columns are immutable, so merged states can share
ownership.
+ _value = to._value;
+ }
+
+ bool set_if_smaller(const IColumn& column, size_t row_num, Arena& arena) {
+ if (!has() || column.compare_at(row_num, 0, *_value, 1) < 0) {
+ set(column, row_num, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_smaller(const Self& to, Arena& arena) {
+ if (to.has() && (!has() || to._value->compare_at(0, 0, *_value, 1) <
0)) {
+ set(to, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_greater(const IColumn& column, size_t row_num, Arena& arena) {
+ if (!has() || column.compare_at(row_num, 0, *_value, 1) > 0) {
+ set(column, row_num, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool set_if_greater(const Self& to, Arena& arena) {
+ if (to.has() && (!has() || to._value->compare_at(0, 0, *_value, 1) >
0)) {
+ set(to, arena);
+ return true;
+ }
+ return false;
+ }
+
+ bool is_equal_to(const IColumn& column, size_t row_num) const {
+ return has() && column.compare_at(row_num, 0, *_value, 1) == 0;
+ }
+};
+
+} // namespace doris
diff --git a/be/test/exprs/aggregate/agg_replace_test.cpp
b/be/test/exprs/aggregate/agg_replace_test.cpp
index bb00051cbee..af57cb84c86 100644
--- a/be/test/exprs/aggregate/agg_replace_test.cpp
+++ b/be/test/exprs/aggregate/agg_replace_test.cpp
@@ -51,7 +51,6 @@
#include "core/value/hll.h"
#include "exprs/aggregate/aggregate_function.h"
#include "exprs/aggregate/aggregate_function_reader.h"
-#include "exprs/aggregate/aggregate_function_reader_first_last.h"
#include "exprs/aggregate/aggregate_function_simple_factory.h"
#include "gtest/gtest_pred_impl.h"
@@ -307,129 +306,10 @@ public:
agg_function->destroy(place);
}
- template <typename DataType, typename ColumnType, bool nullable>
- void test_basic_data(int8_t input_nums) {
- DataTypePtr data_type = get_data_type<DataType, nullable, false>();
-
- auto data_column = data_type->create_column();
- add_elements<DataType, nullable>(data_column, input_nums);
-
- EXPECT_EQ(input_nums, data_column->size());
- //test Value
- {
- Value<nullable> value;
- EXPECT_TRUE(value.is_null());
- for (int64_t i = 0; i < input_nums; ++i) {
- value.set_value(data_column.get(), i);
- EXPECT_FALSE(value.is_null());
- auto to_column = data_type->create_column();
- if constexpr (nullable) {
- auto& nullable_col =
assert_cast<ColumnNullable&>(*to_column);
- value.insert_into(nullable_col.get_nested_column());
- } else {
- value.insert_into(*to_column);
- }
-
- EXPECT_EQ(1, to_column->size());
- check_column_basic<DataType, nullable>(to_column.get(), i);
- }
- }
- //test CopiedValue
- {
- CopiedValue<nullable> value;
- EXPECT_TRUE(value.is_null());
- for (int64_t i = 0; i < input_nums; ++i) {
- value.template set_value<ColumnType>(data_column.get(), i);
- EXPECT_FALSE(value.is_null());
- auto to_column = data_type->create_column();
- if constexpr (nullable) {
- auto& nullable_col =
assert_cast<ColumnNullable&>(*to_column);
- value.template
insert_into<ColumnType>(nullable_col.get_nested_column());
- } else {
- value.template insert_into<ColumnType>(*to_column);
- }
- EXPECT_EQ(1, to_column->size());
- check_column_basic<DataType, nullable>(to_column.get(), i);
- }
- }
- }
-
- template <typename DataType, typename ColumnType, bool nullable>
- void test_array_data(int8_t input_nums) {
- DataTypePtr data_type = get_data_type<DataType, nullable, true>();
-
- auto data_column = data_type->create_column();
- array_add_elements<DataType, nullable>(data_column, input_nums);
-
- EXPECT_EQ(input_nums + 1, data_column->size());
- //test Value
- {
- Value<nullable> value;
- EXPECT_TRUE(value.is_null());
- for (int64_t i = 0; i <= input_nums; ++i) {
- value.set_value(data_column.get(), i);
- EXPECT_FALSE(value.is_null());
- auto to_column = data_type->create_column();
- if constexpr (nullable) {
- auto& nullable_col =
assert_cast<ColumnNullable&>(*to_column);
- value.insert_into(nullable_col.get_nested_column());
- } else {
- value.insert_into(*to_column);
- }
- EXPECT_EQ(1, to_column->size());
- check_column_array<DataType, nullable>(to_column.get(), i);
- }
- }
- //test CopiedValue
- {
- CopiedValue<nullable> value;
- EXPECT_TRUE(value.is_null());
- for (int64_t i = 0; i <= input_nums; ++i) {
- value.template set_value<ColumnArray>(data_column.get(), i);
- EXPECT_FALSE(value.is_null());
- auto to_column = data_type->create_column();
- if constexpr (nullable) {
- auto& nullable_col =
assert_cast<ColumnNullable&>(*to_column);
- value.template
insert_into<ColumnArray>(nullable_col.get_nested_column());
- } else {
- value.template insert_into<ColumnArray>(*to_column);
- }
- EXPECT_EQ(1, to_column->size());
- check_column_array<DataType, nullable>(to_column.get(), i);
- }
- }
- }
-
private:
Arena _agg_arena_pool;
};
-TEST_F(VAggReplaceTest, test_basic_data) {
- test_basic_data<DataTypeInt8, ColumnInt8, false>(11);
- test_basic_data<DataTypeInt16, ColumnInt16, false>(11);
- test_basic_data<DataTypeInt32, ColumnInt32, false>(11);
- test_basic_data<DataTypeInt64, ColumnInt64, false>(11);
- test_basic_data<DataTypeInt128, ColumnInt128, false>(11);
- test_basic_data<DataTypeDecimalV2, ColumnDecimal128V2, false>(11);
- test_basic_data<DataTypeString, ColumnString, false>(11);
- test_basic_data<DataTypeInt128, ColumnInt128, false>(11);
- test_basic_data<DataTypeDate, ColumnDate, false>(11);
- test_basic_data<DataTypeDateTime, ColumnDateTime, false>(11);
-}
-
-TEST_F(VAggReplaceTest, test_array_data) {
- test_array_data<DataTypeInt8, ColumnArray, false>(11);
- test_array_data<DataTypeInt16, ColumnArray, false>(11);
- test_array_data<DataTypeInt32, ColumnArray, false>(11);
- test_array_data<DataTypeInt64, ColumnArray, false>(11);
- test_array_data<DataTypeInt128, ColumnArray, false>(11);
- test_array_data<DataTypeDecimalV2, ColumnArray, false>(11);
- test_array_data<DataTypeString, ColumnArray, false>(11);
- test_array_data<DataTypeInt128, ColumnArray, false>(11);
- test_array_data<DataTypeDate, ColumnArray, false>(11);
- test_array_data<DataTypeDateTime, ColumnArray, false>(11);
-}
-
TEST_F(VAggReplaceTest, test_basic_replace_reader) {
test_agg_replace<DataTypeInt8, false>("replace_reader", 10, 0);
test_agg_replace<DataTypeInt16, false>("replace_reader", 10, 0);
diff --git a/be/test/exprs/aggregate/agg_window_nth_value_test.cpp
b/be/test/exprs/aggregate/agg_window_nth_value_test.cpp
index 61ac798ba56..dce7a0729e7 100644
--- a/be/test/exprs/aggregate/agg_window_nth_value_test.cpp
+++ b/be/test/exprs/aggregate/agg_window_nth_value_test.cpp
@@ -78,4 +78,51 @@ TEST(AggregateWindowNthValueTest,
UpperBoundedLowerUnboundedFrame) {
function->destroy(place);
}
+TEST(AggregateWindowValueTest, OwnsValueAfterSourceColumnIsCleared) {
+ AggregateFunctionSimpleFactory factory;
+ register_aggregate_function_window_lead_lag_first_last(factory);
+
+ for (const std::string function_name :
+ {"first_value", "last_value", "nth_value", "lead", "lag"}) {
+ SCOPED_TRACE(function_name);
+ DataTypes argument_types = {std::make_shared<DataTypeString>()};
+ if (function_name == "nth_value") {
+ argument_types.push_back(std::make_shared<DataTypeInt64>());
+ } else if (function_name == "lead" || function_name == "lag") {
+ argument_types.push_back(std::make_shared<DataTypeInt64>());
+ argument_types.push_back(std::make_shared<DataTypeString>());
+ }
+ auto function = factory.get(function_name, argument_types, nullptr,
true, -1,
+ {.is_window_function = true, .column_names
= {}});
+ ASSERT_NE(function, nullptr);
+
+ auto value_column = ColumnString::create();
+ value_column->insert_data("A", 1);
+ value_column->insert_data("B", 1);
+ value_column->insert_data("C", 1);
+ auto offset_column = ColumnInt64::create();
+ offset_column->insert_value(1);
+ auto default_column = ColumnString::create();
+ default_column->insert_data("default", 7);
+ const IColumn* columns[] = {value_column.get(), offset_column.get(),
default_column.get()};
+
+ Arena arena;
+ auto* place =
reinterpret_cast<AggregateDataPtr>(arena.alloc(function->size_of_data()));
+ function->create(place);
+ UInt8 use_null_result = false;
+ UInt8 could_use_previous_result = false;
+ function->add_range_single_place(0, 3, 1, 2, place, columns, arena,
&use_null_result,
+ &could_use_previous_result);
+
+ value_column->clear();
+ auto result_column = ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ function->insert_result_into(place, *result_column);
+
+ ASSERT_EQ(result_column->size(), 1);
+ EXPECT_FALSE(result_column->is_null_at(0));
+ EXPECT_EQ(result_column->get_data_at(0).to_string(), "B");
+ function->destroy(place);
+ }
+}
+
} // namespace doris
diff --git a/be/test/exprs/aggregate/aggregate_function_min_max_test.cpp
b/be/test/exprs/aggregate/aggregate_function_min_max_test.cpp
index 57f8941ced4..411de8f5500 100644
--- a/be/test/exprs/aggregate/aggregate_function_min_max_test.cpp
+++ b/be/test/exprs/aggregate/aggregate_function_min_max_test.cpp
@@ -21,14 +21,30 @@
#include <string>
+#include "agent/be_exec_version_manager.h"
#include "core/arena.h"
#include "core/column/column_string.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_string.h"
#include "core/string_buffer.hpp"
namespace doris {
class SingleValueDataStringTest : public testing::Test {
protected:
+ void set_value(SingleValueDataString& data, const std::string& value) {
+ auto column = ColumnString::create();
+ column->insert_data(value.data(), value.size());
+ data.set(*column, 0, arena);
+ }
+
+ std::string get_value(const SingleValueDataString& data) {
+ auto column = ColumnString::create();
+ data.insert_result_into(*column);
+ return column->get_data_at(0).to_string();
+ }
+
Arena arena;
};
@@ -44,126 +60,123 @@ TEST_F(SingleValueDataStringTest, ResetWhenNoValue) {
ASSERT_FALSE(data.has());
}
-TEST_F(SingleValueDataStringTest, SmallStringChangeImpl) {
+TEST_F(SingleValueDataStringTest, SetSmallString) {
SingleValueDataString data;
std::string small = "hello";
- data.change_impl(StringRef(small.data(), small.size()), arena);
+ set_value(data, small);
ASSERT_TRUE(data.has());
- auto ref = data.get_string_ref();
- ASSERT_EQ(ref.size, small.size());
- ASSERT_EQ(std::string(ref.data, ref.size), small);
+ ASSERT_EQ(get_value(data), small);
}
-TEST_F(SingleValueDataStringTest, LargeStringChangeImpl) {
+TEST_F(SingleValueDataStringTest, SetLargeString) {
SingleValueDataString data;
// Create a string larger than MAX_SMALL_STRING_SIZE
std::string large(SingleValueDataString::MAX_SMALL_STRING_SIZE + 10, 'x');
- data.change_impl(StringRef(large.data(), large.size()), arena);
+ set_value(data, large);
ASSERT_TRUE(data.has());
- auto ref = data.get_string_ref();
- ASSERT_EQ(ref.size, large.size());
- ASSERT_EQ(std::string(ref.data, ref.size), large);
+ ASSERT_EQ(get_value(data), large);
}
-TEST_F(SingleValueDataStringTest, ResetAfterChange) {
+TEST_F(SingleValueDataStringTest, ResetAfterSet) {
SingleValueDataString data;
std::string s = "test";
- data.change_impl(StringRef(s.data(), s.size()), arena);
+ set_value(data, s);
ASSERT_TRUE(data.has());
data.reset();
ASSERT_FALSE(data.has());
}
-TEST_F(SingleValueDataStringTest, ChangeIfLess) {
+TEST_F(SingleValueDataStringTest, SetIfSmaller) {
SingleValueDataString data;
std::string a = "banana";
std::string b = "apple";
- data.change_impl(StringRef(a.data(), a.size()), arena);
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), a);
+ set_value(data, a);
+ ASSERT_EQ(get_value(data), a);
SingleValueDataString other;
- other.change_impl(StringRef(b.data(), b.size()), arena);
+ set_value(other, b);
- ASSERT_TRUE(data.change_if_less(other, arena));
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), b);
+ ASSERT_TRUE(data.set_if_smaller(other, arena));
+ ASSERT_EQ(get_value(data), b);
// "apple" is not less than "apple"
- ASSERT_FALSE(data.change_if_less(other, arena));
+ ASSERT_FALSE(data.set_if_smaller(other, arena));
}
-TEST_F(SingleValueDataStringTest, ChangeIfGreater) {
+TEST_F(SingleValueDataStringTest, SetIfGreater) {
SingleValueDataString data;
std::string a = "apple";
std::string b = "banana";
- data.change_impl(StringRef(a.data(), a.size()), arena);
+ set_value(data, a);
SingleValueDataString other;
- other.change_impl(StringRef(b.data(), b.size()), arena);
+ set_value(other, b);
- ASSERT_TRUE(data.change_if_greater(other, arena));
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), b);
+ ASSERT_TRUE(data.set_if_greater(other, arena));
+ ASSERT_EQ(get_value(data), b);
- ASSERT_FALSE(data.change_if_greater(other, arena));
+ ASSERT_FALSE(data.set_if_greater(other, arena));
}
-TEST_F(SingleValueDataStringTest, ChangeFirstTime) {
+TEST_F(SingleValueDataStringTest, SetFromState) {
SingleValueDataString data;
SingleValueDataString src;
std::string s = "first";
- src.change_impl(StringRef(s.data(), s.size()), arena);
+ set_value(src, s);
- data.change_first_time(src, arena);
+ data.set(src, arena);
ASSERT_TRUE(data.has());
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), s);
+ ASSERT_EQ(get_value(data), s);
- // Second call should not change
SingleValueDataString other;
std::string s2 = "second";
- other.change_impl(StringRef(s2.data(), s2.size()), arena);
- data.change_first_time(other, arena);
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), s);
+ set_value(other, s2);
+ data.set(other, arena);
+ ASSERT_EQ(get_value(data), s2);
}
TEST_F(SingleValueDataStringTest, WriteReadSmallString) {
SingleValueDataString data;
std::string s = "serialize_me";
- data.change_impl(StringRef(s.data(), s.size()), arena);
+ set_value(data, s);
// Write
auto col_write = ColumnString::create();
BufferWritable writer(*col_write);
- data.write(writer);
+ auto data_type = std::make_shared<DataTypeString>();
+ data.write(writer, data_type, -1);
writer.commit();
// Read
auto ref = col_write->get_data_at(0);
BufferReadable reader(ref);
SingleValueDataString data2;
- data2.read(reader, arena);
+ data2.read(reader, data_type, -1, arena);
ASSERT_TRUE(data2.has());
- ASSERT_EQ(std::string(data2.get_string_ref().data,
data2.get_string_ref().size), s);
+ ASSERT_EQ(get_value(data2), s);
}
TEST_F(SingleValueDataStringTest, WriteReadLargeString) {
SingleValueDataString data;
std::string s(SingleValueDataString::MAX_SMALL_STRING_SIZE + 20, 'L');
- data.change_impl(StringRef(s.data(), s.size()), arena);
+ set_value(data, s);
auto col_write = ColumnString::create();
BufferWritable writer(*col_write);
- data.write(writer);
+ auto data_type = std::make_shared<DataTypeString>();
+ data.write(writer, data_type, -1);
writer.commit();
auto ref = col_write->get_data_at(0);
BufferReadable reader(ref);
SingleValueDataString data2;
- data2.read(reader, arena);
+ data2.read(reader, data_type, -1, arena);
ASSERT_TRUE(data2.has());
- ASSERT_EQ(std::string(data2.get_string_ref().data,
data2.get_string_ref().size), s);
+ ASSERT_EQ(get_value(data2), s);
}
TEST_F(SingleValueDataStringTest, WriteReadNoValue) {
@@ -171,13 +184,14 @@ TEST_F(SingleValueDataStringTest, WriteReadNoValue) {
auto col_write = ColumnString::create();
BufferWritable writer(*col_write);
- data.write(writer);
+ auto data_type = std::make_shared<DataTypeString>();
+ data.write(writer, data_type, -1);
writer.commit();
auto ref = col_write->get_data_at(0);
BufferReadable reader(ref);
SingleValueDataString data2;
- data2.read(reader, arena);
+ data2.read(reader, data_type, -1, arena);
ASSERT_FALSE(data2.has());
}
@@ -185,7 +199,7 @@ TEST_F(SingleValueDataStringTest, WriteReadNoValue) {
TEST_F(SingleValueDataStringTest, InsertResultIntoWithValue) {
SingleValueDataString data;
std::string s = "result";
- data.change_impl(StringRef(s.data(), s.size()), arena);
+ set_value(data, s);
auto col = ColumnString::create();
data.insert_result_into(*col);
@@ -208,17 +222,104 @@ TEST_F(SingleValueDataStringTest, LargeStringRealloc) {
SingleValueDataString data;
// First large allocation
std::string s1(SingleValueDataString::MAX_SMALL_STRING_SIZE + 10, 'A');
- data.change_impl(StringRef(s1.data(), s1.size()), arena);
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), s1);
+ set_value(data, s1);
+ ASSERT_EQ(get_value(data), s1);
// Second larger allocation triggers realloc
std::string s2(SingleValueDataString::MAX_SMALL_STRING_SIZE + 200, 'B');
- data.change_impl(StringRef(s2.data(), s2.size()), arena);
- ASSERT_EQ(std::string(data.get_string_ref().data,
data.get_string_ref().size), s2);
+ set_value(data, s2);
+ ASSERT_EQ(get_value(data), s2);
}
TEST_F(SingleValueDataStringTest, SizeStaticAssert) {
static_assert(sizeof(SingleValueDataString) ==
SingleValueDataString::AUTOMATIC_STORAGE_SIZE);
}
+TEST(SingleValueDataColumnTest, OwnsValueAndCreatesColumnLazily) {
+ auto array_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>());
+ SingleValueDataColumn data;
+ EXPECT_FALSE(data.has());
+ EXPECT_EQ(data.allocated_bytes(), 0);
+
+ auto source = array_type->create_column();
+ source->insert(Field::create_field<TYPE_ARRAY>(
+ Array {Field::create_field<TYPE_INT>(1),
Field::create_field<TYPE_INT>(2)}));
+ Arena arena;
+ data.set(*source, 0, arena);
+ ASSERT_TRUE(data.has());
+
+ source->clear();
+ auto result = array_type->create_column();
+ data.insert_result_into(*result);
+ ASSERT_EQ(result->size(), 1);
+ EXPECT_EQ((*result)[0],
+ Field::create_field<TYPE_ARRAY>(
+ Array {Field::create_field<TYPE_INT>(1),
Field::create_field<TYPE_INT>(2)}));
+}
+
+TEST(SingleValueDataColumnTest, AllocationDoesNotScaleWithSourceRows) {
+ constexpr size_t large_source_rows = 4096;
+ auto array_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>());
+ auto create_source = [&](size_t rows) {
+ auto source = array_type->create_column();
+ const auto value = Field::create_field<TYPE_ARRAY>(
+ Array {Field::create_field<TYPE_INT>(1),
Field::create_field<TYPE_INT>(2)});
+ for (size_t i = 0; i < rows; ++i) {
+ source->insert(value);
+ }
+ return source;
+ };
+
+ auto single_row_source = create_source(1);
+ auto large_source = create_source(large_source_rows);
+ ASSERT_GT(large_source->allocated_bytes(),
single_row_source->allocated_bytes());
+
+ Arena arena;
+ SingleValueDataColumn single_row_state;
+ single_row_state.set(*single_row_source, 0, arena);
+ SingleValueDataColumn large_source_state;
+ large_source_state.set(*large_source, large_source_rows - 1, arena);
+
+ EXPECT_EQ(large_source_state.allocated_bytes(),
single_row_state.allocated_bytes());
+ EXPECT_LT(large_source_state.allocated_bytes(),
large_source->allocated_bytes());
+
+ auto result = array_type->create_column();
+ large_source_state.insert_result_into(*result);
+ ASSERT_EQ(result->size(), 1);
+ EXPECT_EQ((*result)[0], (*large_source)[large_source_rows - 1]);
+}
+
+TEST(SingleValueDataColumnTest, CompareCopyAndSerialize) {
+ auto array_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>());
+ const int be_exec_version = BeExecVersionManager::get_newest_version();
+ auto source = array_type->create_column();
+ source->insert(Field::create_field<TYPE_ARRAY>(Array
{Field::create_field<TYPE_INT>(2)}));
+ source->insert(Field::create_field<TYPE_ARRAY>(Array
{Field::create_field<TYPE_INT>(1)}));
+
+ Arena arena;
+ SingleValueDataColumn data;
+ EXPECT_TRUE(data.set_if_smaller(*source, 0, arena));
+ EXPECT_TRUE(data.set_if_smaller(*source, 1, arena));
+ EXPECT_TRUE(data.is_equal_to(*source, 1));
+
+ SingleValueDataColumn copied;
+ copied.set(data, arena);
+ data.reset();
+ ASSERT_TRUE(copied.has());
+
+ auto serialized = ColumnString::create();
+ BufferWritable writer(*serialized);
+ copied.write(writer, array_type, be_exec_version);
+ writer.commit();
+
+ SingleValueDataColumn restored;
+ BufferReadable reader(serialized->get_data_at(0));
+ restored.read(reader, array_type, be_exec_version, arena);
+ auto result = array_type->create_column();
+ restored.insert_result_into(*result);
+ ASSERT_EQ(result->size(), 1);
+ EXPECT_EQ((*result)[0],
+ Field::create_field<TYPE_ARRAY>(Array
{Field::create_field<TYPE_INT>(1)}));
+}
+
} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]