DynamoDB: Validate duplicate expression paths (#5504)
This commit is contained in:
parent
de23d172ea
commit
3118090fdc
@ -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;'
|
||||||
|
|
||||||
|
@ -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 = []
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user