fix: add dynamodb type mismatch validator (#7429)
This commit is contained in:
parent
ef7653335b
commit
4a7ed82e8a
@ -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
|
||||||
|
@ -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"]
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user