Skip to content

Commit 786d6ef

Browse files
authored
feat(schema): scan context support set table schema (alibaba#394)
1 parent 86cc620 commit 786d6ef

8 files changed

Lines changed: 124 additions & 9 deletions

File tree

include/paimon/read_context.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class PAIMON_EXPORT ReadContext {
106106
uint32_t GetRowToBatchThreadNumber() const {
107107
return row_to_batch_thread_number_;
108108
}
109-
const std::optional<std::string>& GetSpecificTableSchema() {
109+
const std::optional<std::string>& GetSpecificTableSchema() const {
110110
return table_schema_;
111111
}
112112
std::shared_ptr<MemoryPool> GetMemoryPool() const {

include/paimon/scan_context.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class PAIMON_EXPORT ScanContext {
4949
const std::shared_ptr<MemoryPool>& memory_pool,
5050
const std::shared_ptr<Executor>& executor,
5151
const std::shared_ptr<FileSystem>& specific_file_system,
52+
const std::optional<std::string>& table_schema,
5253
const std::map<std::string, std::string>& options,
5354
const std::shared_ptr<Cache>& cache);
5455

@@ -88,6 +89,10 @@ class PAIMON_EXPORT ScanContext {
8889
return specific_file_system_;
8990
}
9091

92+
const std::optional<std::string>& GetSpecificTableSchema() const {
93+
return table_schema_;
94+
}
95+
9196
std::shared_ptr<Cache> GetCache() const {
9297
return cache_;
9398
}
@@ -101,6 +106,7 @@ class PAIMON_EXPORT ScanContext {
101106
std::shared_ptr<MemoryPool> memory_pool_;
102107
std::shared_ptr<Executor> executor_;
103108
std::shared_ptr<FileSystem> specific_file_system_;
109+
std::optional<std::string> table_schema_;
104110
std::map<std::string, std::string> options_;
105111
std::shared_ptr<Cache> cache_;
106112
};
@@ -185,6 +191,18 @@ class PAIMON_EXPORT ScanContextBuilder {
185191
/// @note If not set, use default file system (configured in `Options::FILE_SYSTEM`)
186192
ScanContextBuilder& WithFileSystem(const std::shared_ptr<FileSystem>& file_system);
187193

194+
/// Set the table schema as a string to avoid schema loading I/O operations.
195+
///
196+
/// This optimization allows the scanner to use a pre-loaded schema instead of
197+
/// reading it from the table metadata, which can improve performance especially
198+
/// in scenarios with many small scan operations.
199+
///
200+
/// @param table_schema String representation of the table schema.
201+
/// @return Reference to this builder for method chaining.
202+
/// @note The user must ensure that the schema string is valid and matches the table.
203+
/// @note If not set, the schema will be loaded from the table path.
204+
ScanContextBuilder& SetTableSchema(const std::string& table_schema);
205+
188206
/// Inject a cache for scan operations. Passing nullptr disables cache.
189207
/// @return Reference to this builder for method chaining.
190208
ScanContextBuilder& WithCache(const std::shared_ptr<Cache>& cache);

src/paimon/common/reader/prefetch_file_batch_reader_impl.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ PrefetchFileBatchReaderImpl::~PrefetchFileBatchReaderImpl() {
153153
Status PrefetchFileBatchReaderImpl::SetReadSchema(
154154
::ArrowSchema* read_schema, const std::shared_ptr<Predicate>& predicate,
155155
const std::optional<RoaringBitmap32>& selection_bitmap) {
156+
PAIMON_RETURN_NOT_OK(CleanUp());
156157
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Schema> schema,
157158
arrow::ImportSchema(read_schema));
158159
for (const auto& reader : readers_) {
@@ -162,11 +163,15 @@ Status PrefetchFileBatchReaderImpl::SetReadSchema(
162163
}
163164
selection_bitmap_ = selection_bitmap;
164165
predicate_ = predicate;
165-
return RefreshReadRanges();
166+
return RefreshReadRangesAfterCleanUp();
166167
}
167168

168169
Status PrefetchFileBatchReaderImpl::RefreshReadRanges() {
169170
PAIMON_RETURN_NOT_OK(CleanUp());
171+
return RefreshReadRangesAfterCleanUp();
172+
}
173+
174+
Status PrefetchFileBatchReaderImpl::RefreshReadRangesAfterCleanUp() {
170175
bool need_prefetch;
171176
PAIMON_ASSIGN_OR_RAISE(auto read_ranges, readers_[0]->GenReadRanges(&need_prefetch));
172177

@@ -279,6 +284,7 @@ Status PrefetchFileBatchReaderImpl::CleanUp() {
279284
read_ranges_.clear();
280285
read_ranges_in_group_.clear();
281286
current_batch_global_row_ids_.clear();
287+
read_ranges_freshed_ = false;
282288
clean_prefetch_queue();
283289
for (size_t i = 0; i < readers_pos_.size(); i++) {
284290
readers_pos_[i]->store(0);

src/paimon/common/reader/prefetch_file_batch_reader_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class PrefetchFileBatchReaderImpl : public PrefetchFileBatchReader {
129129
static std::vector<std::vector<std::pair<uint64_t, uint64_t>>> DispatchReadRanges(
130130
const std::vector<std::pair<uint64_t, uint64_t>>& read_ranges, size_t reader_count);
131131

132+
Status RefreshReadRangesAfterCleanUp();
132133
Result<std::pair<uint64_t, uint64_t>> EofRange() const;
133134
std::optional<std::pair<uint64_t, uint64_t>> GetCurrentReadRange(size_t reader_idx) const;
134135
Status EnsureReaderPosition(size_t reader_idx,

src/paimon/core/operation/scan_context.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ ScanContext::ScanContext(const std::string& path, bool is_streaming_mode,
3333
const std::shared_ptr<MemoryPool>& memory_pool,
3434
const std::shared_ptr<Executor>& executor,
3535
const std::shared_ptr<FileSystem>& specific_file_system,
36+
const std::optional<std::string>& table_schema,
3637
const std::map<std::string, std::string>& options,
3738
const std::shared_ptr<Cache>& cache)
3839
: path_(path),
@@ -43,6 +44,7 @@ ScanContext::ScanContext(const std::string& path, bool is_streaming_mode,
4344
memory_pool_(memory_pool),
4445
executor_(executor),
4546
specific_file_system_(specific_file_system),
47+
table_schema_(table_schema),
4648
options_(options),
4749
cache_(cache) {}
4850

@@ -62,6 +64,7 @@ class ScanContextBuilder::Impl {
6264
memory_pool_ = GetDefaultPool();
6365
executor_ = CreateDefaultExecutor();
6466
specific_file_system_.reset();
67+
table_schema_ = std::nullopt;
6568
options_.clear();
6669
cache_.reset();
6770
}
@@ -77,6 +80,7 @@ class ScanContextBuilder::Impl {
7780
std::shared_ptr<MemoryPool> memory_pool_ = GetDefaultPool();
7881
std::shared_ptr<Executor> executor_ = CreateDefaultExecutor();
7982
std::shared_ptr<FileSystem> specific_file_system_;
83+
std::optional<std::string> table_schema_;
8084
std::map<std::string, std::string> options_;
8185
std::shared_ptr<Cache> cache_;
8286
};
@@ -147,6 +151,11 @@ ScanContextBuilder& ScanContextBuilder::WithFileSystem(
147151
return *this;
148152
}
149153

154+
ScanContextBuilder& ScanContextBuilder::SetTableSchema(const std::string& table_schema) {
155+
impl_->table_schema_ = table_schema;
156+
return *this;
157+
}
158+
150159
ScanContextBuilder& ScanContextBuilder::WithCache(const std::shared_ptr<Cache>& cache) {
151160
impl_->cache_ = cache;
152161
return *this;
@@ -162,7 +171,7 @@ Result<std::unique_ptr<ScanContext>> ScanContextBuilder::Finish() {
162171
std::make_shared<ScanFilter>(impl_->predicates_, impl_->partition_filters_,
163172
impl_->bucket_filter_),
164173
impl_->global_index_result_, impl_->memory_pool_, impl_->executor_,
165-
impl_->specific_file_system_, impl_->options_, impl_->cache_);
174+
impl_->specific_file_system_, impl_->table_schema_, impl_->options_, impl_->cache_);
166175
impl_->Reset();
167176
return ctx;
168177
}

src/paimon/core/operation/scan_context_test.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ TEST(ScanContextTest, TestSetContent) {
6666
builder.WithExecutor(executor);
6767
auto fs = std::make_shared<MockFileSystem>();
6868
builder.WithFileSystem(fs);
69+
builder.SetTableSchema("table-schema-json");
6970
auto manifest_cache = std::make_shared<LruCache>(1024);
7071
builder.WithCache(manifest_cache);
7172
ASSERT_OK_AND_ASSIGN(auto ctx, builder.Finish());
@@ -79,6 +80,8 @@ TEST(ScanContextTest, TestSetContent) {
7980
ASSERT_EQ("{1,2,4,5}", ctx->GetGlobalIndexResult()->ToString());
8081
ASSERT_EQ(memory_pool, ctx->GetMemoryPool());
8182
ASSERT_EQ(executor, ctx->GetExecutor());
83+
ASSERT_TRUE(ctx->GetSpecificTableSchema().has_value());
84+
ASSERT_EQ("table-schema-json", ctx->GetSpecificTableSchema().value());
8285
std::map<std::string, std::string> expected_options = {{"key", "value"}};
8386
ASSERT_EQ(expected_options, ctx->GetOptions());
8487
ASSERT_EQ(fs, ctx->GetSpecificFileSystem());

src/paimon/core/table/source/table_scan.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,20 @@ Result<std::unique_ptr<TableScan>> NewDataTableScan(const std::shared_ptr<ScanCo
194194
CoreOptions tmp_options,
195195
CoreOptions::FromMap(context->GetOptions(), context->GetSpecificFileSystem(), {}));
196196
std::string branch = BranchManager::NormalizeBranch(tmp_options.GetBranch());
197-
SchemaManager schema_manager(tmp_options.GetFileSystem(), context->GetPath(), branch);
198-
PAIMON_ASSIGN_OR_RAISE(std::optional<std::shared_ptr<TableSchema>> latest_table_schema,
199-
schema_manager.Latest());
200-
if (latest_table_schema == std::nullopt) {
201-
return Status::Invalid("not found latest schema");
197+
std::shared_ptr<TableSchema> table_schema;
198+
const auto& specific_table_schema = context->GetSpecificTableSchema();
199+
if (branch == BranchManager::DEFAULT_MAIN_BRANCH && specific_table_schema) {
200+
PAIMON_ASSIGN_OR_RAISE(table_schema,
201+
TableSchema::CreateFromJson(specific_table_schema.value()));
202+
} else {
203+
SchemaManager schema_manager(tmp_options.GetFileSystem(), context->GetPath(), branch);
204+
PAIMON_ASSIGN_OR_RAISE(std::optional<std::shared_ptr<TableSchema>> latest_table_schema,
205+
schema_manager.Latest());
206+
if (latest_table_schema == std::nullopt) {
207+
return Status::Invalid("not found latest schema");
208+
}
209+
table_schema = latest_table_schema.value();
202210
}
203-
const auto& table_schema = latest_table_schema.value();
204211
// merge options
205212
auto options = table_schema->Options();
206213
for (const auto& [key, value] : context->GetOptions()) {

test/inte/scan_inte_test.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <cstdint>
1818
#include <cstring>
19+
#include <filesystem>
1920
#include <limits>
2021
#include <map>
2122
#include <memory>
@@ -396,6 +397,76 @@ TEST_P(ScanInteTest, TestScanAppendWithSnapshot3) {
396397
CheckResult(expected_data_splits, result_data_splits);
397398
}
398399

400+
TEST_P(ScanInteTest, TestScanAppendWithSpecificTableSchema) {
401+
std::string data_dir = paimon::test::GetDataDir();
402+
if (!std::filesystem::exists(data_dir + "orc/append_09.db/append_09/schema/schema-0")) {
403+
data_dir = "../" + data_dir;
404+
}
405+
std::string table_path = data_dir + "orc/append_09.db/append_09";
406+
407+
auto check_result = [&](const std::optional<std::string>& specific_table_schema) {
408+
ScanContextBuilder context_builder(table_path);
409+
context_builder.AddOption(Options::SCAN_SNAPSHOT_ID, "3");
410+
if (specific_table_schema) {
411+
context_builder.SetTableSchema(specific_table_schema.value());
412+
}
413+
ASSERT_OK_AND_ASSIGN(auto scan_context, FinishScanContext(context_builder));
414+
ASSERT_OK_AND_ASSIGN(auto table_scan, TableScan::Create(std::move(scan_context)));
415+
ASSERT_OK_AND_ASSIGN(auto result_plan, table_scan->CreatePlan());
416+
417+
ASSERT_EQ(result_plan->SnapshotId().value(), 3);
418+
419+
auto result_data_splits = CollectDataSplits(result_plan);
420+
DataSplitImpl::Builder builder1(BinaryRowGenerator::GenerateRow({10}, pool_.get()),
421+
/*bucket=*/0, /*bucket_path=*/
422+
data_dir + "orc/append_09.db/append_09/f1=10/bucket-0",
423+
{meta_snapshot1_partition10_bucket0_});
424+
auto expected_data_split1 =
425+
std::dynamic_pointer_cast<DataSplitImpl>(builder1.WithTotalBuckets(2)
426+
.WithSnapshot(3)
427+
.IsStreaming(false)
428+
.RawConvertible(true)
429+
.Build()
430+
.value());
431+
432+
DataSplitImpl::Builder builder2(
433+
BinaryRowGenerator::GenerateRow({10}, pool_.get()), /*bucket=*/1, /*bucket_path=*/
434+
data_dir + "orc/append_09.db/append_09/f1=10/bucket-1",
435+
{meta_snapshot1_partition10_bucket1_, meta_snapshot2_partition10_bucket1_,
436+
meta_snapshot3_partition10_bucket1_});
437+
auto expected_data_split2 =
438+
std::dynamic_pointer_cast<DataSplitImpl>(builder2.WithTotalBuckets(2)
439+
.WithSnapshot(3)
440+
.IsStreaming(false)
441+
.RawConvertible(true)
442+
.Build()
443+
.value());
444+
445+
DataSplitImpl::Builder builder3(
446+
BinaryRowGenerator::GenerateRow({20}, pool_.get()), /*bucket=*/0, /*bucket_path=*/
447+
data_dir + "orc/append_09.db/append_09/f1=20/bucket-0",
448+
{meta_snapshot1_partition20_bucket0_, meta_snapshot2_partition20_bucket0_});
449+
auto expected_data_split3 =
450+
std::dynamic_pointer_cast<DataSplitImpl>(builder3.WithTotalBuckets(2)
451+
.WithSnapshot(3)
452+
.IsStreaming(false)
453+
.RawConvertible(true)
454+
.Build()
455+
.value());
456+
457+
std::vector<std::shared_ptr<DataSplitImpl>> expected_data_splits = {
458+
expected_data_split1, expected_data_split2, expected_data_split3};
459+
CheckResult(expected_data_splits, result_data_splits);
460+
};
461+
462+
check_result(std::nullopt);
463+
464+
auto fs = std::make_shared<LocalFileSystem>();
465+
std::string schema_str;
466+
ASSERT_OK(fs->ReadFile(table_path + "/schema/schema-0", &schema_str));
467+
check_result(std::optional<std::string>(schema_str));
468+
}
469+
399470
TEST_P(ScanInteTest, TestScanInvalidSnapshot) {
400471
std::string table_path = paimon::test::GetDataDir() + "orc/append_09.db/append_09";
401472
ScanContextBuilder context_builder(table_path);

0 commit comments

Comments
 (0)