duckgres / client-compat
CLAUDE.md189 starsChanged 9 months agoMITView on GitHub
Docker Compose-based test framework that runs real PostgreSQL client libraries against duckgres and collects results into a DuckDB database. Lives under scripts/client-compat/. Key design point: the gatherer does NOT track which clients are done. The justfile handles all lifecycle coordination via docker wait. This avoids hangs when clients crash before reporting. Clients report results via HTTP to $RESULTS_URL (defaults to http://results-gatherer:8080): All reporting is fire-and-forget (errors silently swallowed) so clients also work standalone without the gatherer. Use report_client.py (copied into…
- Installs packages
In this repository6 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- ducklake-testSkill
- multitenant-downSkill
- multitenant-upSkill
- promote-to-prodSkill
The file
We split this CLAUDE.md into the rules it gives an agent
8 rules. About 0 are common, 4 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 60
All reporting is fire-and-forget (errors silently swallowed) so clients also work standalone without the gatherer.
- Very specific: line 64
Use report_client.py (copied into the image):
- Distinctive: line 128
Add a service block (copy an existing one and change names/paths):
- Very specific: line 156
Add the container name to the clients variable:
- Distinctive: line 161
Add a standalone target:
- Distinctive: line 172
Add the client to the tables in the Clients, Client-Specific Suites, and available targets sections.
- Very specific: line 178
Always use env vars: PGHOST, PGPORT, PGUSER, PGPASSWORD.
- Very specific: line 199
Instead, the dbeaver_introspection suite in queries.yaml contains the exact catalog queries DBeaver fires on connect, sourced from …
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Claude Code Context for Client Compatibility Tests
What This Is
Docker Compose-based test framework that runs real PostgreSQL client libraries against duckgres and collects results into a DuckDB database. Lives under scripts/client-compat/.
Directory Layout
scripts/client-compat/
├── CLAUDE.md # you are here
├── README.md # human-facing docs
├── docker-compose.yml # all services
├── justfile # task runner
├── Dockerfile.results # results-gatherer image
├── results_gatherer.py # HTTP server that collects results into DuckDB
├── report_client.py # Python helper for reporting (shared by Python clients)
├── entrypoint.sh # log-tee wrapper (shared by all clients)
├── queries.yaml # shared query catalog executed by every client
├── clients/ # one directory per client
│ ├── psycopg/
│ ├── pgx/
│ ├── psql/
│ ├── jdbc/
│ ├── tokio-postgres/
│ ├── node-postgres/
│ ├── sqlalchemy/
│ ├── dbt/
│ └── pgadmin/
└── results/ # output volume (.gitignored)Lifecycle / How It Works
just all→docker compose up -dstarts duckgres, results-gatherer, and all clients- Clients block on healthchecks (
duckgresTCP +results-gathererHTTP/health) - Each client runs tests, POSTing
{client, suite, test_name, status, detail}toPOST /result - The justfile runs
docker waiton all client containers (handles crashes gracefully) - Once all clients exit, the justfile
docker execs aPOST /shutdownto the gatherer - Gatherer prints report, exports
results_<ts>.duckdband.json, exits 0 docker compose down -vtears everything down
Key design point: the gatherer does NOT track which clients are done. The justfile handles all lifecycle coordination via docker wait. This avoids hangs when clients crash before reporting.
Reporting Protocol
Clients report results via HTTP to $RESULTS_URL (defaults to http://results-gatherer:8080):
POST /result
{
"client": "psycopg", # must match the client name
"suite": "catalog_views", # test grouping
"test_name": "pg_database", # unique within suite
"status": "pass", # "pass" or "fail"
"detail": "4 rows" # optional context (row count, error message)
}All reporting is fire-and-forget (errors silently swallowed) so clients also work standalone without the gatherer.
Python clients
Use report_client.py (copied into the image):
from report_client import ReportClient
rc = ReportClient("my_client")
rc.report("suite_name", "test_name", "pass", "detail")Non-Python clients
Implement HTTP POST directly. See clients/pgx/main.go (Go), clients/jdbc/.../JdbcCompatTest.java (Java), clients/tokio-postgres/src/main.rs (Rust), or clients/psql/test_psql.sh (curl) for examples.
Adding a New Client
Name the directory after the client library, not the language (e.g. tokio-postgres not rust, pgx not go).
1. Create the client directory
clients/<name>/
├── Dockerfile
└── <test script>2. Write the Dockerfile
Build context is scripts/client-compat/ (not the client dir). COPY paths must be relative to that:
FROM python:3.12-slim-bookworm
RUN pip install --no-cache-dir <deps> pyyaml
COPY entrypoint.sh /entrypoint.sh
COPY queries.yaml /queries.yaml
COPY report_client.py /report_client.py # Python clients only
COPY clients/<name>/test_<name>.py /test_<name>.py
ENTRYPOINT ["/entrypoint.sh", "python", "/test_<name>.py"]For compiled languages, use a multi-stage build (see clients/pgx/Dockerfile or clients/tokio-postgres/Dockerfile).
3. Implement the test script
Every client must:
- Wait for duckgres — poll with
SELECT 1, retry up to 30 times with 1s sleep - Run shared queries — load
/queries.yaml, execute each entry, report pass/fail - Run client-specific tests — whatever the library supports (DDL/DML, prepared statements, COPY, ORM, etc.)
- Connect with TLS — use
sslmode=requireand accept self-signed certs - Exit non-zero on failure — so
docker waitcaptures it
The shared queries YAML format:
- suite: catalog_views
name: pg_database
stub: true # optional — flags hardcoded dummy return values
sql: SELECT datname FROM pg_database WHERE datallowconn4. Register in docker-compose.yml
Add a service block (copy an existing one and change names/paths):
<name>:
build:
context: .
dockerfile: clients/<name>/Dockerfile
container_name: compat-<name>
depends_on:
duckgres:
condition: service_healthy
results-gatherer:
condition: service_healthy
volumes:
- ./results:/results
environment:
PGHOST: duckgres
PGPORT: "5432"
PGUSER: postgres
PGPASSWORD: postgres
RESULTS_URL: http://results-gatherer:8080
LOG_DIR: /results/<name>5. Register in justfile
Two changes:
- Add the container name to the
clientsvariable:clients := "compat-psycopg compat-pgx ... compat-<name>" - Add a standalone target:
# Run <name> compatibility tests (standalone, no report) [group('client')] <name>: build docker compose run --rm <name> docker compose down -v
6. Update README.md
Add the client to the tables in the Clients, Client-Specific Suites, and available targets sections.
Common Patterns
Connection parameters
Always use env vars: PGHOST, PGPORT, PGUSER, PGPASSWORD. Defaults should match docker-compose values (duckgres, 5432, postgres, postgres).
TLS
Duckgres auto-generates a self-signed cert. Clients must accept it:
- Python (psycopg2):
sslmode="require"(no cert validation by default) - Go (pgx):
InsecureSkipVerify: truein tls.Config - Java (JDBC):
sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory - Rust (tokio-postgres):
danger_accept_invalid_certs(true)on TlsConnector
Suites
Group related tests under a suite name. Common conventions:
connection— TLS, auth, server version, driver infoddl_dml/core_ddl_dml— CREATE, INSERT, UPDATE, DELETE, DROPdbeaver_introspection— DBeaver CE metadata queries (see below)- Library-specific features get their own suite (
batch,copy,orm,prepared, etc.)
DBeaver introspection suite
DBeaver CE cannot run headlessly in Docker (GTK3 SWT bug freezes the event loop under Xvfb).
Instead, the dbeaver_introspection suite in queries.yaml contains the exact catalog queries
DBeaver fires on connect, sourced from cockroachdb/cockroach#28309.
These queries are executed by every client, testing the same catalog surface a real DBeaver
connection would exercise.
Results Database
Exported to results/results_<timestamp>.duckdb with:
queriestable — the shared query catalog fromqueries.yaml(includesstubboolean)resultstable — all test outcomes (client, suite, test_name, status, detail, ts)coverageview — LEFT JOIN of queries to results (shows gaps, includesstubflag)
Open with just query-last-run or duckdb results/results_*.duckdb.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

