DynamoDB: FilterExpressions/ProjectionExpressions cannot be an empty string (#6636)

This commit is contained in:
Bert Blommers 2023-08-12 07:01:03 +00:00 committed by GitHub
parent 4179de8a61
commit f297c4216f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 12 deletions

View File

@ -316,7 +316,7 @@ class DynamoDBBackend(BaseBackend):
limit: int,
exclusive_start_key: Dict[str, Any],
scan_index_forward: bool,
projection_expression: str,
projection_expression: Optional[str],
index_name: Optional[str] = None,
expr_names: Optional[Dict[str, str]] = None,
expr_values: Optional[Dict[str, str]] = None,
@ -351,11 +351,11 @@ class DynamoDBBackend(BaseBackend):
filters: Dict[str, Any],
limit: int,
exclusive_start_key: Dict[str, Any],
filter_expression: str,
filter_expression: Optional[str],
expr_names: Dict[str, Any],
expr_values: Dict[str, Any],
index_name: str,
projection_expression: str,
projection_expression: Optional[str],
) -> Tuple[List[Item], int, Optional[Dict[str, Any]]]:
table = self.get_table(table_name)

View File

@ -637,7 +637,7 @@ class Table(CloudFormationModel):
limit: int,
exclusive_start_key: Dict[str, Any],
scan_index_forward: bool,
projection_expression: str,
projection_expression: Optional[str],
index_name: Optional[str] = None,
filter_expression: Any = None,
**filter_kwargs: Any,

View File

@ -351,6 +351,22 @@ class DynamoHandler(BaseResponse):
+ dump_list(actual_attrs)
)
def _get_filter_expression(self) -> Optional[str]:
filter_expression = self.body.get("FilterExpression")
if filter_expression == "":
raise MockValidationException(
"Invalid FilterExpression: The expression can not be empty;"
)
return filter_expression
def _get_projection_expression(self) -> Optional[str]:
expression = self.body.get("ProjectionExpression")
if expression == "":
raise MockValidationException(
"Invalid ProjectionExpression: The expression can not be empty;"
)
return expression
def delete_table(self) -> str:
name = self.body["TableName"]
table = self.dynamodb_backend.delete_table(name)
@ -521,7 +537,7 @@ class DynamoHandler(BaseResponse):
f"empty string value. Key: {empty_keys[0]}"
)
projection_expression = self.body.get("ProjectionExpression")
projection_expression = self._get_projection_expression()
attributes_to_get = self.body.get("AttributesToGet")
if projection_expression and attributes_to_get:
raise MockValidationException(
@ -631,9 +647,9 @@ class DynamoHandler(BaseResponse):
def query(self) -> str:
name = self.body["TableName"]
key_condition_expression = self.body.get("KeyConditionExpression")
projection_expression = self.body.get("ProjectionExpression")
projection_expression = self._get_projection_expression()
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
filter_expression = self.body.get("FilterExpression")
filter_expression = self._get_filter_expression()
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
projection_expression = self._adjust_projection_expression(
@ -726,8 +742,8 @@ class DynamoHandler(BaseResponse):
return dynamo_json_dump(result)
def _adjust_projection_expression(
self, projection_expression: str, expr_attr_names: Dict[str, str]
) -> str:
self, projection_expression: Optional[str], expr_attr_names: Dict[str, str]
) -> Optional[str]:
def _adjust(expression: str) -> str:
return (
expr_attr_names[expression]
@ -762,10 +778,10 @@ class DynamoHandler(BaseResponse):
comparison_values = scan_filter.get("AttributeValueList", [])
filters[attribute_name] = (comparison_operator, comparison_values)
filter_expression = self.body.get("FilterExpression")
filter_expression = self._get_filter_expression()
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
projection_expression = self.body.get("ProjectionExpression", "")
projection_expression = self._get_projection_expression()
exclusive_start_key = self.body.get("ExclusiveStartKey")
limit = self.body.get("Limit")
index_name = self.body.get("IndexName")

View File

@ -144,7 +144,7 @@ def test_empty_expressionattributenames_with_empty_projection():
table = ddb.Table("test-table")
with pytest.raises(ClientError) as exc:
table.get_item(
Key={"id": "my_id"}, ProjectionExpression="", ExpressionAttributeNames={}
Key={"id": "my_id"}, ProjectionExpression="a", ExpressionAttributeNames={}
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
@ -1067,3 +1067,30 @@ def test_list_append_errors_for_unknown_attribute_value():
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
ReturnValues="UPDATED_NEW",
)
@mock_dynamodb
def test_query_with_empty_filter_expression():
ddb = boto3.resource("dynamodb", region_name="us-east-1")
ddb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
table = ddb.Table("test-table")
with pytest.raises(ClientError) as exc:
table.query(
KeyConditionExpression="partitionKey = sth", ProjectionExpression=""
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "Invalid ProjectionExpression: The expression can not be empty;"
)
with pytest.raises(ClientError) as exc:
table.query(KeyConditionExpression="partitionKey = sth", FilterExpression="")
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"] == "Invalid FilterExpression: The expression can not be empty;"
)