PostgreSQL FDW / Extension Catalog for Embedded pgserver (REQ-898)¶
Reference for the curated extension catalog: what foreign-data wrappers exist for each Provisa source type, how to acquire them, and how to compile them against a pinned PostgreSQL version. Feeds the Postgres federation engine (REQ-893) and complements the DuckDB engine (REQ-894/895).
Data gathered from upstream repositories and PGXS documentation, July 2026. Verify version numbers and manylinux tags per wheel before pinning — several fields below carry stated uncertainty.
The embedded base: what pgserver gives you¶
pgserver and its forks ship precompiled PostgreSQL binaries plus pgvector only. Every FDW
below except postgres_fdw, file_fdw, and dblink (compiled into the standard source tree)
must be built from source and dropped into the bundled tree.
| Package | Repo | Bundled PG major | Extra bundled | Status |
|---|---|---|---|---|
pgserver |
orm011/pgserver | 16 (16.2) | pgvector | Unmaintained since mid-2024 |
pixeltable-pgserver |
pixeltable/pixeltable-pgserver | 16 (16.10) | pgvector 0.8.1, pgvectorscale | Active 2026 |
pgembed |
Ladybug-Memory/pgembed | 17 | via separate wheels | Most current; separate-wheel extension model |
Confirm the exact minor with <pkg>.POSTGRES_BIN_PATH / "pg_config" --version against the
installed wheel — do not hardcode.
Locating the bundled tree¶
Derive everything else from pg_config, never a hardcoded path:
| Query | Purpose |
|---|---|
pg_config --pkglibdir |
where the compiled .so/.dylib module lands ($libdir) |
pg_config --sharedir |
.control + --version.sql go under sharedir/extension/ |
pg_config --includedir-server |
server headers for PGXS (shipped in the wheel — confirmed) |
pg_config --pgxs |
the PGXS makefile |
The pin is a triple, not a version¶
An externally compiled .so must match three axes, or the server refuses to load it:
- PG major ABI — the module magic block. Cross-major never loads; same-major cross-minor generally does.
- Platform / arch —
.sovs.dylibvs.dll; x86_64 vs arm64. - libc + C++ ABI (Linux) — the wheel's glibc floor, and for C++ extensions
(Arrow / DuckDB) the libstdc++ / GCC ABI and
_GLIBCXX_USE_CXX11_ABIsetting.
The safe build is inside the same manylinux image the wheel was built with, using the
wheel's own pg_config. The linux wheels' exact manylinux tag is undocumented; the
postgresql-wheel lineage targets manylinux2014 (glibc >= 2.17), and pgembed advertises
arm64 + alpine, implying newer/multiple tags. Confirm per wheel via pip download + unzip.
Catalog resolution must fail closed: no artifact for a (pg_major, platform, libc) triple
means the source is unreachable on the Postgres engine — never a silent fallback (project rule).
Generic PGXS build against the pinned server¶
PG_CONFIG=$(python -c "import pgserver,os;print(os.path.join(pgserver.POSTGRES_BIN_PATH,'pg_config'))")
make USE_PGXS=1 PG_CONFIG="$PG_CONFIG"
make USE_PGXS=1 PG_CONFIG="$PG_CONFIG" install
PG_CONFIG=... must come after make, not before — a leading assignment is overridden by
the makefile internals. External native libraries in a non-standard prefix are passed via
PG_CPPFLAGS="-I..." and SHLIB_LINK/CCFLAGS="-L...".
The install step writes <ext>.so to pkglibdir and <ext>.control + <ext>--*.sql to
sharedir/extension/. Activate per database with CREATE EXTENSION <ext>;. For files outside
the main install dir, point the server at them with dynamic_library_path and (PG18+)
extension_control_path.
Distribution model (the catalog itself)¶
Follow the pgembed template: one wheel per (extension × pg_major × platform tag), built in
CI against the pinned base wheel's pg_config. The wheel's install step copies the .so into
pkglibdir and the .control/.sql into sharedir/extension/. Version-lock every extension
wheel to the base's PG major. Bundled native deps (libsqlite3, Arrow, DuckDB) must ride inside
the artifact — the base wheel does not provide them.
Catalog: file and embedded sources¶
file_fdw — CSV (csv source type)¶
- Repo: in-tree
contrib/file_fdw(PostgreSQL License). In core contrib. - No compile needed — ships with any PG built with contrib; activate with
CREATE EXTENSION file_fdw;. If absent:cd contrib/file_fdw && make PG_CONFIG=<bundled> && make install PG_CONFIG=<bundled>. - Read-only, no pushdown, no IMPORT FOREIGN SCHEMA — every column must be declared
(flat files carry no schema). This is why
FileFdwConnector.details()completes theCREATE FOREIGN TABLEcolumn list from registry metadata. filename/programare superuser-restricted; paths are server-side, relative to the data dir. The embedded server can only read files its own process can see.
sqlite_fdw — SQLite (sqlite source type)¶
- Repo: pgspider/sqlite_fdw (canonical, active).
gleu/sqlite_fdwis deprecated and its README redirects to pgspider. BSD/PostgreSQL license. - Latest v2.5.0 (2024-12-10), PG 13–17.
- Native dep:
libsqlite3-dev(any modern SQLite 3.x; confirmed 3.49.0). Optionallibspatialite-devfor SpatiaLite/GIS. - Build:
make USE_PGXS=1 PG_CONFIG=<bundled>; non-standard SQLite prefix viaSQLITE_INCLUDE=/prefix/include SQLITE_LIB=/prefix/lib. - Read/write (INSERT/UPDATE/DELETE, TRUNCATE→DELETE); pushdown of WHERE, ORDER BY, aggregates,
GROUP BY/HAVING, JOIN, LIMIT/OFFSET; IMPORT FOREIGN SCHEMA supported;
force_readonly(v2.5.0). - Gotcha: SQLite type affinity — mismatched column types silently return wrong results.
Use the
column_typeoption (e.g. epoch timestamps asINT, UUID as TEXT/BLOB). Numeric limited to SQLite ~15-digit float precision; NaN read as NULL.
Catalog: Parquet sources (parquet source type)¶
parquet_fdw — local Parquet¶
- Repo: adjust/parquet_fdw (PostgreSQL License). Effectively stale — v0.2.1 (2021),
does not compile on PG16+ without the community
parquet_fdw_16.diff. Given pgserver bundles PG16/17, this one needs patching to build at all. - Native dep: libarrow + libparquet >= 0.15, practically pinned to Arrow 6.x (Arrow 7+ breaks the build). The heaviest dependency in the catalog.
- Read-only. IMPORT FOREIGN SCHEMA directory discovery +
import_parquet(); row-group (min/max) predicate pushdown + projection pushdown; parallel scan; four reader strategies including multifile-merge for presorted files. Local filesystem only.
parquet_s3_fdw — local + S3 Parquet¶
- Repo: pgspider/parquet_s3_fdw (BSD-3, derived from adjust/parquet_fdw, actively maintained). v1.1.1 (2024-10-10), PG 13–17. Prefer this over parquet_fdw for the bundled PG majors.
- Native deps: libarrow + libparquet 16.1.0 and AWS SDK for C++ 1.11.335. The project
recommends building both from source — precompiled Arrow causes libstdc++ ABI mismatch. Arrow
must be built
-DARROW_WITH_SNAPPY=ON -DARROW_WITH_ZSTD=ON. - Read + limited write (no transactions; concurrent same-file writes unsafe). IMPORT FOREIGN SCHEMA, predicate + projection pushdown, parallel scan, schemaless JSONB mode.
- S3 + MinIO: server/table OPTIONS
endpoint,region,use_minio 'true'; credentials viaCREATE USER MAPPING ... OPTIONS (user '<accesskey>', password '<secret>')or the AWS credentials file.endpointoverride targets MinIO / non-AWS S3. - Gotcha: the Arrow/AWS-SDK ABI lock is the dominant risk. Pin Arrow 16.1.0 / AWS SDK 1.11.335 exactly and build inside the wheel's manylinux image.
Catalog: remote RDBMS sources¶
| FDW | Source types | Repo | Latest | PG | Native dep | Read/Write | Pushdown |
|---|---|---|---|---|---|---|---|
| mysql_fdw | mysql, mariadb, singlestore | EnterpriseDB/mysql_fdw | 2.9.3 (2025-09) | 14–18 | libmysqlclient / mariadb-connector-c | R/W | WHERE, JOIN, agg, ORDER BY, LIMIT |
| tds_fdw | sqlserver | tds-fdw/tds_fdw | 2.0.5 (2025-09) | 13–18 | freetds-dev | Read-only | WHERE + column only |
| oracle_fdw | oracle | laurenz/oracle_fdw | 2.9.0 (2026-06) | 9.3+ (15–18) | Oracle Instant Client + SDK | R/W | WHERE, ORDER BY, same-server JOIN |
| jdbc_fdw | any JDBC (fallback) | pgspider/jdbc_fdw | 0.5.0 (2025-03) | 13–17 | JVM/JNI + driver JAR | R/W | WHERE, column, agg |
| clickhousedb_fdw | clickhouse | Percona-Lab/clickhousedb_fdw | untagged (PG≤13) | 11–13 | libclickhouseodbc / libcurl | R/W | agg, JOIN |
Notes:
- mysql_fdw covers SingleStore (MySQL wire-compatible). Put the MySQL/MariaDB client on
PATH; test env
MYSQL_HOST, etc. - tds_fdw is read-only, no JOIN pushdown. SQL auth via username/password on the server/user-mapping; AD/Kerberos needs FreeTDS+GSSAPI. Set the TDS protocol version for Unicode correctness.
- oracle_fdw: Instant Client is Oracle-proprietary and not redistributable — the
catalog cannot bundle it; the deployment supplies it and sets
ORACLE_HOME+LD_LIBRARY_PATH. Build with those set. - jdbc_fdw is the universal fallback (mssql-jdbc, ojdbc, clickhouse-jdbc), but embeds a
full JVM inside each PG backend process — memory/startup cost, and connections originate
from the server host IP. Set
JAVA_HOME(needsjni.h+libjvm.so) to build. - clickhousedb_fdw caps at PG 13 — it will not build against pgserver's PG16/17 without patching. Prefer jdbc_fdw with clickhouse-jdbc for a modern PG, or route ClickHouse through Trino/DuckDB instead.
Catalog: Iceberg / lakehouse (iceberg, delta_lake source types)¶
None of these is a classic single-.so FDW. Two architectures: embedded DuckDB in-process, or
a DuckDB sidecar speaking the PG wire protocol. All require a heavy C++/DuckDB (and sometimes
Rust) build.
| Extension | Repo | License | Arch | Iceberg | Feasible on embedded pgserver? |
|---|---|---|---|---|---|
| pg_duckdb | duckdb/pg_duckdb | MIT | Embedded DuckDB in-process | read + time travel | Best drop-in — single .so, no sidecar; still a large C++/DuckDB build |
| pg_lake | Snowflake-Labs/pg_lake | Apache-2.0 | pgduck_server sidecar (PG wire) |
read + write + ACID | Needs a running sidecar daemon + very heavy vcpkg/DuckDB/Avro/GDAL build; Docker-only in practice |
| pg_mooncake | Mooncake-Labs/pg_mooncake | MIT | pg_duckdb read + Rust bgworker write | read + write | Needs a background worker; heavier than pg_duckdb; early (0.1.x) |
| pg_analytics | paradedb/pg_analytics | PostgreSQL | pgrx + embedded DuckDB | read | Archived Mar 2025 — avoid |
| DuckLake | duckdb/ducklake | MIT | Not a PG extension — PG is catalog only | via DuckDB | N/A: PG is a passive metadata store; the engine is DuckDB |
- pg_duckdb v1.1.1 (2025-12), PG 14–18. Embedded DuckDB, no sidecar. Iceberg/Delta read,
Parquet/CSV/JSON read+write, S3/GCS/Azure/R2 via
duckdb.create_simple_secret(...). Build:make PG_CONFIG=<bundled> install(compiles vendored DuckDB — slow). No Iceberg write/ACID. This is the only realistic in-process.sofor a bundled pgserver. - pg_lake and pg_mooncake deliver Iceberg write, but each requires an accompanying running process, which breaks the single-process embedded model. Treat as out of scope for the v1 catalog.
Alternative: multicorn2 — pure-Python FDWs¶
- Repo: pgsql-io/multicorn2 (maintained fork; original Multicorn is dead). PostgreSQL License. v3.2, PG 14–18, Python 3.9–3.13.
- Compile
multicorn.soonce against the bundledpg_config(needspython3-dev+ server headers); wrappers are then pure Python subclasses — no per-source C build. - Lets you write sqlite / parquet / iceberg wrappers in pure Python (
sqlite3,pyarrow,pyiceberg) with no Arrow/DuckDB C compilation. - Tradeoff: data crosses the C↔Python boundary row-by-row — no columnar transfer, only basic OFFSET/LIMIT pushdown (v3.2), no join/aggregate pushdown. Fine for low-volume/ad-hoc federation; poor for large scans. Also incompatible with PL/Python under Python 3.12+.
This is the escape hatch when a native FDW is too painful to build for a triple: one compiled shim, then Python wrappers, at the cost of scan performance.
Recommendation for the Provisa engines¶
- Prefer the DuckDB engine for
parquet,sqlite,csv,iceberg,delta_lake. It is apip install duckdbwith zero compilation and already implemented (REQ-894/895). The Postgres FDW path for these formats is compile-only and heavy. - Postgres engine ships working out of the box with
postgres_fdw(remote PG) andfile_fdw(CSV) — no build step. This is what REQ-893 currently implements. - sqlite_fdw / parquet_s3_fdw / mysql_fdw / tds_fdw are the realistic curated additions when
a deployment wants everything inside one PG process. Each is a PGXS build against the pinned
pg_configwith a bundled native dep. - oracle_fdw (Instant Client) and jdbc_fdw (embedded JVM) carry licensing / footprint costs — catalog them, but flag the constraints.
- Iceberg write and ClickHouse on modern PG do not fit the embedded single-process model cleanly; route them through the DuckDB or Trino engines instead.
Chosen v1 catalog set¶
Five extensions, built per (pg_major, platform, libc) as separate wheels layered onto the
pinned base:
| Source | Extension | Build cost | Bundleable? |
|---|---|---|---|
| csv | file_fdw (core) | none — ships with PG | n/a (already present) |
| sqlite | pgspider/sqlite_fdw | libsqlite3 | yes |
| parquet / iceberg / delta | duckdb/pg_duckdb | vendored DuckDB (C++) | yes (heavy) |
| sqlserver | tds-fdw | freetds | yes |
| oracle | laurenz/oracle_fdw | Oracle Instant Client | no — deployment supplies it (not redistributable) |
Parquet routes through pg_duckdb, not a dedicated Parquet FDW. parquet_s3_fdw is dropped
from v1: pg_duckdb already reads Parquet (plus iceberg and delta) in one extension with S3/GCS/
Azure support, avoiding a second heavy Arrow+AWS-SDK build and its exact-pin ABI fragility. The
tradeoff is losing parquet_s3_fdw's write path and its endpoint/use_minio server options — if
a deployment needs Parquet write or fine MinIO endpoint control, revisit parquet_s3_fdw then.
Not in v1: parquet_s3_fdw (pg_duckdb covers parquet read), mysql_fdw (deferred),
clickhousedb_fdw (caps at PG13 — route via DuckDB/Trino), jdbc_fdw (embedded JVM),
pg_lake/pg_mooncake (require a sidecar/bgworker — break the single-process model).
Build-order notes for this set:
- file_fdw — no artifact; the wheel just documents
CREATE EXTENSION file_fdw. - sqlite_fdw, tds_fdw — lightest builds; a single system dev lib each. Start here to validate the CI triple harness.
- pg_duckdb — vendored DuckDB is a long compile; one
.so, no sidecar. Covers parquet, iceberg, and delta read in a single extension — the one heavy C++ build in v1. - oracle_fdw — build only; Instant Client +
ORACLE_HOME/LD_LIBRARY_PATHare supplied at the deployment, never shipped in the wheel.