Skip to content

Feature: Apache Iceberg lake tables as an extension (datalake_fdw skeleton) - #1842

Open
MisterRaindrop wants to merge 30 commits into
apache:mainfrom
MisterRaindrop:feature/iceberg-ddl-kernel
Open

Feature: Apache Iceberg lake tables as an extension (datalake_fdw skeleton)#1842
MisterRaindrop wants to merge 30 commits into
apache:mainfrom
MisterRaindrop:feature/iceberg-ddl-kernel

Conversation

@MisterRaindrop

@MisterRaindrop MisterRaindrop commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Important

This PR has been repurposed, in place, from kernel syntax to an extension.

Review here converged on doing lake tables without kernel changes, and
@tuhaihe asked
for the new commits to land in this PR so the review context stays in one
place. So rather than opening a third PR, the branch now carries:

  1. the original 26 kernel commits, untouched, so the 64 inline review
    comments stay anchored to the code they were written about;
  2. one commit that reverts all of them (the tree afterwards is identical to
    the merge base, verified by comparing tree object ids);
  3. the extension itself, moved over from Feature: Apache Iceberg lake tables as an extension (datalake_fdw skeleton) #1881.

Nothing was force-pushed. The kernel work is therefore still recoverable two
ways: revert the single revert commit, or check out
archive/iceberg-ddl-kernel-pr1842 on the author's fork.

The diff is now 100% different from what was reviewed and approved earlier
in this thread, so the existing approval no longer applies to it. Please
re-review from scratch.
The original description is kept, struck through, at
the bottom.

What does this PR do?

Adds contrib/datalake_fdw: Apache Iceberg lake tables as a table access
method, with no kernel changes. A lake table is an ordinary
CREATE TABLE ... USING iceberg WITH (catalog = '...', volume = '...'), where
catalog and volume name foreign servers created through two foreign-data
wrappers this extension registers.

This is deliberately a skeleton, published to get the shape reviewed before
the implementation follows.
CREATE TABLE and DROP TABLE work end to end
against a stub metadata engine -- no catalog service, object store, Arrow or JVM
involved -- and everything that would touch data reports a clean
iceberg: <operation> is not supported yet. The interfaces the later work plugs
into ship whole so they can be argued about now: the metadata-engine vtable with
a capability bitmap the registry validates and dispatches through, the
reader/writer format interfaces, and the storage facade.

The question this change is really asking is whether the seam is in the
right place -- see Additional Context.

CREATE SERVER cat FOREIGN DATA WRAPPER iceberg_catalog_fdw
  OPTIONS (type 'rest', uri 'https://catalog.example:443', warehouse 's3://bucket/wh');
CREATE USER MAPPING FOR CURRENT_USER SERVER cat
  OPTIONS (client_id '...', client_secret '...');   -- credentials only ever live here

CREATE SERVER vol FOREIGN DATA WRAPPER iceberg_volume_fdw
  OPTIONS (base_path 's3://bucket/prefix', region 'us-east-1');
CREATE USER MAPPING FOR CURRENT_USER SERVER vol
  OPTIONS (access_key_id '...', secret_access_key '...');

CREATE TABLE t (a int, b text) USING iceberg
  WITH (catalog = 'cat', volume = 'vol', purge_on_drop = false);

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

Performance: none. Nothing outside the module changes behaviour, and no
statement gains work: the utility and object-access hooks match on the access
method and return immediately for every other relation.

User-facing changes: a new extension, plus two lines of registration
(contrib/Makefile, and a CI matrix entry so the suite actually runs). The
extension installs process-wide hooks, so it must be in
shared_preload_libraries; _PG_init refuses to load any other way rather than
half-initialising. One consequence worth documenting when this ships: because
gpconfig -c shared_preload_libraries replaces the value rather than
appending to it, overwriting that GUC on a cluster where the extension is in use
will break the databases that have it installed.

Dependencies: none. The module builds with no new external dependency -- the
C++ translation units are stubs today, and the object-store SDK and Arrow arrive
with the changes that need them.

Checklist

Additional Context

This is the upstream half of an implementation that already exists. A
datalake_fdw with the same shape -- the same two hooks, the same table access
method, largely the same file names -- runs in production elsewhere, and the
intent is that this becomes the single source for it rather than a second
lookalike. Two consequences a reviewer should see explicitly:

The mapping accessor is deliberately shaped to that implementation. A lake
table's mapping to its catalog and volume servers is read only through
pg_iceberg_get_table_info(), whose signature and result types
(IcebergTableInfo, IcebergTableOptions) match it field for field. That
implementation keeps the same mapping in a system catalog of its own, which an
extension cannot add, so here the function reads reloptions instead -- but the
difference stops inside the function body, and the layers above it can be the
same code on both sides.

The SQL vocabulary follows the Iceberg specification, not the released
implementation.
Catalog servers take uri and warehouse, and the REST catalog
type is rest. An earlier revision of this change used the other tree's url /
warehouse_location_prefix / polaris on the reasoning that one vocabulary
across both trees was worth more than either name; review pointed out that the
REST catalog specification exists precisely so that these keys are the same
across implementations, and that argument wins. polaris is kept as an accepted
alias for rest so existing SQL keeps working. Convergence with the other tree
is unaffected in substance: the macro names, struct names, field names and
parse-function split are unchanged, so only the string a user types differs.
hadoop and s3 are in the vocabulary but refused until something implements
them.

Semantics that are decided (and why they are in the SQL surface)

  • DROP TABLE does not delete lake data unless the table was created with
    purge_on_drop = true. It is a table option rather than a GUC or a separate
    function so that it travels with the table, cannot turn one DROP into a
    destructive one through session state, and is carried out by a dump.
  • Table identity is the (catalog, namespace, table) name triple, matching
    the existing implementation.
  • Concurrency is Iceberg's: compare-and-swap on the metadata pointer, with
    the external catalog owning the metadata. Nothing here takes distributed locks.
  • A schema that has diverged from the lake is an error, not something to
    adapt to silently. The external table is the truth.

What was and was not verified

Stated plainly, because a skeleton is worth less if you have to guess:

Verified on a three-segment cluster: make installcheck 3/3, and
order-independent (each file alone and in reverse order, on a fresh database);
zero build warnings; exactly 8 exported symbols; a schema-level pg_dump/restore
round trip; Apache RAT clean. Separately, an 84-statement smoke pass over
ordinary objects -- heap/AO/partitioned/temp/unlogged tables, CTAS, matviews,
inherited and typed tables, views, sequences, indexes, functions, another
wrapper's servers and user mappings, every ALTER form, COPY, TRUNCATE, VACUUM
FULL, CLUSTER, REINDEX -- with the library preloaded, both with and without the
extension installed, plus two concurrency probes. Zero errors: the hooks are
inert for everything that is not a lake table.

Verified by CI (this was listed as unverified when the change was first
published, and has since run): the ic-datalake-fdw entry added here passes on
Rocky 8, 9 and 10, and ic-cbdb-parallel passes on every platform.

Still not verified: the error-detail channel has no regression coverage, for
the reason given below.

Why this branch also touches contrib/interconnect

The second commit changes two lines in another module's test, which would
otherwise look like unrelated scope. It is here because that test made this PR's
CI red and the failure is worth fixing rather than retrying:
contrib/interconnect/sql/interconnect.sql restarts the cluster with
gpstop -raiq. An immediate shutdown skips the shutdown checkpoint, so the next
startup runs crash recovery; while the postmaster is in PM_RECOVERY it rejects
the connection gpstart makes to read the segment configuration ("the database
system is not accepting connections"), gpstop -r exits CRITICAL, psql gives up
at the following \c, and every statement in the file is skipped -- leaving the
cluster with only the coordinator running, so the suites that run after it in the
same job fail too. A fast shutdown checkpoints on the way out, so no recovery
runs and the state that produces that error is never entered. Measured before
and after with recovery deliberately slowed: -raiq failed 2/2, -rafq passed
3/3. Fast is also what the rest of the tree already uses; this file was the only
-raiq. Happy to split it into its own PR if that is preferred.

Known limits, stated rather than left to be found

  • The mapping stores server names, so ALTER SERVER ... RENAME on a referenced
    server is refused. Storing OIDs instead would allow it, at the cost of owning
    dump/restore translation; the accessor above is the only thing that would
    change.
  • A full pg_dump of a database containing a lake table fails, because dumping
    table contents means scanning. A schema-level dump round-trips. The data
    belongs to the lake, so it arguably should not be in the dump at all -- but
    pg_dump decides from relkind, which an extension cannot influence, so that
    needs a separate conversation.
  • Every ALTER TABLE form except OWNER TO is refused while the access method
    is unfinished. purge_on_drop is therefore fixed at CREATE time.
  • The error-detail channel has no regression coverage, because no statement can
    make the stub engine fail.

Naming

datalake_fdw is inherited from the existing implementation and is a poor fit:
what this installs is a table access method plus two wrappers, not one FDW. A
better name is welcome while it is still cheap to change.

Discussion: #1683   ·   Superseded PR: #1881


Original description — kernel-side DDL scaffolding (superseded; kept for the review history)

Summary

Kernel-side DDL scaffolding for Iceberg lake tables, per the design proposal in #1683 (this PR is the "core syntax" milestone of the roadmap posted there). It adds the system catalogs, parse nodes, grammar, commands, and client-tool awareness that a datalake provider extension builds on. No table AM implementation or provider is included — those are later milestones (agent / FDW phases in the roadmap).

CREATE FOREIGN CATALOG hive_cat SERVER hive_cat_srv OPTIONS (type 'hive', uri 'thrift://...');
CREATE FOREIGN VOLUME s3_vol SERVER s3_srv OPTIONS (path 's3://bucket/prefix');
CREATE ICEBERG TABLE t (a int, b text) CATALOG hive_cat VOLUME s3_vol OPTIONS (fileformat 'parquet');
DROP CATALOG hive_cat;  DROP VOLUME s3_vol;

What's included (structured for commit-by-commit review)

  1. catalog: pg_foreign_catalog, pg_foreign_volume (metadata service / storage location handles, hanging off pg_foreign_server), pg_lake_table (per-relation lake metadata keyed by relid). All three have TOAST tables; name indexes are single-column unique (see "Design decisions").
  2. nodes: CreateLakeTableStmt (embeds CreateStmt), CreateForeignCatalogStmt, CreateForeignVolumeStmt + hand-maintained node-support functions.
  3. parser: CREATE ICEBERG TABLE ... CATALOG ... VOLUME ... OPTIONS (...), CREATE FOREIGN CATALOG|VOLUME ... SERVER ..., DROP CATALOG|VOLUME via drop_type_name.
  4. commands (foreign catalog/volume): create/drop with IF [NOT] EXISTS, dependencies, ownership, object addressing / COMMENT / event-trigger integration, syscaches, QD→QE dispatch with synchronized OIDs.
  5. commands (CREATE ICEBERG TABLE): resolves catalog/volume (explicit clause or iceberg_default_catalog/iceberg_default_volume GUCs), validates the statement uses the iceberg AM, creates the relation + TOAST + pg_lake_table row + dependencies; guards ALTER TABLE SET ACCESS METHOD / SET DISTRIBUTED BY both directions; lake tables are forced DISTRIBUTED RANDOMLY.
  6. bin: pg_dump skips iceberg tables (by AM name, with a warning — same pattern as PAX); psql tab completion.
  7. tests: lake_table regression in greenplum_schedule (DDL lifecycle, duplicate/USING rejection, TOAST wide rows, ownership, dependency/CASCADE, QD/QE dispatch checks) + catalog-expected refresh across regress, singlenode_regress, and pax copies.

Design decisions to review

  • Provider decoupling: the kernel resolves the AM strictly by name (get_table_am_oid("iceberg")); no hardcoded AM OID and no extension-name checks. Everything degrades gracefully (with a hint) when no provider is installed.
  • Provider integration contract: CreateLakeTable() runs after DefineRelation() and ends with CommandCounterIncrement() + InvokeObjectPostCreateHook(LakeTableRelationId, ...) — a provider performs remote Iceberg creation from that object-access hook (on QD and QEs), where catalog/volume/options metadata is already visible. relation_set_new_filelocator remains local-storage-only.
  • Catalog/volume names are database-global (single-column unique index), like pg_foreign_server — every reference syntax (DROP CATALOG x, the CATALOG x clause, the GUCs) identifies them by bare name, so the uniqueness scope matches what the syntax can express. (The alternative — per-server names like user mappings — would require server-qualified reference syntax everywhere.)
  • CREATE ICEBERG TABLE rejects a USING clause naming any other AM; without it the statement implies USING iceberg.

Known limitations / open questions (deliberately out of scope here)

  • pg_dump / pg_upgrade: FOREIGN CATALOG/VOLUME objects and pg_lake_table metadata are not dumped yet; iceberg tables are skipped with a warning (PAX precedent). Restore/upgrade support is planned as a follow-up.
  • Permission model: creating a catalog/volume requires USAGE on its server. Referencing one from CREATE ICEBERG TABLE currently checks existence only — whether that should require USAGE on the underlying server, or dedicated ACLs (GRANT USAGE ON CATALOG), is an open question we'd like reviewer input on.
  • The GUC defaults are synced to QEs and behave like default_tablespace w.r.t. objects dropped after SET.

Discussion: #1683

Introduce the three system catalogs backing Iceberg lake-table DDL:
- pg_foreign_catalog: named foreign catalog bound to a foreign server
- pg_foreign_volume:  named foreign volume bound to a foreign server
- pg_lake_table:      per-relation lake-table metadata (type, catalog, volume, options)

Register the headers in the catalog Makefile and bump CATALOG_VERSION_NO.
No code references the catalogs yet; DDL/commands land in later commits.

OIDs 8549-8558 / 9901-9902 verified free via unused_oids; duplicate_oids clean.
Add three statement parse nodes and their hand-maintained node-support
plumbing (copy/equal/out/read + fast serialization), mirroring the
existing CreateDirectoryTableStmt and CreateForeignServerStmt patterns:

- CreateLakeTableStmt      (CREATE ICEBERG TABLE ...; embeds CreateStmt)
- CreateForeignCatalogStmt (CREATE FOREIGN CATALOG ...)
- CreateForeignVolumeStmt  (CREATE FOREIGN VOLUME ...)

Nodes are not yet produced by the grammar or dispatched; grammar and
command handling land in later commits. ObjectType additions are
deferred to the command commit to keep exhaustive switches complete.
Add the ICEBERG and VOLUME unreserved keywords and the CREATE-side
grammar productions that build the parse nodes from the previous commit:

- CREATE ICEBERG TABLE name (cols) [FOREIGN CATALOG c] [FOREIGN VOLUME v]
      OPTIONS (...)  -> CreateLakeTableStmt (forced DISTRIBUTED RANDOMLY)
- CREATE FOREIGN CATALOG name SERVER s OPTIONS (...) -> CreateForeignCatalogStmt
- CREATE FOREIGN VOLUME  name SERVER s OPTIONS (...) -> CreateForeignVolumeStmt

Statements parse but are not yet dispatched; command handling, ObjectType
entries and DROP support land in the next commit. Verified: bison reports
no grammar conflicts; the statements parse and reach ProcessUtility.
Make the foreign catalog and foreign volume DDL commands functional on
top of the previously added grammar, parse nodes and system catalogs:

* CreateForeignCatalog()/CreateForeignVolume() insert into
  pg_foreign_catalog/pg_foreign_volume, check server existence and
  USAGE privilege, record dependencies on the server and owner, and
  dispatch to segments with preassigned OIDs.
* Lookup helpers get_foreign_catalog_oid(), get_foreign_volume_oid()
  and GetForeignVolumeByName(); both objects are unique on
  (name, server).
* New object infrastructure: OBJECT_FOREIGN_CATALOG/OBJECT_FOREIGN_VOLUME
  and OCLASS_FOREIGN_CATALOG/OCLASS_FOREIGN_VOLUME with handlers in all
  exhaustive switches (objectaddress, dependency, aclchk, event trigger,
  seclabel, dropcmds, alter). Ownership checks go through the generic
  object_ownercheck() via the new ObjectProperty entries.
* Four new syscaches: FOREIGNCATALOGNAME/FOREIGNCATALOGOID and
  FOREIGNVOLUMENAMESERVER/FOREIGNVOLUMEOID.
* DROP CATALOG / DROP VOLUME grammar via drop_type_name, going through
  the regular RemoveObjects() path with dependency handling, so
  DROP SERVER ... CASCADE also removes dependent catalogs and volumes.
* utility.c dispatch and CREATE/DROP FOREIGN CATALOG|VOLUME command tags.
Make CREATE ICEBERG TABLE functional on top of the existing grammar,
parse nodes and pg_lake_table catalog:

* laketablecmds.c: CreateLakeTable() inserts the pg_lake_table entry
  after DefineRelation and records dependencies on the table's foreign
  catalog and volume; ValidateLakeTableOptions() runs the same
  resolution on the QD before DefineRelation so validation failures
  don't surface as QE-annotated errors; RemoveLakeTableEntry() cleans
  up on drop (hooked into heap_drop_with_catalog).
* iceberg_default_catalog / iceberg_default_volume GUCs (synchronized
  to QEs) provide defaults when CREATE ICEBERG TABLE has no CATALOG or
  VOLUME clause; their check hooks verify the object exists.
* The iceberg table access method is resolved strictly by name
  (get_table_am_oid) and is expected to be provided by a datalake
  extension; without it, CREATE ICEBERG TABLE fails up front with a
  hint. The kernel does not hardcode any extension name or AM OID.
* Guard rails: reject the iceberg AM for every creation path other
  than CreateLakeTableStmt (CREATE TABLE ... USING iceberg, CTAS,
  matview, default_table_access_method, partition children), reject
  ALTER TABLE ... SET ACCESS METHOD to or from iceberg, and reject
  SET DISTRIBUTED BY on lake tables, which must stay DISTRIBUTED
  RANDOMLY. A relation created with the iceberg AM but without its
  pg_lake_table metadata would be unusable and undroppable.
* utility.c dispatch (transformCreateStmt works on the embedded
  CreateStmt) and the CREATE LAKE TABLE command tag.
pg_dump cannot reproduce lake tables (their data lives in external
object storage managed through the iceberg access method's foreign
catalog and volume), so skip them with a warning, matching how other
unsupported access methods are handled.  The access method is matched
by name, not by a hardcoded OID.

psql tab completion learns CREATE FOREIGN CATALOG/VOLUME ... SERVER
... OPTIONS, CREATE ICEBERG TABLE, DROP CATALOG/VOLUME with CASCADE/
RESTRICT, and completes foreign catalog and volume names after the
CATALOG and VOLUME keywords.
Add a lake_table test to the greenplum_schedule covering the new DDL
end to end: CREATE/DROP FOREIGN CATALOG and FOREIGN VOLUME (duplicates,
IF NOT EXISTS/IF EXISTS, missing server), segment dispatch, object
descriptions, CREATE ICEBERG TABLE with explicit CATALOG/VOLUME clauses
and via the iceberg_default_catalog/volume GUCs, the forced random
distribution policy, every iceberg-AM misuse guard, ownership checks,
and dependency behavior (RESTRICT errors, CASCADE, pg_lake_table
cleanup on drop).  The iceberg access method is simulated with a
heap-backed CREATE ACCESS METHOD, since the kernel resolves it by name
and the real AM comes from a datalake extension.

Refresh expected output of tests that enumerate system catalogs for
the three new ones: misc_sanity (pg_lake_table's toast-less varlena
columns), sanity_check (catalog list), and oidjoins (BKI_LOOKUP
references, including pg_lake_table.ltforeign_catalog pointing at
pg_foreign_catalog).
Comment thread src/backend/commands/laketablecmds.c Outdated
@tuhaihe
tuhaihe requested review from leborchuk and my-ship-it July 6, 2026 08:42
Address review: new community-authored files should carry the
standard Apache-2.0 header instead of the PostgreSQL boilerplate.
Comment thread contrib/pax_storage/src/test/regress/expected/oidjoins.out Outdated
Match the community convention (see contrib/pax_storage pax_gbench.cc):
the Apache-2.0 license block comes first, followed by the file name
and IDENTIFICATION.
Comment thread src/backend/commands/foreigncmds.c Outdated
Comment thread src/test/regress/sql/lake_table.sql Outdated
Comment thread src/test/regress/sql/lake_table.sql Outdated
Address review: invert the if_not_exists check and error out early so
the skip path is no longer nested in an else branch, in both
CreateForeignCatalog and CreateForeignVolume.
Comment thread src/backend/commands/laketablecmds.c Outdated
GetIcebergTableAmOid(bool missing_ok)
{
return get_table_am_oid(ICEBERG_TABLE_AM_NAME, missing_ok);
}

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Does this AM refer to the minimal Iceberg AM implementation that does not support scans or inserts? Is that correct?
  2. One more question: CREATE ICEBERG TABLE USING
    Is this syntax designed to support extensions via other frameworks? From my understanding, there should be no need to specify USING am if we're already using CREATE ICEBERG TABLE, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this stage, I believe the regression testing coverage is sufficient, test stub is enough. Introducing the AM layer at this point would only add unnecessary complexity with no practical benefit, as we still cannot run any real data workloads with it.

Comment thread src/backend/commands/laketablecmds.c Outdated
Comment thread src/backend/catalog/heap.c Outdated
Mirror the CREATE side so DROP matches:
- DROP CATALOG / DROP VOLUME -> DROP FOREIGN CATALOG / DROP FOREIGN VOLUME
  (via drop_type_name, same FOREIGN prefix as FOREIGN DATA WRAPPER)
- add DROP ICEBERG TABLE [IF EXISTS] as the pair for CREATE ICEBERG TABLE

DROP ICEBERG TABLE carries a new DropStmt.isiceberg flag and validates in
RangeVarCallbackForDropRelation that the target actually uses the iceberg
access method, erroring '"%s" is not an iceberg table' otherwise (mirrors
DROP FOREIGN TABLE). Plain DROP TABLE still removes an iceberg table.

Adds CMDTAG_DROP_LAKE_TABLE, psql tab completion for the new forms, and
refreshes the lake_table regression with new and negative cases.

Addresses review feedback on PR apache#1842.
Promote the catalog type from a free-form OPTION to a required TYPE
clause backed by a new pg_foreign_catalog.fctype column, per review on
PR apache#1842.  Every foreign catalog has a type (hive, hdfs, polaris, ...),
so it is a property rather than an option; the value is stored verbatim
as an open string and validated by the datalake provider, keeping the
kernel provider-agnostic.

Bumps CATALOG_VERSION_NO for the new column.
Comment thread src/backend/foreign/foreign.c Outdated
Comment thread src/test/regress/sql/lake_table.sql Outdated
Comment thread src/bin/psql/tab-complete.c Outdated
else if (Matches("DROP", "ICEBERG"))
COMPLETE_WITH("TABLE");
else if (Matches("DROP", "ICEBERG", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);

This comment was marked as resolved.

Comment thread src/include/commands/laketablecmds.h Outdated
MisterRaindrop and others added 4 commits July 10, 2026 15:51
Co-authored-by: Andrey Sokolov <sokolov.andrey.yurevich@gmail.com>
Co-authored-by: Andrey Sokolov <sokolov.andrey.yurevich@gmail.com>
Tab completion for DROP ICEBERG TABLE previously offered every ordinary
table.  Add Query_for_list_of_iceberg_tables, which filters to relations
whose access method is the iceberg AM -- the same rule the backend
validates DROP ICEBERG TABLE against -- so completion only suggests
tables the command will actually accept.  When no provider installed the
iceberg AM the scalar subquery is NULL and the list is empty.

Addresses review on PR apache#1842.
…name

The header prototype was renamed to ValidateLakeTableStmt but the
definition and its caller still used the old name, breaking the build
(-Werror=missing-prototypes).  Rename the definition, caller and
comments to match.
@andr-sokolov

This comment was marked as resolved.

@andr-sokolov

This comment was marked as resolved.

@ostinru
ostinru self-requested a review July 13, 2026 09:05
…volume example

- gram.y: lower-case the USING format for the access method name (it was only
  upper-cased for table_type), so a quoted "ICEBERG" / "IceBerg" resolves the
  iceberg AM instead of failing late with "access method ... does not exist".
- regress: add a quoted mixed-case format case to lock this in; rename the
  volume path option in the example from `path` to `base_path` to match the
  agreed name (discussion apache#1683).  Volume options are free-form (no validator),
  so this is example-only.
- create_foreign_volume.sgml: same base_path example.
@MisterRaindrop

Copy link
Copy Markdown
Contributor Author

@andr-sokolov It is ready for review

Comment thread src/test/regress/expected/lake_table.out Outdated
Comment thread src/test/regress/sql/lake_table.sql Outdated
Comment thread src/test/regress/sql/lake_table.sql Outdated
…STRIBUTED

Address PR review (andr-sokolov):

- pg_lake_table drops lttable_type and ltoptions.  A lake table's format is its
  access method (pg_class.relam) and its options are the relation's reloptions
  (pg_class.reloptions), validated by the access method, so the catalog now
  records only the {relation, foreign catalog, foreign volume} binding -- and no
  longer needs a TOAST table.  CREATE LAKE TABLE routes its OPTIONS into the base
  relation's reloptions; the now-dead CreateLakeTableStmt.options node field and
  its node-support handlers are removed.

- CREATE LAKE TABLE ... DISTRIBUTED now raises an error instead of a warning,
  since lake tables are always distributed randomly.

- Bump CATALOG_VERSION_NO for the pg_lake_table layout change.

- regress: read pg_class.relam/reloptions instead of the dropped columns; add a
  case showing OPTIONS become AM-validated reloptions (accepted vs rejected);
  the DISTRIBUTED case is now a failure.
…ure/iceberg-ddl-kernel

# Conflicts:
#	src/include/catalog/catversion.h
Comment thread src/backend/parser/gram.y Outdated
Per PR review (andr-sokolov): lake tables are always distributed randomly, so a
DISTRIBUTED clause has no place in the syntax.  Remove OptDistributedBy from the
CreateLakeTableStmt rules, so a DISTRIBUTED clause is now a plain syntax error
rather than being parsed and then rejected; the relation is still forced to
DISTRIBUTED RANDOMLY internally.
Comment thread src/backend/commands/laketablecmds.c Outdated
Comment thread src/backend/commands/laketablecmds.c Outdated
Comment thread src/backend/commands/laketablecmds.c Outdated
…date format first

Per PR review (andr-sokolov):

- Rename RelationIsIcebergTable -> RelationIsLakeTable and identify a lake table
  by its pg_lake_table entry rather than by the iceberg access method: the check
  answers "is this a lake table", independent of any particular format's AM.

- In ResolveLakeTableOptions, validate the USING format before checking access
  method existence, and resolve the AM by the format name via
  get_table_am_oid(stmt->table_type), so other formats can be supported later.

- validate_table_type compares against ICEBERG_TABLE_AM_NAME rather than a
  literal.

- gram.y: the USING clause names both the format and its like-named access
  method, so normalize it to lower case; a quoted "ICEBERG" resolves the same AM
  as an unquoted iceberg.  The unsupported-format error now shows that name.
Comment thread src/backend/commands/laketablecmds.c Outdated
Per PR review (andr-sokolov): the hint hardcoded "ICEBERG"; use stmt->table_type
so it stays correct once other lake table formats are supported.
Comment thread src/test/regress/sql/lake_table.sql Outdated
Comment thread src/test/regress/sql/lake_table.sql Outdated
Comment thread src/backend/commands/tablecmds.c Outdated
…T AM guard

Per PR review (andr-sokolov):

- The post-DROP checks joined pg_lake_table against pg_class, so once the
  relation was gone they returned 0 rows even if the pg_lake_table entry had
  leaked.  Save the table's OID before the drop and probe pg_lake_table by
  ltrelid directly, so an orphaned row would actually be caught.

- ATPrepSetAccessMethod: test OidIsValid(iceberg_amoid) once for the two
  iceberg checks, and splice ICEBERG_TABLE_AM_NAME into the message at compile
  time instead of passing the constant as a format argument.
Comment thread src/test/regress/sql/lake_table.sql Outdated
FROM gp_dist_random('pg_foreign_volume') WHERE fvname = 'lake_test_vol';

-- Without a provider extension there is no iceberg table AM
CREATE LAKE TABLE lake_test_t0 (a int) USING ICEBERG CATALOG lake_test_cat VOLUME lake_test_vol; -- fail with hint

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can align syntax with crunchydata.com

Building a Postgres Data Warehouse using Iceberg.pdf

> Iceberg as a Postgres table format Capture queries, writes, & schema changes to provide a transactional table experience for Iceberg in S3.

create table chats (
message_id bigserial not null,
thread_id bigint not null,
…
) using iceberg;

@leborchuk @andr-sokolov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yjhjstz How does crunchydata specify catalog? I didn't find this syntax in their presentation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building a Postgres Data Warehouse using Iceberg.pdf

Yes, looks good. But how we setup catalog and lakehouse format? There should exist some definition ... It's good for pre-configured database instances, but your own database needs a more advanced syntax.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Em, currently how about change CREATE LAKE TABLE lake_test_t0 -> CREATE TABLE lake_test_t0, no need LAKE ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andr-sokolov @leborchuk @yjhjstz I looked into how Crunchy Data Warehouse (now open-sourced by Snowflake as pg_lake) handles the catalog — hope this helps answer the question above.

TL;DR: Crunchy doesn't specify a catalog at CREATE time, because Postgres itself acts as the catalog.

  • Managed Iceberg tables are created with plain CREATE TABLE ... USING iceberg. The Iceberg metadata lives inside Postgres and is exposed through an iceberg_tables view compatible with the Iceberg JDBC/SQL catalog protocol, so Spark / pyiceberg / iceberg-rust connect to Postgres as the catalog (JDBC with Postgres credentials; catalog_name = the database name). (Crunchy docs)
  • I couldn't find any syntax for attaching an external catalog (Glue / HMS / REST) — the product doesn't seem to have that concept, which would explain why it doesn't appear in the presentation.
  • Storage location is a GUC default plus an optional per-table reloption: SET crunchy_iceberg.default_location_prefix TO 's3://...', or CREATE TABLE ... USING iceberg WITH (location = 's3://...'). With neither set, the cluster's bundled managed storage is used — so in the demo nothing needs to be specified at all. @leborchuk's reading ("good for pre-configured database instances") matches what I found.

One more data point that may be relevant: pg_lake is currently adding external Iceberg REST catalog support as a client (pg_lake#94), and that roadmap mentions handling multiple endpoints/credentials "likely using a Postgres SERVER-like concept" — quite close to what CREATE FOREIGN CATALOG ... SERVER does in this PR. So the two designs may be converging from different starting points.

For reference, the two syntax shapes side by side:

  • Plain CREATE TABLE ... USING iceberg: the catalog/volume bindings move into reloptions, e.g. WITH (catalog 'c', volume 'v'), with GUC defaults; no new grammar, and plain DROP TABLE applies (the Spark / pg_lake convention).
  • CREATE LAKE TABLE ... USING <format> CATALOG c VOLUME v: catalog/volume are first-class clauses with dedicated CREATE/DROP LAKE TABLE commands (the current PR).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use CREATE TABLE ... USING iceberg WITH (catalog 'c', volume 'v'), we need not change kernel code, iceberg will be totally an extension .

@MisterRaindrop MisterRaindrop Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yjhjstz Agreed — this looks technically feasible, so I sketched what the fully-extension shape could look like.

Since Cloudberry's table AM API already supports per-AM reloptions validation (TableAmRoutine->amoptions, unlike vanilla PG16), WITH (catalog ..., volume ...) can be validated by the iceberg AM itself with no kernel changes. The catalog/volume objects could reuse the existing FDW infrastructure (SERVER + USER MAPPING), which gives ACLs, pg_dump support, QD/QE dispatch and dependency tracking for free:

CREATE EXTENSION pg_iceberg;   -- provides the table AM, two FDWs, and hooks

CREATE SERVER hive_cat FOREIGN DATA WRAPPER iceberg_catalog
    OPTIONS (type 'hive', uri 'thrift://metastore:9083');
CREATE USER MAPPING FOR alice SERVER hive_cat
    OPTIONS (user '...', password '...');

CREATE SERVER s3_vol FOREIGN DATA WRAPPER iceberg_volume
    OPTIONS (base_path 's3://bucket/prefix');

CREATE TABLE t (a int, b text) USING iceberg
    WITH (catalog 'hive_cat', volume 's3_vol');

DROP TABLE t;   -- plain DROP TABLE applies

A few things the extension would need to own, instead of kernel guards:

  • registering table metadata and pg_depend entries (table → server) via object_access_hook, so DROP SERVER stays protected and CASCADE works;
  • intercepting paths that don't make sense for iceberg (CTAS, partition children, ALTER TABLE ... SET ACCESS METHOD) via ProcessUtility_hook, and forcing random distribution;
  • the library would need to be in shared_preload_libraries so the hooks are always installed.

This is essentially the pg_lake architecture, and USER MAPPING would also give a cleaner credentials story than embedding keys in options. If we go this way, the kernel scaffolding in this PR (the grammar, the three catalogs, the guards) would no longer be needed — the work would move into the datalake extension instead. WDYT?
@leborchuk @andr-sokolov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MisterRaindrop If possible, let's not change the kernel to reduce conflicts when updating to new versions of PostgreSQL. I think it's better to use an extension.

@MisterRaindrop

MisterRaindrop commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@andr-sokolov @yjhjstz
I open this draft PR about lake table ext (create table xxx using iceberg xxx), please give some suggest and review it.
#1881

@tuhaihe

tuhaihe commented Aug 11, 2026

Copy link
Copy Markdown
Member

@andr-sokolov @yjhjstz I open this draft PR about lake table ext (create table xxx using iceberg xxx), please give some suggest and review it. #1881

Hi, is there any way to introduce your new commits to this place, where we have a context for review? FYI. @MisterRaindrop

Review of this PR converged on doing lake tables as an extension instead of
as kernel syntax: no new grammar, no new catalogs, no new node types, and
nothing in src/backend that has to be maintained forever for one storage
format.  The replacement is a contrib module that reaches the same place
through mechanisms PostgreSQL already has -- a table access method for
"CREATE TABLE ... USING iceberg", and foreign servers plus user mappings for
catalog endpoints and credentials.

This reverts every kernel change made on this branch in one commit rather
than rewriting the branch, so that the 64 inline review comments stay
anchored to the code they were written about.  It is also the cheapest way
back: if the extension approach is later judged wrong, reverting this single
commit restores the whole kernel scaffolding.  The pre-revert tip is also
kept as archive/iceberg-ddl-kernel-pr1842 on the author's fork.

The resulting tree is identical to the merge base (9c3d48e), verified by
comparing tree object ids rather than by reading the diff.

The extension implementation follows in the next commits.
Add contrib/datalake_fdw, a skeleton for Iceberg lake-table support that needs
no kernel changes: a lake table is an ordinary CREATE TABLE ... USING iceberg,
and it names a catalog server and a volume server in its reloptions, both
created through foreign-data wrappers this extension registers.

Mapping a table to a pair of foreign servers is what keeps the kernel out of it.
Server options, ownership, privileges and dump/restore already exist for foreign
servers, reloptions already reach every segment with pg_class, and recording the
two servers in pg_depend makes DROP SERVER refuse to strand a table -- none of
which needs new catalogs or grammar.

Where that mapping is kept is one function's business.  Every operation reads it
through pg_iceberg_get_table_info(), whose signature and result types match the
existing datalake_fdw implementation this work is the upstream half of -- that
one keeps the same mapping in a system catalog of its own, which an extension
cannot add.  Holding the interface still means the layers above it are the same
code on both sides, and that the storage can be reconsidered later without
touching a caller.

The DDL path is complete against a stub metadata engine, so CREATE TABLE and
DROP TABLE work end to end with no catalog service, object store, Arrow or JVM
in the picture.  Everything that would touch data reports a clean
"iceberg: <operation> is not supported yet".  The interfaces the later work
plugs into ship whole so they can be reviewed before there is an implementation
behind them: the IcebergMetaEngine vtable with a capability bitmap the registry
validates and dispatches through, the FormatReader/FormatWriter instance
interfaces, and the storage facade over open/read/write/list.

Details worth a reviewer's attention:

* Table metadata always goes through one engine, the Java agent, and nothing
  selects between implementations -- no option, no setting.  The vtable stays
  because the implementation is expected to change; that is a property of the
  build, never of a table or a session, so an existing table can never be
  reinterpreted by a configuration change.

* The table access method fills every callback GetTableAmRoutine() asserts.
  ANALYZE succeeds as a zero-sample no-op through relation_acquire_sample_rows,
  which keeps it off the scan path that reports not-supported, and VACUUM is a
  no-op, so database-wide maintenance never dies on a lake table.

* The object-access hook records the server dependencies on the coordinator and
  on every segment, while only GP_ROLE_DISPATCH calls the metadata engine, so
  each node can protect its own catalog and the remote side sees one call.
  Utility-mode DDL is refused rather than creating local state without dispatch.

* VACUUM FULL is refused in the utility hook, not in the access method: relation
  rewriting creates a transient relation first, which reaches OAT_POST_CREATE
  and has the engine create a table remotely before the rewrite reports its
  error, leaving an orphan behind.

* Credentials are refused in server options and belong in user mappings, which
  stay optional so ambient object-store credentials remain usable.  Binding
  resolution never reads them, so DDL and DROP work with none configured.

* Volume URIs are parsed once, in the options layer, into a versioned
  DatalakeLocation; backends receive only that canonical form.

* Option names are macros in per-wrapper option modules, next to the typed
  struct each one parses into and the per-catalog-type parse function that fills
  it, so that support for a further catalog or storage protocol is an addition
  rather than a rewrite.  Option lookup itself is one shared set of accessors.
  The keys users write are Apache Iceberg's -- uri, warehouse, and rest for a
  catalog reached over the REST protocol -- because the specification defines one
  protocol that several implementations answer, and an SQL surface tied to one of
  them would make every other one need a second spelling.  polaris is accepted as
  an alias of rest, since that is what the existing implementation calls it.  The
  macro names, struct names and field names stay that implementation's, so the
  divergence is one string per key rather than a different shape.

* A DlErrCode says which kind of failure occurred and nothing else, which is not
  enough to diagnose one -- a remote catalog's message, its own error class, and
  a stack from wherever it threw have to arrive somewhere.  Implementations
  record that alongside the code they return, and the entry points facing
  PostgreSQL turn both into one report: the message as DETAIL, a stack only for a
  session that asked for log-level detail.  Recording allocates nothing and
  raises nothing, so a cleanup path crossing back from C++ can use it.  The
  SQLSTATE follows the code rather than being internal_error throughout, which
  also keeps a source location out of user-visible output.

* C++ translation units reach the server headers through common/dl_pg_api.h,
  which applies extern "C" -- without it the module builds and then fails to
  dlopen on a mangled errmsg.  The C/C++ boundary macros follow the PAX pattern,
  including deferring ereport() until after the catch handler is left, since
  longjmp() out of a handler is undefined.  Exported symbols are limited to the
  PG entry points listed in exports.txt, ELF and Mach-O each getting the right
  linker mechanism, so a future static Arrow cannot leak into other extensions.

* A schema-level dump round-trips.  pg_dump writes DISTRIBUTED RANDOMLY and
  ALTER TABLE ... OWNER TO for a table like this, so both are accepted -- a
  guard that refuses what this module's own dump emits refuses to restore it.
  Neither can desynchronise anything: the distribution clause asks for the
  policy that would have been injected anyway, and ownership is local catalog
  state.  Every other ALTER form, and a distribution clause naming columns,
  stay refused.  Dumping the *contents* of a lake table still fails, because
  scanning does; a full pg_dump of a database containing one therefore does not
  work yet, and what a dump of externally owned table data should even mean is
  the open question behind that.

Test material lives under test/automation, one directory per category, with the
module's Makefile pointing pg_regress at the category that needs no external
service; make installcheck from the module and make test from the harness run
the same cases.  Testing against a real catalog or object store cannot be done by
comparing against a recorded transcript, so the harness is what those categories
will be added to, and it already reports a category whose services are absent as
skipped rather than passed.

The suite covers the DDL path including per-segment catalog state, the rejection
matrices and the privilege model; installcheck is green on a three-segment
cluster and does not depend on the order the cases run in.  Per-segment
assertions compare against gp_segment_configuration rather than naming segments,
so they hold on a cluster of any size, and each guard has a case showing what it
does *not* refuse -- renaming a schema that holds no lake table, for instance --
because a guard wider than its problem passes its own tests just as well.

CI runs the suite as its own matrix entry, ic-datalake-fdw, whose demo cluster is
created with shared_preload_libraries='datalake_fdw' -- the module installs
process-wide hooks, so _PG_init refuses to load any other way, and a generic
cluster could not run these cases at all.  That is the same mechanism two
existing entries already use.  ("make check" would need the temp-config this
module also ships; it exists in-tree only, since PGXS refuses the target.)

The error channel has no coverage yet: no statement can make the stub engine
fail, so the first implementation that can fail is what brings a case for it.
…covery

The test sets shared_preload_libraries and restarts with "gpstop -raiq".  An
immediate shutdown skips the shutdown checkpoint, so the control file is left
in a state other than DB_SHUTDOWNED and the next startup performs crash
recovery: xlogrecovery.c sets InRecovery, xlog.c calls PerformWalRecovery(),
which signals PMSIGNAL_RECOVERY_STARTED, and the postmaster moves to
PM_RECOVERY.  In that state canAcceptConnections() answers CAC_NOTCONSISTENT,
reported as "the database system is not accepting connections" with detail
"Hot standby mode is disabled".  gpstart makes exactly such a connection right
after pg_ctl returns, to read the segment configuration, so gpstop -r exits
CRITICAL and the restart is reported as failed.

The damage does not stop there.  psql gives up at the \c that follows, so
every statement in the file is skipped and the test fails as a whole; the
cleanup at the end of the file never runs; and gpstart never got past starting
the coordinator in admin mode, so the cluster is left with no segments up.
Suites that run after this one in the same job then lose their Gather Motion
nodes and fail as well.

Shut down fast instead.  A fast shutdown writes the shutdown checkpoint, the
control file says DB_SHUTDOWNED, no recovery runs, PM_RECOVERY is never
entered, and CAC_NOTCONSISTENT cannot be returned -- the failure becomes
unreachable rather than merely less likely.  Fast is also what the rest of the
tree already uses: gpstop -raf/-arf appear in dozens of places, and this file
was the only user of -raiq.

Measured on a three-segment demo cluster, dirtying 1.5M coordinator rows
before each restart so that recovery is slow enough to lose the race
reliably: -raiq failed 2/2 with the message above, -rafq passed 3/3 with all
three segments still up afterwards.  pg_controldata confirms the mechanism at
the other end -- "in production" after an immediate shutdown, "shut down"
after a fast one.  The test still passes under pg_regress with the change.
@MisterRaindrop MisterRaindrop changed the title Add Iceberg lake-table DDL: CREATE ICEBERG TABLE, FOREIGN CATALOG, FOREIGN VOLUME (kernel scaffolding) Feature: Apache Iceberg lake tables as an extension (datalake_fdw skeleton) Aug 13, 2026
@MisterRaindrop

Copy link
Copy Markdown
Contributor Author

@tuhaihe as you asked, the extension work now lives in this PR instead of a separate one, so the review context stays in one place. @andr-sokolov @yjhjstz please note what changed below before looking at the diff again.

How it was moved, and why this way. The branch was not force-pushed. It now has, in order:

  1. the original 26 kernel commits, untouched -- so your 64 inline comments stay anchored to the code they were written about;
  2. Revert the kernel-side lake-table DDL scaffolding -- one commit that undoes all of them. The tree after it is identical to the merge base, verified by comparing tree object ids rather than by reading a diff;
  3. the two extension commits, moved over from Feature: Apache Iceberg lake tables as an extension (datalake_fdw skeleton) #1881. The resulting diff is identical to what Feature: Apache Iceberg lake tables as an extension (datalake_fdw skeleton) #1881 showed -- I compared the two patches line by line; the only differences are blob hashes and one hunk offset, because the two branches had different merge bases.

Rewriting the branch would have been shorter, but it would have orphaned the review history, and it would have made going back expensive. This way the kernel scaffolding is one git revert away if the extension approach turns out to be wrong. The pre-revert tip is also kept as archive/iceberg-ddl-kernel-pr1842 on my fork.

Please re-review from scratch. The diff is now 100% different from what was reviewed and approved earlier in this thread, so the earlier approval does not apply to what is here now. I would rather say that explicitly than have it merged on the strength of it.

One thing that will look out of place: the second commit changes two lines in contrib/interconnect/sql/interconnect.sql. That test restarts the cluster with gpstop -raiq; an immediate shutdown skips the shutdown checkpoint, so the next startup runs crash recovery, and while the postmaster is in PM_RECOVERY it rejects the connection gpstart makes to read the segment configuration. gpstop -r then exits CRITICAL, psql gives up at the \c that follows, the whole file is skipped, and the cluster is left with only the coordinator running -- which is why pg_trgm and indexscan failed after it in the same job. A fast shutdown checkpoints on the way out, so that state is never entered. Measured with recovery deliberately slowed: -raiq failed 2/2, -rafq passed 3/3. Fast is what the rest of the tree already uses; this file was the only -raiq. Happy to split it into its own PR if you would rather keep this one to the extension.

The original description is kept at the bottom of the PR body, struck through. #1881 is superseded by this PR.

Comment thread contrib/Makefile Outdated
btree_gin \
btree_gist \
citext \
datalake_fdw \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is datalake_fdw enabled by default? We can disable it by default and enable it using the --enable-datalake_fdw configure option, the way like PAX.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call -- done in 6f40dd0, following the PAX mechanism: PGAC_ARG_BOOL defaulting to no, substituted into Makefile.global, and a conditional in contrib/Makefile that moves the directory to ALWAYS_SUBDIRS when disabled so the clean targets still reach it.

Three things I decided while doing it, all cheap to reverse if you disagree:

  • The option is spelled --enable-datalake-fdw, with dashes. Every option in this tree uses dashes (--enable-tap-tests, --enable-ic-udp2, --enable-catalog-ext), and autoconf maps the dashes to enable_datalake_fdw for the Makefile variable, so the directory name still matches. Happy to use the underscore form if you would rather it read exactly like the directory.
  • No AC_DEFINE. PAX and ic-udp2 define a macro because C code tests it; nothing here does, and adding an unread macro would mean touching src/include/pg_config.h.in for no reader. --enable-pxf and --enable-orafce already use this shorter four-argument form.
  • --enable-datalake-fdw is also added to devops/build/automation/cloudberry/scripts/configure-cloudberry.sh, next to --enable-pax. Without it the ic-datalake-fdw CI job would install nothing and silently test nothing. I deliberately did not add it to coverity.yml and sonarqube.yml, which also pass --enable-pax -- say the word if you want the module in the weekly scans too.

One thing you should know about configure. I regenerated it by hand rather than running autoconf over the whole file, because the committed configure and configure.ac are currently out of sync in both directions:

  • configure.ac has a restructured PAX liburing check (the case $host_os in linux*) block with the macOS fallback comment) that was never regenerated into configure;
  • configure has a Darwin python shared-library lookup (trying .dylib in addition to DLSUFFIX) that does not exist in configure.ac.

A full autoconf run therefore produced twelve hunks, only four of which were mine; taking all of them would have swept those two unrelated changes into this PR and, in the python case, reverted working behaviour. So I applied only the four hunks for this option and left the drift alone. It is worth someone reconciling those two separately -- I did not want to do it inside this PR.

Verified by configuring a real tree both ways, not by inspection:

configure says Makefile.global SUBDIRS ALWAYS_SUBDIRS
--enable-datalake-fdw ... yes enable_datalake_fdw = yes has datalake_fdw --
default ... no enable_datalake_fdw = no -- has datalake_fdw

Review asked for the module to be off by default and enabled explicitly, the
way PAX is, rather than being added to the unconditional contrib SUBDIRS list.
The mechanism is the same one PAX and ic-udp2 use: a PGAC_ARG_BOOL defaulting
to no, substituted into Makefile.global, and a conditional in contrib/Makefile
that puts the directory in ALWAYS_SUBDIRS when disabled so the clean targets
still reach it.

The option carries no AC_DEFINE.  PAX and ic-udp2 define one because C code
tests it; nothing here does, and adding an unread macro would mean touching
src/include/pg_config.h.in for no reader.  --enable-pxf and --enable-orafce
already use this shorter form.

configure is regenerated by hand rather than wholesale.  The committed
configure and configure.ac are currently out of sync in both directions -- the
PAX liburing block has changes in configure.ac that were never regenerated,
and the Darwin python shared-library lookup exists in configure but not in
configure.ac -- so a full autoconf run would have swept eight unrelated hunks
into this commit.  Only the four hunks belonging to this option were applied.

--enable-datalake-fdw is added to the CI configure line, without which the
ic-datalake-fdw job would install nothing and test nothing.

Verified by configuring both ways on a real tree: with the option,
"checking whether to build with datalake_fdw support ... yes",
Makefile.global gets enable_datalake_fdw = yes, and contrib puts datalake_fdw
in SUBDIRS; without it, no / no / and the directory appears in
ALWAYS_SUBDIRS instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants