This is an automated email from the ASF dual-hosted git repository.

morningman 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 387e1a92a0b [improvement](be) Prune rowsets by TSO before row binlog 
scans (#68050)
387e1a92a0b is described below

commit 387e1a92a0b7d9623825232bfd508c8f27acda23
Author: HappenLee <[email protected]>
AuthorDate: Wed Sep 16 19:22:11 2026 +0800

    [improvement](be) Prune rowsets by TSO before row binlog scans (#68050)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #68012 (merged into `branch-incremental-computation`); this
    PR ports the same change to `master`.
    
    Problem Summary:
    
    Bounded ROW binlog queries initialize rowset readers and open segment
    footers before their TSO predicates reject historical data. A narrow
    time window can therefore pay initialization costs proportional to the
    retained history.
    
    Prune the captured read source before cloning readers or creating
    scanners. For query `[100, 200)`, a rowset with inclusive commit TSO
    range `[10, 99]` can be skipped using its metadata alone.
    
    - Preserve rowsets with unknown TSO endpoints and those that overlap the
    query window, including compacted rowsets. Existing segment/row
    predicates handle the remaining data.
    - Preserve the captured visible-version snapshot and separately captured
    delete predicates.
    - Skip a tablet when only empty rowsets remain, including bootstrap
    rowsets with no TSO. This also prevents a fully pruned source from being
    recaptured during scanner initialization.
    - Add `RowsetTsoPruneTime`, `RowsetsPrunedByTso`, `SegmentsPrunedByTso`,
    and `TabletsPrunedByTso` to the query profile.
    
    The filtering uses `std::erase_if` on the existing rowset vector and
    `std::all_of` for the empty-tablet check. It adds no storage-format or
    protocol changes.
---
 be/src/exec/operator/olap_scan_operator.cpp        |  50 ++++++
 be/src/exec/operator/olap_scan_operator.h          |   6 +
 be/test/exec/operator/olap_scan_operator_test.cpp  | 168 +++++++++++++++++++++
 .../test_binlog_rowset_tso_pruning.out             |  59 ++++++++
 .../test_binlog_rowset_tso_pruning.groovy          | 102 +++++++++++++
 5 files changed, 385 insertions(+)

diff --git a/be/src/exec/operator/olap_scan_operator.cpp 
b/be/src/exec/operator/olap_scan_operator.cpp
index d6e83c3e427..621ce2a1576 100644
--- a/be/src/exec/operator/olap_scan_operator.cpp
+++ b/be/src/exec/operator/olap_scan_operator.cpp
@@ -19,6 +19,7 @@
 
 #include <fmt/format.h>
 
+#include <algorithm>
 #include <memory>
 #include <numeric>
 #include <optional>
@@ -140,6 +141,13 @@ Status OlapScanLocalState::_init_profile() {
     // 2. init timer and counters
     _reader_init_timer = ADD_TIMER(_scanner_profile, "ReaderInitTime");
     _scanner_init_timer = ADD_TIMER(_scanner_profile, "ScannerInitTime");
+    _rowset_tso_prune_timer = ADD_TIMER(custom_profile(), 
"RowsetTsoPruneTime");
+    _rowsets_pruned_by_tso_counter =
+            ADD_COUNTER(custom_profile(), "RowsetsPrunedByTso", TUnit::UNIT);
+    _segments_pruned_by_tso_counter =
+            ADD_COUNTER(custom_profile(), "SegmentsPrunedByTso", TUnit::UNIT);
+    _tablets_pruned_by_tso_counter =
+            ADD_COUNTER(custom_profile(), "TabletsPrunedByTso", TUnit::UNIT);
     _process_conjunct_timer = ADD_TIMER(custom_profile(), 
"ProcessConjunctTime");
     _read_compressed_counter = ADD_COUNTER(_segment_profile, 
"CompressedBytesRead", TUnit::BYTES);
     _read_uncompressed_counter =
@@ -634,6 +642,32 @@ bool OlapScanLocalState::_is_binlog_merge_scan() const {
     return scan_type == TBinlogScanType::MIN_DELTA || scan_type == 
TBinlogScanType::DETAIL;
 }
 
+void OlapScanLocalState::_prune_rowsets_by_tso(const TPaloScanRange& 
scan_range,
+                                               TabletReadSource& read_source) {
+    SCOPED_TIMER(_rowset_tso_prune_timer);
+    int64_t pruned_segments = 0;
+    const auto pruned_rowsets = std::erase_if(read_source.rs_splits, [&](const 
auto& split) {
+        const auto& rowset = split.rs_reader->rowset();
+        const auto tso = rowset->commit_tso();
+        // Old rowsets can lack commit TSO metadata, including compaction 
inputs with an
+        // unknown endpoint. Keep them for the existing segment/row-level 
predicates.
+        if (tso.start_tso() < 0 || tso.end_tso() < 0) {
+            return false;
+        }
+        DCHECK_LE(tso.start_tso(), tso.end_tso());
+        // Rowset metadata is inclusive [min, max]; the query is half-open 
[start, end).
+        const bool outside_window =
+                (scan_range.__isset.start_tso && tso.end_tso() < 
scan_range.start_tso) ||
+                (scan_range.__isset.end_tso && tso.start_tso() >= 
scan_range.end_tso);
+        if (outside_window) {
+            pruned_segments += rowset->num_segments();
+        }
+        return outside_window;
+    });
+    COUNTER_UPDATE(_rowsets_pruned_by_tso_counter, pruned_rowsets);
+    COUNTER_UPDATE(_segments_pruned_by_tso_counter, pruned_segments);
+}
+
 Status OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
     if (_scan_ranges.empty()) {
         _eos = true;
@@ -794,6 +828,22 @@ Status 
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
     int scanners_per_tablet = std::max(1, 64 / (int)_scan_ranges.size());
     for (size_t scan_range_idx = 0; scan_range_idx < _scan_ranges.size(); 
scan_range_idx++) {
         const auto& palo_scan_range = *_scan_ranges[scan_range_idx];
+        if (read_row_binlog &&
+            (palo_scan_range.__isset.start_tso || 
palo_scan_range.__isset.end_tso)) {
+            auto& read_source = _read_sources[scan_range_idx];
+            // The version-consistent read source and delete predicates have 
already been
+            // captured. Prune before cloning readers or opening any segment 
footers.
+            _prune_rowsets_by_tso(palo_scan_range, read_source);
+            if (std::all_of(read_source.rs_splits.begin(), 
read_source.rs_splits.end(),
+                            [](const auto& split) {
+                                return split.rs_reader->rowset()->num_rows() 
== 0;
+                            })) {
+                // Empty bootstrap rowsets may have no TSO. Skip the tablet 
even if those
+                // remain, and do not let OlapScanner recapture an empty read 
source.
+                COUNTER_UPDATE(_tablets_pruned_by_tso_counter, 1);
+                continue;
+            }
+        }
         int64_t version = 0;
         std::from_chars(palo_scan_range.version.data(),
                         palo_scan_range.version.data() + 
palo_scan_range.version.size(), version);
diff --git a/be/src/exec/operator/olap_scan_operator.h 
b/be/src/exec/operator/olap_scan_operator.h
index 11d40452f93..66d96c024ce 100644
--- a/be/src/exec/operator/olap_scan_operator.h
+++ b/be/src/exec/operator/olap_scan_operator.h
@@ -135,6 +135,8 @@ private:
 
     Status _init_scanners(std::list<ScannerSPtr>* scanners) override;
 
+    void _prune_rowsets_by_tso(const TPaloScanRange& scan_range, 
TabletReadSource& read_source);
+
     Status _build_key_ranges_and_filters();
 
     bool _is_tablet_pruned_by_runtime_filter(int64_t partition_id, int32_t 
bucket_seq,
@@ -165,6 +167,10 @@ private:
     RuntimeProfile::Counter* _key_range_counter = nullptr;
     RuntimeProfile::Counter* _reader_init_timer = nullptr;
     RuntimeProfile::Counter* _scanner_init_timer = nullptr;
+    RuntimeProfile::Counter* _rowset_tso_prune_timer = nullptr;
+    RuntimeProfile::Counter* _rowsets_pruned_by_tso_counter = nullptr;
+    RuntimeProfile::Counter* _segments_pruned_by_tso_counter = nullptr;
+    RuntimeProfile::Counter* _tablets_pruned_by_tso_counter = nullptr;
     RuntimeProfile::Counter* _process_conjunct_timer = nullptr;
 
     RuntimeProfile::Counter* _io_timer = nullptr;
diff --git a/be/test/exec/operator/olap_scan_operator_test.cpp 
b/be/test/exec/operator/olap_scan_operator_test.cpp
index bc66290106d..4cc86c526ee 100644
--- a/be/test/exec/operator/olap_scan_operator_test.cpp
+++ b/be/test/exec/operator/olap_scan_operator_test.cpp
@@ -20,11 +20,13 @@
 #include <gtest/gtest.h>
 
 #include <memory>
+#include <optional>
 
 #include "common/object_pool.h"
 #include "core/data_type/data_type_number.h"
 #include "gen_cpp/PlanNodes_types.h"
 #include "gen_cpp/QueryCache_types.h"
+#include "storage/rowset/beta_rowset.h"
 #include "testutil/desc_tbl_builder.h"
 #include "testutil/mock/mock_runtime_state.h"
 
@@ -93,4 +95,170 @@ TEST_F(OlapScanOperatorBinlogPushDownTest, 
AppendOnlyKeepsValuePredicatePushDown
     EXPECT_TRUE(_local_state->can_push_down_column_predicate(_value_slot));
 }
 
+class OlapScanOperatorTsoPruningTest : public 
OlapScanOperatorBinlogPushDownTest {
+protected:
+    void SetUp() override {
+        OlapScanOperatorBinlogPushDownTest::SetUp();
+        _parent->_olap_scan_node.__set_read_row_binlog(true);
+        _profile = std::make_unique<RuntimeProfile>("TsoPruningTest");
+        _local_state->_scanner_init_timer = ADD_TIMER(_profile, 
"ScannerInitTime");
+        _local_state->_rowset_tso_prune_timer = ADD_TIMER(_profile, 
"RowsetTsoPruneTime");
+        _local_state->_rowsets_pruned_by_tso_counter =
+                ADD_COUNTER(_profile, "RowsetsPrunedByTso", TUnit::UNIT);
+        _local_state->_segments_pruned_by_tso_counter =
+                ADD_COUNTER(_profile, "SegmentsPrunedByTso", TUnit::UNIT);
+        _local_state->_tablets_pruned_by_tso_counter =
+                ADD_COUNTER(_profile, "TabletsPrunedByTso", TUnit::UNIT);
+        _local_state->_scan_dependency = Dependency::create_shared(0, 0, 
"Scan", false);
+    }
+
+    RowsetReaderSharedPtr make_reader(std::optional<TsoRange> tso, int64_t 
segments = 1,
+                                      int64_t rows = 1) {
+        auto meta = std::make_shared<RowsetMeta>();
+        meta->set_version(tso && tso->start_tso() == tso->end_tso() ? 
Version(2, 2)
+                                                                    : 
Version(2, 4));
+        meta->set_num_segments(segments);
+        meta->set_num_rows(rows);
+        if (tso) {
+            meta->set_commit_tso(*tso);
+        }
+        auto rowset = 
std::make_shared<BetaRowset>(std::make_shared<TabletSchema>(), meta,
+                                                   
"/nonexistent/tso_pruning_test");
+        RowsetReaderSharedPtr reader;
+        EXPECT_TRUE(rowset->create_reader(&reader).ok());
+        return reader;
+    }
+
+    std::unique_ptr<RuntimeProfile> _profile;
+};
+
+TEST_F(OlapScanOperatorTsoPruningTest, 
HalfOpenBoundsPreserveOverlappingAndUnknownRowsets) {
+    TPaloScanRange range;
+    range.__set_start_tso(100);
+    range.__set_end_tso(200);
+
+    auto before = make_reader(TsoRange {99, 99}, 2);
+    auto lower = make_reader(TsoRange {100, 100});
+    auto inside = make_reader(TsoRange {199, 199}, 3);
+    auto upper = make_reader(TsoRange {200, 200}, 4);
+    auto compacted_before = make_reader(TsoRange {10, 99}, 2);
+    auto touches_lower = make_reader(TsoRange {90, 100}, 3);
+    auto compacted_inside = make_reader(TsoRange {120, 170}, 2);
+    auto spans_window = make_reader(TsoRange {0, 250}, 6);
+    auto compacted_after = make_reader(TsoRange {200, 250}, 5);
+    auto missing = make_reader(std::nullopt, 2);
+    auto unknown_lower = make_reader(TsoRange {-1, 170});
+    auto unknown_upper = make_reader(TsoRange {100, -1});
+
+    TabletReadSource source;
+    for (const auto& reader :
+         {before, lower, inside, upper, compacted_before, touches_lower, 
compacted_inside,
+          spans_window, compacted_after, missing, unknown_lower, 
unknown_upper}) {
+        source.rs_splits.emplace_back(reader);
+    }
+    // Pruning the data source must not discard separately captured delete 
predicates.
+    source.delete_predicates.push_back(before->rowset()->rowset_meta());
+    _local_state->_prune_rowsets_by_tso(range, source);
+
+    const std::vector<RowsetReaderSharedPtr> expected {
+            lower,        inside,  touches_lower, compacted_inside,
+            spans_window, missing, unknown_lower, unknown_upper};
+    ASSERT_EQ(source.rs_splits.size(), expected.size());
+    for (size_t i = 0; i < expected.size(); ++i) {
+        EXPECT_EQ(source.rs_splits[i].rs_reader, expected[i]);
+    }
+    ASSERT_EQ(source.delete_predicates.size(), 1);
+    EXPECT_EQ(source.delete_predicates.front(), 
before->rowset()->rowset_meta());
+    EXPECT_EQ(_local_state->_rowsets_pruned_by_tso_counter->value(), 4);
+    EXPECT_EQ(_local_state->_segments_pruned_by_tso_counter->value(), 13);
+}
+
+TEST_F(OlapScanOperatorTsoPruningTest, LowerBoundOnly) {
+    TPaloScanRange range;
+    range.__set_start_tso(100);
+    TabletReadSource source;
+    auto at_lower = make_reader(TsoRange {100, 100});
+    auto later = make_reader(TsoRange {200, 200});
+    source.rs_splits.emplace_back(make_reader(TsoRange {99, 99}));
+    source.rs_splits.emplace_back(at_lower);
+    source.rs_splits.emplace_back(later);
+
+    _local_state->_prune_rowsets_by_tso(range, source);
+
+    ASSERT_EQ(source.rs_splits.size(), 2);
+    EXPECT_EQ(source.rs_splits[0].rs_reader, at_lower);
+    EXPECT_EQ(source.rs_splits[1].rs_reader, later);
+    EXPECT_EQ(_local_state->_rowsets_pruned_by_tso_counter->value(), 1);
+}
+
+TEST_F(OlapScanOperatorTsoPruningTest, UpperBoundOnly) {
+    TPaloScanRange range;
+    range.__set_end_tso(100);
+    TabletReadSource source;
+    auto earlier = make_reader(TsoRange {99, 99});
+    source.rs_splits.emplace_back(earlier);
+    source.rs_splits.emplace_back(make_reader(TsoRange {100, 100}));
+    source.rs_splits.emplace_back(make_reader(TsoRange {200, 200}));
+
+    _local_state->_prune_rowsets_by_tso(range, source);
+
+    ASSERT_EQ(source.rs_splits.size(), 1);
+    EXPECT_EQ(source.rs_splits.front().rs_reader, earlier);
+    EXPECT_EQ(_local_state->_rowsets_pruned_by_tso_counter->value(), 2);
+}
+
+TEST_F(OlapScanOperatorTsoPruningTest, NoBoundsPreservesAllRowsets) {
+    TPaloScanRange range;
+    TabletReadSource source;
+    source.rs_splits.emplace_back(make_reader(TsoRange {100, 100}));
+    source.rs_splits.emplace_back(make_reader(std::nullopt));
+
+    _local_state->_prune_rowsets_by_tso(range, source);
+
+    EXPECT_EQ(source.rs_splits.size(), 2);
+    EXPECT_EQ(_local_state->_rowsets_pruned_by_tso_counter->value(), 0);
+    EXPECT_EQ(_local_state->_segments_pruned_by_tso_counter->value(), 0);
+}
+
+TEST_F(OlapScanOperatorTsoPruningTest, EmptyWindowSkipsScannersAndSignalsEos) {
+    auto& range = *_local_state->_scan_ranges.front();
+    range.__set_binlog_scan_type(TBinlogScanType::DETAIL);
+    range.__set_start_tso(100);
+    range.__set_end_tso(200);
+    _local_state->_read_sources.resize(1);
+    auto& source = _local_state->_read_sources.front();
+    source.rs_splits.emplace_back(make_reader(TsoRange {99, 99}, 2));
+    source.rs_splits.emplace_back(make_reader(TsoRange {200, 200}, 3));
+
+    // There are no tablet or segment files. Successful preparation proves 
that the
+    // fully pruned source is not recaptured or passed to a scanner for 
initialization.
+    EXPECT_TRUE(_local_state->_prepare_scanners().ok());
+
+    EXPECT_TRUE(_local_state->_scanners.empty());
+    EXPECT_TRUE(_local_state->_eos);
+    EXPECT_EQ(_local_state->_scan_dependency->is_blocked_by(nullptr), nullptr);
+    EXPECT_EQ(_local_state->_rowsets_pruned_by_tso_counter->value(), 2);
+    EXPECT_EQ(_local_state->_segments_pruned_by_tso_counter->value(), 5);
+    EXPECT_EQ(_local_state->_tablets_pruned_by_tso_counter->value(), 1);
+}
+
+TEST_F(OlapScanOperatorTsoPruningTest, 
EmptyBootstrapDoesNotStartMinDeltaScanner) {
+    auto& range = *_local_state->_scan_ranges.front();
+    range.__set_start_tso(100);
+    range.__set_end_tso(200);
+    _local_state->_read_sources.resize(1);
+    auto& source = _local_state->_read_sources.front();
+    source.rs_splits.emplace_back(make_reader(std::nullopt, 0, 0));
+    source.rs_splits.emplace_back(make_reader(TsoRange {99, 99}, 2));
+
+    EXPECT_TRUE(_local_state->_prepare_scanners().ok());
+
+    EXPECT_TRUE(_local_state->_scanners.empty());
+    EXPECT_TRUE(_local_state->_eos);
+    EXPECT_EQ(_local_state->_scan_dependency->is_blocked_by(nullptr), nullptr);
+    EXPECT_EQ(_local_state->_rowsets_pruned_by_tso_counter->value(), 1);
+    EXPECT_EQ(_local_state->_segments_pruned_by_tso_counter->value(), 2);
+    EXPECT_EQ(_local_state->_tablets_pruned_by_tso_counter->value(), 1);
+}
+
 } // namespace doris
diff --git 
a/regression-test/data/row_binlog_p0/test_binlog_rowset_tso_pruning.out 
b/regression-test/data/row_binlog_p0/test_binlog_rowset_tso_pruning.out
new file mode 100644
index 00000000000..193bd34ca49
--- /dev/null
+++ b/regression-test/data/row_binlog_p0/test_binlog_rowset_tso_pruning.out
@@ -0,0 +1,59 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !detail --
+1      10      2
+1      11      2
+1      11      3
+1      12      3
+2      20      1
+4      40      0
+
+-- !min_delta --
+1      10      2
+1      12      3
+2      20      1
+4      40      0
+
+-- !start_only --
+1      10      2
+1      11      2
+1      11      3
+1      12      3
+2      20      1
+4      40      0
+5      50      0
+
+-- !end_only --
+1      10      0
+2      20      0
+3      30      0
+
+-- !empty_detail --
+
+-- !empty_min_delta --
+
+-- !snapshot --
+1      12
+3      30
+4      40
+5      50
+
+-- !compacted_detail --
+1      10      2
+1      11      2
+1      11      3
+1      12      3
+2      20      1
+4      40      0
+
+-- !compacted_min_delta --
+1      10      2
+1      12      3
+2      20      1
+4      40      0
+
+-- !compacted_snapshot --
+1      12
+3      30
+4      40
+5      50
+
diff --git 
a/regression-test/suites/row_binlog_p0/test_binlog_rowset_tso_pruning.groovy 
b/regression-test/suites/row_binlog_p0/test_binlog_rowset_tso_pruning.groovy
new file mode 100644
index 00000000000..ea224ef47d0
--- /dev/null
+++ b/regression-test/suites/row_binlog_p0/test_binlog_rowset_tso_pruning.groovy
@@ -0,0 +1,102 @@
+// 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_binlog_rowset_tso_pruning", "nonConcurrent") {
+    sql "DROP TABLE IF EXISTS test_binlog_rowset_tso_pruning FORCE"
+    sql """
+        CREATE TABLE test_binlog_rowset_tso_pruning (k INT, v INT)
+        UNIQUE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true",
+            "disable_auto_compaction" = "true",
+            "binlog.enable" = "true",
+            "binlog.format" = "ROW",
+            "binlog.need_historical_value" = "true"
+        )
+    """
+
+    // Read timestamps from FE so that the window uses the same clock and 
timezone
+    // as SQL parsing. Leave a whole second after each group of committed 
writes.
+    def nextBoundary = {
+        sleep(1200)
+        sql("SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s')")[0][0].toString()
+    }
+    sql "INSERT INTO test_binlog_rowset_tso_pruning VALUES (1, 10), (2, 20), 
(3, 30)"
+    def start = nextBoundary()
+    sql "INSERT INTO test_binlog_rowset_tso_pruning VALUES (1, 11)"
+    sql "INSERT INTO test_binlog_rowset_tso_pruning VALUES (4, 40)"
+    sql "DELETE FROM test_binlog_rowset_tso_pruning WHERE k = 2"
+    sql "INSERT INTO test_binlog_rowset_tso_pruning VALUES (1, 12)"
+    def end = nextBoundary()
+    sql "INSERT INTO test_binlog_rowset_tso_pruning VALUES (5, 50)"
+    def emptyStart = nextBoundary()
+    def emptyEnd = nextBoundary()
+
+    def detail = """
+        SELECT k, v, __DORIS_BINLOG_OP__
+        FROM test_binlog_rowset_tso_pruning@incr(
+            'startTimestamp' = '${start}', 'endTimestamp' = '${end}',
+            'incrementType' = 'DETAIL')
+    """
+    def minDelta = detail.replace("'DETAIL'", "'MIN_DELTA'")
+
+    // The before-images must survive pruning of the seed rowset. Repeated 
updates
+    // must still fold correctly in MIN_DELTA, and deletes must remain visible.
+    order_qt_detail detail
+    order_qt_min_delta minDelta
+    order_qt_start_only """
+        SELECT k, v, __DORIS_BINLOG_OP__
+        FROM test_binlog_rowset_tso_pruning@incr(
+            'startTimestamp' = '${start}', 'incrementType' = 'DETAIL')
+    """
+    order_qt_end_only """
+        SELECT k, v, __DORIS_BINLOG_OP__
+        FROM test_binlog_rowset_tso_pruning@incr(
+            'endTimestamp' = '${start}', 'incrementType' = 'DETAIL')
+    """
+    order_qt_empty_detail """
+        SELECT k, v FROM test_binlog_rowset_tso_pruning@incr(
+            'startTimestamp' = '${emptyStart}', 'endTimestamp' = '${emptyEnd}',
+            'incrementType' = 'DETAIL')
+    """
+    order_qt_empty_min_delta """
+        SELECT k, v FROM test_binlog_rowset_tso_pruning@incr(
+            'startTimestamp' = '${emptyStart}', 'endTimestamp' = '${emptyEnd}',
+            'incrementType' = 'MIN_DELTA')
+    """
+    order_qt_snapshot "SELECT k, v FROM test_binlog_rowset_tso_pruning"
+
+    // The existing helper triggers local compaction. Cloud mode still 
exercises
+    // the common pruning path above, without relying on local compaction APIs.
+    if (!isCloudMode()) {
+        setBeConfigTemporary([
+            binlog_compaction_goal_size_mbytes: 0,
+            binlog_compaction_file_count_threshold: 2,
+            binlog_compaction_wait_timesec_after_visible: 0,
+            binlog_compaction_time_threshold_seconds: 86400
+        ]) {
+            trigger_and_wait_compaction("test_binlog_rowset_tso_pruning", 
"cumulative")
+            // A compacted rowset can span both sides of the query window. Its
+            // remaining segment/row predicates must enforce the original 
bounds.
+            order_qt_compacted_detail detail
+            order_qt_compacted_min_delta minDelta
+            order_qt_compacted_snapshot "SELECT k, v FROM 
test_binlog_rowset_tso_pruning"
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to