Skip to content

Commit 5e936cf

Browse files
committed
fix
1 parent f0cdd8a commit 5e936cf

7 files changed

Lines changed: 109 additions & 11 deletions

File tree

‎docs/source/user_guide/catalog.rst‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,12 @@ already advanced the latest snapshot, the requested snapshot must be published
162162
under the table path. Recreating the writer restores offsets from the catalog's
163163
current snapshot.
164164

165-
Both builders use the catalog's file system for manifests and data, preserving
166-
its object-store credentials. ``WithFileSystem`` overrides it; writers also
167-
allow ``WithFileSystemSchemeToIdentifierMap`` to override file-system selection.
168-
For format tables, use ``WriteContextBuilder(FormatTable)`` instead.
165+
Both builders use the file system of the table being written or committed for
166+
manifests and data, so a catalog that issues temporary credentials per table
167+
serves them through ``Catalog::GetTableFileSystem``. ``WithFileSystem`` overrides
168+
it; writers also allow ``WithFileSystemSchemeToIdentifierMap`` to override
169+
file-system selection. For format tables, use ``WriteContextBuilder(FormatTable)``
170+
instead.
169171

170172
The snapshot being committed carries a uuid generated on the client, and the
171173
commit names the snapshot it is based on by that snapshot's uuid, so the server

‎include/paimon/write_context.h‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,10 @@ class PAIMON_EXPORT WriteContextBuilder {
263263
/// snapshot. Use the same catalog and identifier as `CommitContextBuilder` to restore and
264264
/// refresh real-time progress.
265265
///
266-
/// A branch is addressed by the identifier, as `tbl$branch_dev`, so that the catalog answers
267-
/// for that branch. Its schema is read from the branch directory rather than from the catalog,
268-
/// just as a read of that branch reads it.
269-
/// Data, manifests and historical metadata remain on the file system. The catalog supplies
270-
/// that file system unless overridden by `WithFileSystem()` or
266+
/// Only the main branch is supported. Data, manifests and historical metadata remain on the
267+
/// file system. The catalog supplies that table's file system - including the per-table
268+
/// temporary credentials a catalog that issues them hands out through
269+
/// `Catalog::GetTableFileSystem` - unless overridden by `WithFileSystem()` or
271270
/// `WithFileSystemSchemeToIdentifierMap()`.
272271
/// @param catalog Non-null catalog, kept alive while the writer uses its snapshot loader.
273272
/// @param identifier The native table to write to.

‎src/paimon/core/operation/file_store_commit.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,13 @@ Result<std::unique_ptr<FileStoreCommit>> FileStoreCommit::Create(
195195
// Use catalog credentials unless the caller supplied a file system.
196196
std::shared_ptr<FileSystem> specific_fs = ctx->GetSpecificFileSystem();
197197
if (specific_fs == nullptr && ctx->GetCatalog() != nullptr) {
198-
specific_fs = ctx->GetCatalog()->GetFileSystem();
198+
if (!ctx->GetIdentifier()) {
199+
return Status::Invalid("a catalog commit requires a table identifier");
200+
}
201+
// Use the credentials of this table, which a catalog issuing per-table temporary
202+
// ones only hands out through GetTableFileSystem.
203+
PAIMON_ASSIGN_OR_RAISE(specific_fs,
204+
ctx->GetCatalog()->GetTableFileSystem(ctx->GetIdentifier().value()));
199205
}
200206
PAIMON_ASSIGN_OR_RAISE(CoreOptions tmp_options,
201207
CoreOptions::FromMap(ctx->GetOptions(), specific_fs));

‎src/paimon/core/operation/file_store_commit_test.cpp‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@
3939
#include "paimon/core/io/compact_increment.h"
4040
#include "paimon/core/io/data_increment.h"
4141
#include "paimon/core/operation/file_store_commit_impl.h"
42+
#include "paimon/core/schema/table_schema.h"
4243
#include "paimon/core/table/sink/commit_message_impl.h"
4344
#include "paimon/core/utils/snapshot_manager.h"
4445
#include "paimon/defs.h"
4546
#include "paimon/executor.h"
4647
#include "paimon/fs/local/local_file_system.h"
4748
#include "paimon/memory/memory_pool.h"
4849
#include "paimon/result.h"
50+
#include "paimon/testing/mock/mock_catalog.h"
4951
#include "paimon/testing/utils/binary_row_generator.h"
5052
#include "paimon/testing/utils/testharness.h"
5153

@@ -103,6 +105,35 @@ TEST(FileStoreCommitTest, TestCreateWithCatalogRequiresIdentifier) {
103105
"a catalog commit requires a table identifier");
104106
}
105107

108+
TEST(FileStoreCommitTest, TestCatalogCommitUsesPerTableFileSystem) {
109+
auto dir = UniqueTestDirectory::Create();
110+
ASSERT_TRUE(dir);
111+
const std::string table_path = PathUtil::JoinPath(dir->Str(), "foo.db/bar");
112+
const Identifier identifier("foo", "bar");
113+
const auto logical_schema = arrow::schema(
114+
{arrow::field("id", arrow::int64(), false), arrow::field("value", arrow::utf8())});
115+
ASSERT_OK_AND_ASSIGN(
116+
std::shared_ptr<TableSchema> schema,
117+
TableSchema::Create(0, logical_schema, /*partition_keys=*/{},
118+
/*primary_keys=*/{}, {{Options::FILE_FORMAT, "parquet"}}));
119+
auto catalog = std::make_shared<MockVersionManagedCatalog>();
120+
catalog->SetTableSchema(schema);
121+
// The catalog-wide file system is a different instance, so a commit that reached for it
122+
// instead of the table's own would not be served the credentials the table needs.
123+
catalog->SetFileSystem(std::make_shared<LocalFileSystem>());
124+
catalog->SetTableFileSystem(dir->GetFileSystem());
125+
126+
CommitContextBuilder builder(table_path, "commit_user");
127+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<CommitContext> ctx,
128+
builder.WithCatalog(catalog, identifier).Finish());
129+
ASSERT_OK_AND_ASSIGN(auto commit, FileStoreCommit::Create(std::move(ctx)));
130+
131+
// The file system was resolved per table, not from the catalog-wide one.
132+
const std::vector<Identifier>& requests = catalog->TableFileSystemRequests();
133+
ASSERT_FALSE(requests.empty());
134+
ASSERT_EQ(requests.back().GetFullName(), identifier.GetFullName());
135+
}
136+
106137
TEST(FileStoreCommitTest, TestAppendDvIndexShouldUseOverwriteCommitKind) {
107138
auto string_field = arrow::field("f0", arrow::utf8());
108139
auto int_field = arrow::field("f1", arrow::int32());

‎src/paimon/core/operation/file_store_write.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,13 @@ Result<std::unique_ptr<FileStoreWrite>> FileStoreWrite::Create(std::unique_ptr<W
157157
std::shared_ptr<FileSystem> specific_fs = ctx->GetSpecificFileSystem();
158158
if (specific_fs == nullptr && ctx->GetFileSystemSchemeToIdentifierMap().empty() &&
159159
ctx->GetCatalog() != nullptr) {
160-
specific_fs = ctx->GetCatalog()->GetFileSystem();
160+
if (!ctx->GetIdentifier()) {
161+
return Status::Invalid("a catalog write requires a table identifier");
162+
}
163+
// A catalog issuing per-table temporary credentials only hands them out through
164+
// GetTableFileSystem, so the write uses the credentials of this table.
165+
PAIMON_ASSIGN_OR_RAISE(specific_fs,
166+
ctx->GetCatalog()->GetTableFileSystem(ctx->GetIdentifier().value()));
161167
}
162168
PAIMON_ASSIGN_OR_RAISE(CoreOptions tmp_options,
163169
CoreOptions::FromMap(ctx->GetOptions(), specific_fs,

‎src/paimon/core/operation/file_store_write_test.cpp‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,37 @@ TEST(FileStoreWriteTest, TestCatalogWriteAsksTheCatalogOnlyForTheMainBranchSchem
222222
ASSERT_EQ(catalog->LoadTableSchemaIdentifiers().size(), 1u);
223223
}
224224

225+
TEST(FileStoreWriteTest, TestCatalogWriteUsesPerTableFileSystem) {
226+
auto dir = UniqueTestDirectory::Create();
227+
ASSERT_TRUE(dir);
228+
const std::string table_path = PathUtil::JoinPath(dir->Str(), "foo.db/bar");
229+
const Identifier identifier("foo", "bar");
230+
const auto logical_schema = arrow::schema(
231+
{arrow::field("id", arrow::int64(), false), arrow::field("value", arrow::utf8())});
232+
ASSERT_OK_AND_ASSIGN(
233+
std::shared_ptr<TableSchema> schema,
234+
TableSchema::Create(0, logical_schema, /*partition_keys=*/{},
235+
/*primary_keys=*/{}, {{Options::FILE_FORMAT, "parquet"}}));
236+
auto catalog = std::make_shared<MockVersionManagedCatalog>();
237+
catalog->SetTableSchema(schema);
238+
// The catalog-wide file system is a different instance, so a write that reached for it
239+
// instead of the table's own would not be served the credentials the table needs.
240+
catalog->SetFileSystem(std::make_shared<LocalFileSystem>());
241+
catalog->SetTableFileSystem(dir->GetFileSystem());
242+
243+
WriteContextBuilder builder(table_path, "writer");
244+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<WriteContext> ctx,
245+
builder.WithCatalog(catalog, identifier).Finish());
246+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<FileStoreWrite> writer,
247+
FileStoreWrite::Create(std::move(ctx)));
248+
ASSERT_OK(writer->Close());
249+
250+
// The file system was resolved per table, not from the catalog-wide one.
251+
const std::vector<Identifier>& requests = catalog->TableFileSystemRequests();
252+
ASSERT_FALSE(requests.empty());
253+
ASSERT_EQ(requests.back().GetFullName(), identifier.GetFullName());
254+
}
255+
225256
TEST(FileStoreWriteTest, TestCreateWriterForLoadedMapBlobTable) {
226257
auto dir = UniqueTestDirectory::Create();
227258
std::string table_path = PathUtil::JoinPath(dir->Str(), "foo.db/bar");

‎src/paimon/testing/mock/mock_catalog.h‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,27 @@ class MockVersionManagedCatalog : public Catalog, public VersionManagedCatalog {
227227
std::shared_ptr<FileSystem> GetFileSystem() const override {
228228
return file_system_;
229229
}
230+
231+
/// Records what was asked and serves the per-table file system when one was set, so a
232+
/// test can tell it apart from the catalog-wide one; falls back to `GetFileSystem()`,
233+
/// matching the base default, when none was set.
234+
Result<std::shared_ptr<FileSystem>> GetTableFileSystem(
235+
const Identifier& identifier) const override {
236+
table_file_system_requests_.push_back(identifier);
237+
if (table_file_system_ != nullptr) {
238+
return table_file_system_;
239+
}
240+
return GetFileSystem();
241+
}
242+
243+
void SetTableFileSystem(const std::shared_ptr<FileSystem>& file_system) {
244+
table_file_system_ = file_system;
245+
}
246+
247+
const std::vector<Identifier>& TableFileSystemRequests() const {
248+
return table_file_system_requests_;
249+
}
250+
230251
const std::map<std::string, std::string>& GetOptions() const override {
231252
return options_;
232253
}
@@ -244,6 +265,8 @@ class MockVersionManagedCatalog : public Catalog, public VersionManagedCatalog {
244265
std::string table_uuid_;
245266
std::shared_ptr<Schema> table_schema_;
246267
std::shared_ptr<FileSystem> file_system_;
268+
std::shared_ptr<FileSystem> table_file_system_;
269+
mutable std::vector<Identifier> table_file_system_requests_;
247270
bool supports_version_management_ = true;
248271
std::function<void()> on_commit_;
249272
bool check_table_uuid_ = false;

0 commit comments

Comments
 (0)