How can I update a nested field in DynamoDB? - amazon-web-services

I defined a DynamoDB table and it has a nested field site. I'd like to update the field site.enable in below code. But when I run the update command I got this error:
ValidationException: The document path provided in the update expression is invalid for update`
What should I do in order to fix the issue?
{
TableName: 'MyTable',
Key: {
id: '4b7020d2-2d19-4aeb-7f27e49d5bec',
type: '80422149-c97d-4a1a-7bf20ef57056',
},
UpdateExpression: 'set #site.#siteenable= :siteenable',
ExpressionAttributeValues: {
':siteenable': true,
},
ExpressionAttributeNames: {
'#siteenable': 'enable',
'#site': 'site',
}
}

You don't mention a programming language, so I'm going to assume what I'm used to: Python.
In Python there are two ways you can do this:
The lower level client API, which requires you to format the data the way DynamoDB would
def enable_site_with_client():
ddb = boto3.client("dynamodb")
ddb.update_item(
TableName=TABLE_NAME,
Key={
"PK": {"S": "SITE_ENTRY"}
},
UpdateExpression="SET #site.#enabled = :update_value",
ExpressionAttributeNames={
"#site": "site",
"#enabled": "enabled"
},
ExpressionAttributeValues={
":update_value": {"BOOL": True}
}
)
The higher level resource API, which allows you to use the language native data structures
def enable_site_with_resource():
ddb = boto3.resource("dynamodb")
ddb.Table(TABLE_NAME).update_item(
Key={
"PK": "SITE_ENTRY"
},
UpdateExpression="SET #site.#enabled = :update_value",
ExpressionAttributeNames={
"#site": "site",
"#enabled": "enabled"
},
ExpressionAttributeValues={
":update_value": True
}
)
I have tested both of these and they work.

Given code works fine if the map site exists already, seeing the error message, it looks like the path site doesn't exist.
We could create empty map during the create of the document, then update it easily OR
We could create the map during the update "set #site = :siteValue"
Here is slightly modified query which creates the map.
const dynamodb = new AWS.DynamoDB();
let docClient = new AWS.DynamoDB.DocumentClient();
docClient.update(
{
TableName: "MyTable",
Key: {
id: "4b7020d2-2d19-4aeb-7f27e49d5bec",
type: "80422149-c97d-4a1a-7bf20ef57056",
},
UpdateExpression: "set #site = :siteValue",
ExpressionAttributeValues: {
":siteValue": { enable: true },
},
ExpressionAttributeNames: {
"#site": "site",
},
},
function (error, result) {
console.log("error", error, "result", result);
}
);

Here is an example for Java SDK V2 that will update root->nested->someValue
Map<String, AttributeValue> attributeValues = new HashMap<>();
attributeValues.put(":myValue", AttributeValue.builder().s(jsonString).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder()
.tableName("my_table_name")
.key(keyToUpdate)
.updateExpression("SET nested.someValue= :myValue")
.expressionAttributeValues(attributeValues)
.build();
client.updateItem(updateRequest);

Related

DynamoDB return the modified document (Old or new) when using TransactWrtieItems

Is there a way of making transactWriteItem return the document it updated?
const transactionParams = {
ReturnConsumedCapacity: "INDEXES",
TransactItems: [
{
Delete: {
TableName: reactionTableName,
Key: {
"SOME_PK_",
"SOME_SK_",
},
ReturnValues: 'ALL_OLD',
},
},
{
Update: {
TableName: reviewTableName,
Key: { PK: "SOME_PK", SK: "SOME_SK" },
ReturnValues: 'ALL_OLD',
},
},
],
};
try {
const result = await docClient.transactWrite(transactionParams).promise();
} catch (error) {
context.done(error, null);
}
For example in the above code get the documents that were touched (before or after update)?
No, TransactWriteItems API does not provide the ability to return values of a modified item, however, you could obtain those values using DynamoDB Streams, otherwise you would need to default to the singleton APIs UpdateItem/DeleteItem which are not ACID compliant together.

Dynamo DB - Is is possible to update/insert nested field

I have a document in the dynamo DB in the following format.
{
key1: "value1",
key2: {sKeya: "A", sKeyB: "B" }
}
Is it possible to update (via JavaScript) directly nested field in the dynamo DB document? BCOZ I don't want to update key2 with all values again.
Example: I tried the below query, but did not work. It rather created one more field key2.sKeya: "ABC"
UpdateExpression: "set #field = :value",
ExpressionAttributeNames: {
"#field": "key2.sKeya"
},
ExpressionAttributeValues: {
":value": "ABC",
}
Has any one faced similar issue?
You need to define the object key as separate Attribute names.
So one alias for "key2" and one alias for "sKeya".
const AWS = require("aws-sdk");
const DOCCLIENT = new AWS.DynamoDB.DocumentClient();
exports.handler = (event, context, callback) => {
let entry = {
TableName: "test",
Key: {
"key1": "value1",
},
UpdateExpression: "set #parent.#child = :value",
ExpressionAttributeNames: {
"#parent": "key2",
"#child": "sKeya"
},
ExpressionAttributeValues: {
":value": "ABC",
},
ReturnValues: "UPDATED_NEW"
};
DOCCLIENT.update(entry, function(err, data) {
callback(null, 'done');
});
};

AWS.DynamoDB: KeyConditionExpression is missing

I wanted to make a search in DynamoDB equivalent to the following SQL query:
SELECT *
from db
where attr="abc"
Therefore, I wrote the following code:
function searchFile(carModel, busMapping, from_date, until_date, location, attackTraffic, isTagged) {
AWS.config = new AWS.Config({accessKeyId: '***', secretAccessKey: '***', region: 'us-west-2'});
var dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
"TableName" : 'db',
QueryFilter: {
"attr": {
ComparisonOperator: 'EQ',
AttributeValueList: [
{S: 'abc'}
]
}
}
}
dynamodb.query(params, function(err,data) {
if (err) {
alert(err);
}
})
}
I got the error message:
"Expected params.KeyConditionExpression to be a string"
I looked at the AWS documentation (http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#query-property) and saw that params.KeyConditionExpression must be defined. However, I still didn't understand what is expected to be inside this key. Can you propose something that can make my query work?
To perform a query against DynamoDB you must provide it with the primary/hash/partition key for the table.
KeyConditionExpression: The condition must perform an equality test on a single partition key value. The partition key equality test is required, and must be specified in the following format: partitionKeyName = :partitionkeyval
What you probably want is to use the scan method and provide it with a ScanFilter instead of a QueryFilter:
function searchFile(carModel, busMapping, from_date, until_date, location, attackTraffic, isTagged) {
AWS.config = new AWS.Config({accessKeyId: '***', secretAccessKey: '***', region: 'us-west-2'});
var dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
TableName : 'db',
ScanFilter: {
attr: {
ComparisonOperator: 'EQ',
AttributeValueList: [
{S: 'abc'}
]
}
}
}
dynamodb.scan(params, function(err,data) {
if (err) {
alert(err);
}
})
}

Append to or create StringSet if it doesn't exist

So this should be simple...
I want to append a string to a StringSet in a DynamoDB if it exists, or create the StringSet property if it doesn't and set the value. If we could initialize the StringSet on creation with an empty array, it would be fine, but alas we can not.
Here's what I have so far:
const companiesTable = 'companies';
dynamodb.updateItem({
TableName: companiesTable,
Key: {
id: {
S: company.id
}
},
UpdateExpression: 'ADD socialAccounts = list_append(socialAccount, :socialAccountId)',
ExpressionAttributeValues: {
':socialAccountId': {
'S': [socialAccountId]
}
},
ReturnValues: "ALL_NEW"
}, function(err, companyData) {
if (err) return cb({ error: true, message: err });
const response = {
error: false,
message: 'Social account created',
socialAccountData
};
cb(response);
});
I've also tried...
UpdateExpression: 'SET socialAccounts = list_append(socialAccounts, :socialAccountId)',
ExpressionAttributeValues: {
':socialAccountId': {
S: socialAccountId
}
},
and...
UpdateExpression: 'ADD socialAccounts = :socialAccountId',
ExpressionAttributeValues: {
':socialAccountId': {
S: socialAccountId
}
},
and...
UpdateExpression: 'SET socialAccounts = [:socialAccountId]',
ExpressionAttributeValues: {
':socialAccountId': socialAccountId
},
and...
UpdateExpression: 'ADD socialAccounts = :socialAccountId',
ExpressionAttributeValues: {
':socialAccountId': socialAccountId
},
Among about every other variation of the above. Am I dumb? Is DynamoDB not capable of simple write/updates to an array type field? Do I REALLY have to lookup the item first to see if it has that field before I try to either add or set that field, because I can't instantiate that field with an empty array?
The ADD action handles the create/update logic, but only supports numbers and sets. You are trying to add a string type 'S'. You need to wrap this string in an array and pass it as a string set 'SS'. You also don't need the equals sign.
Your UpdateExpression and ExpressionAttributeValues should look like this:
UpdateExpression: 'ADD socialAccounts :socialAccountId',
ExpressionAttributeValues: {
':socialAccountId': {
'SS': [socialAccountId]
}
},
More information about updating items can be found here

AWS DynamoDB Attempting to ADD to a Set - Incorrect Operand

I am creating an API using Nodejs and DynamoDB as a back end. I am attempting to update an item to add to a set of "friends". When I update the user, I get the error, "Invalid UpdateExpression: Incorrect operand type for operator or function; operator: ADD, operand type: MAP". My understanding is that when adding to a set that does not exist, the set will be created. If it already exists, the new value should be added to the set. I do not understand why the set I attempt to ADD is being read as a map.
How users are created:
var params = {
TableName: "users",
Item:{
"id": Number(id),
"name": name,
"password": password
}
};
documentClient.put(params, function(err, data) {
if(err)
res.json(500, err);
else
res.json(200, data);
});
How friends are added:
var params = {
TableName: "users",
Key: {
"id": id
},
UpdateExpression: "ADD friends :friendId",
ExpressionAttributeValues: {
":friendId": { "NS": [friendId] }
},
ReturnValues: "UPDATED_NEW"
};
documentClient.update(params, function(err, data) {
if(err)
res.json(500, err);
else
res.json(200, data);
});
This question has an answer here
https://stackoverflow.com/a/38960676/4975772
Here's the relevant code formatted to fit your question
let AWS = require('aws-sdk');
let docClient = new AWS.DynamoDB.DocumentClient();
...
var params = {
TableName : 'users',
Key: {'id': id},
UpdateExpression : 'ADD #friends :friendId',
ExpressionAttributeNames : {
'#friends' : 'friends'
},
ExpressionAttributeValues : {
':friendId' : docClient.createSet([friendId])
},
ReturnValues: 'UPDATED_NEW'
};
docClient.update(params, callback);
If the set doesn't exist, then that code will create it for you. You can also run that code with a different set to update the set's elements. Super convenient.
Here is the working code. You don't need ADD here. Just use "set friends = :friendId" as friends attribute is not already present in the table (i.e. before the update you have only id, name and password in the table). The friend attribute is being added newly as part of the update.
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "users";
var userid = 1;
var friendId = [123];
var params = {
TableName : table,
Key: {
"id" : userid
},
"UpdateExpression": "set friends = :friendId",
"ExpressionAttributeValues": {
":friendId": {"NS": friendId}
},
"ReturnValues" : "UPDATED_NEW"
};