fix(powersync): applyTransaction hangs forever when a transaction mixes delete+insert on one collection for a same-millisecond tie#1649
Conversation
Update main
Update From Upstream
chore: Update from upstream
…e+insert on one collection
📝 WalkthroughWalkthroughChanges the diff-operation readback query in PowerSyncTransactor to order by operation_id instead of timestamp, fixing a hang when delete and insert operations occur within the same millisecond. Includes a new regression test, type-only import fixes, and a changeset entry. ChangesPowerSync Transaction Completion Fix
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/powersync-db-collection/tests/powersync.test.ts (1)
306-310: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winStatic analysis flags raw table-name interpolation; also comment references pre-fix ordering.
trackedTableNamehere is internally generated (random hex suffix), not user input, so this isn't an exploitable injection — but every other query inPowerSyncTransactor.tswraps identifiers withsanitizeSQLfor consistency and to keep the SAST finding clean. Separately, the comment on Line 307-308 describes the oldORDER BY timestamp DESCbehavior; since production code now orders byoperation_id DESC, consider clarifying that this simulates the pre-fix bug rather than current behavior, to avoid confusing future readers.🔒 Optional consistency fix
- await context.execute( - `UPDATE ${trackedTableName} SET timestamp = '9999-12-31T23:59:59.999Z'`, - ) + await context.execute( + sanitizeSQL`UPDATE ${trackedTableName} SET timestamp = '9999-12-31T23:59:59.999Z'`, + )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/powersync-db-collection/tests/powersync.test.ts` around lines 306 - 310, Update the `context.execute` statement in `powersync.test.ts` to use the same identifier sanitization pattern as `PowerSyncTransactor.ts` by wrapping `trackedTableName` with `sanitizeSQL`, so the SAST finding is consistent with the rest of the codebase. Also revise the nearby test comment around the timestamp tie simulation to explicitly say it is modeling the pre-fix `ORDER BY timestamp DESC` behavior, not the current `operation_id DESC` ordering, so the intent stays clear for future readers.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/powersync-db-collection/tests/powersync.test.ts`:
- Around line 306-310: Update the `context.execute` statement in
`powersync.test.ts` to use the same identifier sanitization pattern as
`PowerSyncTransactor.ts` by wrapping `trackedTableName` with `sanitizeSQL`, so
the SAST finding is consistent with the rest of the codebase. Also revise the
nearby test comment around the timestamp tie simulation to explicitly say it is
modeling the pre-fix `ORDER BY timestamp DESC` behavior, not the current
`operation_id DESC` ordering, so the intent stays clear for future readers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ad426405-63eb-459b-bc14-e7849029753b
📒 Files selected for processing (3)
.changeset/hot-bats-camp.mdpackages/powersync-db-collection/src/PowerSyncTransactor.tspackages/powersync-db-collection/tests/powersync.test.ts
🎯 Changes
Addresses #1647
When a transaction deletes key
aand inserts keybon the same collection, the diff trigger writes two rows to the tracked table, usually within the same millisecond.handleOperationWithCompletionreads back "the last mutation's diff row" withORDER BY timestamp DESC LIMIT 1, which ties on the timestamp and returns theDELETErow, but pairs its id/timestamp with theINSERTmutation type. The resulting pending operation{id: a, op: INSERT, ts: T}never matches anythingresolvePendingForemits, soapplyTransactionhangs forever.The suggested (and verified) fix is to order the readback by the tracked table's
operation_id, theAUTOINCREMENTprimary key.✅ Checklist
pnpm test.🚀 Release Impact
Summary by CodeRabbit
Bug Fixes
Chores