fix: add dynamodb type mismatch validator (#7429)

This commit is contained in:
Louis 2024-03-08 03:02:54 +08:00 committed by GitHub
parent ef7653335b
commit 4a7ed82e8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 0 deletions

View File

@ -1,6 +1,7 @@
""" """
See docstring class Validator below for more details on validation See docstring class Validator below for more details on validation
""" """
from abc import abstractmethod from abc import abstractmethod
from copy import deepcopy from copy import deepcopy
from typing import Any, Callable, Dict, List, Type, Union from typing import Any, Callable, Dict, List, Type, Union
@ -12,6 +13,7 @@ from moto.dynamodb.exceptions import (
ExpressionAttributeNameNotDefined, ExpressionAttributeNameNotDefined,
ExpressionAttributeValueNotDefined, ExpressionAttributeValueNotDefined,
IncorrectOperandType, IncorrectOperandType,
InvalidAttributeTypeError,
InvalidUpdateExpressionInvalidDocumentPath, InvalidUpdateExpressionInvalidDocumentPath,
MockValidationException, MockValidationException,
ProvidedKeyDoesNotExist, ProvidedKeyDoesNotExist,
@ -379,6 +381,34 @@ class EmptyStringKeyValueValidator(DepthFirstTraverser): # type: ignore[misc]
return node return node
class TypeMismatchValidator(DepthFirstTraverser): # type: ignore[misc]
def __init__(self, key_attributes_type: List[Dict[str, str]]):
self.key_attributes_type = key_attributes_type
def _processing_map(
self,
) -> Dict[
Type[UpdateExpressionSetAction],
Callable[[UpdateExpressionSetAction], UpdateExpressionSetAction],
]:
return {UpdateExpressionSetAction: self.check_for_type_mismatch}
def check_for_type_mismatch(
self, node: UpdateExpressionSetAction
) -> UpdateExpressionSetAction:
"""A node representing a SET action. Check that type matches with the definition"""
assert isinstance(node, UpdateExpressionSetAction)
assert len(node.children) == 2
key = node.children[0].children[0].children[0]
val_node = node.children[1].children[0]
for dct in self.key_attributes_type:
if dct["AttributeName"] == key and dct["AttributeType"] != val_node.type:
raise InvalidAttributeTypeError(
key, dct["AttributeType"], val_node.type
)
return node
class UpdateHashRangeKeyValidator(DepthFirstTraverser): # type: ignore[misc] class UpdateHashRangeKeyValidator(DepthFirstTraverser): # type: ignore[misc]
def __init__( def __init__(
self, self,
@ -464,6 +494,7 @@ class UpdateExpressionValidator(Validator):
UpdateExpressionFunctionEvaluator(), UpdateExpressionFunctionEvaluator(),
NoneExistingPathChecker(), NoneExistingPathChecker(),
ExecuteOperations(), ExecuteOperations(),
TypeMismatchValidator(self.table.attr),
EmptyStringKeyValueValidator(self.table.attribute_keys), EmptyStringKeyValueValidator(self.table.attribute_keys),
] ]
return processors return processors

View File

@ -5880,3 +5880,80 @@ def test_invalid_projection_expressions():
ClientError, match="ProjectionExpression: Attribute name contains white space" ClientError, match="ProjectionExpression: Attribute name contains white space"
): ):
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, na me") client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, na me")
@mock_aws
def test_update_item_with_global_secondary_index():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table
dynamodb.create_table(
TableName="test",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "gsi_hash_key_s", "AttributeType": "S"},
{"AttributeName": "gsi_hash_key_b", "AttributeType": "B"},
{"AttributeName": "gsi_hash_key_n", "AttributeType": "N"},
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
GlobalSecondaryIndexes=[
{
"IndexName": "test_gsi_s",
"KeySchema": [
{"AttributeName": "gsi_hash_key_s", "KeyType": "HASH"},
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
},
{
"IndexName": "test_gsi_b",
"KeySchema": [
{"AttributeName": "gsi_hash_key_b", "KeyType": "HASH"},
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
},
{
"IndexName": "test_gsi_n",
"KeySchema": [
{"AttributeName": "gsi_hash_key_n", "KeyType": "HASH"},
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
},
},
],
)
table = dynamodb.Table("test")
table.put_item(
Item={"id": "test1"},
)
for key_name, values in {
"gsi_hash_key_s": [None, 0, b"binary"],
"gsi_hash_key_b": [None, "", 0],
"gsi_hash_key_n": [None, "", b"binary"],
}.items():
for v in values:
with pytest.raises(ClientError) as ex:
table.update_item(
Key={"id": "test1"},
UpdateExpression=f"SET {key_name} = :gsi_hash_key",
ExpressionAttributeValues={":gsi_hash_key": v, ":abc": ""},
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
"One or more parameter values were invalid: Type mismatch"
in err["Message"]
)