Skip to content

Commit 1f100b8

Browse files
committed
fix
1 parent 5e936cf commit 1f100b8

28 files changed

Lines changed: 1507 additions & 143 deletions

‎docs/source/user_guide/catalog.rst‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,23 @@ it; writers also allow ``WithFileSystemSchemeToIdentifierMap`` to override
169169
file-system selection. For format tables, use ``WriteContextBuilder(FormatTable)``
170170
instead.
171171

172+
A file system passed to ``WithFileSystem`` is used exactly as it is, so a custom
173+
one that signs each request itself stays authenticated by drawing the table's
174+
temporary credentials from ``Catalog::GetTableCredentialProvider``: hold the provider
175+
and call ``CredentialProvider::GetCredentials`` at each access, which returns
176+
credentials that are still valid and reloads them before they expire. A catalog
177+
that issues no per-table credentials returns a null provider, and the custom file
178+
system's own configuration authenticates the access.
179+
180+
.. code-block:: cpp
181+
182+
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<paimon::CredentialProvider> provider,
183+
shared_catalog->GetTableCredentialProvider(paimon::Identifier("db", "tbl")));
184+
// MyFileSystem merges provider->GetCredentials() over its own options per access.
185+
auto my_fs = std::make_shared<MyFileSystem>(provider);
186+
write_builder.WithCatalog(shared_catalog, paimon::Identifier("db", "tbl"))
187+
.WithFileSystem(my_fs);
188+
172189
The snapshot being committed carries a uuid generated on the client, and the
173190
commit names the snapshot it is based on by that snapshot's uuid, so the server
174191
can tell a commit that raced another. A commit which lost such a race is
@@ -235,6 +252,52 @@ so its restored file references are visible to the expiration operation.
235252
A branch the catalog holds without that directory, or one created while
236253
expiration runs, is not found: do not expire a table with such a branch, and
237254
serialize branch creation and expiration through the upstream coordinator.
255+
=======
256+
Reading through the catalog
257+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258+
Pass the catalog and table identifier to ``ReadContextBuilder::WithCatalog`` or
259+
``ScanContextBuilder::WithCatalog`` to read a native table the same way, in one
260+
call: the table's schema comes from the catalog, its data is read through the
261+
table's file system, and a scan reads the latest snapshot from the catalog:
262+
263+
.. code-block:: cpp
264+
265+
// Readers and scanners share ownership of the catalog.
266+
std::shared_ptr<paimon::Catalog> shared_catalog(std::move(catalog));
267+
268+
paimon::ScanContextBuilder scan_builder(table_path);
269+
scan_builder.WithCatalog(shared_catalog, paimon::Identifier("db", "tbl"));
270+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<paimon::ScanContext> scan_context,
271+
scan_builder.Finish());
272+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<paimon::TableScan> scan,
273+
paimon::TableScan::Create(std::move(scan_context)));
274+
275+
paimon::ReadContextBuilder read_builder(table_path);
276+
read_builder.WithCatalog(shared_catalog, paimon::Identifier("db", "tbl"));
277+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<paimon::ReadContext> read_context,
278+
read_builder.Finish());
279+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<paimon::TableRead> read,
280+
paimon::TableRead::Create(std::move(read_context)));
281+
282+
A scan takes only its latest snapshot from the catalog; the manifests that
283+
snapshot points at and any historical snapshots still come from the table path.
284+
Both builders read the table's schema from the catalog when ``Finish()`` builds
285+
the context, which is one catalog request; call ``SetTableSchema`` with a schema
286+
you already hold to skip it. The table's file system, including the per-table
287+
temporary credentials a catalog serves through ``Catalog::GetTableFileSystem``,
288+
is used for both. ``WithFileSystem`` overrides it, and a custom file system
289+
that signs its own requests can draw the table's credentials from
290+
``Catalog::GetTableCredentialProvider`` as shown above; the read builder also allows
291+
``WithFileSystemSchemeToIdentifierMap``.
292+
293+
Only the main branch is supported. Both builders reject other branches in the
294+
identifier or ``branch`` option; the read builder also checks ``WithBranch``.
295+
Explicit ``tbl$branch_main`` and ``branch=main`` are accepted. For a format
296+
table, use ``ReadContextBuilder(FormatTable)`` or ``ScanContextBuilder(FormatTable)``
297+
instead; ``WithCatalog`` is rejected. A system table path such as ``tbl$snapshots``
298+
reads snapshots from under the table path, which a version-managed catalog
299+
publishes nowhere but in itself, so it is rejected with ``NotImplemented``; scan
300+
such a table with ``WithFileSystem(Catalog::GetTableFileSystem(...))`` instead.
238301

239302
The C++ REST catalog covers the database, table, snapshot and commit operations
240303
of the ``Catalog`` API. The parts of the Java REST catalog that have no C++
@@ -298,3 +361,66 @@ identifier. The request body carries the table id but no table name and no
298361
branch, so the branch has to appear in the URL the caller sends that body to:
299362
the commit endpoint of ``tbl$branch_dev``, not the one of ``tbl``. A body sent
300363
to the bare table's URL publishes the branch's snapshot on the main branch.
364+
=======
365+
tags, branch management and consumers — are not supported.
366+
367+
Authenticating with credentials of your own
368+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
369+
Credentials that a catalog does not issue — from a token service of your own, or
370+
from a catalog Paimon has no client for — reach the built-in file systems through
371+
a ``CredentialProvider``. Implement it together with a
372+
``CredentialProviderFactory`` that builds it, register the factory with
373+
``REGISTER_PAIMON_FACTORY``, and name it in the ``fs.credential.provider``
374+
option: the file system then builds the provider once and asks it for credentials
375+
as it signs, so credentials are reloaded as they expire without the file system
376+
being rebuilt.
377+
378+
.. code-block:: cpp
379+
380+
class MyCredentialProvider : public paimon::CredentialProvider {
381+
public:
382+
// Called as each access is signed; reload the credentials before they expire.
383+
paimon::Result<std::map<std::string, std::string>> GetCredentials() const override {
384+
return std::map<std::string, std::string>{
385+
{"fs.oss.accessKeyId", ...}, {"fs.oss.accessKeySecret", ...},
386+
{"fs.oss.securityToken", ...}};
387+
}
388+
};
389+
390+
class MyCredentialProviderFactory : public paimon::CredentialProviderFactory {
391+
public:
392+
const char* Identifier() const override {
393+
return "my-token-service";
394+
}
395+
396+
paimon::Result<std::shared_ptr<paimon::CredentialProvider>> Create(
397+
const std::string& path,
398+
const std::map<std::string, std::string>& options) const override {
399+
return std::make_shared<MyCredentialProvider>(path, options);
400+
}
401+
};
402+
403+
REGISTER_PAIMON_FACTORY(MyCredentialProviderFactory);
404+
405+
The credentials are file-system options, e.g. ``fs.oss.securityToken``, and are
406+
merged over the options the file system was configured with, so a provider serves
407+
only what rotates and the endpoint and the rest stay in the options. The options
408+
the factory is called with are those of the accesses to authenticate, which is
409+
where an implementation reads its own configuration, such as the address of the
410+
token service, from.
411+
412+
.. code-block:: none
413+
414+
fs.credential.provider=my-token-service
415+
416+
Only the OSS file system consults a provider today; the others report
417+
``NotImplemented`` when the option names one, rather than signing with the
418+
credentials of the moment they were built and failing once those expire. All
419+
factories share one identifier space, so an identifier a file system factory
420+
already takes — ``oss``, ``s3``, ``local``, ``jindo`` — would replace it; name the
421+
provider after where its credentials come from instead.
422+
423+
This is the other of the two ways a provider is reached. A custom file system
424+
passed to ``WithFileSystem`` signs its own requests and pulls from the provider
425+
itself, which is what ``Catalog::GetTableCredentialProvider`` serves; a built-in
426+
file system signs on your behalf and is pointed at a provider by this option.

‎include/paimon/catalog/catalog.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ class PAIMON_EXPORT Catalog {
192192
/// @note A catalog that hands out per-table temporary credentials returns a file
193193
/// system that refreshes them, so the returned instance must be used for the
194194
/// table it was requested for. Pass it to `ReadContextBuilder::WithFileSystem`,
195-
/// `ScanContextBuilder::WithFileSystem` or `WriteContextBuilder::WithFileSystem`.
195+
/// `ScanContextBuilder::WithFileSystem` or `WriteContextBuilder::WithFileSystem`,
196+
/// or let a builder request it for you through `ReadContextBuilder::WithCatalog`,
197+
/// `ScanContextBuilder::WithCatalog` or `WriteContextBuilder::WithCatalog`.
196198
///
197199
/// @param identifier The identifier (database and table name) of the table.
198200
/// @return A shared pointer to the file system instance; the catalog-level file system
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <map>
22+
#include <string>
23+
24+
#include "paimon/result.h"
25+
#include "paimon/visibility.h"
26+
27+
namespace paimon {
28+
29+
/// The source of the credentials a file system authenticates its accesses with.
30+
///
31+
/// Implement this to hand out credentials that expire: the provider is asked again at
32+
/// every access, so it can reload them before they expire without the file system being
33+
/// torn down and built again.
34+
///
35+
/// A provider is for a caller that brings its own `FileSystem`. Build one with
36+
/// `CredentialProviderFactory::Get`, consult it from every access of your file system, and
37+
/// pass that file system in through `Catalog::Create`, `ReadContextBuilder::WithFileSystem`,
38+
/// `ScanContextBuilder::WithFileSystem` or `WriteContextBuilder::WithFileSystem`. A file
39+
/// system passed this way is used as-is; the built-in file systems are not involved and
40+
/// authenticate with the static credentials of their own options.
41+
///
42+
/// An implementation is consulted from every thread that accesses the file system and
43+
/// has to be thread-safe.
44+
class PAIMON_EXPORT CredentialProvider {
45+
public:
46+
virtual ~CredentialProvider() = default;
47+
48+
/// Returns the credentials to sign an access with, reloading them when they are about
49+
/// to expire. The keys are file system options, e.g. "fs.oss.accessKeyId" or
50+
/// "fs.oss.securityToken", so a caller merges them over its own file system options.
51+
virtual Result<std::map<std::string, std::string>> GetCredentials() const = 0;
52+
};
53+
54+
} // namespace paimon
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <map>
22+
#include <memory>
23+
#include <string>
24+
25+
#include "paimon/factories/factory.h"
26+
#include "paimon/fs/credential_provider.h"
27+
#include "paimon/result.h"
28+
#include "paimon/visibility.h"
29+
30+
namespace paimon {
31+
32+
/// A factory for creating `CredentialProvider` instances.
33+
///
34+
/// Register an implementation with `REGISTER_PAIMON_FACTORY` to authenticate accesses
35+
/// with credentials of your own making: the built-in file systems build the provider
36+
/// named by their `fs.credential.provider` option and consult it whenever they sign.
37+
///
38+
/// @note All factories share one identifier space, so an identifier that a file system
39+
/// factory already takes - "oss", "s3", "local", "jindo" - would replace it. Name the
40+
/// provider after where its credentials come from instead.
41+
class PAIMON_EXPORT CredentialProviderFactory : public Factory {
42+
public:
43+
/// The option naming the factory a file system builds its provider with.
44+
static const char CREDENTIAL_PROVIDER_OPTION[];
45+
46+
/// Create a `CredentialProvider` of current factory for the accesses below a path.
47+
///
48+
/// The options are the file system options of the accesses to authenticate, so an
49+
/// implementation reads its own configuration out of them.
50+
virtual Result<std::shared_ptr<CredentialProvider>> Create(
51+
const std::string& path, const std::map<std::string, std::string>& options) const = 0;
52+
53+
/// Get a `CredentialProvider` corresponding to identifier for the accesses below a path.
54+
/// @pre Factory is already registered.
55+
static Result<std::shared_ptr<CredentialProvider>> Get(
56+
const std::string& identifier, const std::string& path,
57+
const std::map<std::string, std::string>& fs_options);
58+
59+
/// Get the `CredentialProvider` the options ask for, or a null provider when they name
60+
/// none, in which case the file system options themselves authenticate the accesses.
61+
static Result<std::shared_ptr<CredentialProvider>> GetIfConfigured(
62+
const std::string& path, const std::map<std::string, std::string>& fs_options);
63+
};
64+
65+
} // namespace paimon

‎include/paimon/read_context.h‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727

2828
#include "arrow/c/abi.h"
2929
#include "paimon/cache/cache.h"
30+
#include "paimon/catalog/identifier.h"
3031
#include "paimon/predicate/predicate.h"
3132
#include "paimon/result.h"
3233
#include "paimon/type_fwd.h"
3334
#include "paimon/utils/prefetch_cache_config.h"
3435
#include "paimon/visibility.h"
3536

3637
namespace paimon {
38+
class Catalog;
3739
class Executor;
3840
class FormatTable;
3941
class MemoryPool;
@@ -62,7 +64,9 @@ class PAIMON_EXPORT ReadContext {
6264
const std::shared_ptr<RealtimeContext>& realtime_context,
6365
const std::map<std::string, std::string>& options, bool read_ahead_cache_enabled,
6466
const CacheConfig& cache_config, const std::shared_ptr<Cache>& cache,
65-
const std::shared_ptr<FormatTable>& format_table, WarmupLevel warmup_level);
67+
const std::shared_ptr<FormatTable>& format_table, WarmupLevel warmup_level,
68+
const std::shared_ptr<Catalog>& catalog,
69+
const std::optional<Identifier>& identifier);
6670

6771
~ReadContext();
6872

@@ -115,6 +119,8 @@ class PAIMON_EXPORT ReadContext {
115119
uint32_t GetRowToBatchThreadNumber() const {
116120
return row_to_batch_thread_number_;
117121
}
122+
/// The schema this context reads with, or null when it has to be read from the table path.
123+
/// `WithCatalog()` resolves it to the catalog's schema when `SetTableSchema()` did not answer.
118124
const std::optional<std::string>& GetSpecificTableSchema() const {
119125
return table_schema_;
120126
}
@@ -155,6 +161,16 @@ class PAIMON_EXPORT ReadContext {
155161
return warmup_level_;
156162
}
157163

164+
/// Returns the catalog supplying table metadata, or null if unset.
165+
const std::shared_ptr<Catalog>& GetCatalog() const {
166+
return catalog_;
167+
}
168+
169+
/// Returns the table identifier supplied with the catalog.
170+
const std::optional<Identifier>& GetIdentifier() const {
171+
return identifier_;
172+
}
173+
158174
/// Whether a read schema (C ArrowSchema) for nested column pruning was provided.
159175
bool HasReadSchema() const {
160176
return read_schema_ != nullptr && read_schema_->release != nullptr;
@@ -196,6 +212,8 @@ class PAIMON_EXPORT ReadContext {
196212
std::shared_ptr<Cache> cache_;
197213
std::shared_ptr<FormatTable> format_table_;
198214
WarmupLevel warmup_level_;
215+
std::shared_ptr<Catalog> catalog_;
216+
std::optional<Identifier> identifier_;
199217
// Owns schema resources and releases ArrowSchema::release in destructor.
200218
std::unique_ptr<ArrowSchema> read_schema_;
201219
};
@@ -210,8 +228,8 @@ class PAIMON_EXPORT ReadContextBuilder {
210228
/// Constructs a `ReadContextBuilder` for a format table that is already loaded: the only way
211229
/// to read one whose schema lives in a metastore rather than under its location, such as a
212230
/// table a REST catalog serves. The table carries what such a location does not say, so
213-
/// `SetTableSchema()`, `WithFileSystem()`, `WithFileSystemSchemeToIdentifierMap()` and a
214-
/// branch are refused here rather than ignored.
231+
/// `SetTableSchema()`, `WithFileSystem()`, `WithFileSystemSchemeToIdentifierMap()`,
232+
/// `WithCatalog()` and a branch are refused here rather than ignored.
215233
///
216234
/// @param table The format table to read, as `Catalog::GetFormatTable()` hands it back.
217235
explicit ReadContextBuilder(const std::shared_ptr<FormatTable>& table);
@@ -478,6 +496,19 @@ class PAIMON_EXPORT ReadContextBuilder {
478496
/// @note If not set, use default file system (configured in `Options::FILE_SYSTEM`)
479497
ReadContextBuilder& WithFileSystem(const std::shared_ptr<FileSystem>& file_system);
480498

499+
/// Loads a native table's schema from the catalog and reads its data through that table's file
500+
/// system - including the per-table temporary credentials a catalog that issues them hands out
501+
/// through `Catalog::GetTableFileSystem`.
502+
///
503+
/// Only the main branch is supported. `SetTableSchema()` keeps the caller's schema and spares
504+
/// the catalog request; `WithFileSystem()` and `WithFileSystemSchemeToIdentifierMap()` override
505+
/// the file system.
506+
/// @param catalog Non-null catalog, read when `Finish()` builds the context.
507+
/// @param identifier The native table to read.
508+
/// @return Reference to this builder for method chaining.
509+
ReadContextBuilder& WithCatalog(const std::shared_ptr<Catalog>& catalog,
510+
const Identifier& identifier);
511+
481512
/// Inject a cache for read operations. Passing nullptr disables cache.
482513
/// @return Reference to this builder for method chaining.
483514
ReadContextBuilder& WithCache(const std::shared_ptr<Cache>& cache);

0 commit comments

Comments
 (0)