Skip to content

Fix client-visible transaction status flag desync after DDL implicit commit - #3709

Open
flux423 wants to merge 1 commit into
dolthub:mainfrom
flux423:fix/drupal-transaction-status-flag-desync
Open

Fix client-visible transaction status flag desync after DDL implicit commit#3709
flux423 wants to merge 1 commit into
dolthub:mainfrom
flux423:fix/drupal-transaction-status-flag-desync

Conversation

@flux423

@flux423 flux423 commented Aug 18, 2026

Copy link
Copy Markdown

Problem

ServerInTransaction (SERVER_STATUS_IN_TRANS) is reported based solely on
ctx.GetTransaction() != nil in server/handler.go::setConnStatusFlags().
That's also true for the engine's implicit, per-statement transaction used
for ordinary autocommit statements — not just for an explicit client
BEGIN.

Combined with a second gap — TransactionCommittingIter.Close() in
sql/rowexec/transaction_iters.go clears the transaction on an implicit
commit (e.g. a DDL statement issued mid-explicit-transaction) but never
clears ctx.GetIgnoreAutoCommit(), unlike the explicit COMMIT/ROLLBACK
handlers in sql/rowexec/transaction.go, which do — the very next ordinary
autocommit statement issued right after such a DDL gets misreported to the
client as still being inside a transaction.

This desyncs any MySQL client whose own transaction bookkeeping mirrors the
server status flag — notably PHP's PDO_MySQL, whose PDO::inTransaction()
and the guard inside PDO::beginTransaction() read exactly this flag. The
client correctly believes no transaction is open (it never issued a
matching COMMIT/ROLLBACK for anything), then refuses its own next
explicit BEGIN client-side, before any query reaches the wire, with
"There is already an active transaction".

Real-world repro

Found via Drupal core's drush site:install standard: an unmodified Drupal
installation, using Drupal's stock mysql PDO driver (no custom driver, no
Drupal patches), failed late in install (the site-configure step, after all
~30 standard-profile modules had already installed their schema
successfully) with exactly this PDOException. The identical install
completes cleanly against MySQL 8.0.46 with the same Drupal codebase and
command.

Minimal reproduction

$pdo->beginTransaction();
$pdo->exec('CREATE TABLE t (id INT PRIMARY KEY)'); // implicit commit
$pdo->exec('INSERT INTO t VALUES (1)');             // ordinary autocommit statement

var_dump($pdo->inTransaction());
// MySQL 8.0:                false (correct)
// Dolt (before this fix):   true  (incorrect - no BEGIN was issued for this)

$pdo->beginTransaction();
// MySQL 8.0:                succeeds
// Dolt (before this fix):   PDOException: There is already an active transaction

Fix

  • sql/rowexec/transaction_iters.go: TransactionCommittingIter.Close()
    now calls ctx.SetIgnoreAutoCommit(false) when committing an implicit
    transaction, mirroring the explicit COMMIT/ROLLBACK handlers.
  • server/handler.go: setConnStatusFlags() now additionally requires
    ctx.GetIgnoreAutoCommit() before reporting ServerInTransaction, as
    defense-in-depth so an implicit per-statement transaction can never set
    the client-visible flag even if a future code path reintroduces a similar
    gap.

Testing

  • Added TestSetConnStatusFlagsInTransaction (server/handler_test.go),
    covering no-transaction, implicit-per-statement-transaction, and
    explicit-client-transaction cases.
  • go test ./server/... ./sql/rowexec/...: pass.
  • go test ./enginetest/... -run "Transaction|Commit|Autocommit": pass,
    including the create_table_queries_are_implicitly_committed and related
    DDL-implicit-commit suites.
  • End-to-end: rebuilt Dolt against this branch and re-ran
    drush site:install standard against an unmodified Drupal 11.4-dev
    core — install now completes successfully (previously failed at the same
    point on unpatched Dolt 2.1.10, 2.3.0, and main).

Related, separately-filed issues from the same investigation (distinct root
causes, not fixed by this PR): #3706 (constraint-violation SQLSTATE
mapping), #3707 (SHOW INDEX ... WHERE key_name filter ignored).

…commit

ServerInTransaction (SERVER_STATUS_IN_TRANS) was reported based solely on
ctx.GetTransaction() != nil, which is also true for the engine's implicit,
per-statement transaction used for ordinary autocommit statements. Combined
with a second bug -- TransactionCommittingIter.Close() clearing the
transaction on an implicit commit (e.g. a DDL statement issued mid-explicit-
transaction) without also clearing ctx.GetIgnoreAutoCommit() -- the very
next ordinary autocommit statement after such a DDL was misreported to the
client as still being inside a transaction.

This desyncs any MySQL client whose own BEGIN/COMMIT bookkeeping mirrors the
server status flag (e.g. PHP's PDO_MySQL, whose PDO::inTransaction() and
PDO::beginTransaction() guard read this exact flag): the client believes no
transaction is open (correctly, since it never issued a matching COMMIT/
ROLLBACK for anything), then refuses its own next explicit BEGIN client-side
with "There is already an active transaction", even though the server was
never asked to begin one.

Reproduced against Drupal core's `drush site:install standard`: an
unmodified Drupal installation using Drupal's stock mysql PDO driver failed
late in install (site-configure step) with exactly this PDOException. MySQL
8.0.46 does not exhibit this: DDL correctly implicit-commits and the
transaction-status flag correctly resets for subsequent autocommit
statements.

Minimal reproduction (both statements executed via PDO against a session
with an explicit BEGIN already in effect):

    BEGIN;
    CREATE TABLE t (id INT PRIMARY KEY);  -- implicit commit
    INSERT INTO t VALUES (1);             -- ordinary autocommit statement

    MySQL 8.0:  PDO::inTransaction() is false after both statements.
    Dolt (before this fix): PDO::inTransaction() is true after the INSERT,
    with no BEGIN ever issued for it.

Fixes:
- sql/rowexec/transaction_iters.go: TransactionCommittingIter.Close() now
  clears ctx.SetIgnoreAutoCommit(false) when committing an implicit
  transaction, mirroring what the explicit COMMIT/ROLLBACK handlers in
  sql/rowexec/transaction.go already do.
- server/handler.go: setConnStatusFlags() now additionally requires
  ctx.GetIgnoreAutoCommit() before reporting ServerInTransaction, so an
  implicit per-statement transaction can never set the client-visible flag
  even if a future code path reintroduces a similar gap.

Verified: full `server` and `sql/rowexec` package test suites pass, plus
targeted `enginetest` transaction/DDL-implicit-commit suites. Re-ran
Drupal's `drush site:install standard` end-to-end against a Dolt build with
this fix applied (unmodified Drupal core, stock mysql driver, zero Drupal
patches): install now completes successfully.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants