This is an automated email from the ASF dual-hosted git repository.
Mryange 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 92b46f325ee [fix](function) Fix window_funnel missing boundary events
when window overflows at datetime upper bound (#67823)
92b46f325ee is described below
commit 92b46f325eee7339ad659520142c01762302d4cc
Author: Mryange <[email protected]>
AuthorDate: Tue Sep 15 10:32:43 2026 +0800
[fix](function) Fix window_funnel missing boundary events when window
overflows at datetime upper bound (#67823)
For timestamps near the DATETIME upper bound (e.g. `9999-12-31
23:59:58`), adding the sliding window seconds in
`date_add_interval<SECOND>` overflows the date range. Both
`window_funnel` and `window_funnel_v2` ignored the `false` return value,
leaving the window end timestamp at the starting point, so the boundary
event inside the enlarged window was missed: `window=1` returned 2 while
`window=10` returned 1.
### 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 -->
---
.../aggregate/aggregate_function_window_funnel.h | 20 +++----
.../aggregate_function_window_funnel_v2.h | 8 +--
be/test/exprs/aggregate/vec_window_funnel_test.cpp | 62 ++++++++++++++++++++++
.../exprs/aggregate/vec_window_funnel_v2_test.cpp | 62 ++++++++++++++++++++++
4 files changed, 134 insertions(+), 18 deletions(-)
diff --git a/be/src/exprs/aggregate/aggregate_function_window_funnel.h
b/be/src/exprs/aggregate/aggregate_function_window_funnel.h
index 2ecacd5b083..7b8dff2ff33 100644
--- a/be/src/exprs/aggregate/aggregate_function_window_funnel.h
+++ b/be/src/exprs/aggregate/aggregate_function_window_funnel.h
@@ -160,8 +160,8 @@ struct WindowFunnelState {
}
}
- bool _within_window(const DateValueType& base_timestamp, const
DateValueType& current_timestamp,
- const DateValueType& end_timestamp) const {
+ bool _within_window(const DateValueType& base_timestamp,
+ const DateValueType& current_timestamp) const {
if constexpr (T == TYPE_TIMESTAMP_NS) {
const auto elapsed_nanos =
static_cast<__int128>(current_timestamp.epoch_nanos()) -
base_timestamp.epoch_nanos();
@@ -169,14 +169,13 @@ struct WindowFunnelState {
static_cast<__int128>(window) *
TimeStampNsValue::NANOS_PER_SECOND;
return elapsed_nanos <= window_nanos;
}
- return current_timestamp <= end_timestamp;
+ return
static_cast<__int128>(current_timestamp.datetime_diff_in_microseconds(
+ base_timestamp)) <= static_cast<__int128>(window) *
1000000;
}
template <WindowFunnelMode WINDOW_FUNNEL_MODE>
int _match_event_list(size_t& start_row, size_t row_count) const {
int matched_count = 0;
- DateValueType end_timestamp;
-
if (window < 0) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"the sliding time window must be a positive
integer, but got: {}",
@@ -190,12 +189,6 @@ struct WindowFunnelState {
if (match_row < row_count) {
auto prev_timestamp = timestamp_data[match_row];
const auto first_timestamp = prev_timestamp;
- if constexpr (T != TYPE_TIMESTAMP_NS) {
- TimeInterval interval(SECOND, window, false);
- end_timestamp = first_timestamp;
- end_timestamp.template date_add_interval<SECOND>(interval);
- }
-
matched_count++;
column_idx++;
auto last_match_row = match_row;
@@ -205,7 +198,7 @@ struct WindowFunnelState {
if constexpr (WINDOW_FUNNEL_MODE == WindowFunnelMode::FIXED) {
if (event_data[match_row] == 1) {
auto current_timestamp = timestamp_data[match_row];
- if (_within_window(first_timestamp, current_timestamp,
end_timestamp)) {
+ if (_within_window(first_timestamp,
current_timestamp)) {
matched_count++;
continue;
}
@@ -215,8 +208,7 @@ struct WindowFunnelState {
match_row = simd::find_one(event_data.data(), match_row,
row_count);
if (match_row < row_count) {
auto current_timestamp = timestamp_data[match_row];
- bool is_matched =
- _within_window(first_timestamp, current_timestamp,
end_timestamp);
+ bool is_matched = _within_window(first_timestamp,
current_timestamp);
if (is_matched) {
if constexpr (WINDOW_FUNNEL_MODE ==
WindowFunnelMode::INCREASE) {
is_matched = current_timestamp > prev_timestamp;
diff --git a/be/src/exprs/aggregate/aggregate_function_window_funnel_v2.h
b/be/src/exprs/aggregate/aggregate_function_window_funnel_v2.h
index af2fac10e73..b32c7bae3a7 100644
--- a/be/src/exprs/aggregate/aggregate_function_window_funnel_v2.h
+++ b/be/src/exprs/aggregate/aggregate_function_window_funnel_v2.h
@@ -259,10 +259,10 @@ struct WindowFunnelStateV2 {
static_cast<__int128>(window) *
TimeStampNsValue::NANOS_PER_SECOND;
return elapsed_nanos <= window_nanos;
} else {
- DateValueType end_ts = _ts_from_int(base_ts);
- TimeInterval interval(SECOND, window, false);
- end_ts.template date_add_interval<SECOND>(interval);
- return current_ts <= end_ts.to_date_int_val();
+ const auto base = _ts_from_int(base_ts);
+ const auto current = _ts_from_int(current_ts);
+ return
static_cast<__int128>(current.datetime_diff_in_microseconds(base)) <=
+ static_cast<__int128>(window) * 1000000;
}
}
diff --git a/be/test/exprs/aggregate/vec_window_funnel_test.cpp
b/be/test/exprs/aggregate/vec_window_funnel_test.cpp
index ed87928eec2..6edc0db5ce4 100644
--- a/be/test/exprs/aggregate/vec_window_funnel_test.cpp
+++ b/be/test/exprs/aggregate/vec_window_funnel_test.cpp
@@ -145,6 +145,68 @@ TEST_F(VWindowFunnelTest, testEmpty) {
agg_function->destroy(place2);
}
+TEST_F(VWindowFunnelTest, testWindowOverflow) {
+ AggregateFunctionSimpleFactory factory =
AggregateFunctionSimpleFactory::instance();
+ DataTypes data_types = {std::make_shared<DataTypeInt64>(),
std::make_shared<DataTypeString>(),
+ std::make_shared<DataTypeDateTimeV2>(),
+ std::make_shared<DataTypeUInt8>(),
std::make_shared<DataTypeUInt8>()};
+ auto overflow_agg_function = factory.get("window_funnel", data_types,
nullptr, false,
+
BeExecVersionManager::get_newest_version());
+ ASSERT_NE(overflow_agg_function, nullptr);
+
+ auto column_mode = ColumnString::create();
+ column_mode->insert(Field::create_field<TYPE_STRING>("default"));
+ column_mode->insert(Field::create_field<TYPE_STRING>("default"));
+
+ auto column_timestamp = ColumnDateTimeV2::create();
+ for (const auto& second : {58, 59}) {
+ VecDateTimeValue time_value;
+ time_value.unchecked_set_time(9999, 12, 31, 23, 59, second);
+ auto dtv2 = time_value.to_datetime_v2();
+ column_timestamp->insert_data((char*)&dtv2, 0);
+ }
+
+ auto column_window = ColumnInt64::create();
+ column_window->insert(Field::create_field<TYPE_BIGINT>(10));
+ column_window->insert(Field::create_field<TYPE_BIGINT>(10));
+ auto column_event1 = ColumnUInt8::create();
+ column_event1->insert(Field::create_field<TYPE_BOOLEAN>(1));
+ column_event1->insert(Field::create_field<TYPE_BOOLEAN>(0));
+ auto column_event2 = ColumnUInt8::create();
+ column_event2->insert(Field::create_field<TYPE_BOOLEAN>(0));
+ column_event2->insert(Field::create_field<TYPE_BOOLEAN>(1));
+
+ std::unique_ptr<char[]> memory(new
char[overflow_agg_function->size_of_data()]);
+ AggregateDataPtr place = memory.get();
+ overflow_agg_function->create(place);
+ const IColumn* columns[] = {column_window.get(), column_mode.get(),
column_timestamp.get(),
+ column_event1.get(), column_event2.get()};
+ for (int row = 0; row < 2; ++row) {
+ overflow_agg_function->add(place, columns, row, arena);
+ }
+
+ ColumnInt32 result;
+ overflow_agg_function->insert_result_into(place, result);
+ EXPECT_EQ(result.get_element(0), 2);
+ overflow_agg_function->destroy(place);
+}
+
+TEST(VWindowFunnelDateTimeV2Test, PreservesMicrosecondWindowBoundary) {
+ WindowFunnelState<TYPE_DATETIMEV2> state(2);
+ state.window = 1;
+ state.window_funnel_mode = WindowFunnelMode::DEFAULT;
+ DateV2Value<DateTimeV2ValueType> base;
+ DateV2Value<DateTimeV2ValueType> exact;
+ DateV2Value<DateTimeV2ValueType> outside;
+ base.unchecked_set_time(9999, 12, 31, 23, 59, 58, 0);
+ exact.unchecked_set_time(9999, 12, 31, 23, 59, 59, 0);
+ outside.unchecked_set_time(9999, 12, 31, 23, 59, 59, 1);
+ EXPECT_TRUE(state._within_window(base, exact));
+ EXPECT_FALSE(state._within_window(base, outside));
+ state.window = std::numeric_limits<int64_t>::max();
+ EXPECT_TRUE(state._within_window(base, outside));
+}
+
TEST_F(VWindowFunnelTest, testSerialize) {
const int NUM_CONDS = 4;
auto column_mode = ColumnString::create();
diff --git a/be/test/exprs/aggregate/vec_window_funnel_v2_test.cpp
b/be/test/exprs/aggregate/vec_window_funnel_v2_test.cpp
index 9b907d153d1..bf25af9df8d 100644
--- a/be/test/exprs/aggregate/vec_window_funnel_v2_test.cpp
+++ b/be/test/exprs/aggregate/vec_window_funnel_v2_test.cpp
@@ -118,6 +118,68 @@ TEST_F(VWindowFunnelV2Test, testEmpty) {
agg_function->destroy(place2);
}
+TEST_F(VWindowFunnelV2Test, testWindowOverflow) {
+ AggregateFunctionSimpleFactory factory =
AggregateFunctionSimpleFactory::instance();
+ DataTypes data_types = {std::make_shared<DataTypeInt64>(),
std::make_shared<DataTypeString>(),
+ std::make_shared<DataTypeDateTimeV2>(),
+ std::make_shared<DataTypeUInt8>(),
std::make_shared<DataTypeUInt8>()};
+ auto overflow_agg_function = factory.get("window_funnel_v2", data_types,
nullptr, false,
+
BeExecVersionManager::get_newest_version());
+ ASSERT_NE(overflow_agg_function, nullptr);
+
+ auto column_mode = ColumnString::create();
+ column_mode->insert(Field::create_field<TYPE_STRING>("default"));
+ column_mode->insert(Field::create_field<TYPE_STRING>("default"));
+
+ auto column_timestamp = ColumnDateTimeV2::create();
+ for (const auto& second : {58, 59}) {
+ VecDateTimeValue time_value;
+ time_value.unchecked_set_time(9999, 12, 31, 23, 59, second);
+ auto dtv2 = time_value.to_datetime_v2();
+ column_timestamp->insert_data((char*)&dtv2, 0);
+ }
+
+ auto column_window = ColumnInt64::create();
+ column_window->insert(Field::create_field<TYPE_BIGINT>(10));
+ column_window->insert(Field::create_field<TYPE_BIGINT>(10));
+ auto column_event1 = ColumnUInt8::create();
+ column_event1->insert(Field::create_field<TYPE_BOOLEAN>(1));
+ column_event1->insert(Field::create_field<TYPE_BOOLEAN>(0));
+ auto column_event2 = ColumnUInt8::create();
+ column_event2->insert(Field::create_field<TYPE_BOOLEAN>(0));
+ column_event2->insert(Field::create_field<TYPE_BOOLEAN>(1));
+
+ std::unique_ptr<char[]> memory(new
char[overflow_agg_function->size_of_data()]);
+ AggregateDataPtr place = memory.get();
+ overflow_agg_function->create(place);
+ const IColumn* columns[] = {column_window.get(), column_mode.get(),
column_timestamp.get(),
+ column_event1.get(), column_event2.get()};
+ for (int row = 0; row < 2; ++row) {
+ overflow_agg_function->add(place, columns, row, arena);
+ }
+
+ ColumnInt32 result;
+ overflow_agg_function->insert_result_into(place, result);
+ EXPECT_EQ(result.get_element(0), 2);
+ overflow_agg_function->destroy(place);
+}
+
+TEST(VWindowFunnelV2DateTimeV2Test, PreservesMicrosecondWindowBoundary) {
+ WindowFunnelStateV2<TYPE_DATETIMEV2> state(2);
+ state.window = 1;
+ state.window_funnel_mode = WindowFunnelMode::DEFAULT;
+ DateV2Value<DateTimeV2ValueType> base;
+ DateV2Value<DateTimeV2ValueType> exact;
+ DateV2Value<DateTimeV2ValueType> outside;
+ base.unchecked_set_time(9999, 12, 31, 23, 59, 58, 0);
+ exact.unchecked_set_time(9999, 12, 31, 23, 59, 59, 0);
+ outside.unchecked_set_time(9999, 12, 31, 23, 59, 59, 1);
+ EXPECT_TRUE(state._within_window(base.to_date_int_val(),
exact.to_date_int_val()));
+ EXPECT_FALSE(state._within_window(base.to_date_int_val(),
outside.to_date_int_val()));
+ state.window = std::numeric_limits<int64_t>::max();
+ EXPECT_TRUE(state._within_window(base.to_date_int_val(),
outside.to_date_int_val()));
+}
+
TEST_F(VWindowFunnelV2Test, testSerialize) {
const int NUM_CONDS = 4;
auto column_mode = ColumnString::create();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]