In the following code, the contacts are added to the contacts attribute even if they already exists in it. But I would like to prevent duplicates because it would make no sense to have the same contact twice. I tried to use contains with no success. Do you have any idea on how I could manage that?
const contact = event.contactId
const params = {
TableName,
Key: {
userId: event.userId
},
ReturnValues: 'UPDATED_NEW',
UpdateExpression: 'set #contacts = list_append((if_not_exists(#contacts, :empty_list), :contact)',
ExpressionAttributeNames: {
'#contacts': 'contacts'
},
ExpressionAttributeValues: {
':contact': [contact],
':empty_list': []
}
} // TODO: Must be unique IDs
dynamodb.update(params, (err, data) => {
if (err) {
console.log(err)
callback(err)
} else {
console.log(data)
const response = {
statusCode: 200,
message: "Successfully added contact " + contact,
updatedContacts: data.Attributes.contacts
}
callback(null, response)
}
})
Storing the unique values This means we need to create a new table that has the unique values (the email address, in this example) as keys. Since DynamoDB supports composite keys, where the key is made up of a partition key and a sort key, it's best to store the type of the unique constraint as well as the value.
UpdateItem behaves as an “UPSERT” operation. This means that if you try to update an item that doesn't exist, DynamoDB will automatically create it for you. Like with PutItem , you can add conditions to your UpdateItem API calls to modify this behavior, but there is no way to implement it service-side.
Can we update the sort key in DynamoDB? No, you can not update the sort key after the table is provisioned. However, you can create a new table and put the existing data in the newly created table, and delete the old table.
You can use the UpdateItem operation to implement an atomic counter—a numeric attribute that is incremented, unconditionally, without interfering with other write requests. (All write requests are applied in the order in which they were received.) With an atomic counter, the updates are not idempotent.
To the best of my knowledge, you need to use a Set
instead of a List
of contact IDs to achieve what you want in only one call to DynamoDB. That being said, there are two ways to accomplish it with a Set
, and it is possible to do this with a list if you’re okay making a read before you save the new contact.
Option 1
DynamoDB supports using ADD
for Set attributes, and a Set will guarantee that no duplicate IDs are present. You can use ADD
to silently succeed without causing a duplicate contact using this method.
If you use ADD
, your update expression would be
ADD #contacts :contact
This method will work whether your contact IDs are a Number or a String.
Option 2
Use a condition expression to prevent the contact from being added if it’s already present. DynamoDB has a contains(path, operand)
function that supports String and StringSet attributes.
You would use the same update expression as in Option 1, and the condition expression for this would be
attribute_not_exists(#contacts) OR NOT contains(#contacts, :contact)
This option will let you know that the contact is already present because if the contact is already present, you will get a ConditionalCheckFailed
response from DynamoDB.
Option 3
Begin by reading the current List
of contacts from DynamoDB. Verify in your application that you will not be adding a duplicate. If the new contact would be a duplicate, then you have nothing more to do. If the contact would be a duplicate, then you can save the updated contact list using these expressions
UpdateExpression: “SET #contacts = :updatedContacts”
ConditionExpression: "#contacts = :oldContacts”
In this case, :oldContacts
should be the list that you initially read from DynamoDB.
The drawback of this approach is that a concurrent update to the list could cause a valid update to fail.
Option 4
This is the most involved of all the approaches. You can add a version
attribute to the items in the table, and like Option 3, you first read the item, verify that the new contact is not a duplicate, and then make a conditional write. However, in this case, your conditional write would include
UpdateExpression: “SET #contacts = :updatedContacts, #version = :newVersion”,
ConditionExpression: “#version = :oldVersion”
This is known as Optimistic Locking. While not part of the JavaScript SDK, it is simple enough to implement on your own in this case.
Concluding Notes
You should be aware that Options 3 and 4 won’t work for a global table which is being concurrently update in multiple regions. Option 2 may fail to inform you of an attempt to add a duplicate under those same conditions, but it will still guarantee that there are no duplicates present. (Global Tables are eventually consistent between regions, so the condition expression may evaluate to true for one region, but not for another.)
Finally, you may find the Comparison Operator and Function Reference helpful if you want more details about any of the functions that are available to use in a condition or update expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With