This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch branch-incremental-computation
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-incremental-computation
by this push:
new 4c321761520 branch-incremental-computation: pick the merged
incremental-computation PRs from master in merge order (#67783 #68034 #68033
#68057 #68094 #68087) (#68151)
4c321761520 is described below
commit 4c3217615207309c8c63c64b2919466f3019b3b2
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Fri Sep 18 10:55:09 2026 +0800
branch-incremental-computation: pick the merged incremental-computation PRs
from master in merge order (#67783 #68034 #68033 #68057 #68094 #68087) (#68151)
Cherry-picked from #67783, #68034, #68033, #68057, #68094, #68087
Batch pick of every merged PR carrying the `incremental-computation`
label that `branch-incremental-computation` does not have yet (no
`incremental-computation-picked` label), in the order they landed on
master (`git log --first-parent`). One commit per PR, each created with
`git cherry-pick -x` so the message ends with `(cherry picked from
commit <master sha>)`. Follows the same convention as #67830, #68017 and
#68073.
| # | Master commit | PR | Title |
|---|---|---|---|
| 1 | e545b13e089 | #67783 | [fix](policy) Enforce row policies on MOW
time travel |
| 2 | f7a0842f968 | #68034 | [fix](binlog) Fix missing DELETE events in
row binlog |
| 3 | e85575e3ad6 | #68033 | [fix](binlog) Preserve row binlog
compaction policy |
| 4 | 21160d78b42 | #68057 | [fix](binlog) Decouple row binlog
compaction from CCR binlog config |
| 5 | 04aa5a11584 | #68094 | [fix](binlog) Persist row binlog config
updates in cloud mode |
| 6 | da8feed859d | #68087 | [fix](binlog) Report streams with missing
base tables as stale |
Not included on purpose:
- The 20 labelled PRs that already carry
`incremental-computation-picked` (#62606 in the fork point, #67508 via
#67712, the nine of #67830, the six of #68017, the two of #68073, and
#68050 whose content this branch got directly through #68012).
- #68012 carries the label but is a PR against this branch itself
(merged as `6f7c87fa892`); nothing to pick.
- #67820 is still open on master; this branch already carries its
content via #67861.
### Prerequisite check
For every pick I listed the master commits between the fork point
(`efedf10c7e3`) and the pick that touch the same files and are not on
this branch, and checked whether the pick's behavior depends on them.
- **#67783** declares no related PR. It builds on the MOW time-travel
rewrite (`BindRelation.buildMowTimeTravelUnion`, #67480) which is
already here. Two unlabelled master commits overlap: #66770
(authorization plugin SPI: reworks the data-mask / row-filter API of
`LogicalCheckPolicy` and its test) and #67811 (removes the
`isPlayNereidsDump()` check in `CheckPolicy`). Neither is a functional
prerequisite: the fix consists of `getPolicyTable()` unwrapping
`OlapTableWrapper`, `CheckPolicy` collecting the whole filter chain
below the policy mask, and `BindRelation` putting a `LogicalCheckPolicy`
above each union branch — none of that uses the #66770 API. Not picked;
adapted instead (see below).
- **#68034**, **#68033**, **#68087** (#68087 relates to #67173, which is
before the fork point): no unpicked master commit touches any of their
files; the picks applied cleanly and are byte-identical to master.
- **#68057**: only `be/test/cloud/cloud_compaction_test.cpp` overlaps
with the unrelated #67972 (refresh tablet meta of continuously ingested
tablets); auto-merged, the pick only adds new `TEST_F` blocks. Main-code
hunks are byte-identical to master.
- **#68094**: overlaps with #68090 / #67972 / #66598
(`cloud_tablet.cpp`), #67295 / #67618 / #68090 (`base_tablet.{h,cpp}`),
#66598 / #67637 (`meta_service.cpp`), #66598 (`cloud.proto`,
`cloud_tablet_test.cpp`) and #67761 / #66598 / #67637
(`meta_service_test.cpp`). All auto-merged. The fix —
`BaseTablet::binlog_config()` under the meta lock,
`BinlogConfig::operator==`, `CloudTablet::sync_meta()` refreshing the
binlog config, `update_tablet` in the meta service accepting
`binlog_config`, and `CloudSchemaChangeHandler` routing ROW-binlog
property updates through it — does not use anything those commits add.
Its hunks are byte-identical to master except one trailing context line
in `sync_meta()` (`last_sync_tablet_meta_time_s` comes from #67972).
Only #67783 needed adaptation, recorded in its commit message:
- `LogicalCheckPolicy.java`: the conflicting context was master's
`parsePolicyExpression()` helper (#66770), which does not exist here;
the new `getPolicyTable()` is inserted in the same place without it.
- `CheckRowPolicyTest.java`: the data-mask mock is written against this
branch's per-column `AccessControllerManager.evalDataMaskPolicy(...,
column) -> Optional<DataMaskPolicy>` instead of master's
`evalDataMaskPolicies(..., Set<String>) -> Map<String, DataMaskSpec>`.
The masks are the same (concat for the random-distribution table; the
non-movable `k2` mask plus identity masks for the hidden reconstruction
columns of the MOW table). The master-only `Or` import (#66770) is not
carried; `Collections` / `Locale` imports were added because they arrive
with #66770 on master.
### Drift check against master
Each pick's `+`/`-` lines are identical to the master commit's, except
for the #67783 adaptation above. After the six picks, the touched files
still differ from master at `da8feed859d` in: `cloud_tablet.cpp`,
`base_tablet.{h,cpp}`, `cloud_compaction_test.cpp`,
`cloud_tablet_test.cpp`, `meta_service.cpp`, `meta_service_test.cpp`,
`cloud.proto`, `CheckPolicy.java`, `LogicalCheckPolicy.java`,
`CheckRowPolicyTest.java`. Replaying the nine unpicked master commits
listed above (#67637, #67618, #66770, #66598, #67972, #67811, #67761,
#67295, #68090) in a temporary index and removing this branch's own
#67861 (`GetTsoRecoveryTransactions` in `cloud.proto` /
`meta_service_test.cpp`) brings every file to zero diff against master,
except the two `LogicalCheckPolicy.java` / `CheckRowPolicyTest.java`
hunks of #66770 that overlap the adaptation. Nothing else is left over.
### Verification
- FE: `run-fe-ut.sh --run` on this branch (regenerates thrift/protobuf,
compiles fe-core main + test) with the test classes touched by the picks
plus `DeleteFromCommandTest` from #68034's checklist: 5 classes, 58
tests, 0 failures, 0 errors, BUILD SUCCESS — `CheckRowPolicyTest` 9 (the
three tests #67783 adds included), `ExplainTableStreamPlanTest` 24,
`CloudSchemaChangeHandlerTest` 18 (the tests of #68033 and #68094
included), `DropTableStreamTest` 5, `DeleteFromCommandTest` 2.
- FE checkstyle on fe-core: 0 violations.
- BE: `-fsyntax-only` with the flags of the Release build
(`compile_commands.json`, regenerated `gen_cpp` headers incl. the new
`TabletMetaInfoPB.binlog_config`) passes for `cloud/cloud_tablet.cpp`,
`cloud/cloud_storage_engine.cpp`, `storage/olap_server.cpp`,
`storage/tablet/tablet.cpp`, `storage/tablet/base_tablet.cpp`, and with
`-DBE_TEST -fno-access-control` for
`test/cloud/cloud_compaction_test.cpp`,
`test/cloud/cloud_tablet_test.cpp`,
`test/storage/compaction/compaction_task_test.cpp`,
`test/storage/tablet/tablet_test.cpp`.
- Meta service: the two-line `update_tablet` hunk of #68094 only uses
the generated `TabletMetaInfoPB::has_binlog_config()/binlog_config()`
and `TabletMetaCloudPB::mutable_binlog_config()` accessors, all present
in the regenerated headers.
- The two new groovy suites
(`row_binlog_p0/test_row_binlog_mow_light_delete`,
`time_travel_p0/test_mow_time_travel_row_policy`) parse cleanly (groovy
parser check).
---------
Co-authored-by: morrySnow <[email protected]>
Co-authored-by: Luwei <[email protected]>
---
be/src/cloud/cloud_storage_engine.cpp | 2 +-
be/src/cloud/cloud_tablet.cpp | 10 +-
be/src/storage/binlog_config.h | 2 +
be/src/storage/olap_server.cpp | 2 +-
be/src/storage/tablet/base_tablet.cpp | 4 +-
be/src/storage/tablet/base_tablet.h | 5 +
be/src/storage/tablet/tablet.cpp | 3 +-
be/test/cloud/cloud_compaction_test.cpp | 50 ++++++
be/test/cloud/cloud_tablet_test.cpp | 30 ++++
.../storage/compaction/compaction_task_test.cpp | 40 +++++
be/test/storage/tablet/tablet_test.cpp | 26 +++
cloud/src/meta-service/meta_service.cpp | 2 +
cloud/test/meta_service_test.cpp | 34 ++++
.../doris/catalog/stream/TableStreamManager.java | 12 +-
.../cloud/alter/CloudSchemaChangeHandler.java | 22 ++-
.../java/org/apache/doris/load/DeleteHandler.java | 7 +-
.../doris/nereids/rules/analysis/BindRelation.java | 44 ++---
.../doris/nereids/rules/analysis/CheckPolicy.java | 21 +--
.../trees/plans/commands/DeleteFromCommand.java | 5 +-
.../trees/plans/logical/LogicalCheckPolicy.java | 19 ++-
.../apache/doris/catalog/DropTableStreamTest.java | 69 ++++++++
.../cloud/alter/CloudSchemaChangeHandlerTest.java | 128 ++++++++++++++
.../nereids/rules/analysis/CheckRowPolicyTest.java | 183 +++++++++++++++++++--
.../trees/plans/ExplainTableStreamPlanTest.java | 24 +++
gensrc/proto/cloud.proto | 1 +
.../test_row_binlog_mow_light_delete.out | 16 ++
.../test_mow_time_travel_row_policy.out | 15 ++
.../test_row_binlog_mow_light_delete.groovy | 104 ++++++++++++
.../test_mow_time_travel_row_policy.groovy | 107 ++++++++++++
29 files changed, 913 insertions(+), 74 deletions(-)
diff --git a/be/src/cloud/cloud_storage_engine.cpp
b/be/src/cloud/cloud_storage_engine.cpp
index 6a2d46469d8..b9d88260ee4 100644
--- a/be/src/cloud/cloud_storage_engine.cpp
+++ b/be/src/cloud/cloud_storage_engine.cpp
@@ -681,7 +681,7 @@ void
CloudStorageEngine::_binlog_compaction_tasks_producer_callback() {
int64_t interval = config::generate_compaction_tasks_interval_ms;
do {
int64_t cur_time = UnixMillis();
- if (config::enable_feature_binlog && !config::disable_auto_compaction)
{
+ if (!config::disable_auto_compaction) {
Status st = _adjust_compaction_thread_num();
if (!st.ok()) {
break;
diff --git a/be/src/cloud/cloud_tablet.cpp b/be/src/cloud/cloud_tablet.cpp
index feb6f514668..542a33b2278 100644
--- a/be/src/cloud/cloud_tablet.cpp
+++ b/be/src/cloud/cloud_tablet.cpp
@@ -846,8 +846,7 @@ Result<std::unique_ptr<RowsetWriter>>
CloudTablet::create_rowset_writer(
context.enable_unique_key_merge_on_write =
enable_unique_key_merge_on_write();
context.encrypt_algorithm = tablet_meta()->encryption_algorithm();
if (context.write_binlog_opt().enable) {
- context.write_binlog_opt().set_need_before(
- tablet_meta()->binlog_config().need_historical_value());
+
context.write_binlog_opt().set_need_before(binlog_config().need_historical_value());
}
context.inverted_index_storage_format =
tablet_meta()->inverted_index_storage_format();
context.persist_inverted_index_storage_format =
@@ -891,8 +890,7 @@ Result<std::unique_ptr<RowsetWriter>>
CloudTablet::create_transient_rowset_write
context.is_transient_rowset_writer = true;
if (rowset.rowset_meta() != nullptr &&
rowset.rowset_meta()->is_row_binlog()) {
context.write_binlog_opt().enable = true;
- context.write_binlog_opt().set_need_before(
- tablet_meta()->binlog_config().need_historical_value());
+
context.write_binlog_opt().set_need_before(binlog_config().need_historical_value());
}
context.rowset_id = rowset.rowset_id();
context.tablet_id = tablet_id();
@@ -1554,6 +1552,7 @@ Status CloudTablet::sync_meta() {
auto new_disable_auto_compaction =
tablet_meta->tablet_schema()->disable_auto_compaction();
auto new_vertical_compaction_num_columns_per_group =
tablet_meta->vertical_compaction_num_columns_per_group();
+ auto new_binlog_config = tablet_meta->binlog_config();
{
std::unique_lock wlock(_meta_lock);
@@ -1598,6 +1597,9 @@ Status CloudTablet::sync_meta() {
_tablet_meta->set_vertical_compaction_num_columns_per_group(
new_vertical_compaction_num_columns_per_group);
}
+ if (_tablet_meta->binlog_config() != new_binlog_config) {
+ _tablet_meta->set_binlog_config(new_binlog_config);
+ }
}
return Status::OK();
diff --git a/be/src/storage/binlog_config.h b/be/src/storage/binlog_config.h
index 696eb696322..76751389a43 100644
--- a/be/src/storage/binlog_config.h
+++ b/be/src/storage/binlog_config.h
@@ -46,6 +46,8 @@ public:
BinlogConfig& operator=(BinlogConfig&&) = default;
~BinlogConfig() = default;
+ bool operator==(const BinlogConfig&) const = default;
+
bool is_enable() const { return _enable; }
void set_enable(bool enable) { _enable = enable; }
diff --git a/be/src/storage/olap_server.cpp b/be/src/storage/olap_server.cpp
index 2dc715cd327..7bcf615cf04 100644
--- a/be/src/storage/olap_server.cpp
+++ b/be/src/storage/olap_server.cpp
@@ -776,7 +776,7 @@ void
StorageEngine::_binlog_compaction_tasks_producer_callback() {
int64_t interval = config::generate_compaction_tasks_interval_ms;
do {
int64_t cur_time = UnixMillis();
- if (config::enable_feature_binlog && !config::disable_auto_compaction
&&
+ if (!config::disable_auto_compaction &&
(!config::enable_compaction_pause_on_high_memory ||
!GlobalMemoryArbitrator::is_exceed_soft_mem_limit(GB_EXCHANGE_BYTE))) {
_adjust_compaction_thread_num();
diff --git a/be/src/storage/tablet/base_tablet.cpp
b/be/src/storage/tablet/base_tablet.cpp
index c4a487f80ce..f9dc3d3ed4a 100644
--- a/be/src/storage/tablet/base_tablet.cpp
+++ b/be/src/storage/tablet/base_tablet.cpp
@@ -1516,9 +1516,9 @@ Status BaseTablet::update_delete_bitmap(const
BaseTabletSPtr& self, TabletTxnInf
binlog_rs->rowset_meta()->is_row_binlog()) {
DCHECK(txn_info->attach_row_binlog.tablet != nullptr);
row_binlog_rowset = binlog_rs;
- const auto& binlog_tablet_meta =
txn_info->attach_row_binlog.tablet->tablet_meta();
build_row_binlog =
- is_partial_update ||
binlog_tablet_meta->binlog_config().need_historical_value();
+ is_partial_update ||
+
txn_info->attach_row_binlog.tablet->binlog_config().need_historical_value();
}
// rewrite conflict only when partial update or need before
diff --git a/be/src/storage/tablet/base_tablet.h
b/be/src/storage/tablet/base_tablet.h
index 0bf620b7e15..ef5eb1e100b 100644
--- a/be/src/storage/tablet/base_tablet.h
+++ b/be/src/storage/tablet/base_tablet.h
@@ -99,6 +99,11 @@ public:
// Property encapsulated in TabletMeta
const TabletMetaSharedPtr& tablet_meta() const { return _tablet_meta; }
+ BinlogConfig binlog_config() const {
+ std::shared_lock rlock(_meta_lock);
+ return _tablet_meta->binlog_config();
+ }
+
int32_t max_version_config();
// FIXME(plat1ko): It is not appropriate to expose this lock
diff --git a/be/src/storage/tablet/tablet.cpp b/be/src/storage/tablet/tablet.cpp
index 9da3e99b512..9a549b84ac1 100644
--- a/be/src/storage/tablet/tablet.cpp
+++ b/be/src/storage/tablet/tablet.cpp
@@ -1019,8 +1019,7 @@ bool Tablet::can_do_compaction(size_t path_hash,
CompactionType compaction_type)
return false;
}
- if (compaction_type == CompactionType::CUMU_BINLOG_COMPACTION &&
- (!config::enable_feature_binlog || !is_row_binlog_tablet())) {
+ if (compaction_type == CompactionType::CUMU_BINLOG_COMPACTION &&
!is_row_binlog_tablet()) {
return false;
}
diff --git a/be/test/cloud/cloud_compaction_test.cpp
b/be/test/cloud/cloud_compaction_test.cpp
index 9f0ac135bc0..f0e4ad7a3a2 100644
--- a/be/test/cloud/cloud_compaction_test.cpp
+++ b/be/test/cloud/cloud_compaction_test.cpp
@@ -389,6 +389,56 @@ TEST_F(CloudCompactionTest,
generate_cloud_binlog_compaction_tasks_updates_only_
ASSERT_EQ(metrics->tablet_time_series_max_compaction_score->value(), 103);
}
+TEST_F(CloudCompactionTest, binlog_compaction_producer_ignores_ccr_switch) {
+ const bool old_enable_feature_binlog = config::enable_feature_binlog;
+ const bool old_disable_auto_compaction = config::disable_auto_compaction;
+ const int32_t old_binlog_compaction_task_num_per_disk =
+ config::binlog_compaction_task_num_per_disk;
+ Defer restore_config {[&]() {
+ config::enable_feature_binlog = old_enable_feature_binlog;
+ config::disable_auto_compaction = old_disable_auto_compaction;
+ config::binlog_compaction_task_num_per_disk =
old_binlog_compaction_task_num_per_disk;
+ }};
+
+ ASSERT_TRUE(ThreadPoolBuilder("BaseCompactionTaskThreadPoolTest")
+ .set_min_threads(1)
+ .set_max_threads(1)
+ .build(&_engine._base_compaction_thread_pool)
+ .ok());
+ ASSERT_TRUE(ThreadPoolBuilder("CumuCompactionTaskThreadPoolTest")
+ .set_min_threads(1)
+ .set_max_threads(1)
+ .build(&_engine._cumu_compaction_thread_pool)
+ .ok());
+ ASSERT_TRUE(ThreadPoolBuilder("BinlogCompactionTaskThreadPoolTest")
+ .set_min_threads(1)
+ .set_max_threads(1)
+ .build(&_engine._binlog_compaction_thread_pool)
+ .ok());
+
+ auto binlog_meta = std::make_shared<TabletMeta>(*_tablet_meta);
+ binlog_meta->_tablet_id = 11004;
+ binlog_meta->set_tablet_role(TabletRolePB::TABLET_ROLE_ROW_BINLOG);
+ auto binlog_tablet = std::make_shared<CloudTablet>(_engine, binlog_meta);
+
binlog_tablet->tablet_meta()->tablet_schema()->set_disable_auto_compaction(false);
+ binlog_tablet->_approximate_cumu_num_deltas = 7;
+ _engine.tablet_mgr().put_tablet_for_UT(binlog_tablet);
+
+ config::enable_feature_binlog = false;
+ config::disable_auto_compaction = false;
+ config::binlog_compaction_task_num_per_disk = 0;
+ auto* metric =
DorisMetrics::instance()->tablet_binlog_max_compaction_score;
+ metric->set_value(0);
+ _engine._stop_background_threads_latch.count_down();
+ _engine._binlog_compaction_tasks_producer_callback();
+ EXPECT_EQ(metric->value(), 7);
+
+ config::disable_auto_compaction = true;
+ metric->set_value(11);
+ _engine._binlog_compaction_tasks_producer_callback();
+ EXPECT_EQ(metric->value(), 11);
+}
+
TEST_F(CloudCompactionTest,
generate_cloud_compaction_tasks_clears_metrics_without_tablets) {
auto* metrics = DorisMetrics::instance();
metrics->tablet_cumulative_max_compaction_score->set_value(101);
diff --git a/be/test/cloud/cloud_tablet_test.cpp
b/be/test/cloud/cloud_tablet_test.cpp
index 3d3982286d5..90ef62825be 100644
--- a/be/test/cloud/cloud_tablet_test.cpp
+++ b/be/test/cloud/cloud_tablet_test.cpp
@@ -24,6 +24,8 @@
#include <chrono>
#include <cstdint>
+#include <future>
+#include <mutex>
#include "cloud/cloud_meta_mgr.h"
#include "cloud/cloud_storage_engine.h"
@@ -931,6 +933,8 @@ TEST_F(CloudTabletSyncMetaTest,
TestSyncMetaMultipleProperties) {
mock_tablet_meta->set_time_series_compaction_empty_rowsets_threshold(9);
mock_tablet_meta->set_time_series_compaction_level_threshold(7);
mock_tablet_meta->set_vertical_compaction_num_columns_per_group(13);
+ mock_tablet_meta->set_binlog_config(
+ BinlogConfig(true, 3600, 4096, 7, BinlogFormatPB::ROW, true));
// Mock get_tablet_meta to return tablet_meta with updated properties
sp->set_call_back("CloudMetaMgr::get_tablet_meta",
[mock_tablet_meta](auto&& args) {
@@ -954,11 +958,37 @@ TEST_F(CloudTabletSyncMetaTest,
TestSyncMetaMultipleProperties) {
EXPECT_EQ(_tablet->tablet_meta()->time_series_compaction_empty_rowsets_threshold(),
9);
EXPECT_EQ(_tablet->tablet_meta()->time_series_compaction_level_threshold(), 7);
EXPECT_EQ(_tablet->tablet_meta()->vertical_compaction_num_columns_per_group(),
13);
+ auto binlog_config = _tablet->binlog_config();
+ EXPECT_TRUE(binlog_config.is_enable());
+ EXPECT_EQ(binlog_config.ttl_seconds(), 3600);
+ EXPECT_EQ(binlog_config.max_bytes(), 4096);
+ EXPECT_EQ(binlog_config.max_history_nums(), 7);
+ EXPECT_EQ(binlog_config.binlog_format(), BinlogFormatPB::ROW);
+ EXPECT_TRUE(binlog_config.need_historical_value());
sp->disable_processing();
sp->clear_all_call_backs();
}
+TEST_F(CloudTabletSyncMetaTest, TestBinlogConfigReadUsesMetaLock) {
+ std::unique_lock meta_lock(_tablet->get_header_lock());
+ std::promise<void> reader_started;
+ auto reader_started_future = reader_started.get_future();
+ auto read_future = std::async(std::launch::async, [&]() {
+ reader_started.set_value();
+ return _tablet->binlog_config();
+ });
+
+ auto reader_started_status = reader_started_future.wait_for(seconds(5));
+ auto read_blocked_status = read_future.wait_for(milliseconds(100));
+ meta_lock.unlock();
+
+ EXPECT_EQ(reader_started_status, std::future_status::ready);
+ EXPECT_EQ(read_blocked_status, std::future_status::timeout);
+ ASSERT_EQ(read_future.wait_for(seconds(5)), std::future_status::ready);
+ EXPECT_EQ(read_future.get(), _tablet->binlog_config());
+}
+
TEST_F(CloudTabletSyncMetaTest, TestSyncMetaSyncsTtlWithoutChangingInMemory) {
EXPECT_EQ(0, _tablet->tablet_meta()->ttl_seconds());
EXPECT_FALSE(_tablet->tablet_meta()->tablet_schema()->is_in_memory());
diff --git a/be/test/storage/compaction/compaction_task_test.cpp
b/be/test/storage/compaction/compaction_task_test.cpp
index 85ae15a7357..3f2c1d29c99 100644
--- a/be/test/storage/compaction/compaction_task_test.cpp
+++ b/be/test/storage/compaction/compaction_task_test.cpp
@@ -130,6 +130,46 @@ TEST_F(CompactionTaskTest, TestSubmitCompactionTask) {
EXPECT_EQ(executing_task_num, 2);
}
+TEST_F(CompactionTaskTest, BinlogCompactionProducerIgnoresCcrSwitch) {
+ const bool old_enable_feature_binlog = config::enable_feature_binlog;
+ const bool old_disable_auto_compaction = config::disable_auto_compaction;
+ const bool old_enable_compaction_pause_on_high_memory =
+ config::enable_compaction_pause_on_high_memory;
+ Defer restore_config {[&]() {
+ config::enable_feature_binlog = old_enable_feature_binlog;
+ config::disable_auto_compaction = old_disable_auto_compaction;
+ config::enable_compaction_pause_on_high_memory =
old_enable_compaction_pause_on_high_memory;
+ }};
+
+ auto* sp = SyncPoint::get_instance();
+ sp->enable_processing();
+ int generation_count = 0;
+
sp->set_call_back("StorageEngine::_adjust_compaction_thread_num.return_void",
+ [](auto&& args) { *try_any_cast<bool*>(args.back()) =
true; });
+ sp->set_call_back("olap_server::_generate_compaction_tasks.return_empty",
+ [&generation_count](auto&& values) {
+ ++generation_count;
+ auto* ret =
+
try_any_cast_ret<std::vector<TabletCompactionContext>>(values);
+ ret->second = true;
+ });
+ Defer clear_sync_points {[&]() {
+ sp->clear_all_call_backs();
+ sp->disable_processing();
+ }};
+
+ config::enable_feature_binlog = false;
+ config::disable_auto_compaction = false;
+ config::enable_compaction_pause_on_high_memory = false;
+ _storage_engine->_stop_background_threads_latch.count_down();
+ _storage_engine->_binlog_compaction_tasks_producer_callback();
+ EXPECT_EQ(generation_count, 1);
+
+ config::disable_auto_compaction = true;
+ _storage_engine->_binlog_compaction_tasks_producer_callback();
+ EXPECT_EQ(generation_count, 1);
+}
+
TEST_F(CompactionTaskTest, TestAutoSetCompactionIncreaseTaskNum) {
auto st = ThreadPoolBuilder("BaseCompactionTaskThreadPool")
.set_min_threads(2)
diff --git a/be/test/storage/tablet/tablet_test.cpp
b/be/test/storage/tablet/tablet_test.cpp
index ea4719e31c9..21c6d91afba 100644
--- a/be/test/storage/tablet/tablet_test.cpp
+++ b/be/test/storage/tablet/tablet_test.cpp
@@ -26,6 +26,7 @@
#include <memory>
+#include "common/config.h"
#include "gtest/gtest_pred_impl.h"
#include "io/fs/local_file_system.h"
#include "json2pb/json_to_pb.h"
@@ -39,6 +40,7 @@
#include "storage/tablet/tablet_meta.h"
#include "storage/utils.h"
#include "testutil/mock_rowset.h"
+#include "util/defer_op.h"
#include "util/time.h"
#include "util/uid_util.h"
@@ -269,6 +271,30 @@ TEST_F(TestTablet, delete_expired_stale_rowset) {
_tablet.reset();
}
+TEST_F(TestTablet, RowBinlogCompactionDoesNotDependOnCcrSwitch) {
+ const bool old_enable_feature_binlog = config::enable_feature_binlog;
+ Defer restore_config {[&]() { config::enable_feature_binlog =
old_enable_feature_binlog; }};
+ config::enable_feature_binlog = false;
+
+ ASSERT_TRUE(_data_dir->init().ok());
+ _tablet_meta->set_tablet_role(TabletRolePB::TABLET_ROLE_ROW_BINLOG);
+ TabletSharedPtr row_binlog_tablet(
+ new Tablet(*k_engine, _tablet_meta, _data_dir.get(),
CUMULATIVE_SIZE_BASED_POLICY));
+ ASSERT_TRUE(row_binlog_tablet->init().ok());
+ EXPECT_TRUE(row_binlog_tablet->can_do_compaction(_data_dir->path_hash(),
+
CompactionType::CUMU_BINLOG_COMPACTION));
+ EXPECT_FALSE(row_binlog_tablet->can_do_compaction(_data_dir->path_hash(),
+
CompactionType::CUMULATIVE_COMPACTION));
+
+ auto data_tablet_meta = new_tablet_meta(TTabletSchema());
+ data_tablet_meta->set_tablet_role(TabletRolePB::TABLET_ROLE_DATA);
+ TabletSharedPtr data_tablet(
+ new Tablet(*k_engine, data_tablet_meta, _data_dir.get(),
CUMULATIVE_SIZE_BASED_POLICY));
+ ASSERT_TRUE(data_tablet->init().ok());
+ EXPECT_FALSE(data_tablet->can_do_compaction(_data_dir->path_hash(),
+
CompactionType::CUMU_BINLOG_COMPACTION));
+}
+
TEST_F(TestTablet, pad_rowset) {
std::vector<RowsetMetaSharedPtr> rs_metas;
auto ptr1 = std::make_shared<RowsetMeta>();
diff --git a/cloud/src/meta-service/meta_service.cpp
b/cloud/src/meta-service/meta_service.cpp
index c6aa71e38c8..2d6bd800223 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -1204,6 +1204,8 @@ void
MetaServiceImpl::update_tablet(::google::protobuf::RpcController* controlle
tablet_meta.set_is_persistent(tablet_meta_info.is_persistent());
} else if (tablet_meta_info.has_ttl_seconds()) {
tablet_meta.set_ttl_seconds(tablet_meta_info.ttl_seconds());
+ } else if (tablet_meta_info.has_binlog_config()) {
+
tablet_meta.mutable_binlog_config()->CopyFrom(tablet_meta_info.binlog_config());
} else if (tablet_meta_info.has_compaction_policy()) {
tablet_meta.set_compaction_policy(tablet_meta_info.compaction_policy());
} else if
(tablet_meta_info.has_time_series_compaction_goal_size_mbytes()) {
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index 25ac956415c..15f5483cf18 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -5122,6 +5122,40 @@ TEST(MetaServiceTest, UpdateTablet) {
ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
}
get_and_check_tablet_meta(tablet_id1, 300, true, true);
+ {
+ brpc::Controller cntl;
+ UpdateTabletRequest req;
+ UpdateTabletResponse resp;
+ req.set_cloud_unique_id(cloud_unique_id);
+ TabletMetaInfoPB* tablet_meta_info = req.add_tablet_meta_infos();
+ tablet_meta_info->set_tablet_id(tablet_id1);
+ auto* binlog_config = tablet_meta_info->mutable_binlog_config();
+ binlog_config->set_enable(true);
+ binlog_config->set_ttl_seconds(3600);
+ binlog_config->set_max_bytes(4096);
+ binlog_config->set_max_history_nums(7);
+ binlog_config->set_binlog_format(BinlogFormatPB::ROW);
+ binlog_config->set_need_historical_value(true);
+ meta_service->update_tablet(&cntl, &req, &resp, nullptr);
+ ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+ }
+ {
+ brpc::Controller cntl;
+ GetTabletRequest req;
+ req.set_cloud_unique_id(cloud_unique_id);
+ req.set_tablet_id(tablet_id1);
+ GetTabletResponse resp;
+ meta_service->get_tablet(&cntl, &req, &resp, nullptr);
+ ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+ ASSERT_TRUE(resp.tablet_meta().has_binlog_config());
+ const auto& binlog_config = resp.tablet_meta().binlog_config();
+ EXPECT_TRUE(binlog_config.enable());
+ EXPECT_EQ(binlog_config.ttl_seconds(), 3600);
+ EXPECT_EQ(binlog_config.max_bytes(), 4096);
+ EXPECT_EQ(binlog_config.max_history_nums(), 7);
+ EXPECT_EQ(binlog_config.binlog_format(), BinlogFormatPB::ROW);
+ EXPECT_TRUE(binlog_config.need_historical_value());
+ }
}
TEST(MetaServiceTest, GetTabletStatsTest) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/stream/TableStreamManager.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/stream/TableStreamManager.java
index f768f5afeb9..35275748397 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/stream/TableStreamManager.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/stream/TableStreamManager.java
@@ -63,6 +63,7 @@ import java.util.concurrent.locks.LockSupport;
public class TableStreamManager extends MasterDaemon implements Writable,
GsonPostProcessable {
private static final Logger LOG =
LogManager.getLogger(TableStreamManager.class);
+ private static final String BASE_TABLE_NOT_FOUND_STALE_REASON = "Base
table does not exist";
@SerializedName(value = "dbStreamMap")
private Map<Long, Set<Long>> dbStreamMap;
protected MonitoredReentrantReadWriteLock rwLock;
@@ -356,7 +357,8 @@ public class TableStreamManager extends MasterDaemon
implements Writable, GsonPo
// STREAM_COMMENT
trow.addToColumnValue(new
TCell().setStringVal(stream.getComment()));
TableIf baseTable = stream.getBaseTableNullable();
- if (baseTable == null) {
+ boolean baseTableExists = baseTable != null;
+ if (!baseTableExists) {
// BASE_TABLE_NAME
trow.addToColumnValue(new
TCell().setStringVal("N/A"));
// BASE_TABLE_DB
@@ -377,11 +379,13 @@ public class TableStreamManager extends MasterDaemon
implements Writable, GsonPo
trow.addToColumnValue(new
TCell().setStringVal(baseTable.getType().name()));
}
// ENABLED
- trow.addToColumnValue(new
TCell().setBoolVal(!stream.isDisabled()));
+ trow.addToColumnValue(
+ new TCell().setBoolVal(baseTableExists &&
!stream.isDisabled()));
// IS_STALE
- trow.addToColumnValue(new
TCell().setBoolVal(stream.isStale()));
+ trow.addToColumnValue(new
TCell().setBoolVal(!baseTableExists || stream.isStale()));
// STALE_REASON
- trow.addToColumnValue(new
TCell().setStringVal(stream.getStaleReason()));
+ trow.addToColumnValue(new
TCell().setStringVal(baseTableExists
+ ? stream.getStaleReason() :
BASE_TABLE_NOT_FOUND_STALE_REASON));
dataBatch.add(trow);
} finally {
stream.readUnlock();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandler.java
b/fe/fe-core/src/main/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandler.java
index 41c9827cb9a..ed6008c8391 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandler.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandler.java
@@ -18,6 +18,7 @@
package org.apache.doris.cloud.alter;
import org.apache.doris.alter.SchemaChangeHandler;
+import org.apache.doris.catalog.BinlogConfig;
import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.KeysType;
@@ -426,6 +427,18 @@ public class CloudSchemaChangeHandler extends
SchemaChangeHandler {
DynamicPartitionUtil.registerOrRemoveDynamicPartitionTable(db.getId(),
olapTable, false);
}
+ @Override
+ public void updatePartitionProperties(Database db, String tableName,
String partitionName,
+ long storagePolicyId, int isInMemory, BinlogConfig binlogConfig,
String compactionPolicy,
+ Map<String, Long> timeSeriesCompactionConfig, int
skipWriteIndexOnLoad,
+ int disableAutoCompaction, int
verticalCompactionNumColumnsPerGroup) throws UserException {
+ Preconditions.checkNotNull(binlogConfig);
+ UpdatePartitionMetaParam param = new UpdatePartitionMetaParam();
+ param.binlogConfig = binlogConfig;
+ param.type = UpdatePartitionMetaParam.TabletMetaType.BINLOG_CONFIG;
+ updateCloudPartitionMeta(db, tableName, partitionName, param);
+ }
+
private static class UpdatePartitionMetaParam {
public enum TabletMetaType {
INMEMORY,
@@ -443,6 +456,7 @@ public class CloudSchemaChangeHandler extends
SchemaChangeHandler {
DISABLE_AUTO_COMPACTION,
ENABLE_MOW_LIGHT_DELETE,
VERTICAL_COMPACTION_NUM_COLUMNS_PER_GROUP,
+ BINLOG_CONFIG,
}
TabletMetaType type;
@@ -461,6 +475,7 @@ public class CloudSchemaChangeHandler extends
SchemaChangeHandler {
boolean disableAutoCompaction = false;
boolean enableMowLightDelete = false;
int verticalCompactionNumColumnsPerGroup = 5;
+ BinlogConfig binlogConfig;
}
public void updateCloudPartitionMeta(Database db,
@@ -476,7 +491,9 @@ public class CloudSchemaChangeHandler extends
SchemaChangeHandler {
throw new DdlException(
"Partition[" + partitionName + "] does not exist in
table[" + olapTable.getName() + "]");
}
- for (MaterializedIndex index :
partition.getMaterializedIndices(IndexExtState.VISIBLE, true)) {
+ boolean includeRowBinlog = param.type !=
UpdatePartitionMetaParam.TabletMetaType.COMPACTION_POLICY;
+ for (MaterializedIndex index
+ : partition.getMaterializedIndices(IndexExtState.VISIBLE,
includeRowBinlog)) {
for (Tablet tablet : index.getTablets()) {
tabletIds.add(tablet.getId());
}
@@ -548,6 +565,9 @@ public class CloudSchemaChangeHandler extends
SchemaChangeHandler {
infoBuilder.setVerticalCompactionNumColumnsPerGroup(
param.verticalCompactionNumColumnsPerGroup);
break;
+ case BINLOG_CONFIG:
+
infoBuilder.setBinlogConfig(param.binlogConfig.toProtobuf());
+ break;
default:
throw new UserException("Unknown TabletMetaType");
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/load/DeleteHandler.java
b/fe/fe-core/src/main/java/org/apache/doris/load/DeleteHandler.java
index 8f2aee7f9af..aed66e1508d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/load/DeleteHandler.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/load/DeleteHandler.java
@@ -20,7 +20,6 @@ package org.apache.doris.load;
import org.apache.doris.analysis.Predicate;
import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.Env;
-import org.apache.doris.catalog.KeysType;
import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Partition;
import org.apache.doris.common.AnalysisException;
@@ -112,10 +111,10 @@ public class DeleteHandler implements Writable {
try {
targetTbl.readLock();
try {
- if (targetTbl.needRowBinlog() && targetTbl.getKeysType() ==
KeysType.DUP_KEYS) {
+ if (targetTbl.needRowBinlog()) {
throw new AnalysisException(
- "DELETE with predicates is not supported on
DUPLICATE KEY tables when binlog<row>"
- + "is enabled. Please disable binlog<row>
for this table or avoid DELETE.");
+ "DELETE with predicates is not supported when
binlog<row> is enabled because it cannot "
+ + "produce row-level DELETE events.");
}
if (targetTbl.getState() != OlapTable.OlapTableState.NORMAL) {
// table under alter operation can also do delete.
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
index 60479e23a8b..6b5a8628c6b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
@@ -59,6 +59,7 @@ import org.apache.doris.nereids.StatementContext.TableFrom;
import org.apache.doris.nereids.analyzer.Unbound;
import org.apache.doris.nereids.analyzer.UnboundRelation;
import org.apache.doris.nereids.analyzer.UnboundResultSink;
+import org.apache.doris.nereids.analyzer.UnboundSlot;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.exceptions.ParseException;
import org.apache.doris.nereids.hint.LeadingHint;
@@ -98,6 +99,7 @@ import org.apache.doris.nereids.trees.plans.algebra.Relation;
import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCheckPolicy;
import org.apache.doris.nereids.trees.plans.logical.LogicalFileScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
import org.apache.doris.nereids.trees.plans.logical.LogicalOdbcScan;
@@ -587,22 +589,27 @@ public class BindRelation extends OneAnalysisRuleFactory {
}
/**
- * mow time-travel: A|t1 = base(survived rows, tso<=t1) UNION ALL
binlog(before-image of
- * UPDATE_BEFORE/DELETE since t1). The binlog right branch reuses the
@incr (MIN_DELTA) machinery;
+ * mow time-travel: A|t1 = base(survived rows, tso < targetTso) UNION
ALL binlog(before-image of
+ * UPDATE_BEFORE/DELETE from targetTso). The binlog right branch reuses
the @incr (MIN_DELTA) machinery;
* BE splits each change into rows where UPDATE_BEFORE/DELETE rows already
carry the before value.
*/
private LogicalPlan buildMowTimeTravelUnion(LogicalOlapScan baseScan,
OlapTable olapTable,
long targetTso, UnboundRelation unboundRelation, List<String>
qualifier,
List<Long> partIds, List<Long> tabletIds, CascadesContext
cascadesContext) {
- // union baseline = base visible columns (key + value); hidden cols
are filtered out.
- List<Slot> visibleOutput = baseScan.getOutput().stream()
+ // Use unbound visible columns so each branch projection binds after
its policy is expanded.
+ // Otherwise the projections keep the scan's raw slots and can bypass
data masking.
+ // Keep the original qualifier in the UnboundSlot name parts so
projectFromUnboundSlots can
+ // preserve it on the projection alias.
+ List<UnboundSlot> visibleOutput = baseScan.getOutput().stream()
.filter(slot -> !(slot instanceof SlotReference)
|| ((SlotReference) slot).isVisible())
+ .map(slot -> new
UnboundSlot(Utils.qualifiedNameParts(slot.getQualifier(), slot.getName())))
.collect(Collectors.toList());
// left: base survived rows at t1 = delete_sign=0 AND commit_tso <
targetTso, projected to visible.
LogicalPlan left = checkAndAddDeleteSignFilter(baseScan,
ConnectContext.get(), olapTable, true);
- left = projectFromOriginSlots(addCommitTsoFilter(left, targetTso,
olapTable), visibleOutput);
+ left = addCommitTsoFilter(left, targetTso, olapTable);
+ left = projectFromUnboundSlots(new LogicalCheckPolicy<>(left),
visibleOutput);
// right: binlog MIN_DELTA over tso >= targetTso, keep
UPDATE_BEFORE/DELETE rows (before image),
// projected to the same visible schema. BE splits each change so
UPDATE_BEFORE/DELETE rows
@@ -625,9 +632,9 @@ public class BindRelation extends OneAnalysisRuleFactory {
new TableScanParams(TableScanParams.INCREMENTAL_READ,
incrParams, Lists.newArrayList()));
LogicalPlan right = checkAndAddChangeScanFilter(binlogScan,
StreamScanType.MIN_DELTA, true);
- right = projectFromOriginSlots(right, visibleOutput);
+ right = projectFromUnboundSlots(new LogicalCheckPolicy<>(right),
visibleOutput);
- // both children are bound; BindExpression aligns by position and
fills the union output.
+ // BindExpression binds both branch projections, aligns them by
position, and fills the union output.
// buildNewOutputs() rebuilds the union output slots with empty
qualifiers, so wrap the union
// in a subquery alias to restore the original catalog.db.table
qualifier. Use the scan's
// fully-qualified name (catalog.db.table) rather than the table-less
qualifier, otherwise
@@ -1057,7 +1064,6 @@ public class BindRelation extends OneAnalysisRuleFactory {
*/
public static LogicalPlan checkAndAddChangeScanFilter(LogicalOlapScan scan,
StreamScanType
scanType, boolean beforeImageOnly) {
- LogicalPlan plan = scan;
Slot opSlot = null;
for (Slot slot : scan.getOutput()) {
if (slot.getName().equals(Column.BINLOG_OPERATION_COL)) {
@@ -1068,26 +1074,22 @@ public class BindRelation extends
OneAnalysisRuleFactory {
if (scanType.equals(StreamScanType.APPEND_ONLY)) {
Preconditions.checkArgument(opSlot != null, "opSlot is null");
return new LogicalFilter<>(ImmutableSet.of(new EqualTo(opSlot,
- new BigIntLiteral(BinlogUtils.ROW_BINLOG_APPEND))), plan);
+ new BigIntLiteral(BinlogUtils.ROW_BINLOG_APPEND))), scan);
} else if (beforeImageOnly) {
return new LogicalFilter<>(ImmutableSet.of(new InPredicate(opSlot,
ImmutableList.of(
new BigIntLiteral(BinlogUtils.ROW_BINLOG_DELETE),
- new
BigIntLiteral(BinlogUtils.ROW_BINLOG_UPDATE_BEFORE)))), plan);
+ new
BigIntLiteral(BinlogUtils.ROW_BINLOG_UPDATE_BEFORE)))), scan);
}
- return plan;
+ return scan;
}
- private LogicalPlan projectFromOriginSlots(LogicalPlan plan, List<Slot>
wantedSlots) {
- Map<String, Slot> childSlotByName = new
HashMap<>(plan.getOutput().size());
- for (Slot slot : plan.getOutput()) {
- childSlotByName.put(slot.getName(), slot);
- }
+ private LogicalPlan projectFromUnboundSlots(LogicalPlan plan,
List<UnboundSlot> wantedSlots) {
List<NamedExpression> project = new ArrayList<>(wantedSlots.size());
- for (Slot wanted : wantedSlots) {
- Slot match = childSlotByName.get(wanted.getName());
- Preconditions.checkArgument(match != null,
- "column %s not found in child output", wanted.getName());
- project.add(new Alias(match, wanted.getName()));
+ for (UnboundSlot wanted : wantedSlots) {
+ List<String> nameParts = wanted.getNameParts();
+ int nameIndex = nameParts.size() - 1;
+ project.add(new Alias(wanted, nameParts.get(nameIndex),
+ nameParts.subList(0, nameIndex)));
}
return new LogicalProject<>(project, plan);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/CheckPolicy.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/CheckPolicy.java
index 0fb1c9a32e0..2577c2770b5 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/CheckPolicy.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/CheckPolicy.java
@@ -61,20 +61,15 @@ public class CheckPolicy implements AnalysisRuleFactory {
RuleType.CHECK_ROW_POLICY.build(
logicalCheckPolicy(any().when(child -> !(child
instanceof UnboundRelation))).thenApply(ctx -> {
LogicalCheckPolicy<Plan> checkPolicy = ctx.root;
- LogicalFilter<Plan> upperFilter = null;
Plan upAgg = null;
Plan child = checkPolicy.child();
- // Because the unique table will automatically
include a filter condition
- if ((child instanceof LogicalFilter)) {
- upperFilter = (LogicalFilter) child;
- if (child.child(0) instanceof LogicalRelation)
{
- child = child.child(0);
- } else if (child.child(0) instanceof
LogicalAggregate
- && child.child(0).child(0) instanceof
LogicalRelation) {
- upAgg = child.child(0);
- child = child.child(0).child(0);
- }
+ Set<Expression> upperFilterConjuncts = new
LinkedHashSet<>();
+ // Unique tables and time-travel reconstruction
can add a chain of filters around
+ // the relation. Keep all of them below a policy
mask when the policy is expanded.
+ while (child instanceof LogicalFilter) {
+
upperFilterConjuncts.addAll(((LogicalFilter<?>) child).getConjuncts());
+ child = child.child(0);
}
if ((child instanceof LogicalAggregate) &&
child.child(0) instanceof LogicalRelation) {
upAgg = child;
@@ -105,9 +100,7 @@ public class CheckPolicy implements AnalysisRuleFactory {
relatedPolicy.rowPolicyFilter.ifPresent(expression
-> combineFilter.addAll(
ExpressionUtils.extractConjunctionToSet(expression)));
Plan result = upAgg != null ?
upAgg.withChildren(child) : child;
- if (upperFilter != null) {
-
combineFilter.addAll(upperFilter.getConjuncts());
- }
+ combineFilter.addAll(upperFilterConjuncts);
if (!combineFilter.isEmpty()) {
result = new LogicalFilter<>(combineFilter,
result);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java
index 8b426283e6b..bda236a15bc 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DeleteFromCommand.java
@@ -210,9 +210,10 @@ public class DeleteFromCommand extends Command implements
ForwardWithSync, Expla
}
}
- // if table's enable_mow_light_delete is false, use
`DeleteFromUsingCommand`
+ // Row binlog needs row-bearing deletes to emit DELETE events.
Predicate deletes only write
+ // delete predicates, so use `DeleteFromUsingCommand` even when MOW
light delete is enabled.
if (olapTable.getKeysType() == KeysType.UNIQUE_KEYS &&
olapTable.getEnableUniqueKeyMergeOnWrite()
- && !olapTable.getEnableMowLightDelete()) {
+ && (!olapTable.getEnableMowLightDelete() ||
olapTable.needRowBinlog())) {
new DeleteFromUsingCommand(nameParts, tableAlias, isTempPart,
partitions, logicalQuery,
Optional.empty(), false).run(ctx, executor);
return;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
index 6d204304cb1..920aca603f0 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalCheckPolicy.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.trees.plans.logical;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.OlapTableWrapper;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.datasource.CatalogIf;
import org.apache.doris.mysql.privilege.AccessControllerManager;
@@ -146,10 +147,11 @@ public class LogicalCheckPolicy<CHILD_TYPE extends Plan>
extends LogicalUnary<CH
if (!(logicalPlan instanceof CatalogRelation || logicalPlan instanceof
LogicalView)) {
return RelatedPolicy.NO_POLICY;
}
+ TableIf table = getPolicyTable(logicalPlan);
Optional<Map<TableIf, Set<Expression>>> mvRefreshPredicates =
cascadesContext.getStatementContext()
.getMvRefreshPredicates();
if (mvRefreshPredicates.isPresent()) {
- return findPolicyByMvRefresh(mvRefreshPredicates.get(),
logicalPlan);
+ return findPolicyByMvRefresh(mvRefreshPredicates.get(), table);
}
ConnectContext connectContext = cascadesContext.getConnectContext();
AccessControllerManager accessManager =
connectContext.getEnv().getAccessManager();
@@ -158,8 +160,6 @@ public class LogicalCheckPolicy<CHILD_TYPE extends Plan>
extends LogicalUnary<CH
return RelatedPolicy.NO_POLICY;
}
- TableIf table = logicalPlan instanceof CatalogRelation ?
((CatalogRelation) logicalPlan).getTable()
- : ((LogicalView<?>) logicalPlan).getView();
DatabaseIf database = table.getDatabase();
if (database == null) {
return RelatedPolicy.NO_POLICY;
@@ -213,10 +213,19 @@ public class LogicalCheckPolicy<CHILD_TYPE extends Plan>
extends LogicalUnary<CH
);
}
- private RelatedPolicy findPolicyByMvRefresh(Map<TableIf, Set<Expression>>
mvRefreshPredicates,
- LogicalPlan logicalPlan) {
+ private static TableIf getPolicyTable(LogicalPlan logicalPlan) {
TableIf table = logicalPlan instanceof CatalogRelation ?
((CatalogRelation) logicalPlan).getTable()
: ((LogicalView<?>) logicalPlan).getView();
+ // A wrapper changes how a scan reads data, not which table owns its
authorization policy. Unwrap
+ // recursively so future wrappers and stacked wrappers keep using the
original table identity.
+ while (table instanceof OlapTableWrapper) {
+ table = ((OlapTableWrapper) table).getOriginTable();
+ }
+ return table;
+ }
+
+ private RelatedPolicy findPolicyByMvRefresh(Map<TableIf, Set<Expression>>
mvRefreshPredicates,
+ TableIf table) {
if (mvRefreshPredicates.containsKey(table)) {
return new
RelatedPolicy(Optional.of(ExpressionUtils.or(mvRefreshPredicates.get(table))),
Optional.empty());
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/DropTableStreamTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/DropTableStreamTest.java
index 83a43edfb00..5e223251ecc 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/DropTableStreamTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/DropTableStreamTest.java
@@ -17,6 +17,7 @@
package org.apache.doris.catalog;
+import org.apache.doris.catalog.stream.OlapTableStream;
import org.apache.doris.common.Config;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.ExceptionChecker;
@@ -25,11 +26,15 @@ import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.trees.plans.commands.DropStreamCommand;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.qe.StmtExecutor;
+import org.apache.doris.thrift.TRow;
import org.apache.doris.utframe.TestWithFeService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.ArrayList;
+import java.util.List;
+
public class DropTableStreamTest extends TestWithFeService {
@Override
@@ -67,6 +72,33 @@ public class DropTableStreamTest extends TestWithFeService {
}
}
+ private void createBaseTableAndStream(String tableName, String streamName)
throws Exception {
+ createTable("create table test_stream." + tableName + " (k1 int, k2
int) "
+ + "unique key(k1) distributed by hash(k1) buckets 1 "
+ + "properties('replication_num' = '1', 'binlog.enable' =
'true', 'binlog.format' = 'ROW', "
+ + "'binlog.need_historical_value' = 'true')");
+ createTable("create stream test_stream." + streamName + " on table
test_stream." + tableName
+ + " properties('show_initial_rows' = 'true')");
+ }
+
+ private TRow getStreamMetadataRow(String streamName) {
+ List<TRow> rows = new ArrayList<>();
+
Env.getCurrentEnv().getTableStreamManager().fillTableStreamValuesMetadataResult(rows);
+ return rows.stream()
+ .filter(row ->
streamName.equals(row.getColumnValue().get(1).getStringVal()))
+ .findFirst()
+ .orElseThrow(AssertionError::new);
+ }
+
+ private void assertStreamMetadataState(String streamName, String
baseTableType,
+ boolean enabled, boolean stale, String staleReason) {
+ TRow row = getStreamMetadataRow(streamName);
+ Assertions.assertEquals(baseTableType,
row.getColumnValue().get(9).getStringVal());
+ Assertions.assertEquals(enabled,
row.getColumnValue().get(10).isBoolVal());
+ Assertions.assertEquals(stale,
row.getColumnValue().get(11).isBoolVal());
+ Assertions.assertEquals(staleReason,
row.getColumnValue().get(12).getStringVal());
+ }
+
@Test
public void testNormalDropStream() throws Exception {
// test drop
@@ -104,6 +136,43 @@ public class DropTableStreamTest extends TestWithFeService
{
}
}
+ @Test
+ public void testStreamMetadataFollowsRecoverableBaseTable() throws
Exception {
+ createBaseTableAndStream("tbl_recover", "s_recover");
+ Database db =
Env.getCurrentInternalCatalog().getDbOrMetaException("test_stream");
+ long baseTableId = db.getTableOrMetaException("tbl_recover").getId();
+
+ assertStreamMetadataState("s_recover", "OLAP", true, false, "N/A");
+
+ dropTableWithSql("drop table test_stream.tbl_recover");
+ assertStreamMetadataState("s_recover", "N/A", false, true, "Base table
does not exist");
+
+ recoverTable("recover table test_stream.tbl_recover");
+ Assertions.assertEquals(baseTableId,
db.getTableOrMetaException("tbl_recover").getId());
+ assertStreamMetadataState("s_recover", "OLAP", true, false, "N/A");
+ }
+
+ @Test
+ public void testStreamMetadataRejectsSameNameBaseTable() throws Exception {
+ createBaseTableAndStream("tbl_force", "s_force");
+ Database db =
Env.getCurrentInternalCatalog().getDbOrMetaException("test_stream");
+ long oldBaseTableId = db.getTableOrMetaException("tbl_force").getId();
+ OlapTableStream stream = (OlapTableStream)
db.getTableOrMetaException("s_force");
+
+ dropTableWithSql("drop table test_stream.tbl_force force");
+ Assertions.assertNull(stream.getBaseTableNullable());
+ assertStreamMetadataState("s_force", "N/A", false, true, "Base table
does not exist");
+
+ createTable("create table test_stream.tbl_force (k1 int, k2 int) "
+ + "unique key(k1) distributed by hash(k1) buckets 1 "
+ + "properties('replication_num' = '1', 'binlog.enable' =
'true', 'binlog.format' = 'ROW', "
+ + "'binlog.need_historical_value' = 'true')");
+
+ Assertions.assertNotEquals(oldBaseTableId,
db.getTableOrMetaException("tbl_force").getId());
+ Assertions.assertNull(stream.getBaseTableNullable());
+ assertStreamMetadataState("s_force", "N/A", false, true, "Base table
does not exist");
+ }
+
@Override
protected void runAfterAll() throws Exception {
dropDatabase("test_stream");
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandlerTest.java
b/fe/fe-core/src/test/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandlerTest.java
index 885e39052fb..c5037746aec 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandlerTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/cloud/alter/CloudSchemaChangeHandlerTest.java
@@ -17,6 +17,7 @@
package org.apache.doris.cloud.alter;
+import org.apache.doris.catalog.BinlogConfig;
import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.KeysType;
@@ -155,6 +156,104 @@ public class CloudSchemaChangeHandlerTest {
Assertions.assertEquals(Arrays.asList(103L),
syncRequests.get(3).getTabletIdsList());
}
+ @Test
+ public void testUpdateBinlogConfigUsesCloudTabletMeta() throws Exception {
+ CloudSchemaChangeHandler handler = new CloudSchemaChangeHandler();
+ Database db = createMockDatabaseWithThreeTablets();
+ BinlogConfig binlogConfig = new BinlogConfig(true, 3600L, 4096L, 7L,
+ BinlogConfig.BinlogFormat.ROW, true);
+
+ MetaServiceProxy metaServiceProxy =
Mockito.mock(MetaServiceProxy.class);
+
Mockito.when(metaServiceProxy.updateTablet(Mockito.any())).thenReturn(okUpdateTabletResponse());
+ Config.enable_debug_points = true;
+
DebugPointUtil.addDebugPoint("CloudSchemaChangeHandler.notifyBackendsToSyncTabletMeta.skip");
+
+ try (MockedStatic<MetaServiceProxy> metaProxyMock =
Mockito.mockStatic(MetaServiceProxy.class)) {
+
metaProxyMock.when(MetaServiceProxy::getInstance).thenReturn(metaServiceProxy);
+ handler.updatePartitionProperties(db, "tbl", "p1", -1, -1,
binlogConfig,
+ null, null, -1, -1, -1);
+ }
+
+ ArgumentCaptor<Cloud.UpdateTabletRequest> updateCaptor =
+ ArgumentCaptor.forClass(Cloud.UpdateTabletRequest.class);
+ Mockito.verify(metaServiceProxy,
Mockito.times(2)).updateTablet(updateCaptor.capture());
+ List<Cloud.TabletMetaInfoPB> tabletMetaInfos =
updateCaptor.getAllValues().stream()
+ .flatMap(request -> request.getTabletMetaInfosList().stream())
+ .collect(Collectors.toList());
+ Assertions.assertEquals(Arrays.asList(101L, 102L, 103L),
tabletMetaInfos.stream()
+
.map(Cloud.TabletMetaInfoPB::getTabletId).collect(Collectors.toList()));
+ for (Cloud.TabletMetaInfoPB tabletMetaInfo : tabletMetaInfos) {
+ Assertions.assertTrue(tabletMetaInfo.hasBinlogConfig());
+
Assertions.assertTrue(tabletMetaInfo.getBinlogConfig().getEnable());
+ Assertions.assertEquals(3600L,
tabletMetaInfo.getBinlogConfig().getTtlSeconds());
+ Assertions.assertEquals(4096L,
tabletMetaInfo.getBinlogConfig().getMaxBytes());
+ Assertions.assertEquals(7L,
tabletMetaInfo.getBinlogConfig().getMaxHistoryNums());
+
Assertions.assertEquals(org.apache.doris.proto.OlapFile.BinlogFormatPB.ROW,
+ tabletMetaInfo.getBinlogConfig().getBinlogFormat());
+
Assertions.assertTrue(tabletMetaInfo.getBinlogConfig().getNeedHistoricalValue());
+ }
+ }
+
+ @Test
+ public void testUpdateCompactionPolicyExcludesRowBinlogTablets() throws
Exception {
+ CloudSchemaChangeHandler handler = new CloudSchemaChangeHandler();
+ Database db = createMockDatabaseWithRowBinlogTablet();
+ Env env = Mockito.mock(Env.class);
+ MetaServiceProxy metaServiceProxy =
Mockito.mock(MetaServiceProxy.class);
+
Mockito.when(metaServiceProxy.updateTablet(Mockito.any())).thenReturn(okUpdateTabletResponse());
+
+ Config.enable_debug_points = true;
+
DebugPointUtil.addDebugPoint("CloudSchemaChangeHandler.notifyBackendsToSyncTabletMeta.skip");
+ try (MockedStatic<Env> envMock = Mockito.mockStatic(Env.class);
+ MockedStatic<MetaServiceProxy> metaProxyMock =
Mockito.mockStatic(MetaServiceProxy.class)) {
+ envMock.when(Env::getCurrentEnv).thenReturn(env);
+
metaProxyMock.when(MetaServiceProxy::getInstance).thenReturn(metaServiceProxy);
+
+ Map<String, String> properties = new HashMap<>();
+ properties.put(PropertyAnalyzer.PROPERTIES_COMPACTION_POLICY,
+ PropertyAnalyzer.TIME_SERIES_COMPACTION_POLICY);
+ handler.updateTableProperties(db, "tbl", properties);
+ }
+
+ ArgumentCaptor<Cloud.UpdateTabletRequest> updateCaptor =
+ ArgumentCaptor.forClass(Cloud.UpdateTabletRequest.class);
+ Mockito.verify(metaServiceProxy).updateTablet(updateCaptor.capture());
+ List<Cloud.TabletMetaInfoPB> tabletMetaInfos =
updateCaptor.getValue().getTabletMetaInfosList();
+ Assertions.assertEquals(Arrays.asList(101L), tabletMetaInfos.stream()
+
.map(Cloud.TabletMetaInfoPB::getTabletId).collect(Collectors.toList()));
+ Assertions.assertTrue(tabletMetaInfos.stream().allMatch(info ->
+
PropertyAnalyzer.TIME_SERIES_COMPACTION_POLICY.equals(info.getCompactionPolicy())));
+ }
+
+ @Test
+ public void testUpdateTtlIncludesRowBinlogTablets() throws Exception {
+ CloudSchemaChangeHandler handler = new CloudSchemaChangeHandler();
+ Database db = createMockDatabaseWithRowBinlogTablet();
+ Env env = Mockito.mock(Env.class);
+ MetaServiceProxy metaServiceProxy =
Mockito.mock(MetaServiceProxy.class);
+
Mockito.when(metaServiceProxy.updateTablet(Mockito.any())).thenReturn(okUpdateTabletResponse());
+
+ Config.enable_debug_points = true;
+
DebugPointUtil.addDebugPoint("CloudSchemaChangeHandler.notifyBackendsToSyncTabletMeta.skip");
+ try (MockedStatic<Env> envMock = Mockito.mockStatic(Env.class);
+ MockedStatic<MetaServiceProxy> metaProxyMock =
Mockito.mockStatic(MetaServiceProxy.class)) {
+ envMock.when(Env::getCurrentEnv).thenReturn(env);
+
metaProxyMock.when(MetaServiceProxy::getInstance).thenReturn(metaServiceProxy);
+
+ Map<String, String> properties = new HashMap<>();
+ properties.put(PropertyAnalyzer.PROPERTIES_FILE_CACHE_TTL_SECONDS,
"300");
+ handler.updateTableProperties(db, "tbl", properties);
+ }
+
+ ArgumentCaptor<Cloud.UpdateTabletRequest> updateCaptor =
+ ArgumentCaptor.forClass(Cloud.UpdateTabletRequest.class);
+ Mockito.verify(metaServiceProxy).updateTablet(updateCaptor.capture());
+ List<Cloud.TabletMetaInfoPB> tabletMetaInfos =
updateCaptor.getValue().getTabletMetaInfosList();
+ Assertions.assertEquals(Arrays.asList(101L, 201L),
tabletMetaInfos.stream()
+
.map(Cloud.TabletMetaInfoPB::getTabletId).collect(Collectors.toList()));
+ Assertions.assertTrue(tabletMetaInfos.stream().allMatch(info ->
info.getTtlSeconds() == 300));
+ }
+
@Test
public void testUpdateTablePropertiesThrowsWhenUpdateTabletFails() throws
Exception {
CloudSchemaChangeHandler handler = new CloudSchemaChangeHandler();
@@ -552,6 +651,8 @@ public class CloudSchemaChangeHandlerTest {
Mockito.when(partition.getName()).thenReturn("p1");
Mockito.when(partition.getMaterializedIndices(MaterializedIndex.IndexExtState.VISIBLE,
true))
.thenReturn(Arrays.asList(index));
+
Mockito.when(partition.getMaterializedIndices(MaterializedIndex.IndexExtState.VISIBLE,
false))
+ .thenReturn(Arrays.asList(index));
Mockito.when(index.getTablets()).thenReturn(Arrays.asList(tablet1,
tablet2, tablet3));
Mockito.when(tablet1.getId()).thenReturn(101L);
Mockito.when(tablet2.getId()).thenReturn(102L);
@@ -559,6 +660,33 @@ public class CloudSchemaChangeHandlerTest {
return db;
}
+ private Database createMockDatabaseWithRowBinlogTablet() throws Exception {
+ Database db = Mockito.mock(Database.class);
+ OlapTable table = Mockito.mock(OlapTable.class);
+ Partition partition = Mockito.mock(Partition.class);
+ MaterializedIndex baseIndex = Mockito.mock(MaterializedIndex.class);
+ MaterializedIndex rowBinlogIndex =
Mockito.mock(MaterializedIndex.class);
+ org.apache.doris.catalog.Tablet baseTablet =
Mockito.mock(org.apache.doris.catalog.Tablet.class);
+ org.apache.doris.catalog.Tablet rowBinlogTablet =
Mockito.mock(org.apache.doris.catalog.Tablet.class);
+
+ Mockito.when(db.getTableOrMetaException("tbl",
Table.TableType.OLAP)).thenReturn(table);
+ Mockito.when(table.getName()).thenReturn("tbl");
+
Mockito.when(table.getCompactionPolicy()).thenReturn(PropertyAnalyzer.SIZE_BASED_COMPACTION_POLICY);
+ Mockito.when(table.getKeysType()).thenReturn(KeysType.DUP_KEYS);
+
Mockito.when(table.getPartitions()).thenReturn(Arrays.asList(partition));
+ Mockito.when(table.getPartition("p1")).thenReturn(partition);
+ Mockito.when(partition.getName()).thenReturn("p1");
+
Mockito.when(partition.getMaterializedIndices(MaterializedIndex.IndexExtState.VISIBLE,
true))
+ .thenReturn(Arrays.asList(baseIndex, rowBinlogIndex));
+
Mockito.when(partition.getMaterializedIndices(MaterializedIndex.IndexExtState.VISIBLE,
false))
+ .thenReturn(Arrays.asList(baseIndex));
+
Mockito.when(baseIndex.getTablets()).thenReturn(Arrays.asList(baseTablet));
+
Mockito.when(rowBinlogIndex.getTablets()).thenReturn(Arrays.asList(rowBinlogTablet));
+ Mockito.when(baseTablet.getId()).thenReturn(101L);
+ Mockito.when(rowBinlogTablet.getId()).thenReturn(201L);
+ return db;
+ }
+
private InternalService.PSyncTabletMetaResponse okSyncTabletMetaResponse()
{
return InternalService.PSyncTabletMetaResponse.newBuilder()
.setStatus(org.apache.doris.proto.Types.PStatus.newBuilder()
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
index a67de805825..649bdd50f75 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
@@ -22,39 +22,58 @@ import org.apache.doris.analysis.UserDesc;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.AccessPrivilege;
import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.Database;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.OlapTableWrapper;
+import org.apache.doris.common.Config;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.jmockit.Deencapsulation;
import org.apache.doris.mysql.privilege.AccessControllerManager;
import org.apache.doris.mysql.privilege.DataMaskPolicy;
+import org.apache.doris.nereids.CascadesContext;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.analyzer.UnboundRelation;
import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.properties.PhysicalProperties;
+import org.apache.doris.nereids.trees.expressions.Alias;
import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.commands.CreateUserCommand;
import
org.apache.doris.nereids.trees.plans.commands.GrantTablePrivilegeCommand;
import org.apache.doris.nereids.trees.plans.commands.info.CreateUserInfo;
import org.apache.doris.nereids.trees.plans.logical.LogicalCheckPolicy;
+import
org.apache.doris.nereids.trees.plans.logical.LogicalCheckPolicy.RelatedPolicy;
import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.util.PlanChecker;
import org.apache.doris.nereids.util.PlanRewriter;
import org.apache.doris.utframe.TestWithFeService;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
public class CheckRowPolicyTest extends TestWithFeService {
@@ -63,6 +82,7 @@ public class CheckRowPolicyTest extends TestWithFeService {
private static String tableName = "table1";
private static String tableNameRanddomDist = "tableRandomDist";
+ private static String tableNameMow = "tableMow";
private static String userName = "user1";
private static String policyName = "policy1";
@@ -71,6 +91,7 @@ public class CheckRowPolicyTest extends TestWithFeService {
@Override
protected void runBeforeAll() throws Exception {
FeConstants.runningUnitTest = true;
+ Config.enable_feature_binlog = true;
createDatabase(dbName);
useDatabase(dbName);
createTable("create table "
@@ -81,6 +102,13 @@ public class CheckRowPolicyTest extends TestWithFeService {
+ tableNameRanddomDist
+ " (k1 int, k2 int) AGGREGATE KEY(k1, k2) distributed by
random buckets 1"
+ " properties(\"replication_num\" = \"1\");");
+ createTable("create table "
+ + tableNameMow
+ + " (k1 int, k2 int) UNIQUE KEY(k1) distributed by hash(k1)
buckets 1"
+ + " properties(\"replication_num\" = \"1\","
+ + " \"enable_unique_key_merge_on_write\" = \"true\","
+ + " \"binlog.enable\" = \"true\", \"binlog.format\" = \"ROW\","
+ + " \"binlog.need_historical_value\" = \"true\");");
Database db =
Env.getCurrentInternalCatalog().getDbOrMetaException(fullDbName);
olapTable = (OlapTable) db.getTableOrAnalysisException(tableName);
@@ -104,19 +132,50 @@ public class CheckRowPolicyTest extends TestWithFeService
{
Mockito.doAnswer(invocation -> {
String tbl = invocation.getArgument(3);
String col = invocation.getArgument(4);
- return tbl.equalsIgnoreCase(tableNameRanddomDist)
- ? Optional.of(new DataMaskPolicy() {
- @Override
- public String getMaskTypeDef() {
- return String.format("concat(%s, '_****_', %s)",
col, col);
- }
-
- @Override
- public String getPolicyIdent() {
- return String.format("custom policy: concat(%s,
'_****_', %s)", col, col);
- }
- })
- : Optional.empty();
+ if (tbl.equalsIgnoreCase(tableNameRanddomDist)) {
+ return Optional.of(new DataMaskPolicy() {
+ @Override
+ public String getMaskTypeDef() {
+ return String.format("concat(%s, '_****_', %s)", col,
col);
+ }
+
+ @Override
+ public String getPolicyIdent() {
+ return String.format("custom policy: concat(%s,
'_****_', %s)", col, col);
+ }
+ });
+ }
+ if (!tbl.equalsIgnoreCase(tableNameMow)) {
+ return Optional.empty();
+ }
+ String column = col.toLowerCase(Locale.ROOT);
+ if (column.equalsIgnoreCase("k2")) {
+ String mask = "if(assert_true(k2 >= 0, 'post-snapshot row
reached mask'), k2, NULL)";
+ return Optional.of(new DataMaskPolicy() {
+ @Override
+ public String getMaskTypeDef() {
+ return mask;
+ }
+
+ @Override
+ public String getPolicyIdent() {
+ return "custom non-movable policy: " + mask;
+ }
+ });
+ }
+ // Mask hidden reconstruction columns too. Their aliases
deliberately get new ExprIds,
+ // so a reconstruction filter left above the mask would fail
CheckAfterRewrite.
+ return Optional.of(new DataMaskPolicy() {
+ @Override
+ public String getMaskTypeDef() {
+ return column;
+ }
+
+ @Override
+ public String getPolicyIdent() {
+ return "custom identity policy: " + column;
+ }
+ });
}).when(spyAcm).evalDataMaskPolicy(
Mockito.any(UserIdentity.class), Mockito.anyString(),
Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
@@ -236,4 +295,102 @@ public class CheckRowPolicyTest extends TestWithFeService
{
+ " ON "
+ tableNameRanddomDist);
}
+
+ @Test
+ public void checkPolicyOnOlapTableWrapperUsesOriginTable() throws
Exception {
+ useUser(userName);
+ connectContext.setStatementContext(new StatementContext());
+ LogicalOlapScan relation = new
LogicalOlapScan(StatementScopeIdGenerator.newRelationId(),
+ new RenamedOlapTableWrapper(olapTable),
Arrays.asList(fullDbName));
+ LogicalCheckPolicy<LogicalOlapScan> checkPolicy = new
LogicalCheckPolicy<>(relation);
+ createPolicy("CREATE ROW POLICY " + policyName + " ON " + tableName
+ + " AS PERMISSIVE TO " + userName + " USING (k1 = 1)");
+ try {
+ Plan plan = PlanRewriter.bottomUpRewrite(checkPolicy,
connectContext,
+ new CheckPolicy(), new BindExpression());
+
+ Assertions.assertTrue(plan instanceof LogicalFilter);
+ LogicalFilter<?> filter = (LogicalFilter<?>) plan;
+ Assertions.assertEquals(relation, filter.child());
+
Assertions.assertTrue(filter.getConjuncts().toString().contains("k1"));
+ } finally {
+ dropPolicy("DROP ROW POLICY " + policyName + " ON " + tableName);
+ }
+ }
+
+ @Test
+ public void checkMvRefreshPolicyOnNestedOlapTableWrapperUsesOriginTable()
throws Exception {
+ useUser(userName);
+ StatementContext statementContext = new StatementContext();
+ connectContext.setStatementContext(statementContext);
+ OlapTableWrapper wrapper = new OlapTableWrapper(olapTable,
Collections.emptyMap());
+ OlapTableWrapper nestedWrapper = new OlapTableWrapper(wrapper,
Collections.emptyMap());
+ LogicalOlapScan relation = new
LogicalOlapScan(StatementScopeIdGenerator.newRelationId(),
+ nestedWrapper, Arrays.asList(fullDbName));
+ Expression predicate = new EqualTo(relation.getOutput().get(0), new
IntegerLiteral(1));
+ statementContext.setMvRefreshPredicates(ImmutableMap.of(olapTable,
ImmutableSet.of(predicate)));
+
+ LogicalCheckPolicy<LogicalOlapScan> checkPolicy = new
LogicalCheckPolicy<>(relation);
+ RelatedPolicy policy = checkPolicy.findPolicy(relation,
+ CascadesContext.initContext(statementContext, relation,
PhysicalProperties.GATHER));
+
+ Assertions.assertEquals(Optional.of(predicate),
policy.rowPolicyFilter);
+ }
+
+ @Test
+ public void mowTimeTravelReconstructionFiltersRunBeforeNonMovableMask()
throws Exception {
+ useUser(userName);
+ connectContext.getState().setIsQuery(true);
+
+ Plan rewrittenPlan = PlanChecker.from(connectContext)
+ .analyze("select k1, k2 from " + tableNameMow + " for version
as of 1001")
+ .rewrite()
+ .getPlan();
+ Set<LogicalUnion> unions = rewrittenPlan.collect(node -> node
instanceof LogicalUnion);
+ Assertions.assertEquals(1, unions.size());
+
+ LogicalUnion union = unions.iterator().next();
+ Assertions.assertEquals(2, union.children().size());
+ Set<String> reconstructionFilterSlots = new java.util.HashSet<>();
+ for (Plan branch : union.children()) {
+ Set<LogicalProject<?>> branchProjects = branch.collect(node ->
node instanceof LogicalProject);
+ int maskedK2ProjectCount = 0;
+ for (LogicalProject<?> project : branchProjects) {
+ for (NamedExpression namedExpression : project.getProjects()) {
+ if (namedExpression.getName().equalsIgnoreCase("k2")
+ && namedExpression instanceof Alias
+ && !(((Alias) namedExpression).child() instanceof
Slot)) {
+ maskedK2ProjectCount++;
+ }
+ }
+ }
+ Assertions.assertTrue(maskedK2ProjectCount >= 1,
+ "each MOW time-travel branch must retain its k2 data
mask");
+
+ Set<LogicalProject<?>> maskProjects = branch.collect(node -> node
instanceof LogicalProject
+ && ((LogicalProject<?>)
node).containsNoneMovableFunction());
+ Assertions.assertFalse(maskProjects.isEmpty(),
+ "each branch must contain the non-movable data mask");
+ for (LogicalProject<?> maskProject : maskProjects) {
+ Set<LogicalFilter<?>> filtersBelowMask =
maskProject.collect(node -> node instanceof LogicalFilter);
+ Assertions.assertFalse(filtersBelowMask.isEmpty(),
+ "reconstruction filters must run before every
non-movable data mask");
+ reconstructionFilterSlots.addAll(filtersBelowMask.stream()
+ .flatMap(filter -> filter.getConjuncts().stream())
+ .flatMap(conjunct -> conjunct.getInputSlots().stream())
+ .map(Slot::getName)
+ .collect(Collectors.toSet()));
+ }
+ }
+
Assertions.assertTrue(reconstructionFilterSlots.contains(Column.DELETE_SIGN));
+
Assertions.assertTrue(reconstructionFilterSlots.contains(Column.COMMIT_TSO_COL));
+
Assertions.assertTrue(reconstructionFilterSlots.contains(Column.BINLOG_OPERATION_COL));
+ }
+
+ private static class RenamedOlapTableWrapper extends OlapTableWrapper {
+ private RenamedOlapTableWrapper(OlapTable originTable) {
+ super(originTable, "renamed_policy_wrapper",
originTable.getBaseSchema(),
+ originTable.getKeysType(), Collections.emptyMap());
+ }
+ }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ExplainTableStreamPlanTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ExplainTableStreamPlanTest.java
index e139ab4ab5e..06698b6b966 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ExplainTableStreamPlanTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ExplainTableStreamPlanTest.java
@@ -57,6 +57,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapTableStreamScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
import org.apache.doris.nereids.util.MemoTestUtils;
import org.apache.doris.nereids.util.PlanChecker;
@@ -710,6 +711,29 @@ public class ExplainTableStreamPlanTest extends
TestWithFeService {
}
}
+ @Test
+ public void testMowTimeTravelBranchProjectionPreservesQualifier() {
+ Plan plan = PlanChecker.from(connectContext)
+ .analyze("select * from test_stream.tbl_stream_base for
version as of 1001")
+ .getCascadesContext().getRewritePlan();
+ Set<LogicalUnion> unions = plan.collect(node -> node instanceof
LogicalUnion);
+ Assertions.assertEquals(1, unions.size());
+
+ List<String> expectedQualifier = java.util.Arrays.asList(
+ "internal", "test_stream", "tbl_stream_base");
+ LogicalUnion union = unions.iterator().next();
+ Assertions.assertEquals(2, union.children().size());
+ for (Plan branch : union.children()) {
+ Assertions.assertInstanceOf(LogicalProject.class, branch);
+ LogicalProject<?> projection = (LogicalProject<?>) branch;
+ Assertions.assertFalse(projection.getProjects().isEmpty());
+ for (NamedExpression output : projection.getProjects()) {
+ Assertions.assertEquals(expectedQualifier,
output.getQualifier(),
+ "MOW time-travel branch aliases must keep the scan
qualifier");
+ }
+ }
+ }
+
@Test
public void testMowTimeTravelQualifiedColumnCanBind() {
// MOW time-travel goes through a union whose outputs are rebuilt with
empty qualifiers.
diff --git a/gensrc/proto/cloud.proto b/gensrc/proto/cloud.proto
index 62e94a9b75a..a6cfecdcb85 100644
--- a/gensrc/proto/cloud.proto
+++ b/gensrc/proto/cloud.proto
@@ -736,6 +736,7 @@ message TabletMetaInfoPB { // For update tablet meta
optional bool enable_mow_light_delete = 14;
optional int32 vertical_compaction_num_columns_per_group = 15;
optional string group_commit_mode = 16;
+ optional doris.BinlogConfigPB binlog_config = 17;
}
message TabletCompactionJobPB {
diff --git
a/regression-test/data/row_binlog_p0/test_row_binlog_mow_light_delete.out
b/regression-test/data/row_binlog_p0/test_row_binlog_mow_light_delete.out
new file mode 100644
index 00000000000..5b12cd39019
--- /dev/null
+++ b/regression-test/data/row_binlog_p0/test_row_binlog_mow_light_delete.out
@@ -0,0 +1,16 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !without_history_base --
+2 two
+
+-- !without_history_binlog --
+0 1 one
+0 2 two
+2 1 \N
+
+-- !with_history_base --
+2 two
+
+-- !with_history_binlog --
+0 1 one \N
+0 2 two \N
+2 1 one one
diff --git
a/regression-test/data/time_travel_p0/test_mow_time_travel_row_policy.out
b/regression-test/data/time_travel_p0/test_mow_time_travel_row_policy.out
new file mode 100644
index 00000000000..fab45bf8500
--- /dev/null
+++ b/regression-test/data/time_travel_p0/test_mow_time_travel_row_policy.out
@@ -0,0 +1,15 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !latest_image --
+1 10
+
+-- !historical_image --
+1 10
+3 30
+
+-- !historical_dotted_column_unqualified --
+100
+300
+
+-- !historical_dotted_column_qualified --
+100
+300
diff --git
a/regression-test/suites/row_binlog_p0/test_row_binlog_mow_light_delete.groovy
b/regression-test/suites/row_binlog_p0/test_row_binlog_mow_light_delete.groovy
new file mode 100644
index 00000000000..096e0fdee65
--- /dev/null
+++
b/regression-test/suites/row_binlog_p0/test_row_binlog_mow_light_delete.groovy
@@ -0,0 +1,104 @@
+// 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_row_binlog_mow_light_delete", "nonConcurrent") {
+ sql "DROP TABLE IF EXISTS test_row_binlog_mow_light_delete_without_history
FORCE"
+ sql "DROP TABLE IF EXISTS test_row_binlog_mow_light_delete_with_history
FORCE"
+ sql "DROP TABLE IF EXISTS test_row_binlog_dup_delete FORCE"
+
+ sql """
+ CREATE TABLE test_row_binlog_mow_light_delete_without_history (
+ k INT,
+ v STRING
+ )
+ UNIQUE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "enable_unique_key_merge_on_write" = "true",
+ "enable_mow_light_delete" = "true",
+ "binlog.enable" = "true",
+ "binlog.format" = "ROW",
+ "binlog.need_historical_value" = "false"
+ )
+ """
+
+ sql "INSERT INTO test_row_binlog_mow_light_delete_without_history VALUES
(1, 'one'), (2, 'two')"
+ sql "DELETE FROM test_row_binlog_mow_light_delete_without_history WHERE k
= 1"
+
+ order_qt_without_history_base """
+ SELECT k, v
+ FROM test_row_binlog_mow_light_delete_without_history
+ """
+
+ qt_without_history_binlog """
+ SELECT __DORIS_BINLOG_OP__ AS op, k, v
+ FROM binlog("table" =
"test_row_binlog_mow_light_delete_without_history")
+ ORDER BY __DORIS_BINLOG_TSO__, __DORIS_BINLOG_LSN__
+ """
+
+ sql """
+ CREATE TABLE test_row_binlog_mow_light_delete_with_history (
+ k INT,
+ v STRING
+ )
+ UNIQUE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "enable_unique_key_merge_on_write" = "true",
+ "enable_mow_light_delete" = "true",
+ "binlog.enable" = "true",
+ "binlog.format" = "ROW",
+ "binlog.need_historical_value" = "true"
+ )
+ """
+
+ sql "INSERT INTO test_row_binlog_mow_light_delete_with_history VALUES (1,
'one'), (2, 'two')"
+ sql "DELETE FROM test_row_binlog_mow_light_delete_with_history WHERE k = 1"
+
+ order_qt_with_history_base """
+ SELECT k, v
+ FROM test_row_binlog_mow_light_delete_with_history
+ """
+
+ qt_with_history_binlog """
+ SELECT __DORIS_BINLOG_OP__ AS op, k, v, __BEFORE__v__
+ FROM binlog("table" = "test_row_binlog_mow_light_delete_with_history")
+ ORDER BY __DORIS_BINLOG_TSO__, __DORIS_BINLOG_LSN__
+ """
+
+ sql """
+ CREATE TABLE test_row_binlog_dup_delete (
+ k INT,
+ v STRING
+ )
+ DUPLICATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "binlog.enable" = "true",
+ "binlog.format" = "ROW"
+ )
+ """
+
+ sql "INSERT INTO test_row_binlog_dup_delete VALUES (1, 'one')"
+ test {
+ sql "DELETE FROM test_row_binlog_dup_delete WHERE k = 1"
+ exception "DELETE with predicates is not supported when binlog<row> is
enabled"
+ }
+}
diff --git
a/regression-test/suites/time_travel_p0/test_mow_time_travel_row_policy.groovy
b/regression-test/suites/time_travel_p0/test_mow_time_travel_row_policy.groovy
new file mode 100644
index 00000000000..275b20f04c9
--- /dev/null
+++
b/regression-test/suites/time_travel_p0/test_mow_time_travel_row_policy.groovy
@@ -0,0 +1,107 @@
+// 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_mow_time_travel_row_policy", "nonConcurrent,p0,auth") {
+ if (isCloudMode()) {
+ return
+ }
+
+ String dbName = "test_mow_time_travel_row_policy_db"
+ String tableName = "test_mow_time_travel_row_policy_table"
+ String user = "test_mow_time_travel_row_policy_user"
+ String password = "C123_567p"
+ String restrictivePolicy = "test_mow_time_travel_value_policy"
+ String firstKeyPolicy = "test_mow_time_travel_first_key_policy"
+ String thirdKeyPolicy = "test_mow_time_travel_third_key_policy"
+
+ try_sql "DROP ROW POLICY IF EXISTS ${restrictivePolicy} ON
${dbName}.${tableName} FOR ${user}"
+ try_sql "DROP ROW POLICY IF EXISTS ${firstKeyPolicy} ON
${dbName}.${tableName} FOR ${user}"
+ try_sql "DROP ROW POLICY IF EXISTS ${thirdKeyPolicy} ON
${dbName}.${tableName} FOR ${user}"
+ try_sql "DROP USER IF EXISTS ${user}"
+ sql "DROP DATABASE IF EXISTS ${dbName}"
+
+ sql "CREATE DATABASE ${dbName}"
+ sql """
+ CREATE TABLE ${dbName}.${tableName} (
+ k INT,
+ v INT,
+ `v1.v2` INT
+ )
+ UNIQUE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ 'replication_num' = '1',
+ 'enable_unique_key_merge_on_write' = 'true',
+ 'binlog.enable' = 'true',
+ 'binlog.format' = 'ROW',
+ 'binlog.need_historical_value' = 'true'
+ )
+ """
+
+ sql "INSERT INTO ${dbName}.${tableName} VALUES (1, 10, 100), (2, 20, 200),
(3, 30, 300), (5, 50, 500)"
+ sql "SET show_hidden_columns = true"
+ long snapshotTso = sql("SELECT MAX(__DORIS_COMMIT_TSO_COL__) FROM
${dbName}.${tableName}")[0][0] as Long
+ sql "SET show_hidden_columns = false"
+
+ // The historical left branch contains unchanged keys 1 and 5. The
row-binlog right branch
+ // restores updated key 2 and deleted key 3. A new key 4 must not appear
in the snapshot.
+ sql "INSERT INTO ${dbName}.${tableName} VALUES (2, 200, 2000)"
+ sql "DELETE FROM ${dbName}.${tableName} WHERE k = 3"
+ sql "INSERT INTO ${dbName}.${tableName} VALUES (4, 40, 400)"
+
+ sql "CREATE USER '${user}' IDENTIFIED BY '${password}'"
+ sql "GRANT SELECT_PRIV ON internal.${dbName}.${tableName} TO ${user}"
+ sql """
+ CREATE ROW POLICY ${restrictivePolicy} ON ${dbName}.${tableName}
+ AS RESTRICTIVE TO ${user} USING (v <= 30)
+ """
+ sql """
+ CREATE ROW POLICY ${firstKeyPolicy} ON ${dbName}.${tableName}
+ AS PERMISSIVE TO ${user} USING (k = 1)
+ """
+ sql """
+ CREATE ROW POLICY ${thirdKeyPolicy} ON ${dbName}.${tableName}
+ AS PERMISSIVE TO ${user} USING (k = 3)
+ """
+
+ String userJdbcUrl = org.apache.doris.regression.Config.buildUrlWithDb(
+ context.config.jdbcUrl, dbName)
+ connect(user, password, userJdbcUrl) {
+ sql "SET enable_nereids_planner = true"
+ sql "SET enable_fallback_to_original_planner = false"
+ sql "SET enable_sql_cache = false"
+
+ // Restrictive AND (permissive OR permissive) leaves only key 1 in the
latest image.
+ order_qt_latest_image "SELECT k, v FROM ${tableName} ORDER BY k"
+
+ // key 1 is supplied by the base-scan branch and key 3 by the
row-binlog branch. A
+ // missing policy marker on either scan leaks key 5 or key 2
respectively.
+ order_qt_historical_image """
+ SELECT k, v FROM ${tableName} FOR VERSION AS OF ${snapshotTso}
ORDER BY k
+ """
+
+ // Keep the raw name of a dotted primitive column through both union
branches. Exercise
+ // unqualified and table-qualified binding because
UnboundSlot.getName() renders backticks.
+ order_qt_historical_dotted_column_unqualified """
+ SELECT `v1.v2` FROM ${tableName} FOR VERSION AS OF ${snapshotTso}
ORDER BY k
+ """
+ order_qt_historical_dotted_column_qualified """
+ SELECT ${tableName}.`v1.v2` FROM ${tableName} FOR VERSION AS OF
${snapshotTso}
+ ORDER BY ${tableName}.k
+ """
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]