Skip to content

fix: preserve operation order in TransactionWriteSet#255

Merged
ThePumpingLemma merged 1 commit into
cashapp:mainfrom
polyatail:roo/transaction-write-set-ordering
Jun 12, 2026
Merged

fix: preserve operation order in TransactionWriteSet#255
ThePumpingLemma merged 1 commit into
cashapp:mainfrom
polyatail:roo/transaction-write-set-ordering

Conversation

@polyatail

@polyatail polyatail commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #254

Problem

TransactionWriteSet stores saves/puts/deletes/checks in four separate sets, and toTransactionWriteRequest emits them grouped by kind — all saves, then puts, then deletes, then checks — regardless of the order the caller added them.

DynamoDB returns a List<CancellationReason> on TransactionCanceledException that is positionally aligned with the TransactWriteItems in the submitted request. Because Tempest reorders the items by kind, a caller who interleaves operations (e.g. delete(B); save(A)) cannot reliably map a returned cancellation reason back to the operation that triggered it.

Fix

  • Introduce a WriteOperation sealed type (Save/Put/Delete/Check) and track the caller's insertion order in TransactionWriteSet.Builder.
  • Expose it as TransactionWriteSet.operations, populated in build(). It is kept out of the data class constructor to preserve the public equals/copy/destructuring signature; direct construction falls back to the historical save → put → delete → check order.
  • Replay operations in both toTransactionWriteRequest (covers the sync and async paths) and describeOperations, so the submitted request items — and the failure message — line up with the returned cancellation reasons.
  • Builder.addAll now replays the source builder's operations in order rather than re-adding bucket-by-bucket.

Scoped to tempest2 (AWS SDK v2).

Test plan

  • New transactionWritePreservesOperationOrderAcrossKinds adds a delete before a save and asserts both TransactionWriteSet.operations and the cancellation failure message reflect insertion order (Delete, Save) — fails under the old grouped behavior, passes now.
  • Existing LogicalDbTransactionTest, AsyncLogicalDbTransactionTest, WritingPagerTest, and AsyncWritingPagerTest pass.

🤖 Generated with Claude Code

polyatail added a commit to polyatail/tempest that referenced this pull request Jun 11, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TransactionWriteSet stored saves/puts/deletes/checks in separate sets and
toTransactionWriteRequest emitted them grouped by kind (all saves, then puts,
then deletes, then checks). DynamoDB returns a List<CancellationReason> that is
positionally aligned with the submitted TransactWriteItems, so grouping by kind
meant callers could not reliably map a cancellation reason back to the operation
that triggered it once operations were interleaved across kinds.

Track the caller's insertion order in TransactionWriteSet.Builder via a new
WriteOperation sealed type, expose it as TransactionWriteSet.operations, and
replay that order in both toTransactionWriteRequest and describeOperations.
The field is populated in build() and kept out of the data class constructor to
preserve the public equals/copy/destructuring signature; direct construction
falls back to the historical save -> put -> delete -> check order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@polyatail polyatail force-pushed the roo/transaction-write-set-ordering branch from 0a00137 to b2d30f3 Compare June 11, 2026 22:19
@ThePumpingLemma ThePumpingLemma merged commit ac3b850 into cashapp:main Jun 12, 2026
5 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.

tempest2: TransactionWriteSet discards operation order, breaking the DynamoDB cancellationReasons() ↔ TransactItems positional contract

2 participants