Skip to content

Commit a804a3f

Browse files
feat: Add metadata system tables (alibaba#285)
1 parent 4e582cb commit a804a3f

31 files changed

Lines changed: 1647 additions & 216 deletions

src/paimon/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ set(PAIMON_CORE_SRCS
201201
core/index/index_file_handler.cpp
202202
core/index/global_index_meta.cpp
203203
core/index/index_file_meta_serializer.cpp
204+
core/io/generic_row_to_arrow_array_converter.cpp
204205
core/io/meta_to_arrow_array_converter.cpp
205206
core/io/async_key_value_producer_and_consumer.cpp
206207
core/io/data_file_meta_09_serializer.cpp
@@ -319,11 +320,14 @@ set(PAIMON_CORE_SRCS
319320
core/table/source/data_evolution_batch_scan.cpp
320321
core/table/system/audit_log_system_table.cpp
321322
core/table/system/binlog_system_table.cpp
322-
core/table/system/options_system_table.cpp
323+
core/table/system/in_memory_system_table.cpp
324+
core/table/system/metadata_system_tables.cpp
323325
core/table/system/system_table.cpp
324326
core/table/system/system_table_scan.cpp
325327
core/table/system/system_table_schema.cpp
326328
core/tag/tag.cpp
329+
core/utils/branch_manager.cpp
330+
core/utils/consumer_manager.cpp
327331
core/utils/field_mapping.cpp
328332
core/utils/file_store_path_factory.cpp
329333
core/utils/file_utils.cpp
@@ -706,6 +710,7 @@ if(PAIMON_BUILD_TESTS)
706710
core/table/system/system_table_test.cpp
707711
core/tag/tag_test.cpp
708712
core/utils/branch_manager_test.cpp
713+
core/utils/consumer_manager_test.cpp
709714
core/utils/file_store_path_factory_cache_test.cpp
710715
core/utils/field_mapping_test.cpp
711716
core/utils/file_store_path_factory_test.cpp

src/paimon/common/reader/prefetch_file_batch_reader_impl_test.cpp

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

1717
#include "paimon/common/reader/prefetch_file_batch_reader_impl.h"
1818

19+
#include <atomic>
1920
#include <set>
2021

2122
#include "arrow/compute/api.h"
@@ -90,7 +91,7 @@ class ControlledMockFormatReaderBuilder : public ReaderBuilder {
9091

9192
Result<std::unique_ptr<FileBatchReader>> Build(
9293
const std::shared_ptr<InputStream>& path) const override {
93-
size_t index = build_count_++;
94+
size_t index = build_count_.fetch_add(1);
9495
Status set_read_ranges_status = index < set_read_ranges_statuses_.size()
9596
? set_read_ranges_statuses_[index]
9697
: Status::OK();
@@ -109,7 +110,7 @@ class ControlledMockFormatReaderBuilder : public ReaderBuilder {
109110
std::vector<std::pair<uint64_t, uint64_t>> read_ranges_;
110111
bool need_prefetch_ = true;
111112
std::vector<Status> set_read_ranges_statuses_;
112-
mutable size_t build_count_ = 0;
113+
mutable std::atomic<size_t> build_count_{0};
113114
};
114115

115116
struct TestParam {

src/paimon/common/utils/date_time_utils.h

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,28 @@ class DateTimeUtils {
105105
return static_cast<uint64_t>(ts.tv_sec) * 1000000ULL + static_cast<uint64_t>(ts.tv_usec);
106106
}
107107

108-
static inline Result<uint64_t> GetCurrentLocalTimeUs() {
109-
uint64_t utc_micro = GetCurrentUTCTimeUs();
108+
static inline Result<Timestamp> ToLocalTimestamp(const Timestamp& utc_timestamp) {
109+
int64_t utc_micro = utc_timestamp.ToMicrosecond();
110110
auto utc_ts_scalar = std::make_shared<arrow::TimestampScalar>(
111-
static_cast<int64_t>(utc_micro), arrow::TimeUnit::MICRO, GetLocalTimezoneName());
111+
utc_micro, arrow::TimeUnit::MICRO, GetLocalTimezoneName());
112112
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
113113
arrow::Datum local_micro, arrow::compute::LocalTimestamp(arrow::Datum(utc_ts_scalar)));
114114
auto local_ts_scalar =
115115
std::dynamic_pointer_cast<arrow::TimestampScalar>(local_micro.scalar());
116-
return *(static_cast<const int64_t*>(local_ts_scalar->data()));
116+
auto [millisecond, nano_of_millisecond] = DateTimeUtils::TimestampConverter(
117+
*(static_cast<const int64_t*>(local_ts_scalar->data())),
118+
DateTimeUtils::TimeType::MICROSECOND, DateTimeUtils::TimeType::MILLISECOND,
119+
DateTimeUtils::TimeType::NANOSECOND);
120+
return Timestamp(millisecond, nano_of_millisecond);
121+
}
122+
123+
static inline Result<uint64_t> GetCurrentLocalTimeUs() {
124+
auto [millisecond, nano_of_millisecond] = DateTimeUtils::TimestampConverter(
125+
GetCurrentUTCTimeUs(), DateTimeUtils::TimeType::MICROSECOND,
126+
DateTimeUtils::TimeType::MILLISECOND, DateTimeUtils::TimeType::NANOSECOND);
127+
Timestamp utc_timestamp(millisecond, nano_of_millisecond);
128+
PAIMON_ASSIGN_OR_RAISE(Timestamp local_timestamp, ToLocalTimestamp(utc_timestamp));
129+
return local_timestamp.ToMicrosecond();
117130
}
118131

119132
static inline Result<int32_t> GetCurrentLocalHour() {

src/paimon/common/utils/date_time_utils_test.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ TEST(DateTimeUtilsTest, TestGetCurrentLocalTimeUs) {
281281
ASSERT_GE(local_ts - utc_ts, 28800000000l);
282282
}
283283

284+
TEST(DateTimeUtilsTest, TestToLocalTimestamp) {
285+
{
286+
TimezoneGuard guard("Asia/Shanghai");
287+
ASSERT_OK_AND_ASSIGN(Timestamp timestamp, DateTimeUtils::ToLocalTimestamp(
288+
Timestamp::FromEpochMillis(1700000000123L)));
289+
ASSERT_EQ(timestamp, Timestamp::FromEpochMillis(1700028800123L));
290+
}
291+
{
292+
TimezoneGuard guard("UTC");
293+
ASSERT_OK_AND_ASSIGN(Timestamp timestamp, DateTimeUtils::ToLocalTimestamp(
294+
Timestamp::FromEpochMillis(1700000000123L)));
295+
ASSERT_EQ(timestamp, Timestamp::FromEpochMillis(1700000000123L));
296+
}
297+
}
298+
284299
TEST(DateTimeUtilsTest, TestGetCurrentLocalHour) {
285300
int32_t shanghai_hour = 0;
286301
int32_t utc_hour = 0;

src/paimon/core/catalog/file_system_catalog.cpp

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -365,27 +365,11 @@ Result<std::vector<std::string>> FileSystemCatalog::GetSchemaExternalPaths(
365365

366366
Result<std::vector<std::string>> FileSystemCatalog::GetTableBranches(
367367
const std::string& table_path) const {
368-
std::vector<std::string> branches;
369-
std::string branch_dir = PathUtil::JoinPath(table_path, "branch");
370-
PAIMON_ASSIGN_OR_RAISE(bool branch_dir_exists, fs_->Exists(branch_dir));
371-
if (!branch_dir_exists) {
372-
return branches;
373-
}
374-
375-
std::vector<std::unique_ptr<BasicFileStatus>> file_status_list;
376-
PAIMON_RETURN_NOT_OK(fs_->ListDir(branch_dir, &file_status_list));
377-
378-
for (const auto& file_status : file_status_list) {
379-
if (file_status->IsDir()) {
380-
std::string dir_name = PathUtil::GetName(file_status->GetPath());
381-
// Branch directory name format: branch-{branch_name}
382-
const std::string branch_prefix = BranchManager::BRANCH_PREFIX;
383-
if (StringUtils::StartsWith(dir_name, branch_prefix, /*start_pos=*/0)) {
384-
std::string branch_name = dir_name.substr(branch_prefix.length());
385-
branches.push_back(branch_name);
386-
}
387-
}
388-
}
368+
PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> branches,
369+
BranchManager::ListBranches(fs_, table_path));
370+
branches.erase(
371+
std::remove(branches.begin(), branches.end(), BranchManager::DEFAULT_MAIN_BRANCH),
372+
branches.end());
389373
return branches;
390374
}
391375

src/paimon/core/catalog/file_system_catalog_test.cpp

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ TEST(FileSystemCatalogTest, TestCreateSystemDatabaseAndTable) {
9090
/*ignore_if_exists=*/true),
9191
"Cannot create database for system database");
9292
}
93-
// do not support create system table
93+
/// Do not support create system table.
9494
{
9595
std::map<std::string, std::string> options;
9696
options[Options::FILE_SYSTEM] = "local";
@@ -281,6 +281,100 @@ TEST(FileSystemCatalogTest, TestAuditLogAndBinlogSystemTableCatalog) {
281281
"Cannot rename system table");
282282
}
283283

284+
TEST(FileSystemCatalogTest, TestMetadataSystemTableCatalog) {
285+
std::map<std::string, std::string> options;
286+
options[Options::FILE_SYSTEM] = "local";
287+
options[Options::FILE_FORMAT] = "orc";
288+
ASSERT_OK_AND_ASSIGN(auto core_options, CoreOptions::FromMap(options));
289+
auto dir = UniqueTestDirectory::Create();
290+
ASSERT_TRUE(dir);
291+
FileSystemCatalog catalog(core_options.GetFileSystem(), dir->Str());
292+
ASSERT_OK(catalog.CreateDatabase("db1", options, /*ignore_if_exists=*/true));
293+
294+
auto typed_schema =
295+
arrow::schema({arrow::field("pk", arrow::utf8()), arrow::field("v", arrow::int32())});
296+
::ArrowSchema schema;
297+
ASSERT_TRUE(arrow::ExportSchema(*typed_schema, &schema).ok());
298+
ASSERT_OK(catalog.CreateTable(Identifier("db1", "tbl1"), &schema,
299+
/*partition_keys=*/{}, /*primary_keys=*/{"pk"}, options,
300+
/*ignore_if_exists=*/false));
301+
ArrowSchemaRelease(&schema);
302+
303+
std::vector<std::string> metadata_tables = {"snapshots", "schemas", "tags", "branches",
304+
"consumers"};
305+
for (const auto& table_name : metadata_tables) {
306+
Identifier system_identifier("db1", "tbl1$" + table_name);
307+
ASSERT_OK_AND_ASSIGN(bool exists, catalog.TableExists(system_identifier));
308+
ASSERT_TRUE(exists) << table_name;
309+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> system_schema,
310+
catalog.LoadTableSchema(system_identifier));
311+
ASSERT_TRUE(std::dynamic_pointer_cast<SystemTableSchema>(system_schema) != nullptr)
312+
<< table_name;
313+
ASSERT_OK_AND_ASSIGN(auto c_schema, system_schema->GetArrowSchema());
314+
auto loaded_schema_result = arrow::ImportSchema(c_schema.get());
315+
ASSERT_TRUE(loaded_schema_result.ok()) << loaded_schema_result.status().ToString();
316+
ASSERT_GT(loaded_schema_result.ValueUnsafe()->num_fields(), 0) << table_name;
317+
}
318+
319+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> snapshots_schema,
320+
catalog.LoadTableSchema(Identifier("db1", "tbl1$snapshots")));
321+
ASSERT_OK_AND_ASSIGN(auto snapshots_c_schema, snapshots_schema->GetArrowSchema());
322+
auto snapshots_arrow_schema = arrow::ImportSchema(snapshots_c_schema.get()).ValueUnsafe();
323+
ASSERT_EQ(snapshots_arrow_schema->field_names(),
324+
(std::vector<std::string>{
325+
"snapshot_id", "schema_id", "commit_user", "commit_identifier", "commit_kind",
326+
"commit_time", "base_manifest_list", "delta_manifest_list",
327+
"changelog_manifest_list", "total_record_count", "delta_record_count",
328+
"changelog_record_count", "watermark", "next_row_id"}));
329+
ASSERT_EQ(snapshots_arrow_schema->field(5)->type()->id(), arrow::Type::TIMESTAMP);
330+
331+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> schemas_schema,
332+
catalog.LoadTableSchema(Identifier("db1", "tbl1$schemas")));
333+
ASSERT_OK_AND_ASSIGN(auto schemas_c_schema, schemas_schema->GetArrowSchema());
334+
auto schemas_arrow_schema = arrow::ImportSchema(schemas_c_schema.get()).ValueUnsafe();
335+
ASSERT_EQ(schemas_arrow_schema->field_names(),
336+
(std::vector<std::string>{"schema_id", "fields", "partition_keys", "primary_keys",
337+
"options", "comment", "update_time"}));
338+
ASSERT_EQ(schemas_arrow_schema->field(6)->type()->id(), arrow::Type::TIMESTAMP);
339+
340+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> tags_schema,
341+
catalog.LoadTableSchema(Identifier("db1", "tbl1$tags")));
342+
ASSERT_OK_AND_ASSIGN(auto tags_c_schema, tags_schema->GetArrowSchema());
343+
auto tags_arrow_schema = arrow::ImportSchema(tags_c_schema.get()).ValueUnsafe();
344+
ASSERT_EQ(tags_arrow_schema->field_names(),
345+
(std::vector<std::string>{"tag_name", "snapshot_id", "schema_id", "commit_time",
346+
"record_count", "create_time", "time_retained"}));
347+
ASSERT_EQ(tags_arrow_schema->field(3)->type()->id(), arrow::Type::TIMESTAMP);
348+
ASSERT_EQ(tags_arrow_schema->field(5)->type()->id(), arrow::Type::TIMESTAMP);
349+
350+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> branches_schema,
351+
catalog.LoadTableSchema(Identifier("db1", "tbl1$branches")));
352+
ASSERT_OK_AND_ASSIGN(auto branches_c_schema, branches_schema->GetArrowSchema());
353+
auto branches_arrow_schema = arrow::ImportSchema(branches_c_schema.get()).ValueUnsafe();
354+
ASSERT_EQ(branches_arrow_schema->field_names(),
355+
(std::vector<std::string>{"branch_name", "create_time"}));
356+
ASSERT_EQ(branches_arrow_schema->field(1)->type()->id(), arrow::Type::TIMESTAMP);
357+
358+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> consumers_schema,
359+
catalog.LoadTableSchema(Identifier("db1", "tbl1$consumers")));
360+
ASSERT_OK_AND_ASSIGN(auto consumers_c_schema, consumers_schema->GetArrowSchema());
361+
auto consumers_arrow_schema = arrow::ImportSchema(consumers_c_schema.get()).ValueUnsafe();
362+
ASSERT_EQ(consumers_arrow_schema->field_names(),
363+
(std::vector<std::string>{"consumer_id", "next_snapshot_id"}));
364+
ASSERT_FALSE(consumers_arrow_schema->field(1)->nullable());
365+
366+
Identifier snapshots_identifier("db1", "tbl1$snapshots");
367+
::ArrowSchema system_create_schema;
368+
ASSERT_TRUE(arrow::ExportSchema(*typed_schema, &system_create_schema).ok());
369+
ASSERT_NOK_WITH_MSG(
370+
catalog.CreateTable(snapshots_identifier, &system_create_schema, {}, {}, options, false),
371+
"Cannot create table for system table");
372+
ArrowSchemaRelease(&system_create_schema);
373+
ASSERT_NOK_WITH_MSG(catalog.DropTable(snapshots_identifier, false), "Cannot drop system table");
374+
ASSERT_NOK_WITH_MSG(catalog.RenameTable(snapshots_identifier, Identifier("db1", "tbl2"), false),
375+
"Cannot rename system table");
376+
}
377+
284378
TEST(FileSystemCatalogTest, TestCreateTableWithBlob) {
285379
std::map<std::string, std::string> options;
286380
options[Options::FILE_SYSTEM] = "local";
@@ -625,7 +719,7 @@ TEST(FileSystemCatalogTest, TestDropTable) {
625719
ASSERT_OK_AND_ASSIGN(bool exist, catalog.TableExists(Identifier("test_db", "tbl1")));
626720
ASSERT_FALSE(exist);
627721

628-
// Test 4: Drop system table
722+
/// Test 4: Drop system table.
629723
ASSERT_NOK_WITH_MSG(
630724
catalog.DropTable(Identifier("test_db", "tbl$system"),
631725
/*ignore_if_not_exists=*/false),
@@ -691,7 +785,7 @@ TEST(FileSystemCatalogTest, TestRenameTable) {
691785
/*ignore_if_not_exists=*/false),
692786
"Cannot rename table across databases. Cross-database rename is not supported.");
693787

694-
// Test 6: Rename system table
788+
/// Test 6: Rename system table.
695789
ASSERT_NOK_WITH_MSG(catalog.RenameTable(Identifier("test_db", "tbl$system"),
696790
Identifier("test_db", "new_system_tbl"),
697791
/*ignore_if_not_exists=*/false),

src/paimon/core/core_options.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,6 @@ struct CoreOptions::Impl {
669669
// Parse table-read.sequence-number.enabled - expose sequence number in system tables
670670
PAIMON_RETURN_NOT_OK(parser.Parse<bool>(Options::TABLE_READ_SEQUENCE_NUMBER_ENABLED,
671671
&table_read_sequence_number_enabled));
672-
// Parse key-value.sequence_number.enabled - internal sequence number read switch
673672
PAIMON_RETURN_NOT_OK(parser.Parse<bool>(Options::KEY_VALUE_SEQUENCE_NUMBER_ENABLED,
674673
&key_value_sequence_number_enabled));
675674
// Parse partial-update.remove-record-on-sequence-group
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/core/io/generic_row_to_arrow_array_converter.h"
18+
19+
#include <cassert>
20+
#include <memory>
21+
#include <utility>
22+
#include <vector>
23+
24+
#include "arrow/array/builder_nested.h"
25+
#include "arrow/memory_pool.h"
26+
#include "arrow/util/checked_cast.h"
27+
#include "paimon/common/utils/arrow/status_utils.h"
28+
29+
namespace paimon {
30+
31+
Result<std::unique_ptr<GenericRowToArrowArrayConverter>> GenericRowToArrowArrayConverter::Create(
32+
const std::shared_ptr<arrow::Schema>& schema, arrow::MemoryPool* pool) {
33+
std::unique_ptr<arrow::ArrayBuilder> array_builder;
34+
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::MakeBuilder(
35+
pool, std::make_shared<arrow::StructType>(schema->fields()), &array_builder));
36+
37+
auto struct_builder =
38+
arrow::internal::checked_pointer_cast<arrow::StructBuilder>(std::move(array_builder));
39+
assert(struct_builder);
40+
std::vector<RowToArrowArrayConverter::AppendValueFunc> appenders;
41+
appenders.reserve(schema->num_fields());
42+
int32_t reserve_count = 1;
43+
for (int32_t i = 0; i < schema->num_fields(); ++i) {
44+
PAIMON_ASSIGN_OR_RAISE(
45+
RowToArrowArrayConverter::AppendValueFunc func,
46+
AppendField(/*use_view=*/true, struct_builder->field_builder(i), &reserve_count));
47+
appenders.emplace_back(std::move(func));
48+
}
49+
return std::unique_ptr<GenericRowToArrowArrayConverter>(new GenericRowToArrowArrayConverter(
50+
reserve_count, std::move(appenders), std::move(struct_builder), nullptr));
51+
}
52+
53+
Result<BatchReader::ReadBatch> GenericRowToArrowArrayConverter::NextBatch(
54+
const std::vector<GenericRow>& rows) {
55+
PAIMON_RETURN_NOT_OK(ResetAndReserve());
56+
PAIMON_RETURN_NOT_OK_FROM_ARROW(
57+
array_builder_->AppendValues(rows.size(), /*valid_bytes=*/nullptr));
58+
for (size_t i = 0; i < appenders_.size(); ++i) {
59+
for (const auto& row : rows) {
60+
PAIMON_RETURN_NOT_OK_FROM_ARROW(appenders_[i](row, i));
61+
}
62+
}
63+
64+
return FinishAndAccumulate();
65+
}
66+
67+
} // namespace paimon

0 commit comments

Comments
 (0)