DynamoDB - check nested attrs when validating duplicate set-actions (#5560)

This commit is contained in:
Bert Blommers 2022-10-13 10:09:18 +00:00 committed by GitHub
parent 67c688b187
commit 121e3eadb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 4 deletions

View File

@ -27,9 +27,8 @@ class Node(metaclass=abc.ABCMeta):
if nr_of_clauses > 1:
raise TooManyAddClauses()
set_actions = self.find_clauses(UpdateExpressionSetAction)
set_attributes = [
c.children[0].children[0].children[0] for c in set_actions
]
# set_attributes = ["attr", "map.attr", attr.list[2], ..]
set_attributes = [s.children[0].to_str() for s in set_actions]
# We currently only check for duplicates
# We should also check for partial duplicates, i.e. [attr, attr.sub] is also invalid
if len(set_attributes) != len(set(set_attributes)):
@ -148,7 +147,8 @@ class UpdateExpressionDeleteAction(UpdateExpressionClause):
class UpdateExpressionPath(UpdateExpressionClause):
pass
def to_str(self):
return "".join(x.to_str() for x in self.children)
class UpdateExpressionValue(UpdateExpressionClause):
@ -186,6 +186,9 @@ class UpdateExpressionDeleteClause(UpdateExpressionClause):
class ExpressionPathDescender(Node):
"""Node identifying descender into nested structure (.) in expression"""
def to_str(self):
return "."
class ExpressionSelector(LeafNode):
"""Node identifying selector [selection_index] in expresion"""
@ -201,6 +204,9 @@ class ExpressionSelector(LeafNode):
def get_index(self):
return self.children[0]
def to_str(self):
return f"[{self.get_index()}]"
class ExpressionAttribute(LeafNode):
"""An attribute identifier as used in the DDB item"""
@ -211,6 +217,9 @@ class ExpressionAttribute(LeafNode):
def get_attribute_name(self):
return self.children[0]
def to_str(self):
return self.get_attribute_name()
class ExpressionAttributeName(LeafNode):
"""An ExpressionAttributeName is an alias for an attribute identifier"""
@ -221,6 +230,9 @@ class ExpressionAttributeName(LeafNode):
def get_attribute_name_placeholder(self):
return self.children[0]
def to_str(self):
return self.get_attribute_name_placeholder()
class ExpressionAttributeValue(LeafNode):
"""An ExpressionAttributeValue is an alias for an value"""

View File

@ -0,0 +1,32 @@
import boto3
import sure # noqa # pylint: disable=unused-import
from moto import mock_dynamodb
@mock_dynamodb
def test_update_different_map_elements_in_single_request():
# https://github.com/spulec/moto/issues/5552
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="example_table",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
record = {
"id": "example_id",
"d": {"hello": "h", "world": "w"},
}
table = dynamodb.Table("example_table")
table.put_item(Item=record)
updated = table.update_item(
Key={"id": "example_id"},
UpdateExpression="set d.hello = :h, d.world = :w",
ExpressionAttributeValues={":h": "H", ":w": "W"},
ReturnValues="ALL_NEW",
)
updated["Attributes"].should.equal(
{"id": "example_id", "d": {"hello": "H", "world": "W"}}
)