agentsclimarketplace

Fdw development

Skill matejformanek/postgres-claude/.claude/skills/fdw-development

Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.

Install
npx -y skills add matejformanek/postgres-claude --skill fdw-development

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Building or modifying a Foreign Data Wrapper (FDW) — the SQL/MED interface for accessing external data as if it were PG tables. Covers the `FdwRoutine` callback set (`GetForeignRelSize` / `GetForeignPaths` / `GetForeignPlan` / `BeginForeignScan` / `IterateForeignScan` / `ReScanForeignScan` / `EndForeignScan` for scans, `BeginForeignInsert` / `ExecForeignInsert` / … for DML), the `postgres_fdw` reference implementation, plan pushdown (WHERE / JOIN / AGG / LIMIT), extended-explain support, and the FDW-related core code (`src/backend/foreign/foreign.c`, foreign-table catalog, user mappings). Loads when the user asks about writing an FDW, `CREATE FOREIGN TABLE`, why postgres_fdw is fast, pushdown decisions (`use_remote_estimate`, `fdw_startup_cost`), user mappings and password security (`password_required`), the loopback-back-to-self bypass-RLS pattern from postgres_fdw, or adding a new pushdown-shape. Skip when the ask is about specific non-postgres_fdw wrappers (file_fdw, oracle_fdw external, mysql_fdw external) — those are separate implementations.

SKILL.md

11.1 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it

fdw-development — Foreign Data Wrappers

An FDW lets a PG backend access external data (another PG, MySQL, files, HTTP APIs) as if it were a local relation. The system supports:

  • SELECT (with pushdown of quals, joins, aggregates, LIMITs).
  • INSERT / UPDATE / DELETE (with pushdown for postgres_fdw).
  • TRUNCATE.
  • COPY FROM.
  • Async scans (PG 14+).
  • Extended-explain output.

The core FDW support is small — src/backend/foreign/foreign.c (22 KB) — and lives around the FdwRoutine callback struct. The heavy work is in per-wrapper contrib modules (contrib/postgres_fdw/ for PG-to-PG, contrib/file_fdw/ for CSV/text files).

The file map

FileRole
src/backend/foreign/foreign.cCore FDW support — foreign-table cache, GetForeignServer / GetUserMapping helpers, wrapper's handler fn is looked up + cached here.
src/backend/commands/foreigncmds.cCREATE / ALTER / DROP for SERVER / USER MAPPING / FOREIGN TABLE.
src/backend/executor/nodeForeignscan.cForeignScan executor node — dispatches to the FDW's Begin/Iterate/End callbacks.
src/backend/optimizer/util/appendinfo.c + plancat.cPlanner-side foreign-table hooks.
src/include/foreign/fdwapi.hThe FdwRoutine struct definition — 30+ callback pointers.
contrib/postgres_fdw/postgres_fdw.cReference implementation. Big and non-trivial (has to handle pushdown + connection pooling + prepared statements + async).
contrib/postgres_fdw/deparse.cDeparse — turn PG parse-tree back into SQL text to send to the remote. Where "shipping expressions" lives.
contrib/postgres_fdw/option.cServer/user-mapping/foreign-table options parsing.
contrib/postgres_fdw/connection.cLibpq connection cache — one connection per (user, server) tuple, kept warm.

The FdwRoutine callback taxonomy

Grouped by lifecycle stage:

Planner-time

  • GetForeignRelSize — populate baserel->rows estimate.
  • GetForeignPaths — add ForeignPath entries to baserel->pathlist.
  • GetForeignPlan — build the ForeignScan node from a picked Path.
  • EstimateDSMForeignScan / InitializeDSMForeignScan / InitializeWorkerForeignScan — parallel-aware FDW support (rare).

Executor-time, scan

  • BeginForeignScan — one-time setup (open connection, prepare remote statement).
  • IterateForeignScan — return the next tuple (or NULL for end-of-stream).
  • ReScanForeignScan — restart if re-iterated in a nested loop.
  • EndForeignScan — clean up (release connection back to pool).

Executor-time, DML

  • BeginForeignInsert / ExecForeignInsert / EndForeignInsert — INSERT path.
  • BeginForeignModify / ExecForeignUpdate / ExecForeignDelete / EndForeignModify — UPDATE + DELETE.
  • Also: BeginDirectModify / IterateDirectModify / EndDirectModify — for pushed-down "delete this whole range" queries.

Planner-time, pushdown decisions

  • GetForeignJoinPaths — add ForeignPath for a foreign-side JOIN.
  • GetForeignUpperPaths — add ForeignPath for GROUP BY / DISTINCT / LIMIT / etc. that can be pushed to the foreign side.
  • IsForeignRelUpdatable — is UPDATE/DELETE supported on this table?

Extras

  • ExplainForeignScan / ExplainForeignModify — populate the EXPLAIN output with FDW-specific info.
  • AnalyzeForeignTable — support for local ANALYZE of a foreign table (sample rows).
  • ImportForeignSchema — for IMPORT FOREIGN SCHEMA — return CreateForeignTableStmt list.

The postgres_fdw pushdown philosophy

By default, postgres_fdw tries to push as much work as possible to the remote:

  • QualsWHERE predicates that are shippable (see is_foreign_expr) go to the remote. Deterministic + non-volatile + no local-only functions.
  • Joins — foreign-to-foreign joins on the same server get pushed as a subquery.
  • AggregatesGROUP BY / SUM / COUNT on foreign-only data pushed via GetForeignUpperPaths.
  • LIMIT — pushed as long as ORDER BY is also shippable.
  • UPDATE / DELETEDirectModify — if the WHERE clause is shippable, PG sends one UPDATE/DELETE statement instead of scanning + updating row-by-row.

use_remote_estimate (server option) toggles whether to run EXPLAIN on the remote to get better cost estimates (slower to plan; better plans).

The password_required two-layered defense

A recurring security concern with postgres_fdw: an unprivileged user's foreign-table access could be abused to send commands as the FDW's connecting user (potentially a superuser on the remote).

Two-layered protection:

  1. password_required = true (default) — the user mapping MUST provide a password. Excludes trust / peer auth from being used.
  2. User mapping ownership — only the mapping's owner can use it (unless PUBLIC). Superuser + application user split.

Non-superusers who create user mappings CAN'T set password_required = false. This is the gold-standard defense pattern; audit any FDW you write for equivalent.

Common patch shapes

Write a new FDW (minimum viable)

  1. _PG_init in the extension registers the wrapper via pg_foreign_data_wrapper (create-time SQL).
  2. Handler function returns FdwRoutine * filled with your callbacks.
  3. Minimal callbacks: GetForeignRelSize, GetForeignPaths, GetForeignPlan, BeginForeignScan, IterateForeignScan, EndForeignScan, ReScanForeignScan.
  4. ExplainForeignScan — even a stub — makes debugging much easier.
  5. Test via CREATE EXTENSION my_fdw; CREATE SERVER ...; CREATE USER MAPPING ...; CREATE FOREIGN TABLE ....
  6. See contrib/file_fdw/ for the simplest example.

Add a new pushdown

Consider carefully — pushdown is where FDWs get correctness bugs:

  • Add a case in is_foreign_expr for the expression you want to push.
  • Verify the deparse.c handles it — deparseExpr needs to know how to write it back as SQL.
  • Add regression coverage — every pushdown case needs a test that verifies:
    • Result correctness.
    • EXPLAIN shows the pushed-down form.
    • It DOESN'T push when a non-shippable qual is involved.

Cache connection to remote

  • Per-user, per-server connection cache in contrib/postgres_fdw/connection.c.
  • Held in the backend's process-lifetime (no shmem).
  • Re-established on subtransaction abort — connection state is torn down.
  • If you're writing an FDW that talks to something with expensive connection setup (HTTP, etc.), copy this pattern.

Debug "postgres_fdw isn't pushing my WHERE clause"

  • EXPLAIN VERBOSE — shows the exact SQL being sent to remote as Remote SQL.
  • Common blockers: qual uses a volatile function, references a local table, uses a subquery not shippable.
  • SET postgres_fdw.debug_pushdown = on — logs pushdown decisions.
  • Check if the qual is a JOIN condition — join pushdown has stricter rules.

Pitfalls

  • User mapping password can leak — if the FDW forwards the password to a debug/error message. postgres_fdw scrubs but a naive new FDW may not.
  • Loopback to self bypasses RLS — creating a postgres_fdw server pointing at the same cluster with a superuser mapping lets you bypass RLS on your own database. Well-documented but frequently misused.
  • Volatility marking of remote functions — the planner assumes remote functions have the volatility marked on the local shell function. Mismatch → wrong plans (caching wrong result).
  • Async scans require careful state — the async API (ForeignAsyncRequest etc.) has multiple interleaved iterators. Managing state per-iterator is fiddly.
  • User Mapping owner vs current_user — some FDWs check the "connect as" user carefully; others don't. Confusing "who owns the mapping" with "who's using it" is a security bug source.
  • fdw_startup_cost + fdw_tuple_cost — the planner uses these to decide whether to bother pushing down. Wrong defaults produce nonsensical plans.
  • use_remote_estimate for JOINs — enabling it can turn planning into O(N²) remote round-trips for cross-joins. Enable per-table where it helps.
  • DML doesn't push implicitlyINSERT ... SELECT doesn't push the SELECT's INSERT logic; it fetches the rows to local then re-INSERTs. Only DirectModify (UPDATE/DELETE with all-remote WHERE) pushes.
  • postgres_fdw prepared-statement cache is per-connection — if the connection restarts, all prepared statements are re-prepared.

Related corpus

  • Idioms: fdw-iterate-scan (the pull-next-tuple discipline), fdw-routine-callbacks (the FdwRoutine struct), cursor-and-portal (foreign-scan portals).
  • Subsystems: foreign (this skill's home subsystem), executor (ForeignScan node), contrib-postgres_fdw (the reference), contrib-file_fdw (the simplest example), contrib-dblink (different pattern — imperative not declarative FDW).
  • Sessions: 2026-06-04-a11-contrib-top.md (deep-read of the top-4 contrib modules including postgres_fdw's password_required gold-standard).

Corpus-chain shortcut

python3 scripts/corpus-chain.py --idiom fdw-routine-callbacks
python3 scripts/corpus-chain.py --file contrib/postgres_fdw/postgres_fdw.c

Second surfaces the reference implementation's neighborhood: 6 files in the contrib module.

Boundary

Use this skill for FDW architecture + postgres_fdw internals + writing new FDWs.

Don't use for:

  • Specific non-core FDWs (oracle_fdw / mysql_fdw / etc.) — external contribs with their own idioms.
  • dblink — different pattern (per-call connection + explicit dblink('SELECT ...')), not an FDW.
  • postgres_fdw bug fixing at the connection layer — if you're on the wire protocol level, wire-protocol skill has the byte-level details.
  • ImportForeignSchema DDL processing — related but handled by commands/foreigncmds.c; that's more DDL-plumbing than FDW-authoring.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most databases sql skills give in ~2.4k tokens

Counted across 589 of the 662 authors here whose files we hold, read 2026-08-07

  • Use parameterized queriesin 37 of 589, across 34 files
  • Use timestamptz for timestampsin 30 of 589, across 14 files
  • Index foreign keysin 29 of 589, across 18 files
  • Create indexes concurrentlyin 29 of 589, across 24 files
  • Use numeric type for moneyin 25 of 589, across 8 files
  • Use cursor pagination instead of offsetin 24 of 589, across 17 files
  • Select only required columnsin 24 of 589, across 20 files
  • Add indexes manually on foreign key columnsin 22 of 589, across 12 files
  • Normalize to third normal formin 19 of 589, across 10 files
  • Configure connection poolingin 19 of 589, across 17 files
  • Put equality columns before range columns in indexesin 18 of 589, across 10 files
  • Read individual rule files for detailed explanationsin 18 of 589, across 4 files

Said here and by no other author read

  • populate the FdwRoutine callback struct
  • implement the minimum viable scan callbacks
  • copy the connection cache pattern for expensive setups
  • add a case in is_foreign_expr for new pushdown
  • update the deparse logic for the new expression
  • scrub passwords from debug and error messages

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 327,069. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.