1

In my Lambda function, I want to conditionally put items into my DynamoDB only if the value DOESN'T EXIST already. I saw multiple different sources where they use this ConditionExpression and i cant figure out whats wrong with that.

body = await dynamo.put({
                    TableName: 'polit-stream',
                    Item: {
                        urlPath: data.urlPath,
                    },
                    ConditionExpression: "attribute_not_exists(urlPath)"
                }).promise();

The put will always be successful, even if my secondary index value (urlPath) already exists.

Full Code:

const AWS = require('aws-sdk');
const crypto = require("crypto");
const dynamo = new AWS.DynamoDB.DocumentClient();

/**
 * Demonstrates a simple HTTP endpoint using API Gateway. You have full
 * access to the request and response payload, including headers and
 * status code.
 *
 * To scan a DynamoDB table, make a GET request with the TableName as a
 * query string parameter. To put, update, or delete an item, make a POST,
 * PUT, or DELETE request respectively, passing in the payload to the
 * DynamoDB API as a JSON body.
 */
exports.handler = async(event, context) => {

    let body;
    let statusCode = '200';
    const headers = {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '* '
    }

    const data = JSON.parse(event.body);
    const generateUUID = () => crypto.randomBytes(16).toString("hex");

    try {
        switch (event.httpMethod) {
            case 'DELETE':
                body = await dynamo.delete(JSON.parse(event.body)).promise();
                break;
            case 'GET':
                body = await dynamo.scan({
                        TableName: 'db',
                        IndexName: 'urlPath',
                        FilterExpression: "urlPath = :urlPath",
                        ExpressionAttributeValues: {
                            ":urlPath": event.queryStringParameters.urlPath
                        }
                    },
                    function(data) {

                    }).promise();
                break;
            case 'POST':
                body = await dynamo.put({
                    TableName: 'db',
                    Item: {
                        id: generateUUID(),
                        name: data.name,
                        date: data.date,
                        place: data.place,
                        goals: data.goals,
                        type: data.type,
                        org: data.org,
                        email: data.email,
                        urlPath: data.urlPath,
                        createdAt: new Date().toISOString()
                    },
                    ConditionExpression: "attribute_not_exists(urlPath)"
                }).promise();
                break;
            case 'PUT':
                body = await dynamo.update(JSON.parse(event.body)).promise();
                break;
            default:
                throw new Error(`Unsupported method "${event.httpMethod}"`);
        }
    }
    catch (err) {
        statusCode = '400';
        body = err.message;
    }
    finally {
        body = JSON.stringify(body);
    }

    return {
        statusCode,
        body,
        headers,
    };
};
John Rotenstein
  • 241,921
  • 22
  • 380
  • 470
King Bufo
  • 181
  • 1
  • 13

2 Answers2

3

Your error is that you misunderstood what ConditionExpression can do. Your full PutItem code is:

               body = await dynamo.put({
                    TableName: 'db',
                    Item: {
                        id: generateUUID(),
                        name: data.name,
                        date: data.date,
                        place: data.place,
                        goals: data.goals,
                        type: data.type,
                        org: data.org,
                        email: data.email,
                        urlPath: data.urlPath,
                        createdAt: new Date().toISOString()
                    },
                    ConditionExpression: "attribute_not_exists(urlPath)"
                }

What did you expect ConditionExpression: "attribute_not_exists(urlPath)" to do?

Apparently you thought that it will check whether any item exists with this value of urlPath. But this is not, unfortunately, what this expression does. What it does is to look at one specific item - the item with the same key (I don't know what is your key, id?) and check whether this specific item has a urlPath attribute (with any value).

If urlPath was the item's key, this work like you hoped it would. If the urlPath is unique (which it seems it is, according to what you wanted to do) then it can indeed serve as the item key.

Nadav Har'El
  • 11,785
  • 1
  • 24
  • 45
  • 1
    Adding to @nadav's answer, put-item operations only work on the main partition key. Looking at your code snippets, it looks like url is a partition key on an index. Indexes are read-only copies, and hence cannot be used to update. – sandboxbohemian Sep 01 '20 at 17:28
1

In order to use the ConditionExpression you need to provide name and value for the attributes. Try this:

await dynamo.put({
  TableName: 'db',
  Item: {
    id: generateUUID(),
    name: data.name,
    date: data.date,
    place: data.place,
    goals: data.goals,
    type: data.type,
    org: data.org,
    email: data.email,
    urlPath: data.urlPath,
    createdAt: new Date().toISOString(),
  },
  ConditionExpression: "attribute_not_exists(#u) or (#u=:urlPath)",
  ExpressionAttributeNames: { "#u": "urlPath" },
  ExpressionAttributeValues: { ":urlPath": data.urlPath },
});
atomNULL
  • 209
  • 1
  • 2
  • 6
  • I tried that but unfortunately i get the same result (item gets inserted even if the condition matches). maybe i need to use putItem? – King Bufo Sep 01 '20 at 10:36
  • putItem operation will insert the item if it does not exist and it will completely replace the item if it already exists. The condition above, if true, will create the new item. If the item exists already, it should not replace it. Another approach would be to first perform a GetItem operation and if the item with that primary key exists, then do nothing. If it does not, create it. – atomNULL Sep 01 '20 at 11:13