You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat: add transaction search_path mode for PgBouncer pooling - #26
Session-level SET search_path leaks between clients behind PgBouncer in transaction pooling: the next transaction runs on a server connection that still has another client's schema, so preview requests read and write the wrong schema.
What Changed
Opt-in SearchPathMode::Transaction: schema applied with SET LOCAL
DBAL driver middleware covering queries, exec and prepared statements
Schema commands skip the session SET in transaction mode
Integration tests on PostgreSQL 14/18 behind PgBouncer, new CI job
README docs; phpcs ^3.13 (3.7 is blocked by a Composer advisory)
Alternatives & Trade-offs
PgBouncer session pooling was rejected: one server connection per client, max_connections grows with autotests. Skipping SET LOCAL for the default schema saves round trips but inherits leaked session paths, so strict is the default.
Risk & Blast Radius
Middleware wraps every connection; default Session mode passes calls through unchanged
Transaction mode adds two round trips per statement outside a transaction
Non-transactional statements (CREATE INDEX CONCURRENTLY) fail for schemas that need SET LOCAL
Reviewer Guide
TransactionSearchPathConnection: implicit transaction, savepoint and raw transaction-control handling.
~1000 lines, mostly integration tests and README; the core logic is ~250 lines.
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds an opt-in transaction-scoped search_path mode to prevent schema leakage when using PgBouncer in transaction pooling by applying schema via SET LOCAL per transaction/statement.
Changes:
Introduces SearchPathMode::{Session,Transaction} and wiring in SchemaConnection to enable transaction-safe schema application.
Adds DBAL middleware (TransactionSearchPathDriver/Connection/Statement) to apply SET LOCAL search_path for queries, exec, and prepared statements.
Adds PgBouncer-backed integration tests, CI service setup, and documentation updates for the new mode and limitations.
File
Description
src/Doctrine/SearchPathMode.php
Defines the new search_path mode enum.
src/Doctrine/SchemaConnection.php
Integrates transaction-mode selection and middleware driver wrapping.
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
A batch such as BEGIN; INSERT INTO marker (v) VALUES ('x'); COMMIT is classified as a single BEGIN operation and sent unchanged, so the write receives no SET LOCAL and targets the server's default or leaked schema. Transaction tracking also remains active after the batch commits. In Transaction mode, reject transaction-control batches or handle their individual statements with the appropriate schema application.
Reconcile transaction and schema state after failed raw COMMIT
If a raw COMMIT fails on a deferred constraint, PostgreSQL ends the transaction, but the exception skips the state update here. The cached transaction and schema state then make the next query skip both implicit wrapping and SET LOCAL, so it runs against the pooled session's default or leaked schema. Reconcile transaction state and invalidate the schema cache on this error path, accounting for errors that leave a transaction open, and add a failed raw-commit integration test.
Track chained transactions after COMMIT or ROLLBACK AND CHAIN
COMMIT AND CHAIN and ROLLBACK AND CHAIN leave a new PostgreSQL transaction open, but this branch marks the connection idle. The next schema-dependent statement gets an implicit wrapper whose COMMIT prematurely commits the caller's chained transaction, so a later rollback cannot undo its writes. Recognize chained endings, keep transaction tracking active while resetting the schema cache, and cover both forms with integration tests.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Critical gaps in transaction-control parsing and prepared-statement mode validation can bypass SET LOCAL protection and cause writes to the wrong schema.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The regression test can miss a cross-schema write because the caught exception does not prove execution was prevented; it must also assert that public.marker contains no batch row.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Session-level
SET search_pathleaks between clients behind PgBouncer in transaction pooling: the next transaction runs on a server connection that still has another client's schema, so preview requests read and write the wrong schema.What Changed
SearchPathMode::Transaction: schema applied withSET LOCALSETin transaction mode^3.13(3.7 is blocked by a Composer advisory)Alternatives & Trade-offs
PgBouncer session pooling was rejected: one server connection per client,
max_connectionsgrows with autotests. SkippingSET LOCALfor the default schema saves round trips but inherits leaked session paths, so strict is the default.Risk & Blast Radius
CREATE INDEX CONCURRENTLY) fail for schemas that needSET LOCALReviewer Guide
TransactionSearchPathConnection: implicit transaction, savepoint and raw transaction-control handling.Context & Links
Description composed by pr-why
🤖 Generated with Claude Code