3636 "Invalid ConditionExpression: Attribute name is a reserved keyword; "
3737 "reserved keyword: status"
3838)
39+ REDUNDANT_PARENS_CONDITION = (
40+ "Invalid ConditionExpression: The expression has redundant parentheses;"
41+ )
42+ UNUSED_NAME_MSG = (
43+ "Value provided in ExpressionAttributeNames unused in expressions: keys: {#n}"
44+ )
45+ UNUSED_VALUE_MSG = (
46+ "Value provided in ExpressionAttributeValues unused in expressions: keys: {:v}"
47+ )
3948
4049
4150@pytest .fixture (scope = "class" )
@@ -137,6 +146,44 @@ def test_tgi_get_names_no_projection_accepted(self, dynamodb_client, hash_table)
137146 assert resp ["Responses" ][0 ]["Item" ]["pk" ]["S" ] == "k1"
138147
139148
149+ class TestTransactReferencedExpressionAccepted :
150+ """Names/values that ARE referenced by an expression are accepted."""
151+
152+ def test_twi_put_name_referenced_by_condition_accepted (
153+ self , dynamodb_client , hash_table
154+ ):
155+ dynamodb_client .transact_write_items (
156+ TransactItems = [
157+ {
158+ "Put" : {
159+ "TableName" : hash_table ,
160+ "Item" : {"pk" : {"S" : "p-ref" }},
161+ "ConditionExpression" : "attribute_not_exists(#n)" ,
162+ "ExpressionAttributeNames" : {"#n" : "pk" },
163+ }
164+ }
165+ ]
166+ )
167+ got = dynamodb_client .get_item (TableName = hash_table , Key = {"pk" : {"S" : "p-ref" }})
168+ assert got ["Item" ]["pk" ]["S" ] == "p-ref"
169+
170+ def test_twi_update_value_referenced_accepted (self , dynamodb_client , hash_table ):
171+ dynamodb_client .transact_write_items (
172+ TransactItems = [
173+ {
174+ "Update" : {
175+ "TableName" : hash_table ,
176+ "Key" : {"pk" : {"S" : "k1" }},
177+ "UpdateExpression" : "SET foo = :v" ,
178+ "ExpressionAttributeValues" : {":v" : {"S" : "set" }},
179+ }
180+ }
181+ ]
182+ )
183+ got = dynamodb_client .get_item (TableName = hash_table , Key = {"pk" : {"S" : "k1" }})
184+ assert got ["Item" ]["foo" ]["S" ] == "set"
185+
186+
140187class TestTransactUnusedWithExpression :
141188 """With an expression present, unused names/values are still rejected."""
142189
@@ -156,7 +203,45 @@ def test_twi_put_unused_name_with_condition_rejected(
156203 }
157204 ]
158205 ),
159- "Value provided in ExpressionAttributeNames unused in expressions: keys: {#n}" ,
206+ UNUSED_NAME_MSG ,
207+ )
208+
209+ def test_twi_delete_unused_value_with_condition_rejected (
210+ self , dynamodb_client , hash_table
211+ ):
212+ _expect_validation (
213+ lambda : dynamodb_client .transact_write_items (
214+ TransactItems = [
215+ {
216+ "Delete" : {
217+ "TableName" : hash_table ,
218+ "Key" : {"pk" : {"S" : "k1" }},
219+ "ConditionExpression" : "attribute_exists(pk)" ,
220+ "ExpressionAttributeValues" : {":v" : {"N" : "1" }},
221+ }
222+ }
223+ ]
224+ ),
225+ UNUSED_VALUE_MSG ,
226+ )
227+
228+ def test_twi_condition_check_unused_name_rejected (
229+ self , dynamodb_client , hash_table
230+ ):
231+ _expect_validation (
232+ lambda : dynamodb_client .transact_write_items (
233+ TransactItems = [
234+ {
235+ "ConditionCheck" : {
236+ "TableName" : hash_table ,
237+ "Key" : {"pk" : {"S" : "k1" }},
238+ "ConditionExpression" : "attribute_exists(pk)" ,
239+ "ExpressionAttributeNames" : {"#n" : "foo" },
240+ }
241+ }
242+ ]
243+ ),
244+ UNUSED_NAME_MSG ,
160245 )
161246
162247 def test_tgi_get_unused_name_with_projection_rejected (
@@ -175,7 +260,7 @@ def test_tgi_get_unused_name_with_projection_rejected(
175260 }
176261 ]
177262 ),
178- "Value provided in ExpressionAttributeNames unused in expressions: keys: {#n}" ,
263+ UNUSED_NAME_MSG ,
179264 )
180265
181266
@@ -249,3 +334,41 @@ def test_twi_delete_condition_reserved_word_prefix(self, dynamodb_client, hash_t
249334 ),
250335 RESERVED_CONDITION ,
251336 )
337+
338+ def test_twi_update_syntax_error_prefix (self , dynamodb_client , hash_table ):
339+ # Syntax error also carries the UpdateExpression prefix.
340+ with pytest .raises (ClientError ) as exc_info :
341+ dynamodb_client .transact_write_items (
342+ TransactItems = [
343+ {
344+ "Update" : {
345+ "TableName" : hash_table ,
346+ "Key" : {"pk" : {"S" : "k1" }},
347+ "UpdateExpression" : "SET foo = " ,
348+ "ExpressionAttributeValues" : {":v" : {"N" : "1" }},
349+ }
350+ }
351+ ]
352+ )
353+ err = exc_info .value .response ["Error" ]
354+ assert err ["Code" ] == "ValidationException"
355+ assert err ["Message" ].startswith ("Invalid UpdateExpression:" )
356+
357+ def test_twi_condition_check_redundant_parens_prefix (
358+ self , dynamodb_client , hash_table
359+ ):
360+ _expect_validation (
361+ lambda : dynamodb_client .transact_write_items (
362+ TransactItems = [
363+ {
364+ "ConditionCheck" : {
365+ "TableName" : hash_table ,
366+ "Key" : {"pk" : {"S" : "k1" }},
367+ "ConditionExpression" : "((pk = :v))" ,
368+ "ExpressionAttributeValues" : {":v" : {"S" : "k1" }},
369+ }
370+ }
371+ ]
372+ ),
373+ REDUNDANT_PARENS_CONDITION ,
374+ )
0 commit comments