DynamoDB if attribute doesn't exist create it, in node.js - amazon-dynamodb

I'm trying to update a user objects attribute which may not exist.
The attribute is called claimed which itself will have a property for each currency type #c1 (USD, euro, ect). A user can have multiple currencies but starts with none so claimed may or may not exist on a user object.
My initial draft was:
let params = {
TableName: 'myproject-user',
Key: {"id":req.user.sub},
UpdateExpression: 'set claimed.#c1 = :o',
ExpressionAttributeValues:{
":o": req.body.currency
},
ExpressionAttributeNames:{ "#c1": req.body.currency.type },
ReturnValues:"UPDATED_NEW"
};
This returns the error:
"Error: ValidationException: The document path provided in the update expression is invalid for update"
I've tried some variations using if_not_exists but I can't seem to get it working. How can I modify the params to have the desired effect?

you can use ConditionExpression: "attribute_not_exists(<YOUR_ATTRIBUTE>)"
for example:
let params = {
TableName: 'myproject-user',
Key: {"id":req.user.sub},
UpdateExpression: 'set claimed.#c1 = :o',
ExpressionAttributeValues:{
":o": req.body.currency
},
ExpressionAttributeNames:{ "#c1": req.body.currency.type },
ConditionExpression: "attribute_not_exists(claimed.#c1)",
ReturnValues:"UPDATED_NEW"
};

Related

JavaScript DynamoDB UpdateItem ADD UpdateExpression

I am running into an issue with the JavaScript v3 client for DynamoDB. According to the AWS documentation, I should be able to create an UpdateExpression which uses an ADD keyword to add an element to a list. When attempting this, it throws an error not recognizing the update expression.
If I run the same command using SET instead of ADD it works just fine, however, SET as noted in the documentation replaces the current value, and does not add to the list.
The Error:
"errorMessage": "Invalid UpdateExpression: Syntax error; token: \"=\", near: \"activeUsers = :au\"",
The Code:
const joinParams = {
TableName: tableName,
Key: {
id: myTable.Items[0].id
},
UpdateExpression: "SET activeUsers = :ac",
ExpressionAttributeValues: {
":ac": [user]
}
};
await docClient.update(joinParams).promise();
Take a look at the answer to a similar question here.
I think your syntax after the ADD is slightly off.
UpdateExpression: "ADD activeUsers :ac", <!-- remove the "="

DynamoDB update - "ValidationException: An operand in the update expression has an incorrect data type"

I am trying to append to a string set (array of strings) column, which may or may not already exist, in a DynamoDB table. I referred to SO questions like this and this when writing my UpdateExpression.
My code looks like this.
const AWS = require('aws-sdk')
const dynamo = new AWS.DynamoDB.DocumentClient()
const updateParams = {
// The table definitely exists.
TableName: process.env.DYNAMO_TABLE_NAME,
Key: {
email: user.email
},
// The column may or may not exist, which is why I am combining list_append with if_not_exists.
UpdateExpression: 'SET #column = list_append(if_not_exists(#column, :empty_list), :vals)',
ExpressionAttributeNames: {
'#column': 'items'
},
ExpressionAttributeValues: {
':vals': ['test', 'test2'],
':empty_list': []
},
ReturnValues: 'UPDATED_NEW'
}
dynamo.update(updateParams).promise().catch((error) => {
console.log(`Error: ${error}`)
})
However, I am getting this error: ValidationException: An operand in the update expression has an incorrect data type. What am I doing incorrectly here?
[Update]
Thanks to Nadav Har'El's answer, I was able to make it work by amending the params to use the ADD operation instead of SET.
const updateParams = {
TableName: process.env.DYNAMO_TABLE_NAME,
Key: {
email: user.email
},
UpdateExpression: 'ADD items :vals',
ExpressionAttributeValues: {
':vals': dynamo.createSet(['test', 'test2'])
}
}
A list and a string set are not the same type - a string set can only hold strings while a list may hold any types (including nested lists and objects), element types don't need to be the same, and a list can hold also duplicate items. So if your original item is indeed as you said a string set, not a list, this explains why this operation cannot work.
To add items to a string set, use the ADD operation, not the SET operation. The parameter you will give to add should be a set (not a list, I don't know the magic js syntax to specify this, check your docs) with a bunch of elements. If the attribute already exists these elements will be added to it (dropping duplicates), and if the attribute doesn't already exit, it will be set to the set of these elements. See the documentation here: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-UpdateExpression

Add to list only if string doesn't already exist in DynamoDB table

I'm trying with the following code
{
ExpressionAttributeNames: {
"#items": "items"
},
ExpressionAttributeValues: {
":item": [slug]
},
Key: {
listId: listId,
userId: userData.userId,
},
UpdateExpression: "SET #items = list_append(#items,:item)",
ConditionExpression: "NOT contains (#items, :item)",
TableName: process.env.listsTableName,
}
but the item is still added even if string already exists in the list. What am I doing wrong?
The list structure is like so:
{
Item: {
userId: userData.userId,
listId: crypto.createHash('md5').update(Date.now() + userData.userId).digest('hex'),
listName: 'Wishlist',
items: [],
},
TableName: process.env.listsTableName,
};
Later Edit: I know I should use SS as it does the condition for me but SS doesn't work in my context because SS can't be empty.
As the documentation explains, the contains() function only works on a string value (checking for a substring) or a set value (checking for membership). But in your case, you don't have a set but rather a list - with are different things in DynamoDB.
If all the items which you want to add to this list are strings, and you anyway don't want duplicates in the list, the most efficient way would be to stop using a list, and instead use the set-of-strings (a.k.a. SS) type. To add an item to the set (without duplicates), you would simply use "ADD #items :item" (no need for any additional condition - duplicates will not be added).

dynamo db FilterExpression, find in json object using value as key

It is possible to somehow filter results by key name that stored in the same object?
I have JSON object "keys", in property "default" stored key of the object that I need. Is it somehow possible to filter like that keys[keys.default].type = some_type?
var params = {
TableName: 'TABLE_NAME',
IndexName: 'TABLE_INDEX', // optional (if querying an index)
KeyConditionExpression: 'myId = :value',
FilterExpression: '#kmap[#kmap.#def].#tp = :keyval',
ExpressionAttributeNames: {names with special characters
'#kmap': 'keys',
'#tp': 'type',
'#def': 'default'
},
ExpressionAttributeValues: { // a map of substitutions for all attribute values
':value': '1',
':keyval': 'some_type'
},
Limit: 10, // optional (limit the number of items to evaluate)
ProjectionExpression: "displayName, #kmap",
ReturnConsumedCapacity: 'TOTLAL', // optional (NONE | TOTAL | INDEXES)
};
docClient.query(params, function(err, data) {
if (err) ppJson(err); // an error occurred
else ppJson(data); // successful response
});
I'm pretty sure the answer is no.
This keys[keys.default] is not even valid json, as far as I can tell.
Of course, you can do this in two steps:
First, query to get the default key
Then query to get the value
Don't forget, filters are obly applied to the result set - it still requires a libear traversal as specified by your Query or Scan operation.
So you can probably more easily run your query on the client.
And lastly, if this is a typical query ypu need to perform, as an optimization, you can lift the default key and value to be top level attributes on the item. Thrn you can actually create a GSI on that attribure and can actually do efficient lookups.

How to update item by primary key and other conditions?

I am trying to update item by email (HASH PK), id and verifyToken. My query looks like this:
params =
TableName: 'users'
Key:
email:
S: 'example#email.com'
AttributeUpdates:
verified:
Action: 'PUT'
Value:
BOOL: true
verifyToken:
Action: 'DELETE'
ExpressionAttributeValues:
':id': { S: '123' }
':verifyToken': { S: 'XXX' }
ConditionExpression: 'id = :id and verifyToken = :verifyToken'
dynamodb.updateItem(params)
In other words I want to update Item where email = 'example#email.com' AND id = '123' AND verifyToken = 'XXX', but I am getting following error:
Can not use both expression and non-expression parameters in the same request:
Non-expression parameters: {AttributeUpdates}
Expression parameters: {ConditionExpression}
You are combining legacy parameters (AttributeUpdates), which are only there for backwards compatibility, with expression parameters (ConditionExpression). As the error states, you cannot do that.
You need to use an UpdateExpression in conjunction with your ConditionExpression.
It would be something like this. You may need to use expression attribute names/values in the UpdateExpression:
ConditionExpression: 'id = :id and verifyToken = :verifyToken'
UpdateExpression: 'SET verified = true, REMOVE verifyToken'
See this documentation for more information on update expressions

Resources