DynamoDB: Add validation for GSI attrs set to None (#5843)
This commit is contained in:
parent
e47a2fd120
commit
6e7d3ec938
@ -1422,7 +1422,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
else:
|
else:
|
||||||
return table.schema
|
return table.schema
|
||||||
|
|
||||||
def get_table(self, table_name):
|
def get_table(self, table_name) -> Table:
|
||||||
if table_name not in self.tables:
|
if table_name not in self.tables:
|
||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
return self.tables.get(table_name)
|
return self.tables.get(table_name)
|
||||||
|
@ -13,7 +13,7 @@ from .exceptions import (
|
|||||||
ResourceNotFoundException,
|
ResourceNotFoundException,
|
||||||
ConditionalCheckFailed,
|
ConditionalCheckFailed,
|
||||||
)
|
)
|
||||||
from moto.dynamodb.models import dynamodb_backends, dynamo_json_dump
|
from moto.dynamodb.models import dynamodb_backends, dynamo_json_dump, Table
|
||||||
from moto.utilities.aws_headers import amz_crc32, amzn_request_id
|
from moto.utilities.aws_headers import amz_crc32, amzn_request_id
|
||||||
|
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ def include_consumed_capacity(val=1.0):
|
|||||||
return _inner
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
def get_empty_keys_on_put(field_updates, table):
|
def get_empty_keys_on_put(field_updates, table: Table):
|
||||||
"""
|
"""
|
||||||
Return the first key-name that has an empty value. None if all keys are filled
|
Return the first key-name that has an empty value. None if all keys are filled
|
||||||
"""
|
"""
|
||||||
@ -105,6 +105,16 @@ def put_has_empty_attrs(field_updates, table):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_put_has_gsi_keys_set_to_none(item, table: Table) -> None:
|
||||||
|
for gsi in table.global_indexes:
|
||||||
|
for attr in gsi.schema:
|
||||||
|
attr_name = attr["AttributeName"]
|
||||||
|
if attr_name in item and item[attr_name] == {"NULL": True}:
|
||||||
|
raise MockValidationException(
|
||||||
|
f"One or more parameter values were invalid: Type mismatch for Index Key {attr_name} Expected: S Actual: NULL IndexName: {gsi.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_projection_expression(expression):
|
def check_projection_expression(expression):
|
||||||
if expression.upper() in ReservedKeywords.get_reserved_keywords():
|
if expression.upper() in ReservedKeywords.get_reserved_keywords():
|
||||||
raise MockValidationException(
|
raise MockValidationException(
|
||||||
@ -375,15 +385,17 @@ class DynamoHandler(BaseResponse):
|
|||||||
if return_values not in ("ALL_OLD", "NONE"):
|
if return_values not in ("ALL_OLD", "NONE"):
|
||||||
raise MockValidationException("Return values set to invalid value")
|
raise MockValidationException("Return values set to invalid value")
|
||||||
|
|
||||||
empty_key = get_empty_keys_on_put(item, self.dynamodb_backend.get_table(name))
|
table = self.dynamodb_backend.get_table(name)
|
||||||
|
empty_key = get_empty_keys_on_put(item, table)
|
||||||
if empty_key:
|
if empty_key:
|
||||||
raise MockValidationException(
|
raise MockValidationException(
|
||||||
f"One or more parameter values were invalid: An AttributeValue may not contain an empty string. Key: {empty_key}"
|
f"One or more parameter values were invalid: An AttributeValue may not contain an empty string. Key: {empty_key}"
|
||||||
)
|
)
|
||||||
if put_has_empty_attrs(item, self.dynamodb_backend.get_table(name)):
|
if put_has_empty_attrs(item, table):
|
||||||
raise MockValidationException(
|
raise MockValidationException(
|
||||||
"One or more parameter values were invalid: An number set may not be empty"
|
"One or more parameter values were invalid: An number set may not be empty"
|
||||||
)
|
)
|
||||||
|
validate_put_has_gsi_keys_set_to_none(item, table)
|
||||||
|
|
||||||
overwrite = "Expected" not in self.body
|
overwrite = "Expected" not in self.body
|
||||||
if not overwrite:
|
if not overwrite:
|
||||||
|
@ -899,3 +899,43 @@ def test_put_item__string_as_integer_value():
|
|||||||
err = exc.value.response["Error"]
|
err = exc.value.response["Error"]
|
||||||
err["Code"].should.equal("SerializationException")
|
err["Code"].should.equal("SerializationException")
|
||||||
err["Message"].should.equal("Start of structure or map found where not expected")
|
err["Message"].should.equal("Start of structure or map found where not expected")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb
|
||||||
|
def test_gsi_key_cannot_be_empty():
|
||||||
|
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
|
||||||
|
hello_index = {
|
||||||
|
"IndexName": "hello-index",
|
||||||
|
"KeySchema": [{"AttributeName": "hello", "KeyType": "HASH"}],
|
||||||
|
"Projection": {"ProjectionType": "ALL"},
|
||||||
|
}
|
||||||
|
table_name = "lilja-test"
|
||||||
|
|
||||||
|
# Let's create a table with [id: str, hello: str], with an index to hello
|
||||||
|
dynamodb.create_table(
|
||||||
|
TableName=table_name,
|
||||||
|
KeySchema=[
|
||||||
|
{"AttributeName": "id", "KeyType": "HASH"},
|
||||||
|
],
|
||||||
|
AttributeDefinitions=[
|
||||||
|
{"AttributeName": "id", "AttributeType": "S"},
|
||||||
|
{"AttributeName": "hello", "AttributeType": "S"},
|
||||||
|
],
|
||||||
|
GlobalSecondaryIndexes=[hello_index],
|
||||||
|
BillingMode="PAY_PER_REQUEST",
|
||||||
|
)
|
||||||
|
|
||||||
|
table = dynamodb.Table(table_name)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
table.put_item(
|
||||||
|
TableName=table_name,
|
||||||
|
Item={
|
||||||
|
"id": "woop",
|
||||||
|
"hello": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"One or more parameter values were invalid: Type mismatch for Index Key hello Expected: S Actual: NULL IndexName: hello-index"
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user