Dynamodb PutItem operation with conditional expression

0

Context

Have a DDB table with schema:

HashKey: UserId
SortKey: UUID
Attribute: status

Question

Want to compose a putItem request with below condition:

  1. If userId does not exist, write to the table with PENDING status
  2. If userId already exist, only allow write if the existing record is NOT in PENDING status. In another word, if the existing record is in SUCCESS status, allow write to the table.

Expectation

Write Request: Andy(UserId) 123(UUID) PENDING Only allow write success if there no existing Andy(UserId) PENDING in the table already, that is, at any time, the table should only have one record with Andy(UserId) PENDING.

What I have tried

First try:

Expression.builder()
   .expression("#status <> :pendingStatus")
   .putExpressionName("#status", "status")
   .putExpressionValue(":pendingStatus", AttributeValue.builder().s("PENDING").build())
   .build();

Does not work as expected: if Andy(UserId) 123(UUID) PENDING already exist in the table, and I want to write Andy(UserId) 456(UUID) PENDING, the operation should fail with conditional check failure, but it was succeeding.

Second try:

Expression.builder()
   .expression("(attribute_exists(userId) AND #status <> :pendingStatus)")
   .putExpressionName("#transactionStatus", "transactionStatus")
   .putExpressionValue(":pendingStatus",
                       AttributeValue.builder()
                                     .s("PENDING")
                                     .build())
   .build();

Does not work as expected: This condition does not allow new userId to be inserted into the table.

Third try:

Expression.builder()
   .expression("(attribute_exists(userId) AND #status <> :pendingStatus) OR attribute_not_exists(userId)")
   .putExpressionName("#transactionStatus", "transactionStatus")
   .putExpressionValue(":pendingStatus",
                       AttributeValue.builder()
                                     .s("PENDING")
                                     .build())
   .build();

Does not work as expected: if Andy(UserId) 123(UUID) PENDING already exist in the table, and I want to write Andy(UserId) 456(UUID) PENDING, the operation should fail with conditional check failure, but it was succeeding.

I think either I have to change change the table key schema or I need a working condition expression to achieve this, any help would be appreciated.

asked a year ago1745 views
2 Answers
1

What you are trying to do cannot be done with a simple ConditionExpression. Conditions are only evaluated on the item on which you are accessing, not on any other item in the table. For example, you want to base your condition on item Andy(UserId) 123(UUID) PENDING but you are writing with item Andy(UserId) 444(UUID) PENDING, to DynamoDB these are 2 completely different items and that is why you are not seeing the expected behaviour from your conditional writes.

This blog will help you to implement your logic in a different manner, however, its not so straight forward:

https://aws.amazon.com/blogs/database/simulating-amazon-dynamodb-unique-constraints-using-transactions/

profile pictureAWS
EXPERT
answered a year ago
0

Looking at your original ask:

Write Request: Andy(UserId) 123(UUID) PENDING Only allow write success if there no existing Andy(UserId) PENDING in the table already, that is, at any time, the table should only have one record with Andy(UserId) PENDING.

I think the condition statement should be:

attribute_not_exists(UserId) or (attribute_exists(UserId) and UserStatus <> :pendingstatus)

As I read this, it means:

  • Allow access when the UserId doesn't exist or
  • Allow access when the UserId does exist and the UserStatus is not PENDING

I tested this on a database and it allows me to add a new user; I cannot update a user when the status is PENDING; but I can update a user when the status is anything else.

Also: Note that Status is a reserved word in DynamoDB so you should consider using another attribute name - for my table I used UserStatus instead. While you can workaround and user a keyword it's easier not to.

profile pictureAWS
EXPERT
answered a year ago
  • Hi, this condition does not seem to work. I tested it with below case: Record already in DB: Andy(UserId) 123(UUID) PENDING

    Try to insert record: Andy(UserId) 444(UUID) PENDING

    Expected: failure, as Andy already exist and in PENDING state Actual: successfully inserted record

  • I don't know what to say - I tested it here and that condition won't let me put another user in with the same UserId where the status is PENDING. If the status is something else, it works fine. The one change I made was to change the attribute status to UserStatus so that it isn't a DynamoDB reserved word. I know that you can work around this but it's easier not to have to do that. Answer updated to reflect that.

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions