Don't savepoint-wrap internal queries at rollback level 0 (Protocol=7.4-0) (#208) - #209
Open
davecramer wants to merge 1 commit into
Open
Don't savepoint-wrap internal queries at rollback level 0 (Protocol=7.4-0) (#208)#209davecramer wants to merge 1 commit into
davecramer wants to merge 1 commit into
Conversation
The per-query savepoint (SAVEPOINT _per_query_svp_; <query>; RELEASE _per_query_svp_) that protects internal driver queries -- notably the DEALLOCATE issued when a prepared statement is freed inside a transaction -- was applied whenever the ROLLBACK_ON_ERROR flag was set, without regard to the connection's rollback-on-error level. With Protocol=7.4-0 the user has explicitly asked for no automatic savepoint handling, typically because the server has no SAVEPOINT support (e.g. QuestDB, Materialize). There the injected SAVEPOINT is rejected and, inside a transaction, aborts it; freeing a prepared statement before COMMIT then silently discarded the transaction's rows. Gate the per-query savepoint on connInfo.rollback_on_error != 0 so that at level 0 internal queries are sent bare. Levels default(-1)/1/2 are unchanged. Verified on PostgreSQL 18: at the default level the DEALLOCATE cleanup is still wrapped, and with Protocol=7.4-0 it is sent as a bare DEALLOCATE.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #208 (scoped to the primary break; see "Not in this PR" below).
Problem
With
Protocol=7.4-0(rollback-on-error level 0), freeing a prepared statement inside a transaction still sendsOn a server without
SAVEPOINTsupport (QuestDB, Materialize, ...) theSAVEPOINTis rejected and aborts the transaction, so a free-before-commit silently loses the transaction's rows.Root cause
psqlODBC has two savepoint mechanisms:
_EXEC_SVP_…-- already gated by the rollback level (StartRollbackState), so7.4-0correctly disables it;_per_query_svp_-- wraps internal driver queries such as theDEALLOCATEfor a freed prepared statement. It was triggered solely by theROLLBACK_ON_ERRORsend flag and never consulted the rollback level.So at level 0 the internal
DEALLOCATEwas still savepoint-wrapped.Fix
Gate the per-query savepoint on the connection's rollback level in
CC_send_query_append:This is the single point every
ROLLBACK_ON_ERRORsend flows through (theDEALLOCATEsites in statement.c, connection.c, qresult.c). Levels default (-1),1and2are unchanged; only explicit7.4-0is affected -- internal queries are then sent bare.Verification (PostgreSQL 18)
Wire log:
SAVEPOINT _per_query_svp_;DEALLOCATE "…";RELEASE _per_query_svp_(unchanged);Protocol=7.4-0: bareDEALLOCATE "…".Functional: free-before-commit commits both rows at each level;
error-rollback-teststill passes. (The break itself only reproduces on a savepoint-less server; PostgreSQL accepts both forms, so it can't be exercised by the PG-based regression suite.)Not in this PR
The report's second point -- that a failing internal cleanup query is swallowed, so
SQLEndTran(SQL_COMMIT)returnsSQL_SUCCESSfor a transaction the server rolled back -- is a separate, broader error-propagation concern touching the destructor/commit paths for all servers. This PR fixes the concrete data loss (the internal query no longer fails at level 0); surfacing internal-query errors is better tracked as a follow-up.