DynamoDB - how to get the item attributes if ConditionExpression failed on "put" - amazon-web-services

I'm trying to save an item if the primary key doesn't exist. If this key already exists, I want to cancel the "put" and get the item that exists in the table.
I've noticed there is a field called "ReturnValuesOnConditionCheckFailure" in transactWrite that may be the solution:
Use ReturnValuesOnConditionCheckFailure to get the item attributes if the Put condition fails.
For ReturnValuesOnConditionCheckFailure, the valid values are: NONE and ALL_OLD.
It didn't work and I get an error of Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed].
This is what I tried to do so far:
docClient.transactWrite({
TransactItems: [{
Put: {
TableName: 'test.users',
Item: {
user_id,
name,
},
ConditionExpression: 'attribute_not_exists(user_id)',
ReturnValuesOnConditionCheckFailure: 'ALL_OLD',
}
}]
})
Any ideas?

Related

Error "The provided key element does not match the schema" without sort key

I have a problem with a DynamoDB table.
I only have the partition key; when I request all the elements these are displayed correctly, while when I want to get only one element with specific id I get the following error:
The provided key element does not match the schema
Yet I only have the partition key (Primary Key). without the sort key.
This is the relative lambda function code:
case "GET /fragments/{id}":
body = await dynamo
.get({
TableName: "frammento",
Key: {
id: event.pathParameters.id
}
.promise();
break;
})
How can I fix this error?

AWS AppSync & DynamoDB Single Table Design: PutItem based on another item's field value

I'm new to AWS AppSync and I have adopted the single table pattern in DynamoDB. Now I am trying to create an item based on a particular field value in the existing item in the same table. For example, I have a table called transaction which holds 2 types of records.
Request
Response
As you can see the above table, I can insert (PutItem) multiple responses for a particular request. Before I insert a new response, I need to validate whether the request (RequestID) is already exists. Is there any way to do via conditional expression in the resolver? Below is my current request resolver code which is not working as expected.
#set( $Id = $util.autoId() )
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"PK": $util.dynamodb.toDynamoDBJson("USER#$ctx.args.input.UserId"),
"SK": $util.dynamodb.toDynamoDBJson("RESPONSE#$Id"),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input),
"condition": {
"expression": "SK = :SK",
"expressionValues" : {
":SK" : {
"S" : "REQUEST#${ctx.args.input.RequestId}"
}
}
}
}
You could do this in one request mapping template by using DynamoDB transactions, see (https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-transact.html).
In one of the "transactWriteItems", you update an arbitrary value (or perhaps, number of responses) in the request item with a condition that checks if the request item exists with that requestId in the SK. If the conditions succeeds, the request item is updated.
Also make sure you have the response item in your "transactWriteItems" so that the response item is also written if the request condition passes.
You won't be able to do a conditional PutItem based on another entry no. In this case you'd want to use pipeline resolvers. In your first function you'd fetch the request item and in the second you can then do your PutItem condition - the previous GetItem result will be available as $ctx.prev.result.

The provided key element does not match the schema - AWS

I have seen a similar question posted on stackoverflow but the answers weren't able to work to solve my issue
I have created a resource in my API GETWAY of type GET. In my Query strings I'm passing the follwing:
email=x#gmail.com
or
racf=XXXX&email=x#gmail.com
I get this error:
The provided key element does not match the schema
But if I do it with the primary key, it works.
racf=XXXX
I have created an index in dynamoDB for the email attribute
LAMBDA FUNCION:
case 'GET':
if (event.queryStringParameters) {
dynamo.getItem({
TableName: "eventregistration-db",
Key:{
//"racf": event.queryStringParameters.racf,
"email": event.queryStringParameters.email
}
},done);
} else {
dynamo.scan({ TableName: tableName }, done);
}
break;
It looks like email is not a part of your Primary Key of the DynamoDb table.
For getItem you have to use table columns the table's primary key (partition key and optionally sort key) is composed from.
scan doesn't need any key, because it makes a full search on the table - that's why it works in that case.
Set email (and racf) as table's primary key to make it work with .
If you want to use an index, you have to use query:
dynamo.query({
TableName: tableName,
IndexName: indexName,
KeyConditionExpression: "email = :email",
ExpressionAttributeValues: {
":email": event.queryStringParameters.email
}
}, done);

How to prevent a DynamoDB item being overwritten if an entry already exists

Im trying to write a lambda function to add new data to a DynamoDB Table.
From reading the docs at:
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property
The PUT method: "Creates a new item, or replaces an old item with a new item by delegating to AWS.DynamoDB.putItem()."
Other than doing a check for an object before 'putting' is there a setting or flag to fail the object exists when the PUT is attempted?
I can see in
params -> Expected -> Exists (Bool)
but can't see any documentation on what this does.
What would be the best architecture (or fasted) to prevent an item overwrite?
Query the table first and if no item exists then add the item
or
Attempt to insert the item and on failure because of duplicate entry report this back? (Is there a way to prevent item overwrite?)
The ConditionExpression can be used to check whether the key attribute values already exists in table and perform the PUT operation only if the key values are not present in the table.
When you run the below code, first time the put operation should be successful. In the second run, the put operation should fail with "Conditional request failed" exception.
My movies table has both partition and sort keys. So, I have used both the attributes in conditional expression.
Sample code with conditional put:-
var table = "Movies";
var year = 1502;
var title = "The Big New Movie";
var params = {
TableName:table,
Item:{
"yearkey": year,
"title": title,
"info":{
"plot": "Nothing happens at all.",
"rating": 0
}
},
ConditionExpression: "yearkey <> :yearKeyVal AND #title <> :title",
ExpressionAttributeNames: {
"#title" : "title"
},
ExpressionAttributeValues: {
":yearKeyVal" : year,
":title": {"S": title}
}
};
console.log("Adding a new item...");
docClient.put(params, function(err, data) {
if (err) {
console.error("Unable to add item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Added item:", JSON.stringify(data, null, 2));
}
});
Exception when put operation is performed second time:-
Unable to add item. Error JSON: {
"message": "The conditional request failed",
"code": "ConditionalCheckFailedException",
"time": "2017-10-02T18:26:26.093Z",
"requestId": "7ae3b0c4-3872-478d-908c-94bc9492a43a",
"statusCode": 400,
"retryable": false,
"retryDelay": 0
}
I see that this question relates to JavaScript language, anyway I will write also for Java (maybe it will be useful for someone):
DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression();
Map<String, ExpectedAttributeValue> expectedAttributes =
ImmutableMapParameter.<String, ExpectedAttributeValue>builder()
.put("hashKeyAttrName", new ExpectedAttributeValue(false))
.put("rangeKeyAttrName", new ExpectedAttributeValue(false))
.build();
saveExpression.setExpected(expectedAttributes);
saveExpression.setConditionalOperator(ConditionalOperator.AND);
try {
dynamoDBMapper.save(item, saveExpression);
} catch (ConditionalCheckFailedException e) {
e.printStackTrace();
}
ConditionalCheckFailedException will be thrown in case we will try to save item with already existing pair of hashKey and rangeKey in DynamoDB.
As an addition to the correct answer of notionquest, the recommended way to express a condition that makes a putItem fail in case the item already exists is to assert that the partition key is not present in any potentially existing item:
"ConditionExpression": "attribute_not_exists(pk)",
This reads "if this item already exists before the putItem, make sure it does not already have a partition key" (pk being the partition key in this example). Since it is impossible for an item to exist without a partition key, this effectively means "make sure this item does not already exist".
See also the "Note" block at the beginning of this page: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodb/put-item.html
There's an example here when used to guarantee uniqueness of non key columns:
https://aws.amazon.com/blogs/database/simulating-amazon-dynamodb-unique-constraints-using-transactions/
As and addition to #Svend solution full Java example (for AWS SDK v2, dynamodb-enchanced)
public <T> void insert(DynamoDbClient client, String tableName, T object, Class<T> clazz) {
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(client).build();
DynamoDbTable<T> table = enhancedClient.table(tableName, TableSchema.fromBean(clazz));
table.putItem(PutItemEnhancedRequest.builder(clazz)
.item(object)
.conditionExpression(Expression.builder()
.expression("attribute_not_exists(primaryKey)")
.build())
.build());
}
where
primaryKey
name of column on which uniqueness is checked
software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException
exception thrown on duplicated put

Passing arbitrary fields in a CUSTOM notification

Is it possible to specify a payload in arbitrary fields in a menu item? Right now, I’m passing them as the id for the menu (which works but feels hacky) but I notice that if I set them as fields of the menu item, I never receive them in the POST:
MenuItem menuItem = new MenuItem()
// ... bunch skipped
.setId("5141...")
.set("eventId", "eventIdGoesHere")
;
Here is what I receive in the callback URL: I see the id in the payload field but I don’t see any "eventId" in the body of the POST:
body: {
"collection": "timeline",
"itemId": "a11d33c2-32d8-49c7-989e-2b69814e260f",
"operation": "UPDATE",
"userToken": "ya29.1....",
"userActions": [
{
"type": "CUSTOM",
"payload": "5141..."
}
]
}
You found the best practice - the ID of the menu item is returned as the payload of the user action when it is sent to you. The ID is opaque, so you are free to set it to whatever values you wish to decode however you want to.
Since the timeline itemId is also sent to you, you may also wish to set the sourceItemId or other (defined) attributes on the original timeline item to values that may contain information that will be useful to you. You can then do a timeline.get on the item to fetch this additional information.