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

lxy-9602 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new ff4194ec fix(catalog): load schemas from the requested branch (#429)
ff4194ec is described below

commit ff4194ec81c1b435f964fe757aa16a70d6043e76
Author: Mr Dk. <[email protected]>
AuthorDate: Thu Oct 15 08:51:23 2026 +0800

    fix(catalog): load schemas from the requested branch (#429)
---
 include/paimon/catalog/table.h                     |  2 +-
 src/paimon/core/catalog/file_system_catalog.cpp    | 22 ++++---
 .../core/catalog/file_system_catalog_test.cpp      | 73 ++++++++++++++++++++++
 src/paimon/core/table/table.cpp                    |  8 ++-
 src/paimon/core/table/table_test.cpp               | 30 +++++++++
 5 files changed, 125 insertions(+), 10 deletions(-)

diff --git a/include/paimon/catalog/table.h b/include/paimon/catalog/table.h
index ef68d1e4..59c82495 100644
--- a/include/paimon/catalog/table.h
+++ b/include/paimon/catalog/table.h
@@ -40,7 +40,7 @@ class PAIMON_EXPORT Table {
     ///
     /// @param file_system File system used to access the table directory and 
schema files.
     /// @param table_path Root path of the table in the file system.
-    /// @param identifier Logical table identifier used for naming and error 
messages.
+    /// @param identifier Logical table identifier selecting the schema branch 
and table name.
     /// @return A table initialized with the latest available schema.
     static Result<std::shared_ptr<Table>> Create(const 
std::shared_ptr<FileSystem>& file_system,
                                                  const std::string& table_path,
diff --git a/src/paimon/core/catalog/file_system_catalog.cpp 
b/src/paimon/core/catalog/file_system_catalog.cpp
index 09be18d7..827e50b4 100644
--- a/src/paimon/core/catalog/file_system_catalog.cpp
+++ b/src/paimon/core/catalog/file_system_catalog.cpp
@@ -105,7 +105,7 @@ Result<bool> FileSystemCatalog::TableExists(const 
Identifier& identifier) const
     if (CatalogUtils::IsSystemDatabase(identifier.GetDatabaseName())) {
         return GlobalSystemTableLoader::IsSupported(identifier.GetTableName(), 
catalog_options_);
     }
-    // The branch component is dropped when the data table identifier is 
rebuilt below, so the
+    // The system table suffix is dropped when the data table identifier is 
rebuilt below, so the
     // identifier is validated as a whole here.
     PAIMON_RETURN_NOT_OK(CatalogUtils::CheckValidTableName(identifier));
     PAIMON_ASSIGN_OR_RAISE(bool is_system_table, identifier.IsSystemTable());
@@ -115,8 +115,9 @@ Result<bool> FileSystemCatalog::TableExists(const 
Identifier& identifier) const
         if (!system_table_name || 
!SystemTableLoader::IsSupported(system_table_name.value())) {
             return false;
         }
-        PAIMON_ASSIGN_OR_RAISE(std::string data_table_name, 
identifier.GetDataTableName());
-        Identifier data_identifier(identifier.GetDatabaseName(), 
data_table_name);
+        PAIMON_ASSIGN_OR_RAISE(std::string branch, 
identifier.GetBranchNameOrDefault());
+        PAIMON_ASSIGN_OR_RAISE(Identifier data_identifier,
+                               CatalogUtils::BranchIdentifier(identifier, 
branch));
         PAIMON_ASSIGN_OR_RAISE(std::optional<std::shared_ptr<TableSchema>> 
latest_schema,
                                TableSchemaExists(data_identifier));
         return latest_schema != std::nullopt;
@@ -181,7 +182,11 @@ Result<std::optional<std::shared_ptr<TableSchema>>> 
FileSystemCatalog::TableSche
             "do not support checking TableSchemaExists for system table.");
     }
     PAIMON_ASSIGN_OR_RAISE(std::string table_path, 
NewDataTablePath(warehouse_, identifier));
-    SchemaManager schema_manager(fs_, table_path);
+    PAIMON_ASSIGN_OR_RAISE(std::string branch, 
identifier.GetBranchNameOrDefault());
+    if (StringUtils::EqualsIgnoreCase(branch, Identifier::kDefaultMainBranch)) 
{
+        branch = Identifier::kDefaultMainBranch;
+    }
+    SchemaManager schema_manager(fs_, table_path, branch);
     return schema_manager.Latest();
 }
 
@@ -291,7 +296,7 @@ Result<std::shared_ptr<Schema>> 
FileSystemCatalog::LoadTableSchema(
                                system_table->ArrowSchema());
         return std::make_shared<SystemTableSchema>(std::move(arrow_schema));
     }
-    // The branch component is dropped when the data table identifier is 
rebuilt below, so the
+    // The system table suffix is dropped when the data table identifier is 
rebuilt below, so the
     // identifier is validated as a whole here.
     PAIMON_RETURN_NOT_OK(CatalogUtils::CheckValidTableName(identifier));
     PAIMON_ASSIGN_OR_RAISE(bool is_system_table, identifier.IsSystemTable());
@@ -301,15 +306,16 @@ Result<std::shared_ptr<Schema>> 
FileSystemCatalog::LoadTableSchema(
         if (!system_table_name || 
!SystemTableLoader::IsSupported(system_table_name.value())) {
             return Status::NotExist(fmt::format("{} not exist", 
identifier.ToString()));
         }
-        PAIMON_ASSIGN_OR_RAISE(std::string data_table_name, 
identifier.GetDataTableName());
-        Identifier data_identifier(identifier.GetDatabaseName(), 
data_table_name);
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> branch, 
identifier.GetBranchName());
+        PAIMON_ASSIGN_OR_RAISE(
+            Identifier data_identifier,
+            CatalogUtils::BranchIdentifier(identifier, 
branch.value_or(std::string())));
         PAIMON_ASSIGN_OR_RAISE(std::optional<std::shared_ptr<TableSchema>> 
latest_schema,
                                TableSchemaExists(data_identifier));
         if (!latest_schema) {
             return Status::NotExist(fmt::format("{} not exist", 
data_identifier.ToString()));
         }
         std::map<std::string, std::string> dynamic_options;
-        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> branch, 
identifier.GetBranchName());
         if (branch) {
             dynamic_options[Options::BRANCH] = branch.value();
         }
diff --git a/src/paimon/core/catalog/file_system_catalog_test.cpp 
b/src/paimon/core/catalog/file_system_catalog_test.cpp
index 74578e39..68aa6ac4 100644
--- a/src/paimon/core/catalog/file_system_catalog_test.cpp
+++ b/src/paimon/core/catalog/file_system_catalog_test.cpp
@@ -31,6 +31,7 @@
 #include "paimon/common/utils/path_util.h"
 #include "paimon/core/catalog/version_managed_catalog.h"
 #include "paimon/core/core_options.h"
+#include "paimon/core/schema/schema_manager.h"
 #include "paimon/core/schema/table_schema.h"
 #include "paimon/core/table/system/global_system_tables.h"
 #include "paimon/core/table/system/system_table_schema.h"
@@ -238,6 +239,78 @@ TEST(FileSystemCatalogTest, TestOptionsSystemTableCatalog) 
{
                         "Cannot 'renameTable' for system table");
 }
 
+TEST(FileSystemCatalogTest, TestBranchTableLoadsBranchSchema) {
+    std::map<std::string, std::string> options;
+    options[Options::FILE_SYSTEM] = "local";
+    options[Options::FILE_FORMAT] = "orc";
+    ASSERT_OK_AND_ASSIGN(CoreOptions core_options, 
CoreOptions::FromMap(options));
+    auto dir = UniqueTestDirectory::Create();
+    ASSERT_TRUE(dir);
+    FileSystemCatalog catalog(core_options.GetFileSystem(), dir->Str(), 
options);
+    ASSERT_OK(catalog.CreateDatabase("db1", options, 
/*ignore_if_exists=*/false));
+
+    auto typed_schema = arrow::schema(
+        {arrow::field("main_c0", arrow::int32()), arrow::field("main_c1", 
arrow::utf8())});
+    ::ArrowSchema c_schema;
+    ASSERT_TRUE(arrow::ExportSchema(*typed_schema, &c_schema).ok());
+    Identifier identifier("db1", "tbl1");
+    ASSERT_OK(catalog.CreateTable(identifier, &c_schema, {}, {}, options,
+                                  /*ignore_if_exists=*/false));
+    ArrowSchemaRelease(&c_schema);
+
+    ASSERT_OK_AND_ASSIGN(std::string table_path, 
catalog.GetTableLocation(identifier));
+    SchemaManager branch_schema_manager(core_options.GetFileSystem(), 
table_path, "b1");
+    ASSERT_OK_AND_ASSIGN(
+        std::unique_ptr<TableSchema> branch_schema,
+        TableSchema::Create(
+            /*schema_id=*/3,
+            arrow::schema({arrow::field("b1_c0", arrow::int32(), 
/*nullable=*/false)}), {}, {},
+            options));
+    ASSERT_OK_AND_ASSIGN(std::string branch_schema_json, 
branch_schema->ToJsonString());
+    
ASSERT_OK(core_options.GetFileSystem()->AtomicStore(branch_schema_manager.ToSchemaPath(3),
+                                                        branch_schema_json));
+
+    Identifier branch_identifier("db1", "tbl1$branch_b1");
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<Schema> loaded_schema,
+                         catalog.LoadTableSchema(branch_identifier));
+    auto data_schema = std::dynamic_pointer_cast<DataSchema>(loaded_schema);
+    ASSERT_NE(nullptr, data_schema);
+    ASSERT_EQ((std::vector<std::string>{"b1_c0"}), data_schema->FieldNames());
+    ASSERT_EQ(3, data_schema->Id());
+
+    ASSERT_OK_AND_ASSIGN(bool exists, catalog.TableExists(branch_identifier));
+    ASSERT_TRUE(exists);
+
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<Table> table, 
catalog.GetTable(branch_identifier));
+    data_schema = std::dynamic_pointer_cast<DataSchema>(table->LatestSchema());
+    ASSERT_NE(nullptr, data_schema);
+    ASSERT_EQ((std::vector<std::string>{"b1_c0"}), data_schema->FieldNames());
+    ASSERT_EQ(3, data_schema->Id());
+
+    for (const auto& main_identifier :
+         {Identifier("db1", "tbl1$branch_main"), Identifier("db1", 
"tbl1$branch_MAIN")}) {
+        ASSERT_OK_AND_ASSIGN(loaded_schema, 
catalog.LoadTableSchema(main_identifier));
+        data_schema = std::dynamic_pointer_cast<DataSchema>(loaded_schema);
+        ASSERT_NE(nullptr, data_schema);
+        ASSERT_EQ((std::vector<std::string>{"main_c0", "main_c1"}), 
data_schema->FieldNames());
+        ASSERT_EQ(0, data_schema->Id());
+    }
+
+    Identifier audit_log_identifier("db1", "tbl1$branch_b1$audit_log");
+    ASSERT_OK_AND_ASSIGN(exists, catalog.TableExists(audit_log_identifier));
+    ASSERT_TRUE(exists);
+    ASSERT_OK_AND_ASSIGN(loaded_schema, 
catalog.LoadTableSchema(audit_log_identifier));
+    ASSERT_EQ((std::vector<std::string>{"rowkind", "b1_c0"}), 
loaded_schema->FieldNames());
+
+    for (const auto& missing_identifier : {Identifier("db1", 
"tbl1$branch_missing"),
+                                           Identifier("db1", 
"tbl1$branch_missing$options")}) {
+        ASSERT_OK_AND_ASSIGN(exists, catalog.TableExists(missing_identifier));
+        ASSERT_FALSE(exists);
+        Status missing_schema = 
catalog.LoadTableSchema(missing_identifier).status();
+        ASSERT_TRUE(missing_schema.IsNotExist()) << missing_schema.ToString();
+    }
+}
+
 TEST(FileSystemCatalogTest, TestBranchIdentifierRejectedForTableOperations) {
     std::map<std::string, std::string> options;
     options[Options::FILE_SYSTEM] = "local";
diff --git a/src/paimon/core/table/table.cpp b/src/paimon/core/table/table.cpp
index eaa57440..c627b458 100644
--- a/src/paimon/core/table/table.cpp
+++ b/src/paimon/core/table/table.cpp
@@ -22,6 +22,7 @@
 
 #include "fmt/format.h"
 #include "paimon/common/utils/checked_cast.h"
+#include "paimon/common/utils/string_utils.h"
 #include "paimon/core/catalog/catalog_utils.h"
 #include "paimon/core/schema/schema_manager.h"
 #include "paimon/core/schema/table_schema.h"
@@ -37,7 +38,12 @@ Result<std::shared_ptr<Table>> Table::Create(const 
std::shared_ptr<FileSystem>&
         return Status::NotExist(fmt::format("{} not exist", 
identifier.ToString()));
     }
 
-    SchemaManager schema_manager(file_system, table_path);
+    PAIMON_RETURN_NOT_OK(CatalogUtils::CheckValidTableName(identifier));
+    PAIMON_ASSIGN_OR_RAISE(std::string branch, 
identifier.GetBranchNameOrDefault());
+    if (StringUtils::EqualsIgnoreCase(branch, Identifier::kDefaultMainBranch)) 
{
+        branch = Identifier::kDefaultMainBranch;
+    }
+    SchemaManager schema_manager(file_system, table_path, branch);
     PAIMON_ASSIGN_OR_RAISE(std::optional<std::shared_ptr<TableSchema>> 
latest_schema,
                            schema_manager.Latest());
     if (!latest_schema) {
diff --git a/src/paimon/core/table/table_test.cpp 
b/src/paimon/core/table/table_test.cpp
index 60113ec8..8032890e 100644
--- a/src/paimon/core/table/table_test.cpp
+++ b/src/paimon/core/table/table_test.cpp
@@ -66,6 +66,36 @@ TEST(TableTest, TestCreateWithUnknownDatabase) {
     EXPECT_EQ(data_schema->PrimaryKeys(), primary_keys);
 }
 
+TEST(TableTest, TestCreateWithBranch) {
+    auto dir = UniqueTestDirectory::Create();
+    ASSERT_TRUE(dir);
+    auto fs = dir->GetFileSystem();
+    SchemaManager schema_manager(fs, dir->Str());
+    ASSERT_OK_AND_ASSIGN([[maybe_unused]] std::unique_ptr<TableSchema> 
main_schema,
+                         schema_manager.CreateTable(
+                             arrow::schema({arrow::field("main_c0", 
arrow::int32())}), {}, {}, {}));
+    SchemaManager branch_schema_manager(fs, dir->Str(), "b1");
+    ASSERT_OK_AND_ASSIGN([[maybe_unused]] std::unique_ptr<TableSchema> 
branch_schema,
+                         branch_schema_manager.CreateTable(
+                             arrow::schema({arrow::field("b1_c0", 
arrow::int32())}), {}, {}, {}));
+
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<Table> table,
+                         Table::Create(fs, dir->Str(), Identifier("db1", 
"tbl1$branch_b1")));
+    ASSERT_EQ("tbl1$branch_b1", table->Name());
+    ASSERT_EQ((std::vector<std::string>{"b1_c0"}), 
table->LatestSchema()->FieldNames());
+    for (const auto& main_identifier :
+         {Identifier("db1", "tbl1$branch_main"), Identifier("db1", 
"tbl1$branch_MAIN")}) {
+        ASSERT_OK_AND_ASSIGN(table, Table::Create(fs, dir->Str(), 
main_identifier));
+        ASSERT_EQ((std::vector<std::string>{"main_c0"}), 
table->LatestSchema()->FieldNames());
+    }
+
+    Status missing_branch =
+        Table::Create(fs, dir->Str(), Identifier("db1", 
"tbl1$branch_missing")).status();
+    ASSERT_TRUE(missing_branch.IsNotExist()) << missing_branch.ToString();
+    ASSERT_NOK_WITH_MSG(Table::Create(fs, dir->Str(), Identifier("db1", 
"tbl1$branch_../outside")),
+                        "branch");
+}
+
 TEST(TableTest, TestCreateRejectsATableTypeThatIsNotAManagedTable) {
     // A `Table` describes a managed paimon table. Handing back another type 
as one would promise
     // snapshots and manifests it never had, so the failure belongs at the 
call that opens the

Reply via email to