3

I have the next error in my DynamoDB request:

{
   "__type":"com.amazon.coral.validate#ValidationException",
   "message":"Invalid UpdateExpression: 
        Two document paths overlap with each other; must remove or rewrite one of
        these paths; path one: [cart, items, [1], vat-item], path two: [cart, items]"
}

I am using the next expression:

"UpdateExpression":"SET
    cart.#listName[0].quantity = cart.#listName[0].quantity + :quantity1,
    cart.#listName[0].price = :price1,
    cart.#listName[0].#vatItem = :vatItem1,
    cart.#listName[1].quantity = cart.#listName[1].quantity + :quantity2, 
    cart.#listName[1].price = :price2,
    cart.#listName[1].#vatItem = :vatItem2,
    cart.#listName = list_append (cart.#listName, :jsonObject)"}

Is there any hack to do work fine "set elements and add anothers"?

I found this question on stackoverflow but this guy is trying to do delete and set, and me only set.

Added an MRE in python for those willing to try at home:

import boto3

TABLE_NAME = "pk-only"

TABLE_RESOURCE = boto3.resource("dynamodb").Table(TABLE_NAME)
DDB_CLIENT = boto3.client("dynamodb")

def create_table():
    DDB_CLIENT.create_table(
        AttributeDefinitions=[{"AttributeName": "PK", "AttributeType": "S"}],
        TableName=TABLE_NAME,
        KeySchema=[{"AttributeName": "PK", "KeyType": "HASH"}],
        BillingMode="PAY_PER_REQUEST"
    )

def add_sample_item():
    TABLE_RESOURCE.put_item(
        Item={
            "PK": "append_and_update",
            "list": [
                {
                    "value": "a"
                }
            ]
        }
    )

def update():

    TABLE_RESOURCE.update_item(
        Key={
            "PK": "append_and_update"
        },
        UpdateExpression="SET #l = list_append(#l, :newItem), #l[0].#val = :newVal",
        ExpressionAttributeNames={
            "#l": "list",
            "#val": "value",
        },
        ExpressionAttributeValues={
            ":newVal": "b",
            ":newItem": {"M": {"value": {"S": "c"}}}
        }
    )

def main():
    # create_table()
    add_sample_item()
    update()

if __name__ == "__main__":
    main()
Maurice
  • 11,482
  • 2
  • 25
  • 45
Aprendiendo Siempre
  • 341
  • 1
  • 6
  • 18
  • 1
    Interesting problem, have been trying to solve it by using ADD in combination with SET, but it still results in the same error. I added an MRE to the question, maybe people with more knowledge can come up with a solution to that :-) – Maurice Mar 08 '21 at 17:50
  • Thank you @Maurice, I added a solution that work fine to me – Aprendiendo Siempre Mar 08 '21 at 23:11

2 Answers2

4

Interesting question.

What you wanted to do is:

"UpdateExpression":"SET
    cart.#listName[0].quantity = cart.#listName[0].quantity + :quantity1,
    cart.#listName[0].price = :price1,
    cart.#listName[0].#vatItem = :vatItem1,
    cart.#listName[1].quantity = cart.#listName[1].quantity + :quantity2, 
    cart.#listName[1].price = :price2,
    cart.#listName[1].#vatItem = :vatItem2,
    cart.#listName = list_append (cart.#listName, :jsonObject)"}

As you noticed, this is not allowed - you are not allowed to replace #listName (which is what the last assignment is doing) and change specific subattributes of #listName ([0] and [1]) in the same update.

But luckly, there is a different, rather obscure, way to append to a list, which will work in your case:

If your expression asks to set cart.#listName[1000], where 1000 is known to be beyond the length of the list (it can be much higher than the length of the list, unlike your solution), then this element is appended at the end of the list. It is appended really at the end of the list - if the list only had 5 items, the element is added as the 6th, not as the 1000th. Another important thing to note is that if you set elements 1000, 1001, and 1002 in the list, they are appended by this numeric order.

So knowing these facts, you can change your code to:

"UpdateExpression":"SET
    cart.#listName[0].quantity = cart.#listName[0].quantity + :quantity1,
    cart.#listName[0].price = :price1,
    cart.#listName[0].#vatItem = :vatItem1,
    cart.#listName[1].quantity = cart.#listName[1].quantity + :quantity2, 
    cart.#listName[1].price = :price2,
    cart.#listName[1].#vatItem = :vatItem2,
    cart.#listName[1000] = :jsonObject0,
    cart.#listName[1001] = :jsonObject1,
    cart.#listName[1002] = :jsonObject2"}

If you're worried that 1000 might not be high enough, just replace it with 1000000. The actual number doesn't matter - the statement will not add a lot of null elements in the array. The indexes can be as high as you want - they just need to be higher than the possible length of the existing list.

Nadav Har'El
  • 11,785
  • 1
  • 24
  • 45
  • 2
    That's a dirty hack - I like it – Maurice Mar 09 '21 at 12:52
  • 2
    It's weird, but it's explicitly documented - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET :" When you use SET to update a list element, the contents of that element are replaced with the new data that you specify. If the element doesn't already exist, SET appends the new element at the end of the list. If you add multiple elements in a single SET operation, the elements are sorted in order by element number. – Nadav Har'El Mar 09 '21 at 12:56
  • 2
    By the way, I was also very surprised when I first thought about what this piece of the documentation means, so I tested it myself - and it does work. – Nadav Har'El Mar 09 '21 at 12:57
  • It does seem like a weird implementation, but I like it :-) – Maurice Mar 09 '21 at 13:01
0

Well, I want to expose the solution that I have adopted to resolve this problem.

I have changed this line cart.#listName = list_append (cart.#listName, :jsonObject) in

"UpdateExpression":"SET
    cart.#listName[0].quantity = cart.#listName[0].quantity + :quantity1,
    cart.#listName[0].price = :price1,
    cart.#listName[0].#vatItem = :vatItem1,
    cart.#listName[1].quantity = cart.#listName[1].quantity + :quantity2, 
    cart.#listName[1].price = :price2,
    cart.#listName[1].#vatItem = :vatItem2,
    cart.#listName = list_append (cart.#listName, :jsonObject)"}

by cart.#listName[2] = :jsonObject0, cart.#listName[3] = :jsonObject1, .....

"UpdateExpression":"SET
    cart.#listName[0].quantity = cart.#listName[0].quantity + :quantity1,
    cart.#listName[0].price = :price1,
    cart.#listName[0].#vatItem = :vatItem1,
    cart.#listName[1].quantity = cart.#listName[1].quantity + :quantity2, 
    cart.#listName[1].price = :price2,
    cart.#listName[1].#vatItem = :vatItem2,
    cart.#listName[2] = :jsonObject0, cart.#listName[3] = :jsonObject1"}

You need to know the number of items in your list and add the new element in last position. I tried this solution and you can add all elements you want.

I know that it is not the best solution, but work fine to me and I hope help you

Aprendiendo Siempre
  • 341
  • 1
  • 6
  • 18
  • 1
    Instead of using the numbers 2 and 3 in your solution, and worrying if 2 is really the length of the list, you can use much higher numbers, that just need to be *more* than the length of the list. E.g., 1000 and 1001. I explained in my own solution, below. – Nadav Har'El Mar 09 '21 at 12:50