PostgreSQL Compatibility Matrix

August 6, 2026 Β· View on GitHub

This is the canonical, feature-by-feature record of what PostgreSQL functionality Duckgres supports and which tests prove it. It exists so that "is feature X supported?" and "where's the test / where's the gap?" have one answer instead of being smeared across the README and test docs.

Duckgres speaks the PostgreSQL wire protocol but executes on DuckDB, an OLAP engine. So the compatibility target is "PostgreSQL semantics for analytical workloads," not full OLTP parity. Many gaps below are deliberate (DuckDB has no SAVEPOINT, no triggers, no sequences) rather than unfinished.

Status legend

SymbolMeaning
βœ… CoveredWorks, and a differential test asserts it (same query run against real PostgreSQL 16 and Duckgres, results compared) β€” tests/integration/.
🟑 PartialWorks, but only unit/transpiler-tested (no differential assertion), or differential-covered with notable cases skipped.
⚠️ Implemented, thin/untestedCode path exists and is reachable by clients, but no real test exercises the happy path. These are the real test gaps.
❌ UnsupportedNot implemented and/or not tested; behavior undefined for clients that rely on it.
β›” Out of scopeIntentionally unsupported (DuckDB limitation or OLTP feature). Often has a skipped test documenting the reason.

Test citations use file.go::TestName. Integration (differential) tests live in tests/integration/; unit tests in server/ and transpiler/; real-driver tests in scripts/client-compat/ and tests/integration/clients/.

Maintenance contract

Per CLAUDE.md, every behavior change ships with a test. When that change adds, removes, or changes a PostgreSQL-visible feature, update the matching row here in the same PR β€” flip the status, fix the test citation, or add the row. A row that points at a real test name rots slower than prose; keep the citations honest. If you mark something β›” Out of scope, link the skipped test that documents why.


1. Queries (DQL)

FeatureStatusTest(s)Notes
SELECT, projections, aliases, arithmetic, ||βœ…dql_test.go::TestDQLBasicSelect
WHERE: comparisons, IS [NOT] DISTINCT FROM, AND/OR/NOTβœ…dql_test.go::TestDQLWhere
IN / NOT IN / BETWEENβœ…dql_test.go::TestDQLWhere
LIKE / ILIKE / regex ~ ~* !~βœ…dql_test.go::TestDQLWhere
SIMILAR TOβ›”dql_test.go (skipped)SkipUnsupportedByDuckDB
ANY / ALL (array)βœ…dql_test.go::TestDQLWhere, types_test.go::TestTypesArray
ORDER BY (position/alias/expr, ASC/DESC, NULLS FIRST/LAST)βœ…dql_test.go::TestDQLOrderBy
LIMIT / OFFSET / FETCH FIRST…ONLYβœ…dql_test.go::TestDQLLimitOffset
DISTINCT / DISTINCT ONβœ…dql_test.go::TestDQLDistinct; transform wiring_ops_test.go::TestOperatorTransform_DistinctOn
GROUP BY / HAVINGβœ…dql_test.go::TestDQLGroupBy
GROUPING SETS / ROLLUP / CUBEβœ…dql_test.go::TestDQLGroupBy
Joins: INNER/LEFT/RIGHT/FULL/CROSS/self/LATERALβœ…dql_test.go::TestDQLJoins
NATURAL JOIN / JOIN USING🟑dql_test.go::TestDQLJoins (skipped)Skipped on fixture column-name mismatch, not a Duckgres gap
Subqueries: scalar / correlated / EXISTS / INβœ…dql_test.go::TestDQLSubqueries
CTEs / RECURSIVE / chainedβœ…dql_test.go::TestDQLCTEs
Writable (data-modifying) CTEβœ…dml_test.go::TestDMLWithCTE; transpiler writablecte_returning_test.go
UNION / INTERSECT / EXCEPT (+ ALL)βœ…dql_test.go::TestDQLSetOperations
Window functions (ranking, offset, value, frames, named windows, distribution)βœ…dql_test.go::TestDQLWindowFunctions; transform wiring_ops_test.go::TestOperatorTransform_InlineWindowDef/_NamedWindowClauseThorough: ROW_NUMBER/RANK/LAG/LEAD/FIRST_VALUE/NTILE/PERCENT_RANK/frames
VALUES (standalone / subquery / CTE)βœ…dql_test.go::TestDQLValues; transform wiring_ops_test.go::TestOperatorTransform_ValuesLists
Quoted identifiers / case sensitivityβœ…dql_test.go::TestDQLCaseSensitivity, edge_cases_test.go::TestQuotedIdentifiers
Complex multi-CTE/join/recursive queriesβœ…dql_test.go::TestDQLComplexQueries
TABLE t commandβ›”dql_test.go (skipped)SkipUnsupportedByDuckDB
TABLESAMPLE (PG syntax)βŒβ€”DuckDB USING SAMPLE works via fallback (fallback_test.go); PG TABLESAMPLE not asserted

2. Data Manipulation (DML)

FeatureStatusTest(s)Notes
INSERT (single / multi-row / INSERT…SELECT / DEFAULT / NULL)βœ…dml_test.go::TestDMLInsert
UPDATE (incl. UPDATE…FROM)βœ…dml_test.go::TestDMLUpdate, ::TestDMLUpdateFromJoin
DELETE (incl. DELETE…USING, subquery)βœ…dml_test.go::TestDMLDelete, ::TestDMLDeleteUsing; transform wiring_ops_test.go::TestOperatorTransform_DeleteUsing
RETURNING (simple query protocol)🟑transform wiring_ops_test.go::TestOperatorTransform_{Insert,Update,Delete}Returning; conn_test.go::TestContainsReturning/::TestIsDMLReturningDifferential tests TestDML{Insert,Update,Delete}Returning are skipIfKnown (stale β€” passes at unit/client level)
RETURNING (extended query protocol)β›”conn_test.go::TestIsDMLReturningRejected at Describe time with 0A000 by design β€” Describe would execute the mutation. See CLAUDE.md "DML RETURNING Detection"
ON CONFLICT / UPSERT (DO UPDATE)🟑dml_test.go::TestDMLInsertOnConflict; transpiler transform/onconflict_test.goDuckDB-backed tables are covered. DuckLake rewrites ON CONFLICTβ†’MERGE, but this is not PostgreSQL-equivalent when conflict keys are duplicated; see known issue below.
ON CONFLICT DO NOTHING🟑dml_test.go::TestDMLInsertOnConflict (subtest skipped)
MERGE (user-facing)β›”β€”Not a PostgreSQL compatibility target; Duckgres only uses MERGE internally for DuckLake ON CONFLICT emulation
TRUNCATEβœ…ddl_test.go::TestDDLTruncate
COPY … FROM STDIN (text/CSV)βœ…copy_test.go::TestCopyFromStdin, ::TestCopyFromStdinWithSpecialChars, ::TestCopyFromStdinMultilineJSONEscape sequences stored literally (documented DuckDB CSV-parser limitation)
COPY … TO STDOUT🟑copy_test.go::TestCopyToStdout (skipped under lib/pq); conn_test.go::TestCopyToStdoutRegex; client-compat psycopg COPY suiteIntegration skip is a lib/pq driver limitation, not a Duckgres gap
COPY binary format🟑conn_test.go::TestShouldHandleCopyBeforeTranspile; types_test.go encode/decodeUnit-level only

Known issue: DuckLake ON CONFLICT with duplicate keys

DuckLake does not enforce UNIQUE / PRIMARY KEY constraints. Duckgres therefore cannot provide true PostgreSQL ON CONFLICT semantics on DuckLake-backed tables.

Today Duckgres rewrites Fivetran-style INSERT ... ON CONFLICT ... into MERGE. If the staging data or target table already contains duplicate conflict keys, that MERGE can match multiple rows and amplify duplicates. The storage-engine fix would be DuckLake uniqueness enforcement, which is not a small compatibility patch.

Operational guidance: allow the import to complete, then manually deduplicate affected tables using the intended business key and retention rule, such as keeping one row per id with the latest _fivetran_synced, or using SELECT DISTINCT when rows are fully identical.


3. Data Definition (DDL)

FeatureStatusTest(s)Notes
CREATE/DROP TABLE (IF [NOT] EXISTS, CASCADE/RESTRICT)βœ…ddl_test.go::TestDDLCreateTable, ::TestDDLDropTable
Constraints: PK / FK / UNIQUE / CHECK / NOT NULL / DEFAULTβœ…ddl_test.go::TestDDLConstraintsEnforcement follows DuckDB semantics
Generated columns🟑transpiler transform/ddl_test.go (GENERATED detection)No differential test
CREATE TABLE AS / TEMP TABLEβœ…ddl_test.go::TestDDLCreateTable
ALTER TABLE add/drop/rename column, rename tableβœ…ddl_test.go::TestDDLAlterTable
VIEW (CREATE OR REPLACE, column aliases)βœ…ddl_test.go::TestDDLViews
Materialized viewsβ›”β€”pg_matviews is an empty stub
INDEX (unique, multi-col, IF [NOT] EXISTS)🟑ddl_test.go::TestDDLIndexesAccepted, but a no-op in DuckDB/DuckLake mode β€” not semantically asserted
SCHEMA (create/drop/cascade)βœ…ddl_test.go::TestDDLSchemas
SEQUENCE / SERIAL / nextval/currvalβ›”catalog_test.go::TestCatalogPgGetSerialSequencepg_get_serial_sequence returns NULL β€” not supported
TYPE / ENUM / DOMAIN / compositeβŒβ€”Unmapped DuckDB ENUM/STRUCT/etc. fall back to OidText
COMMENT ON table/columnβœ…ddl_test.go::TestDDLComment
Partitioning / inheritanceβ›”β€”pg_partitioned_table, pg_inherits are empty stubs

4. Data Types

TypeStatusTest(s)Notes
smallint / int / bigintβœ…types_test.go::TestTypesNumeric; types_test.go(server)::TestEncode/DecodeInt2/4/8
real / doubleβœ…types_test.go::TestTypesNumeric; server TestEncode/DecodeFloat4/8incl. NaN/Β±Infinity
numeric / decimalβœ…types_test.go::TestTypesNumeric; server TestEncodeNumeric/TestDecodeNumerictypmod precision/scale handled
char / varchar / textβœ…types_test.go::TestTypesCharacter, ::TestUnicodeAndSpecialDataunicode, emoji, escapes
byteaβœ…types_test.go::TestTypesBinary; server TestEncode/DecodeByteaescape-format variant skipped (SkipDifferentBehavior)
date / time / timestamp / timestamptz / intervalβœ…types_test.go::TestTypesDateTime; server TestEncode/DecodeDate/Timestamp/Time/Intervalincl. ancient dates, microseconds
booleanβœ…types_test.go::TestTypesBoolean; server TestEncode/DecodeBoolall literal forms (t/f/yes/no/1/0)
uuidβœ…types_test.go::TestTypesUUID; server TestEncodeDecodeUUID
json / jsonbβœ…types_test.go::TestTypesJSON; server TestEncodeJSON/TestEncodeBinaryJSONoperators -> ->> @> ?; JSONPath @? skipped
arraysβœ…types_test.go::TestTypesArraysubscript, slice, concat, contains, ANY/ALL
NULL handling (all types)βœ…types_test.go::TestTypesNullHandling
Casts (CAST, ::, implicit)βœ…types_test.go::TestTypesCasting
moneyβ›”types_test.go::TestTypesUnsupportedpoorly supported in DuckDB
inet / cidr / macaddrβ›”types_test.go::TestTypesUnsupportedSkipNetworkType
point / line / box (geometric)β›”types_test.go::TestTypesUnsupportedSkipGeometricType
int4range / daterange (range/multirange)β›”types_test.go::TestTypesUnsupportedSkipRangeType
tsvector / tsquery (full-text)β›”types_test.go::TestTypesUnsupportedSkipTextSearch
enum / domain / compositeβŒβ€”falls back to OidText
bit string🟑functions_test.go::TestFunctionsString (bit_length only)no bit-type operations
xmlβŒβ€”not tested
OID types (oid / regclass / …)🟑used within catalog_test.go queriesnot type-tested directly

5. Functions & Operators

GroupStatusTest(s)Notes
String (length/case/trim/pad/substring/replace/split/format/regexp/md5/quote_*)βœ…functions_test.go::TestFunctionsStringbroad
Math (abs/round/trunc/mod/power/sqrt/trig/log/width_bucket)βœ…functions_test.go::TestFunctionsNumeric
Date/time (extract/date_part/date_trunc/age/to_char/make_*)βœ…functions_test.go::TestFunctionsDateTime
Conditional (CASE/COALESCE/NULLIF/GREATEST/LEAST)βœ…functions_test.go::TestFunctionsConditional
Aggregates (count/sum/avg/min/max/stddev/variance/bool_/bit_/array_agg/string_agg/json_agg)βœ…functions_test.go::TestFunctionsAggregate
Aggregate FILTER and WITHIN GROUP (percentile/mode)βœ…functions_test.go::TestFunctionsAggregate; transform wiring_ops_test.go::TestOperatorTransform_AggFilter/_AggOrder
Set-returning (generate_series, unnest, json_each, *_array_elements)βœ…functions_test.go::TestFunctionsMisc, ::TestFunctionsJSON, ::TestFunctionsArray
JSON build/extract/modify functionsβœ…functions_test.go::TestFunctionsJSON
Array functionsβœ…functions_test.go::TestFunctionsArray
System info (current_database/schema/user, version, pg_typeof)βœ…functions_test.go::TestFunctionsMisc, session_test.go::TestSessionCurrentFunctions

6. Wire Protocol

FeatureStatusTest(s)Notes
Simple queryβœ…protocol_test.go::TestProtocolSimpleQuery
Extended query (Parse/Bind/Describe/Execute/Sync)βœ…protocol_test.go::TestProtocolExtendedQuery; server conn_bind_test.go, conn_describe_test.go
Portal suspension (Execute row limit / PortalSuspended)βœ…portal_suspension_test.go::TestPortalSuspensionPaging (raw-frontend paging); server conn_portal_suspension_test.goPaging clients (JDBC setFetchSize, Hex) resume with repeat Execute; the query runs once. Suspended portals are destroyed at transaction end (PostgreSQL parity + the same single-connection liveness rule as cursors). Pre-fix, the first page was answered with CommandComplete β€” silent truncation at the client's page size
Extended-query error recovery (skip-until-Sync, pipelining)βœ…server conn_skip_until_sync_test.go; clients clients_test.go::TestExtendedQueryErrorHandling#718
Prepared statements (PREPARE/EXECUTE, reuse, NULL, 20+ params)βœ…edge_cases_test.go::TestPreparedStatementEdgeCases; clients ::TestPreparedStatements
Binary vs text result formatβœ…protocol_test.go::TestProtocolDataTypes; clients ::TestPgxBinaryFormatResults; server types_test.go encode/decode
Row description metadataβœ…protocol_test.go::TestProtocolRowDescription
Large result sets (1000+ rows, wide rows, 100KB values)βœ…protocol_test.go::TestProtocolLargeResults
Query cancellation (CancelRequest)βœ…cancel_test.go::TestCancelQueryDoesNotAffectOtherSessions
Error / notice + recovery (in & out of txn)βœ…protocol_test.go::TestProtocolErrors, edge_cases_test.go::TestErrorRecovery
Empty / comment-only queriesβœ…edge_cases_test.go::TestEmptyQuery, ::TestPgxPing
Multi-statement simple queryβœ…protocol_test.go::TestProtocolMultipleStatements, edge_cases_test.go::TestMultiStatementBehavior
Cursors: DECLARE / FETCH / MOVEβœ…cursor_test.go::TestCursorSimpleQuery + ::TestCursorPostgresParity (differential), ::TestCursorExtendedQuery (pgx, extended protocol); server conn_querylog_feedback_test.go::TestHandleFetchCursorLogsMissingCursorError (error path)Emulated in server/conn_cursor.go. Forward-only: backward fetch β†’ 0A000 (PostgreSQL uses 55000 for non-SCROLL cursors, same message). Re-DECLARE of an open cursor replaces it (PostgreSQL raises 42P03). Cursors close at transaction end, before COMMIT/ROLLBACK executes (also a liveness requirement: an open cursor rowset pins the session's single DuckDB connection β€” server/conn_cursor_test.go::TestCloseCursorsAtTxEnd)
Cursor CLOSEβœ…cursor_test.go::TestCursorSimpleQuery/lifecycle_fetch_close + parity, ::TestCursorExtendedQuery/declare_fetch_close; server conn_test.go (CLOSE detection)FETCH after CLOSE β†’ clean 34000 missing-cursor error
Auth: cleartext passwordβœ…server worker_auth_test.go; integration via connection params
Auth: MD5βŒβ€”Current startup path requests cleartext password auth over TLS
Auth: SCRAM-SHA-256βŒβ€”not tested
TLS / SSL (sslmode=require)βœ…edge_cases_test.go::TestConnectionParameters

7. Session, Config & Transactions

FeatureStatusTest(s)Notes
SET / SHOW / RESET / RESET ALL / DISCARDβœ…session_test.go::TestSessionSetCommands, ::TestSessionShowCommands, ::TestSessionReset, ::TestSessionMiscCommands~20 GUCs accepted (many safely ignored)
search_pathβœ…session_test.go::TestSessionSetSearchPath
current_database / current_schema / current_user / session_userβœ…session_test.go::TestSessionCurrentFunctions
BEGIN / COMMIT / ROLLBACK / START TRANSACTION / ENDβœ…session_test.go::TestSessionTransactionCommands, protocol_test.go::TestProtocolTransactions
Isolation levels / READ ONLY / SESSION CHARACTERISTICS🟑session_test.go::TestSessionTransactionModesParsed & accepted; DuckDB always runs snapshot isolation (β‰ˆ serializable). SHOW transaction_isolation returns read committed for compat
SAVEPOINT / ROLLBACK TO / RELEASEβ›”edge_cases_test.go::TestSavepoints (skipped)DuckDB has no SAVEPOINT β€” affects Django/Rails nested-txn patterns
SELECT FOR UPDATE / FOR SHARE🟑transform wiring_ops_test.go::TestLockingTransform_*; transpiler_test.go (FlagLocking)Locking clause is stripped/accepted (no-op); no differential test
Advisory locks (pg_advisory_*)βŒβ€”not tested
LOCK TABLEβŒβ€”not tested
Two-phase commit (PREPARE TRANSACTION)βŒβ€”not tested
SET ROLE / SET SESSION AUTHORIZATIONβŒβ€”not tested

8. System Catalog & Introspection

FeatureStatusTest(s)Notes
pg_classβœ…catalog_test.go::TestCatalogPgClassDuckLake variant sources from duckdb_tables()/duckdb_views()
pg_namespaceβœ…catalog_test.go::TestCatalogPgNamespacemaps mainβ†’public
pg_attributeβœ…catalog_test.go::TestCatalogPgAttribute
pg_typeβœ…catalog_test.go::TestCatalogPgTypesynthetic entries for json/jsonb/text/array/…
pg_databaseβœ…catalog_test.go::TestCatalogPgDatabase
pg_rolesβœ…catalog_test.go::TestCatalogPgRolessingle hardcoded superuser
pg_settingsβœ…catalog_test.go::TestCatalogPgSettings
pg_stat_activityβœ…pg_stat_activity_test.go::TestPgStatActivity/::TestPgStatActivityFromSecondConnection/::TestPgStatActivityExtendedQuery/::TestPgStatActivityStubViewintercepted at query time for live data
system.query_logβœ…querylog_view_test.go::TestEnsureDuckLakeQueryLogViewContextCreatesViewLive DuckLake view over native Postgres querylog.query_log_entries; not DuckLake snapshot data.
information_schema (tables/columns/views/schemata)βœ…catalog_test.go::TestCatalogInformationSchema{Tables,Columns,Views,Schemata}
information_schema key_column_usage / table_constraints / referential_constraintsβŒβ€”Missing; used by ORMs for FK introspection
System functions (format_type, pg_get_userbyid, pg_table_is_visible, has_privilege, pg_encoding_to_char, size fns, quote)βœ…catalog_test.go::TestCatalogSystemFunctions, ::TestFormatTypeTimePrecision; server pg_compat_macros_test.gomany return permissive/stub values
psql meta-commands (\dt, \dn, \l, \d)βœ…catalog_test.go::TestCatalogPsqlCommands
Qualified-name resolutionβœ…catalog_test.go::TestCatalogQualifiedNames, ::TestCatalogCombinedQueries
Catalog not masked by user dataβœ…catalog_demask_test.go::TestCatalogIsNotMasked
Stub tables return empty (pg_policy/collation/publication/inherits/rules/matviews/stat_statements/partitioned_table/rewrite)βœ…catalog_test.go::TestCatalogStubsintentional
Client/BI introspection (Metabase/Grafana/Superset/Tableau/DBeaver/Fivetran/Airbyte/dbt)βœ…clients/clients_test.go::Test{Metabase,Grafana,Superset,Tableau,DBeaver,Fivetran,Airbyte,Dbt}Queries; jdbc_test.go::TestJDBC*+ scripts/client-compat/queries.yaml (100+ catalog queries)

9. Procedural / Server-Side β€” all β›” (DuckDB does not support)

FeatureStatusTest(s)Notes
PL/pgSQL, CREATE FUNCTION/PROCEDURE, CALLβ›”β€”no server-side procedural language
Triggersβ›”β€”
Rulesβ›”β€”pg_rules empty stub
LISTEN / NOTIFYβ›”β€”
Event triggersβ›”β€”

10. Security & Roles

FeatureStatusTest(s)Notes
GRANT / REVOKE, column/default privilegesβŒβ€”has_*_privilege() return permissive stubs; privilege DDL untested
CREATE / ALTER / DROP ROLE / USERβŒβ€”
Row-level security (RLS)β›”β€”pg_policy empty stub
Managed project readersβœ…server/query_access_test.go, server/session_database_metadata_test.go, tests/mw-dev/e2e/harness.sh::project_reader_isolationControl-plane users with access_mode=project_reader are read-only and restricted to their project's schemas and legacy event/person relations. USE ducklake and a small set of client session settings are supported; SQL cursor statements are not. Catalog compatibility views expose only the same project-owned relations, and direct DuckDB introspection functions are unavailable. This is enforced by the query gateway, not PostgreSQL GRANT statements.
Managed project users (read/write)βœ…server/query_access_test.go, controlplane/configstore/query_access_test.go, tests/mw-dev/e2e/harness.sh::project_user_isolationaccess_mode=project_user is the read/write sibling of a project reader: identical namespaces, plus DML (INSERT/UPDATE/DELETE/MERGE/TRUNCATE) and in-project DDL (CREATE/DROP/ALTER/RENAME of tables, views, indexes, sequences; CREATE TABLE … AS; SELECT … INTO) where every target resolves into the project's schemas. COPY … FROM STDIN is available; the file, URL, PROGRAM and COPY … TO forms are not. Namespace-level DDL (CREATE/DROP SCHEMA, ALTER … SET SCHEMA) is denied β€” the schema set is the project boundary and is derived from the team row. Everything denied to a reader for reachability reasons stays denied: cross-project relations (in read and write positions), the DuckDB escape-hatch functions, and GRANT. Statements the PostgreSQL parser cannot describe cannot be scope-checked, so DuckDB-only spellings (CREATE OR REPLACE TABLE) are rejected; sequence functions (nextval/setval) remain denied because their string argument is not scope-checkable. A project user whose team is missing or disabled is downgraded to an empty read-only policy rather than failing open.

11. DuckDB-Specific Syntax (pass-through, non-PostgreSQL)

Not PostgreSQL features, but exercised because clients may send them and Duckgres must route them to native DuckDB execution rather than the PG transpiler.

FeatureStatusTest(s)
FROM-first, EXCLUDE/REPLACE, DESCRIBE, SUMMARIZE, QUALIFY, lambdas, positional/ASOF joins, COLUMNS(), USING SAMPLEβœ…fallback_test.go::TestFallback*

Summary β€” the real gaps

Sorted by what's worth acting on first.

  1. 🟑 DuckLake ON CONFLICT duplicate-key caveat. Fivetran-style INSERT ... ON CONFLICT ... is rewritten to MERGE on DuckLake, but DuckLake does not enforce unique constraints. Duplicate source or target keys can fan out; affected tables need manual post-import deduplication.

  2. 🟑 Stale skipIfKnown skips. Differential RETURNING (TestDML{Insert,Update,Delete}Returning) and COPY TO STDOUT (TestCopyToStdout) are skipped in tests/integration/ though the behavior is covered at unit/client level. Re-enable or document why they must stay skipped.

  3. 🟑 Transpiler-only, no differential assertion: generated columns, SELECT FOR UPDATE/SHARE (stripped). Add differential cases if these matter to clients.

  4. ❌ Unsupported or untested but plausibly reachable β€” undefined behavior today: MD5/SCRAM auth, enum/domain/composite/xml types, advisory locks, LOCK TABLE, GRANT/REVOKE/roles, information_schema.{key_column_usage,table_constraints, referential_constraints} (ORM FK discovery), TABLESAMPLE. Asserting these (even as "errors cleanly") would pin the compatibility boundary.

  5. β›” Out of scope by design (correctly skipped): SAVEPOINT, MERGE, sequences/SERIAL, materialized views, partitioning/inheritance, triggers/PL-pgSQL/rules/LISTEN-NOTIFY, RLS, and the network/geometric/range/ text-search/money types. These are DuckDB or OLTP limitations.



Appendix A β€” Catalog object, function & startup-parameter reference

This is the emulation-internals view that previously lived in the README: which pg_catalog/information_schema objects and compatibility macros Duckgres provides, and what each returns. "Implemented" = Duckgres-provided wrapper; "Native (DuckDB)" = works through DuckDB's own pg_catalog with no Duckgres wrapper; "Stub" = present but intentionally empty/constant; "Missing" = neither wrapper nor native support. Behavior values (returns NULL / 0 / always true) are deliberate stubs sized to satisfy client introspection, not real implementations.

pg_catalog views

ViewStatusNotes
pg_classImplementedpg_class_full wrapper adding relforcerowsecurity; DuckLake variant sources from duckdb_tables()/duckdb_views()
pg_namespaceImplementedMaps main β†’ public; DuckLake variant derives from duckdb_tables()/duckdb_views()
pg_attributeImplementedMaps DuckDB internal type OIDs to PG OIDs via duckdb_columns() JOIN; fixes atttypmod for NUMERIC
pg_typeImplementedFixes NULLs + adds synthetic entries for missing OIDs (json, jsonb, bpchar, text, record, array types)
pg_databaseImplementedHardcoded: postgres, template0, template1, testdb
pg_stat_user_tablesImplementedUses reltuples from pg_class; zeros for scan/tuple stats
pg_rolesMinimal viewSingle hardcoded superuser row (not empty)
pg_settingsNative (DuckDB)pg_catalog.pg_settings is queryable via DuckDB; the current_setting() macro only special-cases server_version/server_encoding
pg_stat_activityStub (empty)Static view is empty; intercepted at query time for live data
pg_constraintStub (empty)
pg_enumStub (empty)
pg_collationStub (empty)
pg_policyStub (empty)
pg_inheritsStub (empty)
pg_statistic_extStub (empty)
pg_publicationStub (empty)
pg_publication_relStub (empty)
pg_publication_tablesStub (empty)
pg_rulesStub (empty)
pg_matviewsStub (empty)
pg_partitioned_tableStub (empty)
pg_statio_user_tablesStub (empty)
pg_stat_statementsStub (empty)
pg_indexesStub (empty)
pg_procNative (DuckDB)DuckDB has native pg_catalog.pg_proc; no Duckgres wrapper
pg_descriptionMissingHandled via obj_description()/col_description() macros returning NULL
pg_dependMissing
pg_amMissing
pg_attrdefMissing
pg_tablespaceMissing

information_schema views

ViewStatusNotes
tablesImplementedFilters internal views, normalizes main β†’ public
columnsImplementedDuckDB β†’ PG type name normalization, optional metadata overlay
schemataImplementedAdds synthetic entries for pg_catalog, information_schema, pg_toast
viewsImplementedFilters internal views
key_column_usageMissingUsed by ORMs for relationship discovery
table_constraintsMissingUsed by ORMs for relationship discovery
referential_constraintsMissingUsed by ORMs for FK introspection

Functions & macros

FunctionStatusNotes
format_type(oid, int)ImplementedComprehensive OID β†’ name mapping
pg_get_expr(text, oid)ImplementedReturns NULL
pg_get_indexdef(oid)ImplementedReturns empty string
pg_get_constraintdef(oid)ImplementedReturns empty string
pg_get_serial_sequence(text, text)ImplementedReturns NULL (no sequence support)
pg_table_is_visible(oid)ImplementedAlways true
pg_get_userbyid(oid)ImplementedMaps OID 10 β†’ postgres, 6171 β†’ pg_database_owner
obj_description(oid, text)ImplementedReturns NULL
col_description(oid, int)ImplementedReturns NULL
shobj_description(oid, text)ImplementedReturns NULL
has_table_privilege(text, text)ImplementedAlways true
has_schema_privilege(text, text)ImplementedAlways true
pg_encoding_to_char(int)ImplementedAlways UTF8
version()ImplementedReturns PostgreSQL 15.0 … (Duckgres/DuckDB)
current_setting(text)ImplementedSpecial-cases server_version, server_encoding
current_schema()Native (DuckDB)Works via DuckDB; no Duckgres wrapper
current_schemas(bool)Missing
pg_is_in_recovery()ImplementedAlways false
pg_backend_pid()ImplementedReturns 0
pg_size_pretty(bigint)ImplementedFull human-readable formatting
pg_total_relation_size(oid)ImplementedReturns 0
pg_relation_size(oid)ImplementedReturns 0
pg_table_size(oid)ImplementedReturns 0
pg_indexes_size(oid)ImplementedReturns 0
pg_database_size(text)ImplementedReturns 0
quote_ident(text)Implemented
quote_literal(text)Implemented
quote_nullable(text)Implemented
txid_current()ImplementedEpoch-based pseudo ID

Startup parameters

ParameterValue
server_version15.0 (Duckgres)
server_encodingUTF8
client_encodingUTF8
DateStyleISO, MDY
TimeZoneUTC
integer_datetimeson
standard_conforming_stringson
IntervalStyleMissing

Duckgres advertises PostgreSQL 15.0 on the wire (server/catalog.go, server/conn.go). The differential test suite compares results against a real PostgreSQL 16 server, but the emulated version string is intentionally 15.0.


  • README.md β†’ "SQL Client Compatibility" β€” short user-facing summary that links here.
  • tests/integration/README.md β€” test-suite architecture, category counts, and the skip-reason table.
  • TODO.md β€” lightweight backlog for project ideas that do not yet have a better home.
  • scripts/client-compat/README.md β€” real-driver compatibility harness and queries.yaml.