Search code examples
node.jsamazon-web-servicesaws-lambdaamazon-dynamodb

Create or Update DynamoDB Record with Map attributes


Currently, I'm checking if the item exists with a query and then I'm using put or updateItem, I want to change it and make a single call to DDB. I want to make a query that will update or create item.

Here is a example of my item:

{
   id: 'dsfadsf'
   fa: { "apple" : { "S" : "76-100" }, "yolo" : { "S" : "0-25" }
   pa: { "finish" : { "BOOL" : false },    "userKey" : { "S" : "3e299e12-9e66" }  }
   createdAt: 32432432423
}

item types:

createdAt - Number
fa - Map
pa - Map
id - String
finish - Boolean
key - String

If item exists I want to push a new item like {papa: 'dsfadsf'} (never modify existing item inside fa Map) and modify finish value inside pa Map

This is the item after the update:

{
   id: 'dsfadsf'
   fa: { "apple" : { "S" : "76-100" }, "yolo" : { "S" : "0-25" }, "papa": { "S" : "dsfadsf"} }
   pa: { "finish" : { "BOOL" : true },    "userKey" : { "S" : "3e299e12-9e66" }  }
   createdAt: 32432432423
}

Here is what I tried and its not working

{
    TableName: tableName,
    Key: {
      id: "dsfadsf",
    },
    UpdateExpression: `SET #id = :id, fa.${itemName} = if_not_exists(fa.${itemName}, :text), pa.finish = if_not_exists(pa.finish, :finishval), #ca = :ca`,
    ExpressionAttributeNames: {
      "#id": "id",
      "#ca": createdAt
    },
    ExpressionAttributeValues: {
      ":id": "7fd9a81b-7a7c-4cfb-9c84-25dc2798a8f7",
      ":text": itemText,
      ":finishval": true,
      ":ca": 32432432423
    },
    ConditionExpression: `attribute_not_exists(id)`,
  };

Solution

  • TLDR

    This isn't possible with the current structure of your item. Change fa & pa to be string sets, get rid of finish: true & use the ADD update expression.


    This is not possible with the current structure of your item. Here's why — To meet your requirement, UpdateExpression needs to be of the form:

    if "fa" exists, add {"papa":"dsfadsf"} to it
    else, create new "fa"={"papa":"dsfadsf"}
    

    Of all the DynamoDB update expressions, only SET & ADD can be used in this scenario.

    The above if else condition cannot be expressed in an UpdateExpression with SET due to the following reasons:

    • SET fa.papa = 'dsfadsf' will update fa if it exists, but if it doesn't, you get the error The document path provided in the update expression is invalid for update.

    • SET fa = {"papa":"dsfadsf"} will create new fa but overwrite it if it exists.

    If you try to combine ADD & SET to achieve the above if else condition, into something like ADD fa {} SET fa.papa = dsfadsf, you get the error Two document paths overlap with each other; must remove or rewrite one of these paths

    So we're only left with the ADD expression now, but ADD only works on sets. So if you can afford to turn the contents of fa & pa into sets of strings, you can achieve your goal of "create or update in 1 go". Here's how it'll work:

    The original item structure has to be:

    {
      "ca": 32432432423,
      "fa": [
        "apple:76-100",
        "yolo:0-25"
      ],
      "id": "dsfadsf",
      "pa": [
        "key:9e66"
      ]
    }
    

    In DynamoDB JSON, that's:

    {
      "ca": {
        "N": "32432432423"
      },
      "fa": {
        "SS": [
          "apple:76-100",
          "yolo:0-25"
        ]
      },
      "id": {
        "S": "dsfadsf"
      },
      "pa": {
        "SS": [
          "key:9e66"
        ]
      }
    }
    

    Now, using the following code:

    let AWS = require("aws-sdk")
    let docClient = new AWS.DynamoDB.DocumentClient()
    docClient.update({
        TableName: "my-table",
        Key: { id: "dsfadsf" },
        UpdateExpression: `ADD fa :fa, pa :pa SET ca = :ca`,
        ExpressionAttributeValues: {
            ":fa": docClient.createSet(["papa:dsfadsf"]),
            ":pa": docClient.createSet(["finished"]),
            ":ca": 32432432423
        }
    }
    

    If an item with the id "dsfadsf" already exists, it's updated as follows:

    {
      "ca": {
        "N": "32432432423"
      },
      "fa": {
        "SS": [
          "apple:76-100",
          "papa:dsfadsf",
          "yolo:0-25"
        ]
      },
      "id": {
        "S": "dsfadsf"
      },
      "pa": {
        "SS": [
          "finished",
          "key:9e66"
        ]
      }
    }
    

    If an item with the id "dsfadsf" does NOT exist, it's created as follows:

    {
      "ca": {
        "N": "32432432423"
      },
      "fa": {
        "SS": [
          "papa:dsfadsf"
        ]
      },
      "id": {
        "S": "dsfadsf"
      },
      "pa": {
        "SS": [
          "finished"
        ]
      }
    }