BlueMonkMN
BlueMonkMN

Reputation: 25601

DynamoDB putItem ConditionExpression failing to match event.identityId

I'm using the following node.js code in an AWS Lambda function to insert or update records in a DynamoDB table called StoryEntity from source data in another table called Submissions when a Cognito sync event triggers this function by storing a new value in a property called "batch". Everything seems to be working except the ConditionExpression on the putItem call. If the records don't already exist in StoryEntity, everything is fine, but if they already exist, I get errors reported in the log output like:

2017-11-10T19:08:55.685Z 98e60436-c64a-11e7-9a2a-dfba66e9157a Error inserting: ConditionalCheckFailedException: The conditional request failed

2017-11-10T19:08:55.705Z 98e60436-c64a-11e7-9a2a-dfba66e9157a Error inserting: ConditionalCheckFailedException: The conditional request failed

The value in the uid column looks like us-east-2:11476ace-a944-4769-89cb-c5ad8e84b8a9. It doesn't change so long as I'm logged in with the same identity, which I am. I don't understand why it won't update the existing records with that conditional expression in place. (It did before I introduced ConditionExpression.)

'use strict';

console.log('Loading function');

var doc = require('dynamodb-doc');
var dynamodb = new doc.DynamoDB();

exports.handler = (event, context, callback) => {
    var batch = event.datasetRecords.batch.newValue;
    var datetime = new Date().getTime();
    function doPut(err, data) {
        if (err) {
            console.log('Error inserting: ' + err);
            callback(`Error putting item into DB: ${err}.`);
        }}
    function doDelete(err, data) {
        if (err) {
            console.log('Error deleting: ' + err);
            callback(`Error deleting item: ${err}.`);
        }}
    function doQuery(err, data) {
        if (err) {
            console.log("Error querying: " + err);
            callback(`Error querying submission: ${err}`);
        } else {
            data.Items.forEach(function(item) {
                Object.assign(item, {
                   "uid" : event.identityId,
                   "date": datetime
                });
                dynamodb.putItem({"TableName" : "StoryEntity",
                   "Item" : item,
                    "ConditionExpression" :
                        "attribute_not_exists(#u) or #u=:user",
                    "ExpressionAttributeNames" : {"#u" : "uid"},
                    "ExpressionAttributeValues" :
                        {":user":{"S":event.identityId}}
                }, doPut);
                dynamodb.deleteItem({"TableName" : "Submission",
                    "Key" : {"Batch" : batch,
                    "EntityId": item.EntityId}
                }, doDelete);
            });
        }
    }
    dynamodb.query({ TableName: "Submission",
        ProjectionExpression: "EntityId, #t, EntityType, #n, Container, Destination, Target",
        KeyConditionExpression: "#b = :batch",
        ExpressionAttributeNames: {
            "#b" : "Batch",
            "#t" : "Text",
            "#n" : "Name"
        },
        ExpressionAttributeValues: {
            ":batch": batch
        }
    }, doQuery);
    callback(null, event);
};

Upvotes: 3

Views: 7665

Answers (1)

BlueMonkMN
BlueMonkMN

Reputation: 25601

I'm still not clear why my previous attempts to implement this solution were unsuccessful, but apparently the problem was related to the format of the value for :user. The documentation on this is frustratingly inconsistent (I can't find any documentation that matches the syntax I had to use to make this work - eliminating the type specifier keys), and the error messages are frustratingly vague, but after modifying the code to correct some asynchronous sequencing issues, I was able to get a more helpful error message by trying to use the < operator in my expression instead of =. This yielded a message about how < is not a valid operator when comparing values of type M. So apparently, in this context, I need to not include the type specifying character "S" in my value specification because that causes the framework to interpret the whole value as a map, implicitly inserting the "M" key specifying the value type as a map. I had tried that in the above code, but still got an error, so some other change combined with this must have been required to get this working. Below is the final code.

'use strict';

console.log('Loading function');

var doc = require('dynamodb-doc');
var dynamodb = new doc.DynamoDB();

exports.handler = (event, context, callback) => {
    var batch = event.datasetRecords.batch.newValue;
    var datetime = new Date().getTime();
    var putList = [];

    function putError(err) {
        console.log('Error inserting: ' + err);
        throw `Error putting item into DB: ${err}.`;
    }
    function doDelete(err, data) {
        if (err) {
            console.log('Error deleting: ' + err);
            throw `Error deleting item: ${err}.`;
        }}
    function doQuery(err, data) {
        if (err) {
            console.log("Error querying: " + err);
            throw `Error querying submission: ${err}`;
        } else {
            data.Items.forEach(function(item) {
                Object.assign(item, {
                   "uid" : event.identityId,
                   "date": datetime
                });
                var putRequest = {"TableName" : "StoryEntity",
                   "Item" : item,
                    "ConditionExpression" :
                        "attribute_not_exists(#u) or (#u=:user)",
                    "ExpressionAttributeNames" : {"#u":"uid"},
                    "ExpressionAttributeValues" : {":user":event.identityId}
                };
                var promise = dynamodb.putItem(putRequest).promise();
                putList.push({"promise":promise, "entityId":item.EntityId});
            });
            var promiseList = [];
            putList.forEach(function(item){
                promiseList.push(item.promise.then(function() {
                    dynamodb.deleteItem({"TableName" : "Submission",
                        "Key" : {"Batch" : batch,
                            "EntityId": item.entityId
                        }}, doDelete);
                }, putError));
            });
            Promise.all(promiseList).then(function() {
                console.log("Success!");
                callback(null, event);
            }, function(err) {
                console.log(err);
                callback(err);
            });
        }
    }
    dynamodb.query({ TableName: "Submission",
        ProjectionExpression: "EntityId, #t, EntityType, #n, Container, Destination, Target",
        KeyConditionExpression: "#b = :batch",
        ExpressionAttributeNames: {
            "#b" : "Batch",
            "#t" : "Text",
            "#n" : "Name"
        },
        ExpressionAttributeValues: {
            ":batch": batch
        }
    }, doQuery);
};

Upvotes: 3

Related Questions