diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py index 57823cfee..029e8eb32 100644 --- a/moto/dynamodb/exceptions.py +++ b/moto/dynamodb/exceptions.py @@ -19,6 +19,13 @@ class MockValidationException(DynamodbException): 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): pass diff --git a/moto/dynamodb/parsing/key_condition_expression.py b/moto/dynamodb/parsing/key_condition_expression.py index 7638f0e24..2885f7652 100644 --- a/moto/dynamodb/parsing/key_condition_expression.py +++ b/moto/dynamodb/parsing/key_condition_expression.py @@ -1,6 +1,6 @@ from enum import Enum 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 @@ -209,6 +209,8 @@ def validate_schema( ) if comparison != "=": 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") range_key, range_comparison, range_values = next( @@ -219,9 +221,12 @@ def validate_schema( ), (None, None, []), ) - if index_range_key and len(results) > 1 and range_key != index_range_key: - raise MockValidationException( - f"Query condition missed key schema element: {index_range_key}" - ) + if index_range_key: + if len(results) > 1 and range_key != 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] diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py index a751aad8c..1cf70b350 100644 --- a/moto/dynamodb/responses.py +++ b/moto/dynamodb/responses.py @@ -14,6 +14,7 @@ from .exceptions import ( MockValidationException, ResourceNotFoundException, UnknownKeyType, + KeyIsEmptyStringException, ) from moto.dynamodb.models import dynamodb_backends, Table, DynamoDBBackend from moto.dynamodb.models.utilities import dynamo_json_dump @@ -554,10 +555,7 @@ class DynamoHandler(BaseResponse): key = self.body["Key"] empty_keys = [k for k, v in key.items() if not next(iter(v.values()))] if empty_keys: - raise MockValidationException( - "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]}" - ) + raise KeyIsEmptyStringException(empty_keys[0]) projection_expression = self._get_projection_expression() attributes_to_get = self.body.get("AttributesToGet") diff --git a/tests/test_dynamodb/exceptions/test_key_length_exceptions.py b/tests/test_dynamodb/exceptions/test_key_length_exceptions.py index f3749f846..90c200855 100644 --- a/tests/test_dynamodb/exceptions/test_key_length_exceptions.py +++ b/tests/test_dynamodb/exceptions/test_key_length_exceptions.py @@ -3,6 +3,7 @@ import pytest from moto import mock_dynamodb from botocore.exceptions import ClientError +from boto3.dynamodb.conditions import Key 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"] == "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" + )