From 3a35df1d477a07a75ff3f4b421bdfbde4621b84c Mon Sep 17 00:00:00 2001 From: Lee Hannigan Date: Sun, 25 Oct 2026 21:16:18 +0000 Subject: [PATCH] fix(select): add the validation prefix to the ALL_PROJECTED_ATTRIBUTES refusal Amazon DynamoDB answers Select=ALL_PROJECTED_ATTRIBUTES without an IndexName with `1 validation error detected: ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName`, on Query (with a KeyConditionExpression, with legacy KeyConditions, with ConsistentRead true or false) and on Scan (measured 2026-10-25 at 21:13 and 21:53 UTC, each request twice). ExtendDB sent the sentence without the prefix on both. Query and Scan share the refusal in validate_select_projection, so the text changes there. handle_scan holds a second copy of the sentence after the index lookup, which the shared check always reaches first; it gets the same text so a later reorder of the Scan checks cannot bring the old one back. --- crates/core/src/validation/mod.rs | 4 +- crates/engine/src/scan.rs | 3 +- tests/test_select_all_projected_attributes.py | 83 +++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests/test_select_all_projected_attributes.py diff --git a/crates/core/src/validation/mod.rs b/crates/core/src/validation/mod.rs index c144b46b..5c62c654 100755 --- a/crates/core/src/validation/mod.rs +++ b/crates/core/src/validation/mod.rs @@ -1661,7 +1661,7 @@ pub fn validate_select_projection( } if matches!(select, Some(Select::AllProjectedAttributes)) && !has_index_name { return Err(DynamoDbError::ValidationException( - "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName".to_owned(), + "1 validation error detected: ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName".to_owned(), )); } Ok(()) @@ -3457,7 +3457,7 @@ mod tests { .unwrap_err(); assert_eq!( err.to_string(), - "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName" + "1 validation error detected: ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName" ); assert!( validate_select_projection( diff --git a/crates/engine/src/scan.rs b/crates/engine/src/scan.rs index 1c1de227..9530ae55 100755 --- a/crates/engine/src/scan.rs +++ b/crates/engine/src/scan.rs @@ -377,7 +377,8 @@ pub async fn handle_scan( && index_info.is_none() { return Err(DynamoDbError::ValidationException( - "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName".to_owned(), + "1 validation error detected: ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName" + .to_owned(), )); } if let Some(Select::Count) = input.select diff --git a/tests/test_select_all_projected_attributes.py b/tests/test_select_all_projected_attributes.py new file mode 100644 index 00000000..9c84bd84 --- /dev/null +++ b/tests/test_select_all_projected_attributes.py @@ -0,0 +1,83 @@ +# Copyright 2026 ExtendDB contributors +# SPDX-License-Identifier: Apache-2.0 + +"""Select=ALL_PROJECTED_ATTRIBUTES without an IndexName. + +Amazon DynamoDB answers ``1 validation error detected: +ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName`` +on Query, with a KeyConditionExpression or legacy KeyConditions and with +ConsistentRead, and on Scan (measured 2026-10-25 at 21:13 UTC, us-east-1, +each request twice). + +Dual-target against Amazon DynamoDB and extenddb. +""" + +from __future__ import annotations + +import pytest +from botocore.exceptions import ClientError + +from conftest import scoped_table + +MESSAGE = ( + "1 validation error detected: " + "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName" +) + + +@pytest.fixture(scope="module") +def table(dynamodb_client): + with scoped_table( + dynamodb_client, + attribute_definitions=[ + {"AttributeName": "pk", "AttributeType": "S"}, + {"AttributeName": "sk", "AttributeType": "S"}, + ], + key_schema=[ + {"AttributeName": "pk", "KeyType": "HASH"}, + {"AttributeName": "sk", "KeyType": "RANGE"}, + ], + ) as name: + yield name + + +def _message(func) -> str: + with pytest.raises(ClientError) as exc_info: + func() + err = exc_info.value.response["Error"] + assert err["Code"] == "ValidationException", err + return err["Message"] + + +@pytest.mark.parametrize("consistent", [False, True]) +def test_query_key_condition_expression(dynamodb_client, table, consistent): + msg = _message( + lambda: dynamodb_client.query( + TableName=table, + KeyConditionExpression="pk = :p", + ExpressionAttributeValues={":p": {"S": "absent"}}, + Select="ALL_PROJECTED_ATTRIBUTES", + ConsistentRead=consistent, + ) + ) + assert msg == MESSAGE + + +def test_query_legacy_key_conditions(dynamodb_client, table): + msg = _message( + lambda: dynamodb_client.query( + TableName=table, + KeyConditions={ + "pk": {"ComparisonOperator": "EQ", "AttributeValueList": [{"S": "absent"}]} + }, + Select="ALL_PROJECTED_ATTRIBUTES", + ) + ) + assert msg == MESSAGE + + +def test_scan(dynamodb_client, table): + msg = _message( + lambda: dynamodb_client.scan(TableName=table, Select="ALL_PROJECTED_ATTRIBUTES") + ) + assert msg == MESSAGE