DynamoDB - do not allow HashKey to be changed on update (#5620)

This commit is contained in:
Bert Blommers 2022-10-30 20:15:25 -01:00 committed by GitHub
parent cf5e7b750f
commit 0f9a907af0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 80 additions and 2 deletions

View File

@ -341,8 +341,9 @@ class EmptyStringKeyValueValidator(DepthFirstTraverser):
class UpdateHashRangeKeyValidator(DepthFirstTraverser): class UpdateHashRangeKeyValidator(DepthFirstTraverser):
def __init__(self, table_key_attributes): def __init__(self, table_key_attributes, expression_attribute_names):
self.table_key_attributes = table_key_attributes self.table_key_attributes = table_key_attributes
self.expression_attribute_names = expression_attribute_names
def _processing_map(self): def _processing_map(self):
return {UpdateExpressionPath: self.check_for_hash_or_range_key} return {UpdateExpressionPath: self.check_for_hash_or_range_key}
@ -350,6 +351,9 @@ class UpdateHashRangeKeyValidator(DepthFirstTraverser):
def check_for_hash_or_range_key(self, node): def check_for_hash_or_range_key(self, node):
"""Check that hash and range keys are not updated""" """Check that hash and range keys are not updated"""
key_to_update = node.children[0].children[0] key_to_update = node.children[0].children[0]
key_to_update = self.expression_attribute_names.get(
key_to_update, key_to_update
)
if key_to_update in self.table_key_attributes: if key_to_update in self.table_key_attributes:
raise UpdateHashRangeKeyException(key_to_update) raise UpdateHashRangeKeyException(key_to_update)
return node return node
@ -400,7 +404,9 @@ class UpdateExpressionValidator(Validator):
def get_ast_processors(self): def get_ast_processors(self):
"""Get the different processors that go through the AST tree and processes the nodes.""" """Get the different processors that go through the AST tree and processes the nodes."""
processors = [ processors = [
UpdateHashRangeKeyValidator(self.table.table_key_attrs), UpdateHashRangeKeyValidator(
self.table.table_key_attrs, self.expression_attribute_names or {}
),
ExpressionAttributeValueProcessor(self.expression_attribute_values), ExpressionAttributeValueProcessor(self.expression_attribute_values),
ExpressionAttributeResolvingProcessor( ExpressionAttributeResolvingProcessor(
self.expression_attribute_names, self.item self.expression_attribute_names, self.item

View File

@ -798,3 +798,75 @@ def test_transact_write_items_multiple_operations_fail():
err["Message"] err["Message"]
== "TransactItems can only contain one of Check, Put, Update or Delete" == "TransactItems can only contain one of Check, Put, Update or Delete"
) )
@mock_dynamodb
def test_update_primary_key_with_sortkey():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
schema = {
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"},
],
"AttributeDefinitions": [
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
}
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **schema
)
table = dynamodb.Table("test-table")
base_item = {"pk": "testchangepk", "sk": "else"}
table.put_item(Item=base_item)
with pytest.raises(ClientError) as exc:
table.update_item(
Key={"pk": "n/a", "sk": "else"},
UpdateExpression="SET #attr1 = :val1",
ExpressionAttributeNames={"#attr1": "pk"},
ExpressionAttributeValues={":val1": "different"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
)
table.get_item(Key={"pk": "testchangepk", "sk": "else"})["Item"].should.equal(
{"pk": "testchangepk", "sk": "else"}
)
@mock_dynamodb
def test_update_primary_key():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
schema = {
"KeySchema": [{"AttributeName": "pk", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "pk", "AttributeType": "S"}],
}
dynamodb.create_table(
TableName="without_sk", BillingMode="PAY_PER_REQUEST", **schema
)
table = dynamodb.Table("without_sk")
base_item = {"pk": "testchangepk"}
table.put_item(Item=base_item)
with pytest.raises(ClientError) as exc:
table.update_item(
Key={"pk": "n/a"},
UpdateExpression="SET #attr1 = :val1",
ExpressionAttributeNames={"#attr1": "pk"},
ExpressionAttributeValues={":val1": "different"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
)
table.get_item(Key={"pk": "testchangepk"})["Item"].should.equal(
{"pk": "testchangepk"}
)