Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions src/paimon/core/table/source/data_evolution_batch_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ DataEvolutionBatchScan::DataEvolutionBatchScan(

Result<std::shared_ptr<Plan>> DataEvolutionBatchScan::CreatePlan() {
std::optional<std::vector<Range>> row_ranges;
std::optional<int64_t> global_index_snapshot_id;
std::shared_ptr<GlobalIndexResult> final_global_index_result = global_index_result_;
if (!final_global_index_result) {
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<GlobalIndexResult> index_result, EvalGlobalIndex());
if (index_result) {
final_global_index_result = index_result;
PAIMON_ASSIGN_OR_RAISE(row_ranges, index_result->ToRanges());
PAIMON_ASSIGN_OR_RAISE(std::optional<EvaluatedGlobalIndex> evaluated_index,
EvalGlobalIndex());
if (evaluated_index && evaluated_index->result) {
final_global_index_result = evaluated_index->result;
global_index_snapshot_id = evaluated_index->snapshot_id;
PAIMON_ASSIGN_OR_RAISE(row_ranges, evaluated_index->result->ToRanges());
}
} else {
PAIMON_ASSIGN_OR_RAISE(row_ranges, final_global_index_result->ToRanges());
Expand All @@ -57,7 +60,14 @@ Result<std::shared_ptr<Plan>> DataEvolutionBatchScan::CreatePlan() {
return batch_scan_->CreatePlan();
}
if (row_ranges.value().empty()) {
return PlanImpl::EmptyPlan();
if (!global_index_snapshot_id) {
PAIMON_ASSIGN_OR_RAISE(global_index_snapshot_id, ResolveGlobalIndexSnapshotId());
}
if (!global_index_snapshot_id) {
return PlanImpl::EmptyPlan();
}
return std::make_shared<PlanImpl>(global_index_snapshot_id,
std::vector<std::shared_ptr<Split>>());
}
PAIMON_ASSIGN_OR_RAISE(RowRangeIndex row_range_index,
RowRangeIndex::Create(row_ranges.value()));
Expand Down Expand Up @@ -134,27 +144,41 @@ Result<std::shared_ptr<Plan>> DataEvolutionBatchScan::WrapToIndexedSplits(
return std::make_shared<PlanImpl>(data_plan->SnapshotId(), indexed_splits);
}

Result<std::shared_ptr<GlobalIndexResult>> DataEvolutionBatchScan::EvalGlobalIndex() const {
Result<std::optional<DataEvolutionBatchScan::EvaluatedGlobalIndex>>
DataEvolutionBatchScan::EvalGlobalIndex() const {
auto predicate = batch_scan_->GetNonPartitionPredicate();
if (!predicate) {
return std::shared_ptr<GlobalIndexResult>(nullptr);
return std::optional<EvaluatedGlobalIndex>();
}
if (!core_options_.GlobalIndexEnabled()) {
return std::shared_ptr<GlobalIndexResult>(nullptr);
return std::optional<EvaluatedGlobalIndex>();
}
auto partition_filter = batch_scan_->GetPartitionPredicate();
// TODO(lisizhuo.lsz): support time travel
PAIMON_ASSIGN_OR_RAISE(std::optional<int64_t> snapshot_id, ResolveGlobalIndexSnapshotId());
if (!snapshot_id) {
return Status::Invalid("not found latest snapshot");
}
PAIMON_ASSIGN_OR_RAISE(
std::unique_ptr<GlobalIndexScan> index_scan,
GlobalIndexScan::Create(table_path_, core_options_.GetScanSnapshotId(), partition_filter,
core_options_.ToMap(), core_options_.GetFileSystem(), executor_,
pool_));
GlobalIndexScan::Create(table_path_, snapshot_id, partition_filter, core_options_.ToMap(),
core_options_.GetFileSystem(), executor_, pool_));
auto index_scan_impl = dynamic_cast<GlobalIndexScanImpl*>(index_scan.get());
if (!index_scan_impl) {
return Status::Invalid("invalid GlobalIndexScan, cannot cast to GlobalIndexScanImpl");
}

return index_scan_impl->Scan(predicate);
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<GlobalIndexResult> result,
index_scan_impl->Scan(predicate));
return std::optional<EvaluatedGlobalIndex>(EvaluatedGlobalIndex{result, snapshot_id.value()});
}

Result<std::optional<int64_t>> DataEvolutionBatchScan::ResolveGlobalIndexSnapshotId() const {
std::optional<int64_t> snapshot_id = core_options_.GetScanSnapshotId();
if (snapshot_id) {
return snapshot_id;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please validate the snapshot before returning the empty plan. With a pre-supplied empty GlobalIndexResult, a nonexistent scan.snapshot-id (for example, 999) is currently copied directly into a successful plan. Please resolve/load the snapshot here and add a regression test for nonexistent or expired snapshot IDs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior seems to predate the PR: the global-index scan already appears to ignore tag- and timestamp-based time travel and fall back to the latest snapshot. What changes here is that the new resolver makes this more visible by attaching that latest snapshot ID to an empty plan. Since global-index time travel does not seem to be supported at the moment, would it make sense to reject these modes explicitly instead of silently scanning and reporting the latest snapshot?

return snapshot_reader_->GetSnapshotManager()->LatestSnapshotId();
}

} // namespace paimon
9 changes: 8 additions & 1 deletion src/paimon/core/table/source/data_evolution_batch_scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -49,7 +50,13 @@ class DataEvolutionBatchScan : public AbstractTableScan {
const std::map<int64_t, float>& id_to_score);

private:
Result<std::shared_ptr<GlobalIndexResult>> EvalGlobalIndex() const;
struct EvaluatedGlobalIndex {
std::shared_ptr<GlobalIndexResult> result;
int64_t snapshot_id;
};

Result<std::optional<EvaluatedGlobalIndex>> EvalGlobalIndex() const;
Result<std::optional<int64_t>> ResolveGlobalIndexSnapshotId() const;

private:
std::shared_ptr<MemoryPool> pool_;
Expand Down
47 changes: 47 additions & 0 deletions test/inte/global_index_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,53 @@ TEST_P(GlobalIndexTest, TestDataEvolutionBatchScan) {
}
}

TEST_P(GlobalIndexTest, TestDataEvolutionGlobalIndexMissPreservesResolvedSnapshot) {
CreateTable();
std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar");
auto schema = arrow::schema(fields_);
std::vector<std::string> write_cols = schema->field_names();
auto src_array = arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields_), R"([
["Alice", 10, 1, 11.1],
["Bob", 20, 0, 12.1]
])")
.ValueOrDie();

ASSERT_OK_AND_ASSIGN(auto commit_msgs, WriteArray(table_path, write_cols, src_array));
ASSERT_OK(Commit(table_path, commit_msgs));
ASSERT_OK(WriteIndex(table_path, /*partition_filters=*/{}, "f0", "bitmap", /*options=*/{},
Range(0, 1)));
ASSERT_OK(WriteIndex(table_path, /*partition_filters=*/{}, "f1", "bitmap", /*options=*/{},
Range(0, 1)));

auto predicate =
PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::STRING,
Literal(FieldType::STRING, "missing", 7));

ASSERT_OK_AND_ASSIGN(auto latest_plan, ScanGlobalIndexAndData(table_path, predicate));
ASSERT_TRUE(latest_plan->Splits().empty());
ASSERT_EQ(latest_plan->SnapshotId(), std::optional<int64_t>(3));

ASSERT_OK_AND_ASSIGN(
auto explicit_plan,
ScanGlobalIndexAndData(table_path, predicate, {{Options::SCAN_SNAPSHOT_ID, "2"}}));
ASSERT_TRUE(explicit_plan->Splits().empty());
ASSERT_EQ(explicit_plan->SnapshotId(), std::optional<int64_t>(2));

auto empty_index_result = BitmapGlobalIndexResult::FromRanges({});
ASSERT_OK_AND_ASSIGN(auto supplied_latest_plan,
ScanGlobalIndexAndData(table_path, /*predicate=*/nullptr, /*options=*/{},
empty_index_result));
ASSERT_TRUE(supplied_latest_plan->Splits().empty());
ASSERT_EQ(supplied_latest_plan->SnapshotId(), std::optional<int64_t>(3));

ASSERT_OK_AND_ASSIGN(
auto supplied_explicit_plan,
ScanGlobalIndexAndData(table_path, /*predicate=*/nullptr,
{{Options::SCAN_SNAPSHOT_ID, "2"}}, empty_index_result));
ASSERT_TRUE(supplied_explicit_plan->Splits().empty());
ASSERT_EQ(supplied_explicit_plan->SnapshotId(), std::optional<int64_t>(2));
}

TEST_P(GlobalIndexTest, TestDataEvolutionBatchScanWithOnlyOnePartitionHasIndex) {
CreateTable(/*partition_keys=*/{"f1"});
std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar");
Expand Down