This is an automated email from the ASF dual-hosted git repository.
mrhhsg 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 85ef38e0097 [fix](be) Support IP keys in map_agg_v2 (#68426)
85ef38e0097 is described below
commit 85ef38e0097ae8acb85ef8c2bdf970f9ea9803bf
Author: Jerry Hu <[email protected]>
AuthorDate: Tue Oct 13 11:47:12 2026 +0800
[fix](be) Support IP keys in map_agg_v2 (#68426)
### What problem does this PR solve?
Issue Number: None
Problem Summary:
`map_agg_v2` accepts IP key types during analysis, but the backend
aggregate factory did not dispatch IPv4 or IPv6 keys. Queries therefore
failed at execution with an unimplemented aggregate function error.
Adding the missing dispatch exposed an aggregate-state framing issue:
the serializer's uncompressed-size API returns an allocation bound
including the StreamVByte decoder's readable suffix, while `serialize()`
returns the logical frame end. The legacy key state wrote the whole
allocation, whereas value states used the logical length. Trimming the
key frame without version routing would break mixed-version execution,
and an exact-sized string alone does not supply the decoder's private
suffix.
This change:
- adds IPv4 and IPv6 key dispatch and preserves master's UUID key
dispatch;
- stores incoming frames in `PaddedPODArray` so decoders have a private
readable suffix;
- writes exact logical key/value frames at BE execution version 17 while
retaining the legacy key frame through version 16;
- separates transient wire-format routing from persisted aggregate-state
compatibility, so existing version-15/16 Map states remain readable and
compactable at version 17;
- propagates the negotiated execution version into nested aggregate
functions such as `map_agg_v2_foreachv2`;
- reuses one write scratch buffer and one padded read scratch buffer
across key and value frames;
- binds restricted aggregates to their original version-8 support
boundary rather than the moving global maximum, so version-15/16
`stddev_samp` queries and stored states remain usable on upgraded BEs;
and
- enables IP keys only after all backends reach execution version 17.
Master reserved version 16 for Paimon table writes; it cannot also
identify support for the new Map frame.
### Release note
Fix `map_agg_v2` with IPv4 and IPv6 keys after all backends complete the
upgrade to execution version 17. Preserve stored Map and restricted
aggregate states across the upgrade.
### Check List (For Author)
- Test:
- Unit Test: `./run-be-ut.sh -j24 --run
--filter='AggregateFunctionMapAggV2Test.*'` (8 passed)
- Regression tests: `test_restricted_aggregate_versioned_state`
(version-15 direct query and stored state read at versions 16 and 17),
`test_map_agg_v2_versioned_state` (version-16 write, version-17 read and
full compaction), and `test_map_agg_v2_ip_key` passed in an isolated
ASAN cluster
- Build: `./build.sh --be --fe -j16` passed with ASAN after merging
master
- Style: BE clang-format, check-format, build-hygiene, and PR net `git
diff --check` passed
- Static analysis: changed production C++ files had no warnings with
matching Clang 16 plus a compile-flag override. The default Clang 20
invocation could not find `stddef.h`; the BE test translation unit still
reports pre-existing `jni-util.h` static assertions under Clang 16 and
an existing `DataTypeAggState` constructor diagnostic under Clang 20
- A two-version BE cluster was not run; legacy and exact routing were
covered by BE unit tests and versioned regression queries on an upgraded
BE
- Behavior changed: Yes (version-17 exact frames and IP-key support,
legacy wire routing and persisted-state compatibility preserved)
- Does this need documentation: No
---
be/src/agent/be_exec_version_manager.cpp | 56 ++-
be/src/agent/be_exec_version_manager.h | 24 +-
be/src/exprs/aggregate/aggregate_function.h | 2 +
.../exprs/aggregate/aggregate_function_covar.cpp | 3 +-
.../aggregate/aggregate_function_distinct.cpp | 4 +-
.../exprs/aggregate/aggregate_function_foreach.cpp | 4 +-
.../aggregate/aggregate_function_foreachv2.cpp | 4 +-
.../aggregate_function_map_combinator.cpp | 5 +-
.../exprs/aggregate/aggregate_function_map_v2.cpp | 31 +-
be/src/exprs/aggregate/aggregate_function_map_v2.h | 75 ++--
.../aggregate/aggregate_function_percentile.cpp | 12 +-
.../aggregate/aggregate_function_simple_factory.h | 91 +++--
.../exprs/aggregate/aggregate_function_stddev.cpp | 6 +-
.../aggregate/aggregate_function_window_funnel.cpp | 3 +-
.../writer/paimon/paimon_write_backend_test.cpp | 7 +-
be/test/exprs/aggregate/agg_map_agg_v2_test.cpp | 415 +++++++++++++++++++++
.../main/java/org/apache/doris/common/Config.java | 4 +-
.../map/test_map_agg_v2_versioned_state.out | 6 +
.../test_restricted_aggregate_versioned_state.out | 15 +
.../data/datatype_p0/ip/test_map_agg_v2_ip_key.out | 12 +
.../map/test_map_agg_v2_versioned_state.groovy | 91 +++++
...est_restricted_aggregate_versioned_state.groovy | 72 ++++
.../datatype_p0/ip/test_map_agg_v2_ip_key.groovy | 77 ++++
23 files changed, 904 insertions(+), 115 deletions(-)
diff --git a/be/src/agent/be_exec_version_manager.cpp
b/be/src/agent/be_exec_version_manager.cpp
index 91ccb6a6a25..1ccdf978688 100644
--- a/be/src/agent/be_exec_version_manager.cpp
+++ b/be/src/agent/be_exec_version_manager.cpp
@@ -21,6 +21,26 @@
namespace doris {
+namespace {
+
+int get_function_version(const std::map<std::string, std::set<int>>&
function_versions,
+ int be_exec_version, const std::string&
function_name) {
+ auto it = function_versions.find(function_name);
+ if (it == function_versions.end()) {
+ // 0 means no version-specific behavior is registered.
+ return 0;
+ }
+
+ auto version_it = it->second.lower_bound(be_exec_version);
+ if (version_it == it->second.end()) {
+ return 0;
+ }
+
+ return *version_it;
+}
+
+} // namespace
+
Status BeExecVersionManager::check_be_exec_version(int be_exec_version) {
if (be_exec_version > max_be_exec_version || be_exec_version <
min_be_exec_version) {
return Status::InternalError(
@@ -34,25 +54,22 @@ Status BeExecVersionManager::check_be_exec_version(int
be_exec_version) {
int BeExecVersionManager::get_function_compatibility(int be_exec_version,
std::string
function_name) {
- if (_function_restrict_map.contains(function_name) && be_exec_version !=
get_newest_version()) {
- throw Exception(Status::InternalError(
- "function {} do not support old be exec version, maybe it's
because doris are "
- "doing a rolling upgrade. newest_version={},
input_be_exec_version={}",
- function_name, get_newest_version(), be_exec_version));
- }
+ check_function_restriction(be_exec_version, function_name);
+ return get_function_version(_function_change_map, be_exec_version,
function_name);
+}
- auto it = _function_change_map.find(function_name);
- if (it == _function_change_map.end()) {
- // 0 means no compatibility issues need to be dealt with
- return 0;
- }
+int BeExecVersionManager::get_function_alternative(int be_exec_version,
std::string function_name) {
+ return get_function_version(_function_alternative_map, be_exec_version,
function_name);
+}
- auto version_it = it->second.lower_bound(be_exec_version);
- if (version_it == it->second.end()) {
- return 0;
+void BeExecVersionManager::check_function_restriction(int be_exec_version,
+ const std::string&
function_name) {
+ auto it = _function_restrict_map.find(function_name);
+ if (it != _function_restrict_map.end() && be_exec_version < it->second) {
+ throw Exception(Status::InternalError(
+ "function {} does not support be exec versions before {},
input_be_exec_version={}",
+ function_name, it->second, be_exec_version));
}
-
- return *version_it;
}
void BeExecVersionManager::check_function_compatibility(int
current_be_exec_version,
@@ -137,9 +154,12 @@ void
BeExecVersionManager::check_function_compatibility(int current_be_exec_vers
// a. distinguish Hive OpenCSVSerde row semantics from generic CSV decoding
during upgrades.
// 16: start from master
// a. support Paimon table writes.
+// 17: start from master
+// a. use exact logical frames for map_agg_v2 aggregate state serialization.
-const int BeExecVersionManager::max_be_exec_version =
SUPPORT_PAIMON_WRITE_VERSION;
+const int BeExecVersionManager::max_be_exec_version =
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION;
const int BeExecVersionManager::min_be_exec_version = 0;
std::map<std::string, std::set<int>>
BeExecVersionManager::_function_change_map {};
-std::set<std::string> BeExecVersionManager::_function_restrict_map;
+std::map<std::string, std::set<int>>
BeExecVersionManager::_function_alternative_map {};
+std::map<std::string, int> BeExecVersionManager::_function_restrict_map;
} // namespace doris
diff --git a/be/src/agent/be_exec_version_manager.h
b/be/src/agent/be_exec_version_manager.h
index d7ec473127f..01e5f442bad 100644
--- a/be/src/agent/be_exec_version_manager.h
+++ b/be/src/agent/be_exec_version_manager.h
@@ -29,6 +29,8 @@
namespace doris {
+// Version 8 removed the old implementations of these restricted aggregate
functions.
+constexpr inline int SUPPORT_RESTRICTED_AGGREGATES_VERSION = 8;
constexpr inline int USE_NEW_FIXED_OBJECT_SERIALIZATION_VERSION = 10;
constexpr inline int SUPPORT_ICEBERG_MERGE_CARDINALITY_VERSION = 11;
constexpr inline int SUPPORT_ICEBERG_VARIANT_VERSION = 12;
@@ -36,6 +38,7 @@ constexpr inline int SUPPORT_EXTERNAL_TABLE_SINK_HASH_VERSION
= 13;
constexpr inline int SUPPORT_TIMESTAMP_NS_VERSION = 14;
constexpr inline int SUPPORT_HIVE_OPEN_CSV_VERSION = 15;
constexpr inline int SUPPORT_PAIMON_WRITE_VERSION = 16;
+constexpr inline int SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION = 17;
class BeExecVersionManager {
public:
@@ -45,6 +48,10 @@ public:
static int get_function_compatibility(int be_exec_version, std::string
function_name);
+ static int get_function_alternative(int be_exec_version, std::string
function_name);
+
+ static void check_function_restriction(int be_exec_version, const
std::string& function_name);
+
static void check_function_compatibility(int current_be_exec_version, int
data_be_exec_version,
std::string function_name);
@@ -58,10 +65,17 @@ public:
static void registe_old_function_compatibility(int breaking_old_version,
std::string function_name) {
_function_change_map[function_name].insert(breaking_old_version);
+ register_old_function_alternative(breaking_old_version, function_name);
+ }
+
+ static void register_old_function_alternative(int breaking_old_version,
+ std::string function_name) {
+ _function_alternative_map[function_name].insert(breaking_old_version);
}
- static void registe_restrict_function_compatibility(std::string
function_name) {
- _function_restrict_map.insert(function_name);
+ static void registe_restrict_function_compatibility(std::string
function_name,
+ int
first_supported_version) {
+ _function_restrict_map.emplace(function_name, first_supported_version);
}
private:
@@ -69,8 +83,10 @@ private:
static const int min_be_exec_version;
// [function name] -> [breaking change start version]
static std::map<std::string, std::set<int>> _function_change_map;
- // those function must has input newest be exec version
- static std::set<std::string> _function_restrict_map;
+ // [function name] -> [alternative implementation start version]
+ static std::map<std::string, std::set<int>> _function_alternative_map;
+ // [function name] -> [first BE exec version with the retained
implementation]
+ static std::map<std::string, int> _function_restrict_map;
};
} // namespace doris
diff --git a/be/src/exprs/aggregate/aggregate_function.h
b/be/src/exprs/aggregate/aggregate_function.h
index a6c12dcbab5..0b80839cdeb 100644
--- a/be/src/exprs/aggregate/aggregate_function.h
+++ b/be/src/exprs/aggregate/aggregate_function.h
@@ -53,6 +53,8 @@ struct AggregateFunctionAttr {
bool enable_aggregate_function_null_v2 {false};
bool new_version_percentile {false};
std::vector<std::string> column_names;
+ // Negotiated execution version propagated to nested aggregate function
creators.
+ int be_exec_version {-1};
};
template <bool nullable, typename ColVecType>
diff --git a/be/src/exprs/aggregate/aggregate_function_covar.cpp
b/be/src/exprs/aggregate/aggregate_function_covar.cpp
index d1db8b40416..f96b57f45f8 100644
--- a/be/src/exprs/aggregate/aggregate_function_covar.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_covar.cpp
@@ -56,7 +56,8 @@ void
register_aggregate_function_covar_pop(AggregateFunctionSimpleFactory& facto
}
void
register_aggregate_function_covar_samp_old(AggregateFunctionSimpleFactory&
factory) {
-
BeExecVersionManager::registe_restrict_function_compatibility("covar_samp");
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "covar_samp", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
}
void register_aggregate_function_covar_samp(AggregateFunctionSimpleFactory&
factory) {
diff --git a/be/src/exprs/aggregate/aggregate_function_distinct.cpp
b/be/src/exprs/aggregate/aggregate_function_distinct.cpp
index ec9b087cd24..b8c934c33d6 100644
--- a/be/src/exprs/aggregate/aggregate_function_distinct.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_distinct.cpp
@@ -93,8 +93,8 @@ void
register_aggregate_function_combinator_distinct(AggregateFunctionSimpleFact
auto function_combinator =
std::make_shared<AggregateFunctionCombinatorDistinct>();
auto transform_arguments =
function_combinator->transform_arguments(nested_types);
auto nested_function_name =
name.substr(DISTINCT_FUNCTION_PREFIX.size());
- auto nested_function = factory.get(nested_function_name,
transform_arguments, result_type,
- false,
BeExecVersionManager::get_newest_version(), attr);
+ auto nested_function = factory.get_nested(nested_function_name,
transform_arguments,
+ result_type, false,
attr.be_exec_version, attr);
return
function_combinator->transform_aggregate_function(nested_function, types,
result_is_nullable, attr);
};
diff --git a/be/src/exprs/aggregate/aggregate_function_foreach.cpp
b/be/src/exprs/aggregate/aggregate_function_foreach.cpp
index 16ee0ea6782..14605f23821 100644
--- a/be/src/exprs/aggregate/aggregate_function_foreach.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_foreach.cpp
@@ -47,8 +47,8 @@ void
register_aggregate_function_combinator_foreach(AggregateFunctionSimpleFacto
->get_nested_type();
auto nested_function_name = name.substr(0, name.size() -
suffix.size());
auto nested_function =
- factory.get(nested_function_name, transform_arguments,
result_item_type,
- result_is_nullable,
BeExecVersionManager::get_newest_version(), attr);
+ factory.get_nested(nested_function_name, transform_arguments,
result_item_type,
+ result_is_nullable, attr.be_exec_version,
attr);
if (!nested_function) {
throw Exception(
ErrorCode::INTERNAL_ERROR,
diff --git a/be/src/exprs/aggregate/aggregate_function_foreachv2.cpp
b/be/src/exprs/aggregate/aggregate_function_foreachv2.cpp
index 695d583b7e5..f31c4dd58c1 100644
--- a/be/src/exprs/aggregate/aggregate_function_foreachv2.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_foreachv2.cpp
@@ -94,8 +94,8 @@ void
register_aggregate_function_combinator_foreachv2(AggregateFunctionSimpleFac
->get_nested_type();
auto nested_function_name = name.substr(0, name.size() -
suffix.size());
auto nested_function =
- factory.get(nested_function_name, transform_arguments,
result_item_type, true,
- BeExecVersionManager::get_newest_version(), attr);
+ factory.get_nested(nested_function_name, transform_arguments,
result_item_type,
+ true, attr.be_exec_version, attr);
if (!nested_function) {
throw Exception(
ErrorCode::INTERNAL_ERROR,
diff --git a/be/src/exprs/aggregate/aggregate_function_map_combinator.cpp
b/be/src/exprs/aggregate/aggregate_function_map_combinator.cpp
index 2d4e40aad72..ac0e46cae62 100644
--- a/be/src/exprs/aggregate/aggregate_function_map_combinator.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_map_combinator.cpp
@@ -20,7 +20,6 @@
#include <string>
#include <string_view>
-#include "agent/be_exec_version_manager.h"
#include "core/call_on_type_index.h"
#include "core/column/column_const.h"
#include "core/column/column_decimal.h"
@@ -103,9 +102,9 @@ AggregateFunctionPtr create_nested_function(const
std::string& name, const DataT
DataTypePtr nested_result_type = remove_nullable(result_value_type);
const bool nested_result_is_nullable = result_value_type->is_nullable();
- auto nested_function = AggregateFunctionSimpleFactory::instance().get(
+ auto nested_function =
AggregateFunctionSimpleFactory::instance().get_nested(
nested_function_name(name), nested_argument_types,
nested_result_type,
- nested_result_is_nullable,
BeExecVersionManager::get_newest_version(), attr);
+ nested_result_is_nullable, attr.be_exec_version, attr);
if (nested_function == nullptr) {
throw Exception(ErrorCode::INTERNAL_ERROR,
"Can not create nested aggregate function for {}",
name);
diff --git a/be/src/exprs/aggregate/aggregate_function_map_v2.cpp
b/be/src/exprs/aggregate/aggregate_function_map_v2.cpp
index b90fe60e096..9536f405344 100644
--- a/be/src/exprs/aggregate/aggregate_function_map_v2.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_map_v2.cpp
@@ -17,18 +17,21 @@
#include "exprs/aggregate/aggregate_function_map_v2.h"
+#include "agent/be_exec_version_manager.h"
#include "exprs/aggregate/helpers.h"
namespace doris {
+template <bool use_exact_key_frame>
AggregateFunctionPtr create_agg_function_map_agg_v2(const DataTypes&
argument_types,
const bool
result_is_nullable,
const
AggregateFunctionAttr& attr) {
return creator_without_type::create_ignore_nullable<
-
AggregateFunctionMapAggV2<AggregateFunctionMapAggDataV2>>(argument_types,
-
result_is_nullable, attr);
+
AggregateFunctionMapAggV2<AggregateFunctionMapAggDataV2<use_exact_key_frame>>>(
+ argument_types, result_is_nullable, attr);
}
+template <bool use_exact_key_frame>
AggregateFunctionPtr create_aggregate_function_map_agg_v2(const std::string&
name,
const DataTypes&
argument_types,
const DataTypePtr&
result_type,
@@ -57,16 +60,30 @@ AggregateFunctionPtr
create_aggregate_function_map_agg_v2(const std::string& nam
case PrimitiveType::TYPE_TIMEV2:
case PrimitiveType::TYPE_TIMESTAMPTZ:
case PrimitiveType::TYPE_UUID:
- return create_agg_function_map_agg_v2(argument_types,
result_is_nullable, attr);
+ return
create_agg_function_map_agg_v2<use_exact_key_frame>(argument_types,
+
result_is_nullable, attr);
+ case PrimitiveType::TYPE_IPV4:
+ case PrimitiveType::TYPE_IPV6:
+ if constexpr (use_exact_key_frame) {
+ return
create_agg_function_map_agg_v2<use_exact_key_frame>(argument_types,
+
result_is_nullable, attr);
+ }
+ break;
default:
- LOG(WARNING) << fmt::format("unsupported input type {} for aggregate
function {}",
- argument_types[0]->get_name(), name);
- return nullptr;
+ break;
}
+ LOG(WARNING) << fmt::format("unsupported input type {} for aggregate
function {}",
+ argument_types[0]->get_name(), name);
+ return nullptr;
}
void register_aggregate_function_map_agg_v2(AggregateFunctionSimpleFactory&
factory) {
- factory.register_function_both("map_agg_v2",
create_aggregate_function_map_agg_v2);
+ factory.register_function_both("map_agg_v2",
create_aggregate_function_map_agg_v2<true>);
+ constexpr auto old_be_exec_version =
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1;
+ factory.register_transient_alternative_function(
+ "map_agg_v2", create_aggregate_function_map_agg_v2<false>, false,
old_be_exec_version);
+ factory.register_transient_alternative_function(
+ "map_agg_v2", create_aggregate_function_map_agg_v2<false>, true,
old_be_exec_version);
}
} // namespace doris
diff --git a/be/src/exprs/aggregate/aggregate_function_map_v2.h
b/be/src/exprs/aggregate/aggregate_function_map_v2.h
index 5e62dcdfb5c..e70c1ce68a5 100644
--- a/be/src/exprs/aggregate/aggregate_function_map_v2.h
+++ b/be/src/exprs/aggregate/aggregate_function_map_v2.h
@@ -20,17 +20,22 @@
#include <glog/logging.h>
#include <parallel_hashmap/phmap.h>
+#include <string>
+
+#include "common/check.h"
#include "core/assert_cast.h"
#include "core/column/column_decimal.h"
#include "core/column/column_map.h"
#include "core/column/column_string.h"
#include "core/data_type/data_type_map.h"
#include "core/field.h"
+#include "core/pod_array.h"
#include "exprs/aggregate/aggregate_function.h"
#include "exprs/aggregate/aggregate_function_simple_factory.h"
namespace doris {
+template <bool use_exact_key_frame>
struct AggregateFunctionMapAggDataV2 {
using Map = doris::flat_hash_map<Field, int64_t>;
@@ -119,50 +124,52 @@ struct AggregateFunctionMapAggDataV2 {
}
void write(BufferWritable& buf) const {
- auto serialized_bytes =
- _key_type->get_uncompressed_serialized_bytes(*_key_column,
_be_version);
-
std::string serialized_buffer;
- serialized_buffer.resize(serialized_bytes);
-
- auto* buf_ptr = _key_type->serialize(*_key_column,
serialized_buffer.data(), _be_version);
- int64_t written_bytes = buf_ptr - serialized_buffer.data();
- DCHECK_LE(written_bytes, serialized_bytes);
-
- serialized_buffer.resize(serialized_bytes);
- buf.write_binary(serialized_buffer);
-
- serialized_bytes =
- _value_type->get_uncompressed_serialized_bytes(*_value_column,
_be_version);
-
- serialized_buffer.resize(serialized_bytes);
-
- buf_ptr = _value_type->serialize(*_value_column,
serialized_buffer.data(), _be_version);
- written_bytes = buf_ptr - serialized_buffer.data();
- DCHECK_LE(written_bytes, serialized_bytes);
-
- serialized_buffer.resize(written_bytes);
- buf.write_binary(serialized_buffer);
+ write_column(*_key_type, *_key_column, buf, serialized_buffer,
use_exact_key_frame);
+ write_column(*_value_type, *_value_column, buf, serialized_buffer,
true);
}
void read(BufferReadable& buf) {
- std::string deserialized_buffer;
+ PaddedPODArray<UInt8> frame;
+ read_column(*_key_type, &_key_column, buf, frame, use_exact_key_frame);
+ read_column(*_value_type, &_value_column, buf, frame, true);
+ }
- buf.read_binary(deserialized_buffer);
+private:
+ void write_column(const IDataType& type, const IColumn& column,
BufferWritable& buf,
+ std::string& serialized_buffer, bool use_exact_frame)
const {
+ const auto max_serialized_bytes =
+ type.get_uncompressed_serialized_bytes(column, _be_version);
+ serialized_buffer.resize(max_serialized_bytes, '\0');
+
+ const auto* end = type.serialize(column, serialized_buffer.data(),
_be_version);
+ const auto written_bytes = end - serialized_buffer.data();
+ DORIS_CHECK_LE(written_bytes, max_serialized_bytes);
+
+ if (use_exact_frame) {
+ serialized_buffer.resize(written_bytes);
+ }
+ buf.write_binary(serialized_buffer);
+ }
- const auto* ptr =
- _key_type->deserialize(deserialized_buffer.data(),
&_key_column, _be_version);
- auto read_bytes = ptr - deserialized_buffer.data();
- DCHECK_EQ(read_bytes, deserialized_buffer.size());
+ void read_column(const IDataType& type, MutableColumnPtr* column,
BufferReadable& buf,
+ PaddedPODArray<UInt8>& frame, bool use_exact_frame) const
{
+ buf.read_binary(frame);
- buf.read_binary(deserialized_buffer);
+ const auto* begin = reinterpret_cast<const char*>(frame.data());
+ const auto* frame_end = begin + frame.size();
+ const auto* end = type.deserialize(begin, column, _be_version);
+ if (use_exact_frame) {
+ DORIS_CHECK_EQ(end, frame_end);
+ return;
+ }
- ptr = _value_type->deserialize(deserialized_buffer.data(),
&_value_column, _be_version);
- read_bytes = ptr - deserialized_buffer.data();
- DCHECK_EQ(read_bytes, deserialized_buffer.size());
+ DORIS_CHECK_LE(end, frame_end);
+ const auto expected_frame_size =
+ type.get_uncompressed_serialized_bytes(**column, _be_version);
+ DORIS_CHECK_EQ(frame.size(), expected_frame_size);
}
-private:
Map _map;
Arena _arena;
IColumn::MutablePtr _key_column;
diff --git a/be/src/exprs/aggregate/aggregate_function_percentile.cpp
b/be/src/exprs/aggregate/aggregate_function_percentile.cpp
index eb829f1e318..80d7633d4cb 100644
--- a/be/src/exprs/aggregate/aggregate_function_percentile.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_percentile.cpp
@@ -91,13 +91,17 @@ void
register_aggregate_function_percentile(AggregateFunctionSimpleFactory& fact
}
void register_percentile_approx_old_function(AggregateFunctionSimpleFactory&
factory) {
-
BeExecVersionManager::registe_restrict_function_compatibility("percentile_approx");
-
BeExecVersionManager::registe_restrict_function_compatibility("percentile_approx_weighted");
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "percentile_approx", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "percentile_approx_weighted",
SUPPORT_RESTRICTED_AGGREGATES_VERSION);
}
void
register_aggregate_function_percentile_old(AggregateFunctionSimpleFactory&
factory) {
-
BeExecVersionManager::registe_restrict_function_compatibility("percentile");
-
BeExecVersionManager::registe_restrict_function_compatibility("percentile_array");
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "percentile", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "percentile_array", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
}
void
register_aggregate_function_percentile_approx(AggregateFunctionSimpleFactory&
factory) {
diff --git a/be/src/exprs/aggregate/aggregate_function_simple_factory.h
b/be/src/exprs/aggregate/aggregate_function_simple_factory.h
index b6d8085ae92..b93b3d1d01a 100644
--- a/be/src/exprs/aggregate/aggregate_function_simple_factory.h
+++ b/be/src/exprs/aggregate/aggregate_function_simple_factory.h
@@ -122,6 +122,61 @@ public:
AggregateFunctionPtr get(const std::string& name, const DataTypes&
argument_types,
const DataTypePtr& result_type, const bool
result_is_nullable,
int be_version, AggregateFunctionAttr attr = {}) {
+ return get_impl(name, argument_types, result_type, result_is_nullable,
be_version,
+ std::move(attr), true);
+ }
+
+ AggregateFunctionPtr get_nested(const std::string& name, const DataTypes&
argument_types,
+ const DataTypePtr& result_type, const bool
result_is_nullable,
+ int be_version, AggregateFunctionAttr attr
= {}) {
+ // The wrapper has already passed top-level compatibility checks.
Nested lookup still
+ // needs versioned alternative routing, but must not reapply
direct-function restrictions.
+ return get_impl(name, argument_types, result_type, result_is_nullable,
be_version,
+ std::move(attr), false);
+ }
+
+ void register_function(const std::string& name, const Creator& creator,
bool nullable = false) {
+ if (nullable) {
+ nullable_aggregate_functions[name] = creator;
+ } else {
+ aggregate_functions[name] = creator;
+ }
+ }
+
+ void register_function_both(const std::string& name, const Creator&
creator) {
+ register_function(name, creator, false);
+ register_function(name, creator, true);
+ }
+
+ void register_alias(const std::string& name, const std::string& alias) {
+ function_alias[alias] = name;
+ for (const auto& s : combiner_names) {
+ function_alias[alias + std::string(s)] = name + std::string(s);
+ }
+ function_alias[DISTINCT_FUNCTION_PREFIX + alias] =
DISTINCT_FUNCTION_PREFIX + name;
+ }
+
+ void register_alternative_function(const std::string& name, const Creator&
creator,
+ bool nullable, int old_be_exec_version)
{
+ auto new_name = name +
BeExecVersionManager::get_function_suffix(old_be_exec_version);
+ register_function(new_name, creator, nullable);
+
BeExecVersionManager::registe_old_function_compatibility(old_be_exec_version,
name);
+ }
+
+ void register_transient_alternative_function(const std::string& name,
const Creator& creator,
+ bool nullable, int
old_be_exec_version) {
+ auto new_name = name +
BeExecVersionManager::get_function_suffix(old_be_exec_version);
+ register_function(new_name, creator, nullable);
+
BeExecVersionManager::register_old_function_alternative(old_be_exec_version,
name);
+ }
+
+ static AggregateFunctionSimpleFactory& instance();
+
+private:
+ AggregateFunctionPtr get_impl(const std::string& name, const DataTypes&
argument_types,
+ const DataTypePtr& result_type, const bool
result_is_nullable,
+ int be_version, AggregateFunctionAttr attr,
+ bool check_restriction) {
bool nullable = false;
for (const auto& type : argument_types) {
if (type->is_nullable()) {
@@ -130,7 +185,11 @@ public:
}
std::string name_str = name;
+ if (check_restriction) {
+ BeExecVersionManager::check_function_restriction(be_version,
name_str);
+ }
temporary_function_update(be_version, name_str);
+ attr.be_exec_version = be_version;
if (function_alias.contains(name)) {
name_str = function_alias[name];
@@ -158,42 +217,12 @@ public:
}
}
- void register_function(const std::string& name, const Creator& creator,
bool nullable = false) {
- if (nullable) {
- nullable_aggregate_functions[name] = creator;
- } else {
- aggregate_functions[name] = creator;
- }
- }
-
- void register_function_both(const std::string& name, const Creator&
creator) {
- register_function(name, creator, false);
- register_function(name, creator, true);
- }
-
- void register_alias(const std::string& name, const std::string& alias) {
- function_alias[alias] = name;
- for (const auto& s : combiner_names) {
- function_alias[alias + std::string(s)] = name + std::string(s);
- }
- function_alias[DISTINCT_FUNCTION_PREFIX + alias] =
DISTINCT_FUNCTION_PREFIX + name;
- }
-
- void register_alternative_function(const std::string& name, const Creator&
creator,
- bool nullable, int old_be_exec_version)
{
- auto new_name = name +
BeExecVersionManager::get_function_suffix(old_be_exec_version);
- register_function(new_name, creator, nullable);
-
BeExecVersionManager::registe_old_function_compatibility(old_be_exec_version,
name);
- }
-
void temporary_function_update(int fe_version_now, std::string& name) {
- int old_version =
BeExecVersionManager::get_function_compatibility(fe_version_now, name);
+ int old_version =
BeExecVersionManager::get_function_alternative(fe_version_now, name);
if (!old_version) {
return;
}
name = name + BeExecVersionManager::get_function_suffix(old_version);
}
-
- static AggregateFunctionSimpleFactory& instance();
};
}; // namespace doris
diff --git a/be/src/exprs/aggregate/aggregate_function_stddev.cpp
b/be/src/exprs/aggregate/aggregate_function_stddev.cpp
index f8c910f4771..85271a0a7c1 100644
--- a/be/src/exprs/aggregate/aggregate_function_stddev.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_stddev.cpp
@@ -87,8 +87,10 @@ void
register_aggregate_function_stddev_variance_pop(AggregateFunctionSimpleFact
}
void
register_aggregate_function_stddev_variance_samp_old(AggregateFunctionSimpleFactory&
factory) {
-
BeExecVersionManager::registe_restrict_function_compatibility("variance_samp");
-
BeExecVersionManager::registe_restrict_function_compatibility("stddev_samp");
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "variance_samp", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "stddev_samp", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
}
void
register_aggregate_function_stddev_variance_samp(AggregateFunctionSimpleFactory&
factory) {
diff --git a/be/src/exprs/aggregate/aggregate_function_window_funnel.cpp
b/be/src/exprs/aggregate/aggregate_function_window_funnel.cpp
index 060afb7379c..07e35174818 100644
--- a/be/src/exprs/aggregate/aggregate_function_window_funnel.cpp
+++ b/be/src/exprs/aggregate/aggregate_function_window_funnel.cpp
@@ -56,6 +56,7 @@ void
register_aggregate_function_window_funnel(AggregateFunctionSimpleFactory& f
factory.register_alias("window_funnel_v1", "window_funnel");
}
void
register_aggregate_function_window_funnel_old(AggregateFunctionSimpleFactory&
factory) {
-
BeExecVersionManager::registe_restrict_function_compatibility("window_funnel");
+ BeExecVersionManager::registe_restrict_function_compatibility(
+ "window_funnel", SUPPORT_RESTRICTED_AGGREGATES_VERSION);
}
} // namespace doris
diff --git a/be/test/exec/sink/writer/paimon/paimon_write_backend_test.cpp
b/be/test/exec/sink/writer/paimon/paimon_write_backend_test.cpp
index d153ec81a13..d543c18282e 100644
--- a/be/test/exec/sink/writer/paimon/paimon_write_backend_test.cpp
+++ b/be/test/exec/sink/writer/paimon/paimon_write_backend_test.cpp
@@ -63,10 +63,11 @@ TEST(PaimonWriteBackendTest, ReportsConcreteBackendType) {
}
TEST(PaimonWriteBackendTest, AdvertisesPaimonWriteExecVersion) {
- EXPECT_EQ(SUPPORT_PAIMON_WRITE_VERSION,
BeExecVersionManager::get_newest_version());
+ EXPECT_LE(SUPPORT_PAIMON_WRITE_VERSION,
BeExecVersionManager::get_newest_version());
EXPECT_TRUE(BeExecVersionManager::check_be_exec_version(SUPPORT_PAIMON_WRITE_VERSION).ok());
- EXPECT_FALSE(
-
BeExecVersionManager::check_be_exec_version(SUPPORT_PAIMON_WRITE_VERSION +
1).ok());
+ EXPECT_FALSE(BeExecVersionManager::check_be_exec_version(
+ BeExecVersionManager::get_newest_version() + 1)
+ .ok());
}
TEST(PaimonTableSinkOperatorTest, InitializesAsBlockingSink) {
diff --git a/be/test/exprs/aggregate/agg_map_agg_v2_test.cpp
b/be/test/exprs/aggregate/agg_map_agg_v2_test.cpp
new file mode 100644
index 00000000000..c572b81a328
--- /dev/null
+++ b/be/test/exprs/aggregate/agg_map_agg_v2_test.cpp
@@ -0,0 +1,415 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "agent/be_exec_version_manager.h"
+#include "common/exception.h"
+#include "core/column/column_array.h"
+#include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type/data_type_agg_state.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_ipv4.h"
+#include "core/data_type/data_type_ipv6.h"
+#include "core/data_type/data_type_map.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/pod_array.h"
+#include "exprs/aggregate/aggregate_function_map_v2.h"
+#include "exprs/aggregate/aggregate_function_simple_factory.h"
+#include "exprs/aggregate/aggregate_function_state_merge.h"
+#include "exprs/aggregate/aggregate_function_state_union.h"
+
+namespace doris {
+namespace {
+
+void expect_restricted_aggregate_allowed(const char* name, int
be_exec_version) {
+
EXPECT_NO_THROW(BeExecVersionManager::check_function_restriction(be_exec_version,
name));
+}
+
+void expect_restricted_aggregate_rejected(const char* name, int
be_exec_version) {
+
EXPECT_THROW(BeExecVersionManager::check_function_restriction(be_exec_version,
name),
+ Exception);
+}
+
+void check_state_union_and_merge_round_trip(const DataTypePtr& key_type,
+ const std::vector<Field>& keys) {
+ const auto be_exec_version = BeExecVersionManager::get_newest_version();
+ const DataTypes argument_types {make_nullable(key_type),
+
make_nullable(std::make_shared<DataTypeInt32>())};
+ auto state_type = std::make_shared<DataTypeAggState>(argument_types,
false, "map_agg_v2",
+ be_exec_version);
+ auto nested_function = state_type->get_nested_function();
+ ASSERT_NE(nested_function, nullptr);
+
+ auto key_column = argument_types[0]->create_column();
+ auto value_column = argument_types[1]->create_column();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ key_column->insert(keys[i]);
+
value_column->insert(Field::create_field<TYPE_INT>(static_cast<Int32>(i)));
+ }
+ const IColumn* input_columns[] = {key_column.get(), value_column.get()};
+
+ Arena arena;
+ auto state_column = state_type->create_column();
+ nested_function->streaming_agg_serialize_to_column(input_columns,
state_column, keys.size(),
+ arena);
+ ASSERT_EQ(state_column->size(), keys.size());
+
+ auto union_function =
+ AggregateStateUnion::create(nested_function, DataTypes
{state_type}, state_type);
+ union_function->set_version(be_exec_version);
+ AggregateFunctionGuard union_state(union_function.get());
+ const IColumn* state_columns[] = {state_column.get()};
+ union_function->add_batch_single_place(state_column->size(),
union_state.data(), state_columns,
+ arena);
+
+ auto serialized_union = union_function->create_serialize_column();
+ std::vector<AggregateDataPtr> union_places {union_state.data()};
+ union_function->serialize_to_column(union_places, 0, serialized_union, 1);
+ ASSERT_EQ(serialized_union->size(), 1);
+
+ AggregateFunctionGuard round_tripped_union(union_function.get());
+
union_function->deserialize_and_merge_from_column_range(round_tripped_union.data(),
+ *serialized_union,
0, 0, arena);
+ auto union_result = state_type->create_column();
+ union_function->insert_result_into(round_tripped_union.data(),
*union_result);
+ ASSERT_EQ(union_result->size(), 1);
+
+ auto merge_function = AggregateStateMerge::create(nested_function,
DataTypes {state_type},
+
nested_function->get_return_type());
+ merge_function->set_version(be_exec_version);
+ AggregateFunctionGuard merged_state(merge_function.get());
+ const IColumn* union_result_columns[] = {union_result.get()};
+ merge_function->add_batch_single_place(union_result->size(),
merged_state.data(),
+ union_result_columns, arena);
+
+ auto result = nested_function->get_return_type()->create_column();
+ merge_function->insert_result_into(merged_state.data(), *result);
+ const auto& result_map = assert_cast<const ColumnMap&>(*result);
+ ASSERT_EQ(result_map.size(), 1);
+ EXPECT_EQ(result_map.get_keys().size(), keys.size());
+}
+
+template <bool use_exact_key_frame>
+std::pair<size_t, size_t> serialize_and_deserialize_state(const DataTypePtr&
key_type,
+ const
std::vector<Field>& keys,
+ int be_exec_version)
{
+ const DataTypes argument_types {make_nullable(key_type),
+
make_nullable(std::make_shared<DataTypeInt32>())};
+ AggregateFunctionMapAggDataV2<use_exact_key_frame> state(argument_types,
be_exec_version);
+ for (size_t i = 0; i < keys.size(); ++i) {
+ state.add_single(keys[i],
Field::create_field<TYPE_INT>(static_cast<Int32>(i)));
+ }
+
+ auto serialized_state = ColumnString::create();
+ BufferWritable writer(*serialized_state);
+ state.write(writer);
+ writer.commit();
+
+ PaddedPODArray<UInt8> key_frame;
+ PaddedPODArray<UInt8> value_frame;
+ BufferReadable frame_reader(serialized_state->get_data_at(0));
+ frame_reader.read_binary(key_frame);
+ frame_reader.read_binary(value_frame);
+
+ AggregateFunctionMapAggDataV2<use_exact_key_frame>
restored_state(argument_types,
+
be_exec_version);
+ BufferReadable state_reader(serialized_state->get_data_at(0));
+ restored_state.read(state_reader);
+
+ auto result_type = std::make_shared<DataTypeMap>(argument_types[0],
argument_types[1]);
+ auto result = result_type->create_column();
+ restored_state.insert_result_into(*result);
+ const auto& result_map = assert_cast<const ColumnMap&>(*result);
+ EXPECT_EQ(result_map.get_keys().size(), keys.size());
+
+ return {key_frame.size(), value_frame.size()};
+}
+
+template <bool use_exact_key_frame>
+void check_unequal_frame_round_trip(const DataTypePtr& key_type, const Field&
key,
+ const DataTypePtr& value_type, const
Field& value,
+ int be_exec_version) {
+ const DataTypes argument_types {make_nullable(key_type),
make_nullable(value_type)};
+ AggregateFunctionMapAggDataV2<use_exact_key_frame> state(argument_types,
be_exec_version);
+ state.add_single(key, value);
+
+ auto serialized_state = ColumnString::create();
+ BufferWritable writer(*serialized_state);
+ state.write(writer);
+ writer.commit();
+
+ AggregateFunctionMapAggDataV2<use_exact_key_frame>
restored_state(argument_types,
+
be_exec_version);
+ BufferReadable reader(serialized_state->get_data_at(0));
+ restored_state.read(reader);
+ EXPECT_FALSE(reader.has_remaining());
+
+ auto result_type = std::make_shared<DataTypeMap>(argument_types[0],
argument_types[1]);
+ auto result = result_type->create_column();
+ restored_state.insert_result_into(*result);
+ const auto& result_map = assert_cast<const ColumnMap&>(*result);
+ ASSERT_EQ(result_map.get_keys().size(), 1);
+ EXPECT_EQ(result_map.get_keys()[0], key);
+ EXPECT_EQ(result_map.get_values()[0], value);
+}
+
+void check_foreach_v2_state(const AggregateFunctionPtr& function, const
DataTypePtr& result_type,
+ const ColumnString& serialized, Arena& arena,
size_t key_count) {
+ AggregateFunctionGuard received_state(function.get());
+ BufferReadable reader(serialized.get_data_at(0));
+ function->deserialize(received_state.data(), reader, arena);
+ EXPECT_FALSE(reader.has_remaining());
+
+ auto result = result_type->create_column();
+ function->insert_result_into(received_state.data(), *result);
+ const auto& result_array = assert_cast<const ColumnArray&>(*result);
+ ASSERT_EQ(result_array.size(), 1);
+ ASSERT_EQ(result_array.get_offsets()[0], 1);
+ const auto& nested_map = assert_cast<const ColumnMap&>(
+ assert_cast<const
ColumnNullable&>(result_array.get_data()).get_nested_column());
+ EXPECT_EQ(nested_map.get_keys().size(), key_count);
+}
+
+void serialize_foreach_v2_key_frame(int be_exec_version, size_t&
key_frame_size) {
+ const auto key_type = make_nullable(std::make_shared<DataTypeInt32>());
+ const auto value_type = make_nullable(std::make_shared<DataTypeInt32>());
+ const DataTypes argument_types {std::make_shared<DataTypeArray>(key_type),
+
std::make_shared<DataTypeArray>(value_type)};
+ const auto map_type = std::make_shared<DataTypeMap>(key_type, value_type);
+ const auto result_type =
std::make_shared<DataTypeArray>(make_nullable(map_type));
+ auto function = AggregateFunctionSimpleFactory::instance().get(
+ "map_agg_v2_foreachv2", argument_types, result_type, false,
be_exec_version);
+ ASSERT_NE(function, nullptr);
+ function->set_version(be_exec_version);
+
+ constexpr auto key_count = SERIALIZED_MEM_SIZE_LIMIT / sizeof(Int32) + 1;
+ auto key_column = argument_types[0]->create_column();
+ auto value_column = argument_types[1]->create_column();
+ for (size_t i = 0; i < key_count; ++i) {
+ key_column->insert(Field::create_field<TYPE_ARRAY>(
+ Array {Field::create_field<TYPE_INT>(static_cast<Int32>(i +
1))}));
+ value_column->insert(Field::create_field<TYPE_ARRAY>(
+ Array {Field::create_field<TYPE_INT>(static_cast<Int32>(i))}));
+ }
+ const IColumn* input_columns[] = {key_column.get(), value_column.get()};
+
+ Arena arena;
+ AggregateFunctionGuard state(function.get());
+ for (size_t i = 0; i < key_count; ++i) {
+ function->add(state.data(), input_columns, static_cast<ssize_t>(i),
arena);
+ }
+
+ auto serialized_state = ColumnString::create();
+ BufferWritable writer(*serialized_state);
+ function->serialize(state.data(), writer);
+ writer.commit();
+
+ BufferReadable reader(serialized_state->get_data_at(0));
+ size_t dynamic_array_size = 0;
+ reader.read_binary(dynamic_array_size);
+ EXPECT_EQ(dynamic_array_size, 1);
+ PaddedPODArray<UInt8> key_frame;
+ PaddedPODArray<UInt8> value_frame;
+ reader.read_binary(key_frame);
+ reader.read_binary(value_frame);
+ EXPECT_FALSE(reader.has_remaining());
+ key_frame_size = key_frame.size();
+
+ // A newly upgraded BE must read a partial state produced using the
negotiated
+ // legacy format, rather than constructing the nested function at version
17.
+ auto receiving_function = AggregateFunctionSimpleFactory::instance().get(
+ "map_agg_v2_foreachv2", argument_types, result_type, false,
be_exec_version);
+ ASSERT_NE(receiving_function, nullptr);
+ receiving_function->set_version(be_exec_version);
+ check_foreach_v2_state(receiving_function, result_type, *serialized_state,
arena, key_count);
+
+ if (be_exec_version == SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1) {
+ // Build the outer foreach envelope around an old-format nested state
to
+ // simulate a version-16 peer sending to a newly upgraded BE.
+ AggregateFunctionMapAggDataV2<false> legacy_nested({key_type,
value_type}, be_exec_version);
+ for (size_t i = 0; i < key_count; ++i) {
+
legacy_nested.add_single(Field::create_field<TYPE_INT>(static_cast<Int32>(i +
1)),
+
Field::create_field<TYPE_INT>(static_cast<Int32>(i)));
+ }
+ auto legacy_foreach_state = ColumnString::create();
+ BufferWritable legacy_writer(*legacy_foreach_state);
+ legacy_writer.write_binary(size_t {1});
+ legacy_nested.write(legacy_writer);
+ legacy_writer.commit();
+ check_foreach_v2_state(receiving_function, result_type,
*legacy_foreach_state, arena,
+ key_count);
+ }
+}
+
+} // namespace
+
+TEST(AggregateFunctionMapAggV2Test, SupportsIpKeyTypes) {
+ const DataTypes key_types {std::make_shared<DataTypeIPv4>(),
std::make_shared<DataTypeIPv6>()};
+
+ for (const auto& key_type : key_types) {
+ const DataTypes argument_types {make_nullable(key_type),
+
make_nullable(std::make_shared<DataTypeInt32>())};
+ auto function = AggregateFunctionSimpleFactory::instance().get(
+ "map_agg_v2", argument_types, nullptr, false,
+ BeExecVersionManager::get_newest_version());
+
+ ASSERT_NE(function, nullptr) << key_type->get_name();
+
+ auto legacy_function = AggregateFunctionSimpleFactory::instance().get(
+ "map_agg_v2", argument_types, nullptr, false,
+ SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1);
+ EXPECT_EQ(legacy_function, nullptr) << key_type->get_name();
+ }
+}
+
+TEST(AggregateFunctionMapAggV2Test, IpStateUnionAndMergeRoundTrip) {
+ constexpr auto ipv4_key_count = SERIALIZED_MEM_SIZE_LIMIT / sizeof(IPv4) +
1;
+ std::vector<Field> ipv4_keys;
+ ipv4_keys.reserve(ipv4_key_count);
+ for (size_t i = 0; i < ipv4_key_count; ++i) {
+ ipv4_keys.push_back(Field::create_field<TYPE_IPV4>(IPv4(i + 1)));
+ }
+ check_state_union_and_merge_round_trip(std::make_shared<DataTypeIPv4>(),
ipv4_keys);
+
+ constexpr auto ipv6_key_count = SERIALIZED_MEM_SIZE_LIMIT / sizeof(IPv6) +
1;
+ std::vector<Field> ipv6_keys;
+ ipv6_keys.reserve(ipv6_key_count);
+ for (size_t i = 0; i < ipv6_key_count; ++i) {
+ ipv6_keys.push_back(Field::create_field<TYPE_IPV6>(IPv6(i + 1)));
+ }
+ check_state_union_and_merge_round_trip(std::make_shared<DataTypeIPv6>(),
ipv6_keys);
+}
+
+TEST(AggregateFunctionMapAggV2Test, ExactAndLegacyStateFrames) {
+ constexpr auto key_count = SERIALIZED_MEM_SIZE_LIMIT / sizeof(Int32) + 1;
+ std::vector<Field> keys;
+ keys.reserve(key_count);
+ for (size_t i = 0; i < key_count; ++i) {
+ keys.push_back(Field::create_field<TYPE_INT>(static_cast<Int32>(i +
1)));
+ }
+
+ const auto exact_frame_sizes = serialize_and_deserialize_state<true>(
+ std::make_shared<DataTypeInt32>(), keys,
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION);
+ const auto legacy_frame_sizes = serialize_and_deserialize_state<false>(
+ std::make_shared<DataTypeInt32>(), keys,
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1);
+
+ EXPECT_LT(exact_frame_sizes.first, legacy_frame_sizes.first);
+ EXPECT_EQ(exact_frame_sizes.second, legacy_frame_sizes.second);
+}
+
+TEST(AggregateFunctionMapAggV2Test, ReusedScratchHandlesUnequalFrameSizes) {
+ const auto int_type = std::make_shared<DataTypeInt32>();
+ const auto string_type = std::make_shared<DataTypeString>();
+ const auto int_field = Field::create_field<TYPE_INT>(1);
+ const auto string_field =
Field::create_field<TYPE_STRING>(std::string(1024, 'x'));
+
+ check_unequal_frame_round_trip<true>(string_type, string_field, int_type,
int_field,
+
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION);
+ check_unequal_frame_round_trip<true>(int_type, int_field, string_type,
string_field,
+
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION);
+ check_unequal_frame_round_trip<false>(string_type, string_field, int_type,
int_field,
+
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1);
+ check_unequal_frame_round_trip<false>(int_type, int_field, string_type,
string_field,
+
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1);
+}
+
+TEST(AggregateFunctionMapAggV2Test,
PersistedStateCompatibleAcrossFrameVersion) {
+ constexpr auto legacy_version = SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1;
+ const DataTypes argument_types
{make_nullable(std::make_shared<DataTypeInt32>()),
+
make_nullable(std::make_shared<DataTypeInt32>())};
+ auto legacy_state_type =
+ std::make_shared<DataTypeAggState>(argument_types, false,
"map_agg_v2", legacy_version);
+ auto current_state_type = std::make_shared<DataTypeAggState>(
+ argument_types, false, "map_agg_v2",
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION);
+
+ EXPECT_TRUE(legacy_state_type->get_serialized_type()->equals(
+ *current_state_type->get_serialized_type()));
+ EXPECT_NO_THROW(legacy_state_type->check_function_compatibility(
+ SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION));
+}
+
+TEST(AggregateFunctionMapAggV2Test, ForeachV2UsesNegotiatedFrameVersion) {
+ size_t exact_frame_size = 0;
+ size_t legacy_frame_size = 0;
+ serialize_foreach_v2_key_frame(SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION,
exact_frame_size);
+ serialize_foreach_v2_key_frame(SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1,
legacy_frame_size);
+
+ EXPECT_LT(exact_frame_size, legacy_frame_size);
+}
+
+TEST(AggregateFunctionMapAggV2Test,
NestedFunctionsPreserveLegacyWrapperAvailability) {
+ constexpr auto legacy_version = SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1;
+ const auto double_type = std::make_shared<DataTypeFloat64>();
+ const auto array_type = std::make_shared<DataTypeArray>(double_type);
+ const auto result_type =
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeFloat64>()));
+ const std::vector<std::pair<std::string, size_t>> test_cases {
+ {"stddev_samp_foreachv2", 1},
+ {"variance_samp_foreachv2", 1},
+ {"covar_samp_foreachv2", 2},
+ };
+
+ for (const auto& [name, argument_count] : test_cases) {
+ SCOPED_TRACE(name);
+ DataTypes argument_types(argument_count, array_type);
+ AggregateFunctionPtr function;
+ EXPECT_NO_THROW(function =
AggregateFunctionSimpleFactory::instance().get(
+ name, argument_types, result_type, false,
legacy_version));
+ EXPECT_NE(function, nullptr);
+ }
+
+ EXPECT_NE(AggregateFunctionSimpleFactory::instance().get("stddev_samp",
DataTypes {double_type},
+ double_type,
false, legacy_version),
+ nullptr);
+}
+
+TEST(AggregateFunctionMapAggV2Test,
RestrictedAggregatesUseTheirOwnCompatibilityVersion) {
+ constexpr auto legacy_version = SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION - 1;
+ const auto double_type = std::make_shared<DataTypeFloat64>();
+ auto& factory = AggregateFunctionSimpleFactory::instance();
+
+ for (const auto* name :
+ {"variance_samp", "stddev_samp", "covar_samp", "percentile_approx",
+ "percentile_approx_weighted", "percentile", "percentile_array",
"window_funnel"}) {
+ SCOPED_TRACE(name);
+ expect_restricted_aggregate_rejected(name,
SUPPORT_RESTRICTED_AGGREGATES_VERSION - 1);
+ expect_restricted_aggregate_allowed(name,
SUPPORT_RESTRICTED_AGGREGATES_VERSION);
+ expect_restricted_aggregate_allowed(name, legacy_version);
+ expect_restricted_aggregate_allowed(name,
SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION);
+ }
+
+ auto direct =
+ factory.get("stddev_samp", DataTypes {double_type}, double_type,
false, legacy_version);
+ ASSERT_NE(direct, nullptr);
+ auto stored_state = std::make_shared<DataTypeAggState>(DataTypes
{double_type}, false,
+ "stddev_samp",
legacy_version);
+ EXPECT_NO_THROW(
+
stored_state->check_function_compatibility(SUPPORT_MAP_AGG_V2_EXACT_FRAME_VERSION));
+}
+
+} // namespace doris
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index cec742afde5..82064f1ab07 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2019,9 +2019,11 @@ public class Config extends ConfigBase {
public static final int HIVE_OPEN_CSV_MIN_BE_EXEC_VERSION = 15;
// Older backends do not recognize PAIMON_TABLE_SINK and cannot execute
Paimon writes.
public static final int PAIMON_WRITE_MIN_BE_EXEC_VERSION = 16;
+ // Older backends serialize map_agg_v2 keys with the legacy padded frame
format.
+ public static final int MAP_AGG_V2_EXACT_FRAME_MIN_BE_EXEC_VERSION = 17;
@ConfField(mutable = false)
- public static int max_be_exec_version = PAIMON_WRITE_MIN_BE_EXEC_VERSION;
+ public static int max_be_exec_version =
MAP_AGG_V2_EXACT_FRAME_MIN_BE_EXEC_VERSION;
/**
* Min data version of backends serialize block.
diff --git
a/regression-test/data/datatype_p0/agg_state/map/test_map_agg_v2_versioned_state.out
b/regression-test/data/datatype_p0/agg_state/map/test_map_agg_v2_versioned_state.out
new file mode 100644
index 00000000000..4c103c707d6
--- /dev/null
+++
b/regression-test/data/datatype_p0/agg_state/map/test_map_agg_v2_versioned_state.out
@@ -0,0 +1,6 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !before_compaction --
+1 4
+
+-- !after_compaction --
+1 4
diff --git
a/regression-test/data/datatype_p0/agg_state/test_restricted_aggregate_versioned_state.out
b/regression-test/data/datatype_p0/agg_state/test_restricted_aggregate_versioned_state.out
new file mode 100644
index 00000000000..38d3036eaa9
--- /dev/null
+++
b/regression-test/data/datatype_p0/agg_state/test_restricted_aggregate_versioned_state.out
@@ -0,0 +1,15 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !direct_version_15 --
+1
+
+-- !stored_version_15_at_16 --
+1
+
+-- !direct_version_16 --
+1
+
+-- !stored_version_15_at_17 --
+1
+
+-- !direct_version_17 --
+1
diff --git a/regression-test/data/datatype_p0/ip/test_map_agg_v2_ip_key.out
b/regression-test/data/datatype_p0/ip/test_map_agg_v2_ip_key.out
new file mode 100644
index 00000000000..a917817b436
--- /dev/null
+++ b/regression-test/data/datatype_p0/ip/test_map_agg_v2_ip_key.out
@@ -0,0 +1,12 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !ipv4_key --
+["192.168.0.1", "192.168.0.2"]
+
+-- !ipv6_key --
+["2001:db8::1", "2001:db8::2"]
+
+-- !ipv4_state_merge --
+65
+
+-- !ipv6_state_merge --
+17
diff --git
a/regression-test/suites/datatype_p0/agg_state/map/test_map_agg_v2_versioned_state.groovy
b/regression-test/suites/datatype_p0/agg_state/map/test_map_agg_v2_versioned_state.groovy
new file mode 100644
index 00000000000..f6b43537eec
--- /dev/null
+++
b/regression-test/suites/datatype_p0/agg_state/map/test_map_agg_v2_versioned_state.groovy
@@ -0,0 +1,91 @@
+// 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.
+
+suite("test_map_agg_v2_versioned_state", "nonConcurrent") {
+ if (isCloudMode()) {
+ return
+ }
+
+ def originalVersion = sql("ADMIN SHOW FRONTEND CONFIG LIKE
'be_exec_version'")[0][1]
+ try {
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' = '16')"
+ sql "SET enable_agg_state = true"
+ sql "DROP TABLE IF EXISTS test_map_agg_v2_versioned_state"
+ sql """
+ CREATE TABLE test_map_agg_v2_versioned_state (
+ k INT,
+ state AGG_STATE<map_agg_v2(INT, INT)> GENERIC
+ ) AGGREGATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "disable_auto_compaction" = "true"
+ )
+ """
+ for (int key = 1; key <= 4; key++) {
+ sql "INSERT INTO test_map_agg_v2_versioned_state VALUES (1,
map_agg_v2_state(${key}, ${key * 10}))"
+ }
+
+ // The column and its rowsets retain version 16 after the FE switches
to version 17.
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' = '17')"
+ order_qt_before_compaction """
+ SELECT k, map_size(map_agg_v2_merge(state))
+ FROM test_map_agg_v2_versioned_state GROUP BY k ORDER BY k
+ """
+
+ def tablets = sql_return_maparray "SHOW TABLETS FROM
test_map_agg_v2_versioned_state"
+ assertEquals(1, tablets.size())
+ def tabletId = tablets[0].TabletId
+ def backendId = tablets[0].BackendId
+ def backendIdToIp = [:]
+ def backendIdToHttpPort = [:]
+ getBackendIpHttpPort(backendIdToIp, backendIdToHttpPort)
+ def beHost = backendIdToIp["${backendId}"]
+ def bePort = backendIdToHttpPort["${backendId}"]
+ def showTabletCompaction = {
+ def (code, stdout, stderr) = be_show_tablet_status(beHost, bePort,
tabletId)
+ assertEquals(0, code)
+ return parseJson(stdout.trim())
+ }
+ def countDataRowsets = { status ->
+ return status.rowsets.findAll { it.contains(" DATA ") }.size()
+ }
+ def beforeCount = countDataRowsets(showTabletCompaction())
+ assertTrue(beforeCount >= 2)
+
+ sql "ADMIN COMPACT TABLE test_map_agg_v2_versioned_state PARTITION
(test_map_agg_v2_versioned_state) WHERE type = 'full'"
+ def deadline = System.currentTimeMillis() + 60000L
+ def afterCompaction
+ while (System.currentTimeMillis() < deadline) {
+ afterCompaction = showTabletCompaction()
+ if (afterCompaction["last full status"] == "[OK]" &&
+ countDataRowsets(afterCompaction) < beforeCount) {
+ break
+ }
+ sleep(500)
+ }
+ assertEquals("[OK]", afterCompaction["last full status"])
+ assertTrue(countDataRowsets(afterCompaction) < beforeCount)
+
+ order_qt_after_compaction """
+ SELECT k, map_size(map_agg_v2_merge(state))
+ FROM test_map_agg_v2_versioned_state GROUP BY k ORDER BY k
+ """
+ } finally {
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' =
'${originalVersion}')"
+ }
+}
diff --git
a/regression-test/suites/datatype_p0/agg_state/test_restricted_aggregate_versioned_state.groovy
b/regression-test/suites/datatype_p0/agg_state/test_restricted_aggregate_versioned_state.groovy
new file mode 100644
index 00000000000..edb9d3566e1
--- /dev/null
+++
b/regression-test/suites/datatype_p0/agg_state/test_restricted_aggregate_versioned_state.groovy
@@ -0,0 +1,72 @@
+// 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.
+
+suite("test_restricted_aggregate_versioned_state", "nonConcurrent") {
+ if (isCloudMode()) {
+ return
+ }
+
+ def originalVersion = sql("ADMIN SHOW FRONTEND CONFIG LIKE
'be_exec_version'")[0][1]
+ try {
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' = '15')"
+ sql "SET enable_agg_state = true"
+ sql "DROP TABLE IF EXISTS test_restricted_aggregate_versioned_source"
+ sql """
+ CREATE TABLE test_restricted_aggregate_versioned_source (
+ id INT,
+ x DOUBLE
+ ) DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql "INSERT INTO test_restricted_aggregate_versioned_source VALUES (1,
1.0), (2, 2.0), (3, 3.0)"
+ order_qt_direct_version_15 """
+ SELECT stddev_samp(x) FROM
test_restricted_aggregate_versioned_source
+ """
+
+ sql "DROP TABLE IF EXISTS test_restricted_aggregate_versioned_state"
+ sql """
+ CREATE TABLE test_restricted_aggregate_versioned_state (
+ k INT,
+ state AGG_STATE<stddev_samp(DOUBLE)> GENERIC
+ ) AGGREGATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ INSERT INTO test_restricted_aggregate_versioned_state
+ SELECT 1, stddev_samp_state(x) FROM
test_restricted_aggregate_versioned_source
+ """
+
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' = '16')"
+ order_qt_stored_version_15_at_16 """
+ SELECT stddev_samp_merge(state) FROM
test_restricted_aggregate_versioned_state
+ """
+ order_qt_direct_version_16 """
+ SELECT stddev_samp(x) FROM
test_restricted_aggregate_versioned_source
+ """
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' = '17')"
+ order_qt_stored_version_15_at_17 """
+ SELECT stddev_samp_merge(state) FROM
test_restricted_aggregate_versioned_state
+ """
+ order_qt_direct_version_17 """
+ SELECT stddev_samp(x) FROM
test_restricted_aggregate_versioned_source
+ """
+ } finally {
+ sql "ADMIN SET FRONTEND CONFIG ('be_exec_version' =
'${originalVersion}')"
+ }
+}
diff --git
a/regression-test/suites/datatype_p0/ip/test_map_agg_v2_ip_key.groovy
b/regression-test/suites/datatype_p0/ip/test_map_agg_v2_ip_key.groovy
new file mode 100644
index 00000000000..3f3887b775d
--- /dev/null
+++ b/regression-test/suites/datatype_p0/ip/test_map_agg_v2_ip_key.groovy
@@ -0,0 +1,77 @@
+// 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.
+
+suite("test_map_agg_v2_ip_key") {
+ sql "DROP TABLE IF EXISTS test_map_agg_v2_ip_key"
+ sql """
+ CREATE TABLE test_map_agg_v2_ip_key (
+ id INT,
+ ipv4_key IPV4,
+ ipv6_key IPV6,
+ value INT
+ )
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+
+ sql """
+ INSERT INTO test_map_agg_v2_ip_key VALUES
+ (1, '192.168.0.1', '2001:db8::1', 10),
+ (1, '192.168.0.2', '2001:db8::2', 20)
+ """
+
+ sql """
+ INSERT INTO test_map_agg_v2_ip_key
+ SELECT
+ 2,
+ CAST(CONCAT('10.0.0.', CAST(number + 1 AS STRING)) AS IPV4),
+ CAST(CONCAT('2001:db8::', CAST(number + 1 AS STRING)) AS IPV6),
+ CAST(number AS INT)
+ FROM numbers("number" = "65")
+ """
+
+ qt_ipv4_key """
+ SELECT array_sort(map_keys(map_agg_v2(ipv4_key, value)))
+ FROM test_map_agg_v2_ip_key
+ WHERE id = 1
+ """
+
+ qt_ipv6_key """
+ SELECT array_sort(map_keys(map_agg_v2(ipv6_key, value)))
+ FROM test_map_agg_v2_ip_key
+ WHERE id = 1
+ """
+
+ qt_ipv4_state_merge """
+ SELECT map_size(map_agg_v2_merge(state))
+ FROM (
+ SELECT map_agg_v2_state(ipv4_key, value) AS state
+ FROM test_map_agg_v2_ip_key
+ WHERE id = 2
+ ) states
+ """
+
+ qt_ipv6_state_merge """
+ SELECT map_size(map_agg_v2_merge(state))
+ FROM (
+ SELECT map_agg_v2_state(ipv6_key, value) AS state
+ FROM test_map_agg_v2_ip_key
+ WHERE id = 2 AND value < 17
+ ) states
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]