Skip to content

feat(drizzle): tag queries issued inside transactions#18

Merged
veksen merged 2 commits into
masterfrom
feat-patch-transactions
Jul 1, 2026
Merged

feat(drizzle): tag queries issued inside transactions#18
veksen merged 2 commits into
masterfrom
feat-patch-transactions

Conversation

@veksen

@veksen veksen commented Jul 1, 2026

Copy link
Copy Markdown
Member

Fixes a gap surfaced while dogfooding on d2armory: queries issued inside db.transaction(...) had no source provenance — Query Doctor showed them with only db_driver, no file/func_name.

Root cause (verified, not assumed)

patchDrizzle patches the query methods on the top-level db. But db.transaction(cb) hands cb a fresh tx object whose select/insert/update/delete/execute were never patched, so currentCaller is never set for those queries. db_driver still appears because it's added in the session-level prepareQuery patch (shared prototype), but the caller-derived tags are dropped.

Reproduced on pglite:

outside transaction   /*db_driver='drizzle',file='…:12:40',func_name='outsideTx'*/
inside  transaction   /*db_driver='drizzle'*/

Fix

Wrap transaction so the tx handed to the callback is patched the same way — recursively, so nested savepoint transactions are covered too. The wrapper only augments the callback; it doesn't touch transaction control flow.

  • file is now always captured inside a transaction.
  • func_name resolves when the callback (or its enclosing function) is named; anonymous async (tx) => … arrows still get file alone (correct — there's no symbol).

Verification

New test/transaction.spec.ts (6 tests), all grounded in real behavior — and the 4 provenance tests fail on pre-fix code, pass on the fix:

  • query inside a tx gets file; anonymous arrow → no func_name.
  • a named tx callback carries its func_name.
  • nested savepoint transactions are tagged.
  • commit persists / rollback discards — semantics preserved.
  • concurrent transactions each keep their own caller (no clobber).

Full suite: 32 pass (dual ESM/CJS build clean). Drizzle-only — mikroorm's onQuery hook and typeorm's QueryRunner.query patch both already fire inside transactions.

Version

drizzle 0.5.0 → 0.6.0 (new capability; patchDrizzle API unchanged, so a drop-in bump for consumers).

🤖 Generated with Claude Code

veksen and others added 2 commits July 1, 2026 00:13
`patchDrizzle` patched the top-level db, but `db.transaction(cb)` hands `cb` a
fresh `tx` object whose query methods were never patched — so every query built
inside a transaction lost its `file`/`func_name` tags. Only `db_driver` (added
in the session-level `prepareQuery` patch) survived, which is why transaction
writes showed up in Query Doctor with no source provenance.

Wrap `transaction` (recursively, for nested savepoint transactions) so the `tx`
handed to the callback gets the same per-query caller capture. `file` is now
always present inside a transaction; `func_name` resolves when the callback (or
its enclosing function) is named — anonymous `async (tx) => …` arrows still get
`file` alone, as expected.

Verified against pglite: all query types, nested savepoints, concurrency, and —
crucially — commit/rollback semantics are preserved (the wrapper only augments
the callback, it doesn't change transaction control flow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Transaction query tagging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@veksen veksen merged commit 73f8624 into master Jul 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant