Sinks

Postgres

A live Postgres mirror — upsert and collapse semantics, schema mapping, and the grants required.

The Postgres sink lands your tables as plain tables in a PostgreSQL database you manage. Unlike every other sink it is not append-only: inserts and updates become INSERT … ON CONFLICT DO UPDATE, and a delete physically removes the row. There is no tombstone, no history column, and no configurable write mode — the table always holds current state.

Supported deployments

Anywhere the INSERT … ON CONFLICT form is available, which is every PostgreSQL 9.5 or later: self-hosted, Amazon RDS, Amazon Aurora, Google Cloud SQL, Azure Database for PostgreSQL, Supabase, and Neon. The sink only needs a writable database, so a connection-pooler endpoint is fine here — unlike the Postgres source, which needs a direct connection for logical replication.

Prerequisites

  • A reachable PostgreSQL server and a database to land in.
  • A login that can connect and create schema objects, because Fabric issues CREATE SCHEMA IF NOT EXISTS, CREATE TABLE IF NOT EXISTS, and ALTER TABLE … ADD COLUMN as well as the upserts and deletes:
CREATE ROLE fabric_writer LOGIN PASSWORD '<secret>';
GRANT CONNECT ON DATABASE analytics TO fabric_writer;
GRANT CREATE ON DATABASE analytics TO fabric_writer;  -- CREATE SCHEMA
-- If writing into a schema Fabric does not create:
GRANT USAGE, CREATE ON SCHEMA public TO fabric_writer;
GRANT SELECT, INSERT, UPDATE, DELETE
  ON ALL TABLES IN SCHEMA public TO fabric_writer;

When Fabric creates the schema and tables itself it owns them, so no extra grants are needed on those objects.

Connection fields

FieldRequiredDefaultNotes
NameYes—Unique across all destinations.
HostYes—
PortYes5432
DatabaseYes—The database the tables land in.
UsernameYes—
PasswordYes on create—Leave blank on edit to keep the stored password.

How tables land

The schema and table names are not destination fields. They come from the sync's own namespace and table name: <database>.<namespace>.<table>, with the namespace defaulting to raw. In the table editor that field is labeled Postgres schema.

  • On first write, Fabric creates the schema and table if missing. The sync's identifier columns become a real PRIMARY KEY — which is what makes the upsert work.
  • One metadata column is added, _synced_at. The append-only columns (__op, __deleted, __table, …) are deliberately absent.
  • Schema evolution is additive: a new column is added nullable, never retyped or renamed.
  • Deletes are destructive — the row is gone, with no history to recover it.

Large columns and REPLICA IDENTITY

Postgres does not resend a large (out-of-line TOASTed) column that an UPDATE leaves untouched. Fabric handles that so every row still lands complete, and no new column is added — readers merge exactly as before.

  • With REPLICA IDENTITY FULL on the source table (set with ALTER TABLE <table> REPLICA IDENTITY FULL), Fabric takes the unchanged value from the logged old row — no extra reads. Recommended for tables with large text, jsonb, or bytea columns; the setup check warns when a table has such columns without it.
  • Without it, Fabric reads the missing columns back from the source database by key — one query per table per commit — and rows still land complete. The value read is the source's value at commit time, which may be newer than the event.
  • The Postgres destination keeps its stored value for the untouched column and performs no reads.

Gotchas

Watch out

  • Test connection runs only SELECT 1. It proves reachability and credentials, not that the login can create schemas or write — verify those with the first sync.
  • The upsert needs the primary key to exist. Fabric creates it when it creates the table; a pre-existing table without a unique constraint over the identifier columns fails at write time.
  • There is no TLS control in the form — the connection uses libpq's default (sslmode=prefer). Managed Postgres with a strict TLS policy needs a deployment-level workaround.
  • Source columns differing only by case collapse to one Postgres column, and a column named like a metadata field is quarantined.