Skip to content

Expand Counter/TTL functionality to UpdateItem, BatchWriteItem, and TransactWriteItems#1923

Merged
ianbotsf merged 3 commits into
feat-ddb-mapper-mainfrom
ttl-counter-interceptors-expansion
Jun 17, 2026
Merged

Expand Counter/TTL functionality to UpdateItem, BatchWriteItem, and TransactWriteItems#1923
ianbotsf merged 3 commits into
feat-ddb-mapper-mainfrom
ttl-counter-interceptors-expansion

Conversation

@ianbotsf

Copy link
Copy Markdown
Collaborator

Issue #

Related to #472

Description of changes

This change expands the utilization of CounterInterceptor and TtlInterceptor to cover the new mutating operations UpdateItem, BatchWriteItem, and TransactWriteItems. As part of this work a few other related changes were made:

  • During testing, it became apparent that the update { } DSL's previous behavior of replacing any prior updates was flawed. This PR updates update (heh!) to append to any prior updates rather than replace.
  • Changed the type of attribute TtlFields from Set<Pair<String, Long>>> to Map<String, Long>. Maps are more natural & idiomatic than sets of pairs and the single-value-per-key constraint matches the intended use case better (sets of pairs allow duplicated keys).

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@ianbotsf ianbotsf requested a review from a team as a code owner June 12, 2026 15:34
@ianbotsf ianbotsf added acknowledge-api-break Acknowledge that a change is API breaking and may be backwards-incompatible. Review carefully! no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly. labels Jun 12, 2026
@github-actions

Copy link
Copy Markdown

A new generated diff is ready to view.

Comment on lines 41 to 43
fun AttributePath.update(value: Expression) {
_updates += UpdateClauseExpr(action, this, value)
}

@luigi617 luigi617 Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: I don't understand why for update operation, you append the value to the list. For example in CounterInterceptor.augmentUpdateExpr it try to set the counter + 1, but if the conter already exists, you would store 2 counters.
E.g. User calls:

 table.updateItem {
      key = Key("myId")
      update {
          set { attr["counter1"] = 99 }  // user explicitly touches a counter field
      }
  }

The interceptor's modifyBeforeSerialization calls augmentUpdateExpron the user's update expression, appending another SET counter1 = if_not_exists(counter1, 0) + 1.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. Attribute paths cannot be repeated in an update expression, even in separate clauses, so a user attempting to manually update a counter/TTL field would cause a runtime error during high-low conversion. I'll rework the updates field into a map that replaces updates rather than appending them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll withdraw the approval and once you're done, I'll review it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in latest revision. This turned out to be a good change since it simplified/reduced a lot of code! 😀

@luigi617 luigi617 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks incredibly good to me. Just left a question that I did not understant.

@ianbotsf ianbotsf requested a review from luigi617 June 16, 2026 18:39
@github-actions

Copy link
Copy Markdown

A new generated diff is ready to view.

val clauseExp = UpdateClauseExpr(action, this, value)
val previousUpdate = updates.put(clauseExp.target, clauseExp)
if (previousUpdate != null) {
println("Warning: Replacing previous update of ${previousUpdate.target}") // FIXME log a proper warning

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: are you going to fix this in this PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, threading the logger through DDB Mapper will be its own separate PR.

@ianbotsf ianbotsf merged commit f48c6f4 into feat-ddb-mapper-main Jun 17, 2026
21 checks passed
@ianbotsf ianbotsf deleted the ttl-counter-interceptors-expansion branch June 17, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

acknowledge-api-break Acknowledge that a change is API breaking and may be backwards-incompatible. Review carefully! no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants