Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions pg_lake_iceberg/include/pg_lake/rest_catalog/rest_catalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,37 @@ extern char *RestCatalogClientSecret;
extern char *RestCatalogScope;
extern int RestCatalogAuthType;
extern bool RestCatalogEnableVendedCredentials;
extern char *CatalogsConfPath;

/*
* Resolved REST catalog connection options. All REST catalogs --
* built-in ('rest') and user-created (CREATE SERVER ... FOREIGN DATA
* WRAPPER iceberg_catalog) -- are backed by a real pg_foreign_server
* row; ApplyGUCDefaults populates the defaults, ApplyServerOptionOverrides
* layers on any per-server options.
* row.
*
* The canonical identity of a catalog is `serverOid` (the OID of the
* iceberg_catalog server row). Use it for in-memory equality, token
* cache keys, and syscache-driven invalidation. `catalog` stores the
* user-visible short name (e.g. 'rest', 'my_polaris') purely for error
* messages.
* Resolution order, lowest to highest priority:
* 1. GUC defaults (ApplyGUCDefaults)
* 2. Server options (ApplyServerOptionOverrides)
* 3. $PGDATA/catalogs.conf credentials (user-created servers only)
* 4. pg_user_mapping options (user-created servers only)
*
* In-memory identity is the pair (`serverOid`, `umid`):
* - serverOid is the iceberg_catalog server's OID.
* - umid is the OID of the pg_user_mapping row that contributed the
* credentials, or InvalidOid when no user mapping was used (built-in
* pg_lake_rest_catalog, or a user-created server whose credentials
* came entirely from catalogs.conf / GUCs).
*
* `catalog` is the user-visible short name (e.g. 'rest', 'my_polaris')
* kept purely for error messages.
*/
typedef struct RestCatalogOptions
{
Oid serverOid; /* iceberg_catalog server OID; canonical
* identity, never InvalidOid for resolved
* opts */
Oid umid; /* pg_user_mapping row OID that supplied
* credentials, or InvalidOid if none */
char *catalog; /* short user-facing name; used in error
* messages, never for equality */
char *host;
Expand Down Expand Up @@ -138,3 +150,11 @@ extern PGDLLEXPORT RestCatalogRequest * GetRemoveSnapshotCatalogRequest(List *re

/* ProcessUtility handler for iceberg_catalog server DDL validation */
extern PGDLLEXPORT bool ValidateIcebergCatalogServerDDL(ProcessUtilityParams * processUtilityParams, void *arg);

/*
* ProcessUtility handler that scrubs client_id / client_secret out of
* queryString in CREATE/ALTER USER MAPPING for iceberg_catalog
* servers, in place. Register after ValidateIcebergCatalogServerDDL
* so it runs first (the handler list is a prepend-LIFO).
*/
extern PGDLLEXPORT bool RedactRestCatalogUserMappingSecrets(ProcessUtilityParams * processUtilityParams, void *arg);
31 changes: 26 additions & 5 deletions pg_lake_iceberg/pg_lake_iceberg--3.3--3.4.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,36 @@
* configurations via CREATE SERVER so that users are not limited to a
* single global REST catalog configured through GUC settings.
*
* Example:
* Server options (non-secret): rest_endpoint, rest_auth_type,
* oauth_endpoint, scope, enable_vended_credentials, location_prefix,
* catalog_name.
* User mapping options (credentials): client_id, client_secret, scope.
*
* scope is accepted in both server and user mapping; user mapping wins.
*
* Credential resolution order:
* 1. CREATE USER MAPPING for the current user
* 2. $PGDATA/catalogs.conf (platform-provided)
* 3. GUC variables (backward compatibility)
*
* User-defined catalog example:
* CREATE SERVER my_polaris TYPE 'rest'
* FOREIGN DATA WRAPPER iceberg_catalog
* OPTIONS (rest_endpoint 'http://polaris:8181',
* rest_auth_type 'default',
* client_id '...',
* client_secret '...');
* OPTIONS (rest_endpoint 'https://polaris.example.com');
*
* CREATE USER MAPPING FOR user1 SERVER my_polaris
* OPTIONS (client_id '...', client_secret '...');
*
* CREATE TABLE t (a int) USING iceberg WITH (catalog = 'my_polaris');
*
* Platform-provided catalog example:
* CREATE SERVER horizon TYPE 'rest'
* FOREIGN DATA WRAPPER iceberg_catalog
* OPTIONS (rest_endpoint 'https://horizon.example.com');
*
* -- Credentials in $PGDATA/catalogs.conf:
* -- horizon.client_id = 'platform_id'
* -- horizon.client_secret = 'platform_secret'
*/
CREATE FUNCTION lake_iceberg.iceberg_catalog_validator(text[], oid)
RETURNS void
Expand Down
18 changes: 18 additions & 0 deletions pg_lake_iceberg/src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,16 @@ _PG_init(void)
GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
NULL, NULL, NULL);

DefineCustomStringVariable("pg_lake_iceberg.catalogs_conf_path",
gettext_noop("Path to the catalog credentials file. "
"Defaults to $PGDATA/catalogs.conf."),
NULL,
&CatalogsConfPath,
"catalogs.conf",
PGC_SIGHUP,
GUC_SUPERUSER_ONLY,
NULL, NULL, NULL);

DefineCustomBoolVariable("pg_lake_iceberg.unsupported_numeric_as_double",
gettext_noop("When enabled, numeric columns that cannot be represented "
"as Iceberg decimals (unbounded or precision > 38) are "
Expand All @@ -335,6 +345,14 @@ _PG_init(void)
AvroInit();

RegisterUtilityStatementHandler(ValidateIcebergCatalogServerDDL, NULL);

/*
* Register last so it runs first: RegisterUtilityStatementHandler
* prepends to a linked list. Redaction must precede the validator above
* so that the failing built-in-server path never leaks client_id /
* client_secret in its error context.
*/
RegisterUtilityStatementHandler(RedactRestCatalogUserMappingSecrets, NULL);
}


Expand Down
Loading
Loading