Skip to content

feat: basic Postgres foreign tables support - Foreign Data Wrapper #12127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

danielboven
Copy link

What?

This PR introduces basic support for PostgreSQL foreign tables (via FDW) in Payload CMS by adding an adapter flag to disable ON CONFLICT DO UPDATE operations and conditionally skipping foreign key constraints on collections where document locking is disabled.

Why?

PostgreSQL’s Foreign Data Wrapper (FDW) allows tables from external databases to be accessed and queried as if they were local. This is very useful when integrating external data sources, think of accounting systems, directly into the Payload schema.

However, FDW tables do not support ON CONFLICT DO UPDATE statements and disallow foreign key constraints (used when locking documents), causing Payload to fail during schema setup or on updates (upserts).

Supporting foreign tables in Payload:

  • Enables editing and displaying external data (e.g. customer-facing invoices from an accounting system).
  • Avoids duplicating or syncing external systems manually (e.g. avoids Bucardo replication).
  • Aligns with PostgreSQL’s recommended approach for multi-system database access.

For background, see:

  • The PostgreSQL FDW documentation
  • Schönig, H.J. (2019). Mastering PostgreSQL 12: Advanced techniques to build and administer scalable and reliable PostgreSQL database applications (2nd ed., pp. 372–376). Packt Publishing.
  • Community requests like this discussion ask for custom primary key support. When working with external data, it's often better to use Postgres Foreign Data Wrappers (FDW) and map the external primary key to id in the Payload-facing database - which can itself be a foreign table.

How?

  • Added a new disableOnConflictDoUpdate option to the PostgreSQL adapter (and mirrored it in SQLite and Drizzle adapters for type safety).
  • Updated the insert logic to fallback to an update-then-insert approach when the new flag is true.
  • Modified schema generation to skip foreign key constraints when a collection’s lockDocuments config is set to false, avoiding conflicts with FDW restrictions.
  • Updated documentation for the PostgreSQL adapter to explain the new flag and its relevance to foreign tables.

Final note: In my opinion, a clearer approach would be to extract upsert logic into a separate adapter.upsert(...) method, rather than handling upserts within adapter.insert(...). As it stands, it’s not immediately clear, especially to newcomers, that updates are implemented via insert operations using onConflictDoUpdate (which are actually upserts). Refactoring this would require relocating all upsert-related logic (e.g. onConflictDoUpdate calls) into a dedicated upsert function and implementing it across all Drizzle adapters. Perhaps something to think about in the future.

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.

1 participant