feat(rest): support rest catalog for databases, tables and snapshots - #441
feat(rest): support rest catalog for databases, tables and snapshots#441SteNicholas wants to merge 1 commit into
Conversation
8fda86d to
93edb7d
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds a new REST-backed catalog implementation selectable via metastore=rest, including HTTP client + auth + REST API layers plus unit tests and build/CI wiring.
Changes:
- Introduces
HttpClient,RestApi,RestCatalog, auth provider, message models, and URL/path utilities for the REST catalog protocol. - Adds an in-process mock REST server and a new REST unit test binary with coverage across retry logic, message round-trips, and catalog operations.
- Extends catalog factory/options and updates CMake/CI to optionally build/link REST support (libcurl) and run REST tests.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/paimon/rest/rest_util.h | Declares REST helper utilities (URL encode/decode, JSON helpers, prefix extraction). |
| src/paimon/rest/rest_util.cpp | Implements URL encoding/decoding, prefix extraction, and rapidjson serialization helpers. |
| src/paimon/rest/rest_messages_test.cpp | Adds unit tests for REST utilities, paths, and JSON message round-trips. |
| src/paimon/rest/rest_messages.h | Adds REST protocol request/response models (JSON-serializable). |
| src/paimon/rest/rest_messages.cpp | Implements JSON serialization/deserialization and config merge behavior. |
| src/paimon/rest/rest_catalog_test.cpp | End-to-end tests for REST catalog behavior against a mock server. |
| src/paimon/rest/rest_catalog.h | Declares RestCatalog implementing database/table operations + snapshot listing. |
| src/paimon/rest/rest_catalog.cpp | Implements REST catalog behavior and schema conversion/highestFieldId logic. |
| src/paimon/rest/rest_auth.h | Declares REST auth provider interface and bearer-token provider. |
| src/paimon/rest/rest_auth.cpp | Implements bearer-token header injection and provider selection via options. |
| src/paimon/rest/rest_api.h | Declares REST API layer (HTTP+JSON) and error mapping to Status. |
| src/paimon/rest/rest_api.cpp | Implements config fetching/merging, request execution, pagination, and error mapping. |
| src/paimon/rest/resource_paths.h | Declares URL path builder for REST endpoints with optional prefix. |
| src/paimon/rest/resource_paths.cpp | Implements REST endpoint path construction with URL-encoded segments. |
| src/paimon/rest/mock_rest_server.h | Declares a minimal blocking HTTP server for REST unit tests. |
| src/paimon/rest/mock_rest_server.cpp | Implements the mock HTTP server (parsing, dispatch, responses). |
| src/paimon/rest/http_client_test.cpp | Adds unit tests for URI normalization, query encoding, and retry behavior. |
| src/paimon/rest/http_client.h | Declares blocking libcurl client with retry/backoff behavior. |
| src/paimon/rest/http_client.cpp | Implements libcurl execution, retry classification, backoff, and header parsing. |
| src/paimon/core/snapshot.h | Adds SnapshotInfo include and Snapshot::ToSnapshotInfo() declaration. |
| src/paimon/core/snapshot.cpp | Implements Snapshot::ToSnapshotInfo() commit-kind mapping and field transfer. |
| src/paimon/core/catalog/file_system_catalog.cpp | Refactors snapshot listing to reuse Snapshot::ToSnapshotInfo(). |
| src/paimon/core/catalog/catalog.cpp | Adds metastore dispatch for filesystem vs rest (guarded by PAIMON_ENABLE_REST). |
| src/paimon/common/defs.cpp | Defines new option keys: metastore, uri, token, token.provider. |
| src/paimon/CMakeLists.txt | Adds REST sources/linking behind PAIMON_ENABLE_REST and registers REST tests. |
| include/paimon/defs.h | Exposes new public options for selecting/configuring the REST catalog. |
| ci/scripts/build_paimon.sh | Forces -DPAIMON_ENABLE_REST=ON in CI build script. |
| CMakeLists.txt | Adds PAIMON_ENABLE_REST option, finds libcurl, and defines compile macro. |
| .github/workflows/gcc8_test.yaml | Installs libcurl dev package for GCC8 CI. |
| .github/workflows/build_and_test.yaml | Installs libcurl dev package for general CI builds/tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
47960b5 to
9123abd
Compare
9f3b590 to
006176f
Compare
0ffb7ae to
9621d93
Compare
zjw1111
left a comment
There was a problem hiding this comment.
Thanks for putting this together — the alignment with the Java implementation is impressively thorough (config merge precedence, retry classification, isSuccessful status set, paging termination, audit option keys all match).
One small consistency point on snapshot listing, left inline.
38894eb to
6a56445
Compare
Add a REST catalog selected via the "metastore=rest" option:
- RestHttpClient: blocking libcurl client with exponential-backoff
retries. 429/503 are retried for every method, a transient transport
error only for an idempotent one; a server closing the connection
without responding is not retried. A Retry-After header is honored in
both the delta-seconds and the HTTP-date form with a
locale-independent parse, and is never shortened: the overall retry
budget bounds it while the per-sleep cap bounds only the backoff. Easy
handles are pooled, so a request reuses the previous connection
instead of paying a handshake every time; TLS below 1.2 is refused and
requests carry a default user agent. Every transported request is
debug-logged with the server request id.
- RestApi: HTTP + JSON protocol layer. "/v1/config" merges the server
options into the client ones (overrides > client options > defaults,
an override sent as null unsets the client option), "header." options
are sent as request headers and a listing follows the page tokens to
the last page. A failed request maps to a Status by the code of the
parsed error body, falling back to the http status, and carries a
RestErrorDetail with that code so callers can tell e.g. an
authentication failure from another IO error. A response body is never
echoed into an error, since it may carry credentials, so a body that
is not an error object is only reported as unparsable.
- RestCatalog: database, table and snapshot operations, with
"table-default." option defaults and a schema conversion computing the
highest field id (ids at or above the new
SpecialFieldIds::SYSTEM_FIELD_ID_START are excluded, duplicated ids
are rejected at any nesting level). A branch stays in the object name
so the server resolves it and returns the branch's own schema, the
default branch "main" being matched ignoring case and addressed as the
bare table. The "sys" database serves the local global system tables
like FileSystemCatalog.
- Bear token authentication provider ("bear" is the protocol's
historical spelling of "bearer"). RestAuthParameter::Create
url-encodes the query parameter values a signing provider signs.
- CatalogOptions: public catalog-level option keys (metastore, uri,
token, token.provider, "table-default." prefix), kept separate from
the table-level CoreOptions.
- CatalogUtils: system database, system table and branch checks shared
by FileSystemCatalog and RestCatalog.
- RestUtil::ExtractRequestId: the request id lookup shared by the debug
log line and the error message, falling back to any header whose name
carries a request id since a gateway may report it under its own name.
The pieces overlapping with the object store file systems move to
common/utils: UrlUtils carries both URL encoding flavors
(form-urlencoded for the REST api, RFC 3986 for S3), the generic HTTP
client moves there from common/fs, and the new SensitiveConfigUtils
redacts credentials for every surface that may expose them - the
sys.catalog_options table masks the value of a credential-carrying
option key, keeping at most a short tail of an identifier-like value,
and a server error message carrying a secret marker is redacted as a
whole.
libcurl is used from the system, so the new PAIMON_ENABLE_REST option
defaults to OFF like the other optional components with external
requirements; its CMake detection is a single find_package guarded by
PAIMON_ENABLE_S3 OR PAIMON_ENABLE_REST.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Purpose
Linked issue: #92
Add a REST catalog implementation, selected via the
metastore=restoption, that talks the Paimon REST catalog open API.New components
RestHttpClient— blocking libcurl client with exponential-backoff retries:Retry-Afterresponse header — delta-seconds or HTTP-date form, parsed locale-independently — takes precedence over the backoff when it yields a positive delay. Every retry sleep is bounded by a per-sleep cap and an overall retry budget; aRetry-Afterbeyond either bound stops retrying rather than sleeping less than the server requested.CURLOPT_POSTREDIR) and are restricted to http(s) targets.paimon-cppuser agent that aheader.User-Agentoption overrides.RestApi— HTTP + JSON layer:/v1/configoption merging (overrides > client options > defaults),header.options sent as request headers, and paged listing.Status(404 →NotExist, 409 →Exist, 400 →Invalid, 501 →NotImplemented) that prefers the code of the parsed error body over the http status.x-request-id, falling back to anyrequest-idheader) and attach aRestErrorDetailwith the mapped code, so callers can distinguish e.g. authentication failures programmatically.RestCatalog— database/table create/drop/rename/list/get, snapshot listing,table-default.option defaults on table creation, and system/branch table checks:TableSchemawith the computedhighestFieldId: ids at or aboveSpecialFieldIds::SYSTEM_FIELD_ID_STARTare excluded, a duplicated field id at any nesting level is rejected, and a missing or wrong-typedpartitionKeys/primaryKeys/optionsmember fails instead of silently defaulting.mainmaps case-insensitively to the bare table); snapshots of a branch are listed under the branch object name.sysdatabase serves the local global system tables likeFileSystemCatalog.token.provider=bear, the protocol's historical spelling of "bearer").Shared code
common/utils: the generic HTTP client moved there fromcommon/fsand is shared by the S3 file system and the REST catalog,UrlUtilscarries both URL-encoding flavors (form-urlencoded for the REST api, RFC 3986 for S3) with the S3 client reusing it, and libcurl detection in CMake is a singlefind_package(CURL)gated onPAIMON_ENABLE_S3 OR PAIMON_ENABLE_REST.CatalogUtilsholds the system-database and system/branch table checks shared byFileSystemCatalogandRestCatalog, with the check order and messages aligned with the JavaCatalogUtils(Cannot 'createTable' for system table ...).FileSystemCatalognow also rejects branch identifiers on create/drop/rename — a branch identifier resolves to the main table directory, so droppingt$branch_bwould otherwise delete the whole table.SensitiveConfigUtilscentralizes credential redaction with the JavaSensitiveConfigUtilskey markers and masking rule (matched on the key lower-cased with separators removed; a key naming a true secret is masked as a whole, while an identifier-like key such asdlf.access-key-idkeeps at most a four-character tail of a long enough value).sys.catalog_optionsmasks every row through it so credentials such as the bearer token never surface to users who can query that table, and a server error message carrying a secret marker is redacted as a whole.RestUtil::ExtractRequestIdandRestAuthParameter::Createlikewise hold the request-id lookup and the query-parameter encoding in one place, so the log line and the error message report the same id and no call site can sign an unencoded parameter.Build
libcurl is used from the system rather than bundled, so the new CMake option
PAIMON_ENABLE_RESTdefaults toOFF, consistent with the other optional components that need something outside the bundled third-party toolchain (jindo, lance, lumina, lucene, tantivy). CI enables it explicitly inci/scripts/build_paimon.shand installs the libcurl development package, so the REST code and its tests are still built and run on every job. With the option off,Catalog::Createreports thatmetastore=restrequires a build withPAIMON_ENABLE_REST=ON.Follow-ups (not in this PR): DLF signing, data-token file IO, paged listing parameters (
maxResults/name patterns), and the remaining endpoints (alter/commit/views/partitions/tags/functions).Tests
New
rest_testbinary (62 cases, registered under theunittestlabel so it runs in CI), all against an in-process mock HTTP/REST server:RestHttpClientTest: uri normalization, query encoding, a pooled handle carrying no method or body over from the previous request, retry classification (429/503 retried, 404 and non-allowlisted transport errors such as a refused connection or a redirect loop not, truncated responses retried only for idempotent methods),Retry-Afterprecedence in both forms plus the per-sleep and overall budget bounds, retry exhaustion, redirect following with POST keeping its method and body, rejection of invalid header names/values, and transport errors omitting the url and query.RestUtilTest/ResourcePathsTest/RestMessagesTest, each next to its implementation: prefix extraction and the request-id lookup including the gateway-header fallback, resource path building with url-encoded segments, JSON round trips of all request/response messages, and config merge with null-value filtering (a key present in defaults, client options and overrides at once, plus the null-override and null-default paths).RestCatalogTest: client-side option validation (missing uri/token, unsupported token provider), end-to-end catalog operations including pagination,ignore_if_exists/ignore_if_not_existspaths, client/server headers and the json content type,table-default.defaults, system tables, branch tables resolved by the server, broken-schema rejection (missing and wrong-typedpartitionKeys/primaryKeys/optionsmembers), a non-empty partition-key round trip, server errors surfacing as errors instead of "does not exist", and snapshot listing with sorting.RestApiErrorTest: http-status-to-Statusmapping including the request-id fallback, redaction and theRestErrorDetailcode.The shared pieces are covered next to their implementations:
UrlUtilsTest: both encoding flavors, including that a literal%is encoded even when/is preserved, which is what the S3 client relies on.SensitiveConfigUtilsTest: key classification, whole-value and tail-preserving masking, and free-form text redaction.HttpClientUtilTestin the common utils suite.TableSchemaTest.TestComputeHighestFieldId: the highest-field-id computation.SnapshotTest.TestToSnapshotInfo: the snapshot-to-info conversion.FileSystemCatalogTest.TestBranchIdentifierRejectedForTableOperations: the branch rejection.sys.catalog_optionsintegration test: credential-carrying options (token, access-key secret, account key, credential, SAS) are masked in every emitted row, and an access-key id keeps only its four-character tail.API and Format
include/paimon/catalog_options.hwith aPAIMON_EXPORTedCatalogOptionsstruct holding the catalog-level option keysMETASTORE,URI,TOKEN,TOKEN_PROVIDERand theTABLE_DEFAULT_OPTION_PREFIX(table-default.) key prefix; table-level keys stay inOptions(defs.h), mirroring the CatalogOptions/CoreOptions separation of the Java implementation.RestCatalogimplements theCataloginterface includingGetOptions(), which returns the client options merged with the server-side/v1/config.Catalog::Createnow dispatches on themetastoreoption (filesystemremains the default; unknown values are rejected). Formetastore=rest,root_pathis not a filesystem path but the warehouse (instance) name registered on the REST server, documented on the API.Documentation
docs/source/user_guide/catalog.rstnow documents both metastores: the existing filesystem metastore section and a new REST Catalog section covering thePAIMON_ENABLE_RESTbuild requirement, theroot_pathsemantics (warehouse/instance name), theCatalogOptionskeys (metastore,uri,token.provider,token,table-default.<key>) and a configuration example, replacing the note that claimed REST catalog support is future work. It also states which parts of the REST catalog are not implemented yet (altering a database or a table, views, functions, partitions, tags, branch management, consumers and thedlftoken provider), so the guide is not read as promising them.docs/source/building.rstlists-DPAIMON_ENABLE_REST=ONunder the optional components, including its libcurl requirement.Generative AI tooling
Generated-by: Claude Code (claude-fable-5, claude-opus-5)
🤖 Generated with Claude Code