DynamoDB: Validate duplicate expression paths (#5504)

This commit is contained in:
Bert Blommers 2022-09-30 10:07:20 +00:00 committed by GitHub
parent de23d172ea
commit 3118090fdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 1 deletions

View File

@ -262,6 +262,13 @@ class InvalidAttributeTypeError(MockValidationException):
super().__init__(self.msg.format(name, expected_type, actual_type)) super().__init__(self.msg.format(name, expected_type, actual_type))
class DuplicateUpdateExpression(InvalidUpdateExpression):
def __init__(self, names):
super().__init__(
f"Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [{names[0]}], path two: [{names[1]}]"
)
class TooManyAddClauses(InvalidUpdateExpression): class TooManyAddClauses(InvalidUpdateExpression):
msg = 'The "ADD" section can only be used once in an update expression;' msg = 'The "ADD" section can only be used once in an update expression;'

View File

@ -3,7 +3,7 @@ from abc import abstractmethod
from collections import deque from collections import deque
from moto.dynamodb.models import DynamoType from moto.dynamodb.models import DynamoType
from ..exceptions import TooManyAddClauses from ..exceptions import DuplicateUpdateExpression, TooManyAddClauses
class Node(metaclass=abc.ABCMeta): class Node(metaclass=abc.ABCMeta):
@ -26,6 +26,14 @@ class Node(metaclass=abc.ABCMeta):
nr_of_clauses = len(self.find_clauses(UpdateExpressionAddClause)) nr_of_clauses = len(self.find_clauses(UpdateExpressionAddClause))
if nr_of_clauses > 1: if nr_of_clauses > 1:
raise TooManyAddClauses() raise TooManyAddClauses()
set_actions = self.find_clauses(UpdateExpressionSetAction)
set_attributes = [
c.children[0].children[0].children[0] for c 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)):
raise DuplicateUpdateExpression(set_attributes)
def find_clauses(self, clause_type): def find_clauses(self, clause_type):
clauses = [] clauses = []

View File

@ -578,6 +578,45 @@ def test_update_item_non_existent_table():
assert err["Message"].should.equal("Requested resource not found") assert err["Message"].should.equal("Requested resource not found")
@mock_dynamodb
@pytest.mark.parametrize(
"expression",
[
"set example_column = :example_column, example_column = :example_column",
"set example_column = :example_column ADD x :y set example_column = :example_column",
],
)
def test_update_item_with_duplicate_expressions(expression):
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="example_table",
KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
record = {
"pk": "example_id",
"example_column": "example",
}
table = dynamodb.Table("example_table")
table.put_item(Item=record)
with pytest.raises(ClientError) as exc:
table.update_item(
Key={"pk": "example_id"},
UpdateExpression=expression,
ExpressionAttributeValues={":example_column": "test"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [example_column], path two: [example_column]"
)
# The item is not updated
item = table.get_item(Key={"pk": "example_id"})["Item"]
item.should.equal({"pk": "example_id", "example_column": "example"})
@mock_dynamodb @mock_dynamodb
def test_put_item_wrong_datatype(): def test_put_item_wrong_datatype():
if settings.TEST_SERVER_MODE: if settings.TEST_SERVER_MODE: