Make DuckDB run as a PostgreSQL-Server — connect to DuckDB with any tool in the Postgres ecosystem (psql, pgbench, libpq, psycopg2, JDBC, …) and query/insert data using the Postgres wire protocol v3.
- Simple Query protocol (
Q) - Extended Query protocol (
P/B/D/E/C/S/H) - ParameterStatus / BackendKeyData / ReadyForQuery
- ErrorResponse with SQLSTATE
- Text format parameters and results
- Binary format parameters (bool/int2/int4/int8/float4/float8 + fallback)
- Binary format results (bool/int2/int4/int8/float4/float8 + fallback)
- Empty query / multi-statement queries (CommandComplete per statement)
- SSL negotiation (rejected with
N, connection continues in plaintext) - Query cancel (CancelRequest +
Connection::Interrupt) - Column metadata with real PG type OIDs derived from DuckDB
LogicalType - Correct per-statement tags (
SELECT n,INSERT 0 n,UPDATE n,DELETE n, …) - COPY protocol (used by
pg_dump/pg_restore/pgbench -idata-load) - Notification (
LISTEN/NOTIFY)
- psql – simple + extended query,
\d-style introspection via rewrites - pgbench – simple / extended / prepared modes (custom scripts and built-in TPC-B)
- libpq / psycopg2 – extended protocol, parameterized prepared statements
- PostgreSQL JDBC Driver – connect, execute, prepared statements,
DatabaseMetaData(tables/schemas/columns) - pg_dump / pg_restore (require COPY protocol)
When a client sends a Postgres-only construct that DuckDB cannot parse, the server rewrites it on the fly. This includes:
SET <unknown_guc> = ...→ no-opBEGIN [WORK|TRANSACTION ISOLATION LEVEL ...]→BEGIN;- Multi-object
DROP TABLE a, b, c→ separateDROP TABLEstatements - Multi-table
TRUNCATE a, b, c→ separateDELETE FROMstatements - JDBC
DatabaseMetaData.getTables / getSchemas / getColumnscatalog queries are mapped to DuckDB'sduckdb_tables(),duckdb_schemas(),duckdb_views(), andinformation_schema.columns. - Automatic recovery from DuckDB's
current transaction is abortedstate.
CentOS:
yum install boost-devel
Ubuntu:
sudo apt update
sudo apt install libboost-all-dev
Mac:
brew install boost
git clone --recurse-submodules https://github.com/fanvanzh/PostDuck
cd PostDuck
mkdir build && cd build
cmake ..
make -j 16
./postduck --help
./postduck --port 5432 --data /var/lib/postduck --thread 8 --log INFO
--data selects the directory where DuckDB database files (<dbname>.db)
are created on first connection. <dbname> comes from the client's startup
packet (-d in psql, database= in JDBC, …).
Integration tests live under test/. They start a real postduck
binary, connect with psycopg2, and exercise the wire protocol end-to-end.
pip install -r test/requirements.txt
# rebuild first, then:
./test/run_tests.sh -v # or: pytest -v test/See test/README.md for details.
# psql
psql -h 127.0.0.1 -p 5432 -U postduck -d mydb
# pgbench
pgbench -h 127.0.0.1 -p 5432 -U postduck -d mydb -i --init-steps=dt --no-vacuum
pgbench -h 127.0.0.1 -p 5432 -U postduck -d mydb -n -c 4 -t 1000 -M prepared
# psycopg2
python3 -c "import psycopg2; \
c=psycopg2.connect(host='127.0.0.1',port=5432,user='postduck',dbname='mydb'); \
cur=c.cursor(); cur.execute('SELECT %s::int + %s::int', (10,20)); print(cur.fetchone())"
# JDBC
jdbc:postgresql://127.0.0.1:5432/mydb
