DynamoDB: Query with KeyConditionExpression now throws exception on empty keys (#7065)

This commit is contained in:
Bert Blommers 2023-11-24 20:06:38 -01:00 committed by GitHub
parent 9e7295ddef
commit 20abb764a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 9 deletions

View File

@ -19,6 +19,13 @@ class MockValidationException(DynamodbException):
self.exception_msg = message self.exception_msg = message
class KeyIsEmptyStringException(MockValidationException):
def __init__(self, empty_key: str):
super().__init__(
message=f"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: {empty_key}"
)
class InvalidIndexNameError(MockValidationException): class InvalidIndexNameError(MockValidationException):
pass pass

View File

@ -1,6 +1,6 @@
from enum import Enum from enum import Enum
from typing import Any, List, Dict, Tuple, Optional, Union from typing import Any, List, Dict, Tuple, Optional, Union
from moto.dynamodb.exceptions import MockValidationException from moto.dynamodb.exceptions import MockValidationException, KeyIsEmptyStringException
from moto.utilities.tokenizer import GenericTokenizer from moto.utilities.tokenizer import GenericTokenizer
@ -209,6 +209,8 @@ def validate_schema(
) )
if comparison != "=": if comparison != "=":
raise MockValidationException("Query key condition not supported") raise MockValidationException("Query key condition not supported")
if "S" in hash_value and hash_value["S"] == "":
raise KeyIsEmptyStringException(index_hash_key) # type: ignore[arg-type]
index_range_key = get_key(schema, "RANGE") index_range_key = get_key(schema, "RANGE")
range_key, range_comparison, range_values = next( range_key, range_comparison, range_values = next(
@ -219,9 +221,12 @@ def validate_schema(
), ),
(None, None, []), (None, None, []),
) )
if index_range_key and len(results) > 1 and range_key != index_range_key: if index_range_key:
raise MockValidationException( if len(results) > 1 and range_key != index_range_key:
f"Query condition missed key schema element: {index_range_key}" raise MockValidationException(
) f"Query condition missed key schema element: {index_range_key}"
)
if {"S": ""} in range_values:
raise KeyIsEmptyStringException(index_range_key)
return hash_value, range_comparison, range_values # type: ignore[return-value] return hash_value, range_comparison, range_values # type: ignore[return-value]

View File

@ -14,6 +14,7 @@ from .exceptions import (
MockValidationException, MockValidationException,
ResourceNotFoundException, ResourceNotFoundException,
UnknownKeyType, UnknownKeyType,
KeyIsEmptyStringException,
) )
from moto.dynamodb.models import dynamodb_backends, Table, DynamoDBBackend from moto.dynamodb.models import dynamodb_backends, Table, DynamoDBBackend
from moto.dynamodb.models.utilities import dynamo_json_dump from moto.dynamodb.models.utilities import dynamo_json_dump
@ -554,10 +555,7 @@ class DynamoHandler(BaseResponse):
key = self.body["Key"] key = self.body["Key"]
empty_keys = [k for k, v in key.items() if not next(iter(v.values()))] empty_keys = [k for k, v in key.items() if not next(iter(v.values()))]
if empty_keys: if empty_keys:
raise MockValidationException( raise KeyIsEmptyStringException(empty_keys[0])
"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an "
f"empty string value. Key: {empty_keys[0]}"
)
projection_expression = self._get_projection_expression() projection_expression = self._get_projection_expression()
attributes_to_get = self.body.get("AttributesToGet") attributes_to_get = self.body.get("AttributesToGet")

View File

@ -3,6 +3,7 @@ import pytest
from moto import mock_dynamodb from moto import mock_dynamodb
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key
from moto.dynamodb.limits import HASH_KEY_MAX_LENGTH, RANGE_KEY_MAX_LENGTH from moto.dynamodb.limits import HASH_KEY_MAX_LENGTH, RANGE_KEY_MAX_LENGTH
@ -323,3 +324,40 @@ def test_item_add_empty_key_exception():
ex.value.response["Error"]["Message"] ex.value.response["Error"]["Message"]
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: forum_name" == "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: forum_name"
) )
@mock_dynamodb
def test_query_empty_key_exception():
name = "TestTable"
conn = boto3.client("dynamodb", region_name="us-west-2")
conn.create_table(
TableName=name,
KeySchema=[
{"AttributeName": "hk", "KeyType": "HASH"},
{"AttributeName": "rk", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "hk", "AttributeType": "S"},
{"AttributeName": "rk", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = boto3.resource("dynamodb", "us-west-2").Table(name)
with pytest.raises(ClientError) as ex:
table.query(KeyConditionExpression=Key("hk").eq(""))
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: hk"
)
with pytest.raises(ClientError) as ex:
table.query(KeyConditionExpression=Key("hk").eq("sth") & Key("rk").eq(""))
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: rk"
)