Resolve set_config() arguments from the Bind message - #1298
Resolve set_config() arguments from the Bind message#1298IgorOhrimenko wants to merge 3 commits into
Conversation
set_config() called with bound parameters can't be intercepted by the query parser (arguments aren't A_Const), so the session state change went untracked and the server connection poisoned subsequent clients (e.g. search_path='' -> 42P01 on unqualified names). Instead of passing the statement through silently, route it as Command::DirtyQuery: it executes like a regular query, but the server connection is marked dirty at pairing time, so its session state is reset (RESET ALL) before the connection is returned to the pool.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This is a bug in the parser. It should be able to extract |
Extend the parser to read $n arguments from the Bind message instead of only handling constants, so a parameterized set_config() is tracked like any other SET. DirtyQuery is kept for arguments we still cannot resolve: no Bind message, a missing or non-text parameter, or an expression. Describing a portal that returns a row now answers with the row description instead of NoData, which made libpq reject the synthesized response with 'D message without prior T'. A statement that returns no rows now correctly gets NoData instead of an empty row description.
Runs against a dedicated database with a single server connection, so a poisoned connection is always handed to the next client.
|
Thanks — done. The parser now pulls
Making those calls take the Tests added at three levels: parser (bound values, NULL, both fallbacks), protocol (the full extended-protocol exchange — fails without the |
Problem
set_config()interception (#1055, #1072) only handles constant arguments. Over the extended protocol with bound parameters:parse_args()returnsNone(arguments aren'tA_Const) and the statement is passed through as a plainCommand::Querywith no session-state tracking. The server connection is left with an emptysearch_path, goes back into the pool, and every subsequent client that gets it fails with42P01 relation "..." does not existon unqualified table names — until the connection is recycled or the pooler is restarted.Reproduction (verified on v0.1.51 with
query_parser = "on", transaction pooling): https://gist.github.com/IgorOhrimenko/703ef2ab3541f8448372ba83f7c78fb2Fix
Resolve
$narguments from the Bind message, so a parameterizedset_config()is tracked exactly like a constant one. The boolean third argument is decoded from either wire format.Command::DirtyQueryremains as a fallback for arguments that still cannot be resolved — no Bind message at all, a missing or non-text parameter, or an expression such asset_config('search_path', current_setting('search_path'), false). Such a statement executes normally, but the server connection is marked dirty at pairing time so the existing checkin cleanup (RESET ALL,pg_advisory_unlock_all(),DISCARD TEMP) runs before it is handed to another client. Marking has to happen when the client is paired with the server: checkin and cleanup happen insideexecute(), so marking afterwards is too late.The second commit also fixes the synthesized response, which this change made reachable for real clients: describing a portal that returns a row answered with
NoData, so libpq rejected the reply withD message without prior T. A statement that returns no rows now correctly getsNoDatainstead of an empty row description.Scope
This fixes the
set_configpath only. A pooled connection can still end up with an emptysearch_paththrough an unrelated sequence involving noset_config()at all, whichpg_dump -t <table>emits — fixed separately in #1299.Testing
set_config()resolves toCommand::Setwith the bound values; a NULL parameter becomes a reset;$1with no Bind and an expression argument both fall back toCommand::DirtyQueryon a write route.fake.rschange.integration/python/test_set_config_leak.py): a client poisons the pool via bound parameters, a later client must see a cleansearch_path. Runs against a dedicated database with a single server connection so the reuse is deterministic. Verified to fail onmainand pass with this branch.