fix(backend): honor psycopg3 prepare_threshold=None to disable prepared statements
Why
django_vpg aims to be a drop-in for Django's stock psycopg backend — the same DATABASES dict should work on either, unchanged. The OPTIONS → driver-kwarg mapping diverged from psycopg3 in two ways that broke that promise:
prepare_threshold=Nonewas silently ignored. In psycopg3,prepare_threshold=Nonemeans disable prepared statements — the canonical setting for a transaction-mode PgBouncer withoutmax_prepared_statements. django_vpg coercedNone→ the default of5, leaving prepared statements on. A config that is safe on psycopg would send server-side prepared statements into a pooler that cannot honor them.- The on/off switch was a bespoke
pgbouncerbool with no psycopg/Django equivalent. libpq rejects unknown keys, so a config written for django_vpg was not portable back to the stock backend.
This surfaced from a real GlitchTip deployment: its historical psycopg config was prepare_threshold=None (prepared off, PgBouncer-safe), but under the Rust backend that spelling became a no-op.
What
prepare_thresholdnow follows psycopg3 exactly:- int
N→ prepare afterNuses (default5) 0→ prepare on first useNone→ disable prepared statements
- int
- The translation to the driver's
prepared_statementsbool + int threshold lives in one helper (_prepared_statement_params) shared by the sync and async wrappers, so they can't drift. pgbouncer=Trueis kept as a documented legacy alias forprepare_threshold=None— existing configs keep working; docs steer to the psycopg spelling.- README + CHANGELOG updated.
Tests
New TestPreparedStatementOptions covers default (on, threshold 5), explicit None (off), explicit int, 0 (immediate, not disabled), and both pgbouncer alias directions. Full backend suite green (60 passed), ruff clean.
Follow-up
The matching change in gt_rust.django_backend (glitchtip-rust) and the GlitchTip settings decision (disable via prepare_threshold=None, or enable end-to-end with max_prepared_statements on the poolers) will be handled separately.