14
14
from aws_dbesdk_dynamodb_test_vectors .waiting_boto3_ddb_client import WaitingLocalDynamoClient
15
15
16
16
from boto3 .dynamodb .conditions import Key , Attr
17
+ from decimal import Decimal
18
+
19
+ import json
20
+ import os
21
+ from typing import Any , Dict
22
+
23
+ def load_test_data () -> Dict [str , Any ]:
24
+ """Load the test data from data.json file."""
25
+ # Get the directory of the current file
26
+ current_dir = os .getcwd ()
27
+ # Navigate to the data.json file
28
+ data_file = os .path .join (current_dir , 'data.json' )
29
+
30
+ with open (data_file , 'r' ) as f :
31
+ return json .load (f )
32
+
33
+ expression_attribute_values_from_json = load_test_data ()["Values" ]
34
+
35
+ def get_test_value (name ) -> Any :
36
+ """
37
+ Get a test value from the Values section of data.json.
38
+
39
+ Args:
40
+ name: The name of the value to retrieve (e.g. ":zero", ":one", etc.)
41
+
42
+ Returns:
43
+ The value from the Values section
44
+
45
+ Raises:
46
+ KeyError: If the requested value name is not found
47
+ """
48
+ if name not in expression_attribute_values_from_json :
49
+ raise KeyError (f"Value '{ name } ' not found in test data" )
50
+ value = expression_attribute_values_from_json [name ]
51
+ if isinstance (value , dict ):
52
+ if "N" in value :
53
+ return Decimal (value ["N" ])
54
+ elif "SS" in value :
55
+ return set (value ["SS" ])
56
+ elif "L" in value :
57
+ return list (value ["L" ])
58
+ else :
59
+ raise ValueError (f"Unknown value type: { value } " )
60
+ return value
61
+
17
62
18
63
# When querying, DBESDK DDB TestVectors will pass the Table the query as a string.
19
64
# The Table could accept this string as-is and process it correctly.
30
75
# if they are not added, the Table will accept the string as-is.
31
76
known_filter_expression_string_to_condition_map = {
32
77
# "Basic" queries
33
- "RecNum = :zero" : Attr ("RecNum" ).eq (":zero" ),
34
- "RecNum <= :zero" : Attr ("RecNum" ).lte (":zero" ),
35
- "RecNum > :zero" : Attr ("RecNum" ).gt (":zero" ),
36
- "RecNum >= :zero" : Attr ("RecNum" ).gte (":zero" ),
37
- "RecNum <> :zero" : Attr ("RecNum" ).ne (":zero" ),
38
- "RecNum = :one" : Attr ("RecNum" ).eq (":one" ),
39
- "Nine between :zeroD and :three" : Attr ("Nine" ).between (":zeroD" , ":three" ),
40
- "Nine between :nineD and :nine" : Attr ("Nine" ).between (":nineD" , ":nine" ),
41
- "Nine between :nine and :three" : Attr ("Nine" ).between (":nine" , ":three" ),
42
- "Nine between :nine and :nine" : Attr ("Nine" ).between (":nine" , ":nine" ),
43
- "NumberTest = :NumberTest" : Attr ("NumberTest" ).eq (":NumberTest" ),
44
- "RecNum in (:zero, :one)" : Attr ("RecNum" ).is_in ([":zero" , ":one" ]),
45
- "Two = :two" : Attr ("Two" ).eq (":two" ),
46
- "Two = :two or Three = :three or Four = :four OR Five = :five" : Attr ("Two" ).eq (":two" ) | Attr ("Three" ).eq (":three" ) | Attr ("Four" ).eq (":four" ) | Attr ("Five" ).eq (":five" ),
47
- "Two = :two and Three = :three and Four = :four and Five = :five" : Attr ("Two" ).eq (":two" ) & Attr ("Three" ).eq (":three" ) & Attr ("Four" ).eq (":four" ) & Attr ("Five" ).eq (":five" ),
48
- "Two in (:two, :three, :four, :five)" : Attr ("Two" ).is_in ([":two" , ":three" , ":four" , ":five" ]),
49
- "Five in (:two, :three, :four, :five)" : Attr ("Five" ).is_in ([":two" , ":three" , ":four" , ":five" ]),
50
- "Five in (:strset)" : Attr ("Five" ).is_in ([":strset" ]),
51
- "Five in (:strlist)" : Attr ("Five" ).is_in ([":strlist" ]),
52
- "contains(One, :oneA)" : Attr ("One" ).contains (":oneA" ),
53
- "contains(One, :oneB)" : Attr ("One" ).contains (":oneB" ),
78
+ "RecNum = :zero" : Attr ("RecNum" ).eq (get_test_value ( ":zero" ) ),
79
+ "RecNum <= :zero" : Attr ("RecNum" ).lte (get_test_value ( ":zero" ) ),
80
+ "RecNum > :zero" : Attr ("RecNum" ).gt (get_test_value ( ":zero" ) ),
81
+ "RecNum >= :zero" : Attr ("RecNum" ).gte (get_test_value ( ":zero" ) ),
82
+ "RecNum <> :zero" : Attr ("RecNum" ).ne (get_test_value ( ":zero" ) ),
83
+ "RecNum = :one" : Attr ("RecNum" ).eq (get_test_value ( ":one" ) ),
84
+ "Nine between :zeroD and :three" : Attr ("Nine" ).between (get_test_value ( ":zeroD" ), get_test_value ( ":three" ) ),
85
+ "Nine between :nineD and :nine" : Attr ("Nine" ).between (get_test_value ( ":nineD" ), get_test_value ( ":nine" ) ),
86
+ "Nine between :nine and :three" : Attr ("Nine" ).between (get_test_value ( ":nine" ), get_test_value ( ":three" ) ),
87
+ "Nine between :nine and :nine" : Attr ("Nine" ).between (get_test_value ( ":nine" ), get_test_value ( ":nine" ) ),
88
+ "NumberTest = :NumberTest" : Attr ("NumberTest" ).eq (get_test_value ( ":NumberTest" ) ),
89
+ "RecNum in (:zero, :one)" : Attr ("RecNum" ).is_in ([get_test_value ( ":zero" ), get_test_value ( ":one" ) ]),
90
+ "Two = :two" : Attr ("Two" ).eq (get_test_value ( ":two" ) ),
91
+ "Two = :two or Three = :three or Four = :four OR Five = :five" : Attr ("Two" ).eq (get_test_value ( ":two" )) | Attr ("Three" ).eq (get_test_value ( ":three" )) | Attr ("Four" ).eq (get_test_value ( ":four" )) | Attr ("Five" ).eq (get_test_value ( ":five" ) ),
92
+ "Two = :two and Three = :three and Four = :four and Five = :five" : Attr ("Two" ).eq (get_test_value ( ":two" )) & Attr ("Three" ).eq (get_test_value ( ":three" )) & Attr ("Four" ).eq (get_test_value ( ":four" )) & Attr ("Five" ).eq (get_test_value ( ":five" ) ),
93
+ "Two in (:two, :three, :four, :five)" : Attr ("Two" ).is_in ([get_test_value ( ":two" ), get_test_value ( ":three" ), get_test_value ( ":four" ), get_test_value ( ":five" ) ]),
94
+ "Five in (:two, :three, :four, :five)" : Attr ("Five" ).is_in ([get_test_value ( ":two" ), get_test_value ( ":three" ), get_test_value ( ":four" ), get_test_value ( ":five" ) ]),
95
+ "Five in (:strset)" : Attr ("Five" ).is_in ([get_test_value ( ":strset" ) ]),
96
+ "Five in (:strlist)" : Attr ("Five" ).is_in ([get_test_value ( ":strlist" ) ]),
97
+ "contains(One, :oneA)" : Attr ("One" ).contains (get_test_value ( ":oneA" ) ),
98
+ "contains(One, :oneB)" : Attr ("One" ).contains (get_test_value ( ":oneB" ) ),
54
99
# Hard-coding returning the input string for these cases.
55
100
# These conditions test undocumented behavior in DynamoDB that can't be expressed with boto3 Conditions.
56
101
# The undocumented behavior is that `contains`' first parameter can be a value,
70
115
"contains(:strset, One)" : "contains(:strset, One)" ,
71
116
72
117
# "Complex" queries
73
- "Comp1 := :cmp1a" : Attr ("Comp1" ).eq (":cmp1a" ),
74
- "begins_with(Comp1, :cmp1c)" : Attr ("Comp1" ).begins_with (":cmp1c" ),
75
- "cmp1c < Comp1" : Attr ("cmp1c" ).lt ("Comp1" ),
76
- "cmp1c = Comp1" : Attr ("cmp1c" ).eq ("Comp1" ),
77
- "begins_with(Comp1, :cmp1d)" : Attr ("Comp1" ).begins_with (":cmp1d" ),
78
- "contains(Comp1, :cmp1c)" : Attr ("Comp1" ).contains (":cmp1c" ),
79
- "contains(Comp1, :cmp1d)" : Attr ("Comp1" ).contains (":cmp1d" ),
80
- "Comp1 = :cmp1b" : Attr ("Comp1" ).eq (":cmp1b" ),
118
+ "Comp1 := :cmp1a" : Attr ("Comp1" ).eq (get_test_value ( ":cmp1a" ) ),
119
+ "begins_with(Comp1, :cmp1c)" : Attr ("Comp1" ).begins_with (get_test_value ( ":cmp1c" ) ),
120
+ "cmp1c < Comp1" : Attr ("cmp1c" ).lt (get_test_value ( ":cmp1c" ) ),
121
+ "cmp1c = Comp1" : Attr ("cmp1c" ).eq (get_test_value ( ":cmp1c" ) ),
122
+ "begins_with(Comp1, :cmp1d)" : Attr ("Comp1" ).begins_with (get_test_value ( ":cmp1d" ) ),
123
+ "contains(Comp1, :cmp1c)" : Attr ("Comp1" ).contains (get_test_value ( ":cmp1c" ) ),
124
+ "contains(Comp1, :cmp1d)" : Attr ("Comp1" ).contains (get_test_value ( ":cmp1d" ) ),
125
+ "Comp1 = :cmp1b" : Attr ("Comp1" ).eq (get_test_value ( ":cmp1b" ) ),
81
126
82
127
# Another query that can't be translated to boto3 Conditions,
83
128
# since attribute values aren't attribute names.
87
132
88
133
# KeyConditionExpression strings expect Keys, not Attrs.
89
134
known_key_condition_expression_string_to_condition_map = {
90
- "RecNum = :zero" : Attr ("RecNum" ).eq (":zero" ),
91
- "RecNum = :one" : Attr ("RecNum" ).eq (":one" ),
135
+ "RecNum = :zero" : Key ("RecNum" ).eq (get_test_value ( ":zero" ) ),
136
+ "RecNum = :one" : Key ("RecNum" ).eq (get_test_value ( ":one" ) ),
92
137
}
93
138
94
139
class DynamoDBClientWrapperForDynamoDBTable :
@@ -152,17 +197,25 @@ def scan(self, **kwargs):
152
197
# into the boto3.conditions.Key and boto3.conditions.Attr resource-formatted queries.
153
198
if "KeyConditionExpression" in table_input :
154
199
if table_input ["KeyConditionExpression" ] in known_key_condition_expression_string_to_condition_map :
155
- print (f"Converting { table_input ['KeyConditionExpression' ]= } to { known_key_condition_expression_string_to_condition_map [table_input ['KeyConditionExpression' ]]= } " )
156
200
table_input ["KeyConditionExpression" ] = known_key_condition_expression_string_to_condition_map [table_input ["KeyConditionExpression" ]]
201
+ # boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
202
+ # if the KeyConditionExpression is not a string.
203
+ # If the KeyConditionExpression was replaced, remove the now-useless ExpressionAttributeValues.
204
+ if "ExpressionAttributeValues" in table_input and not isinstance (table_input ["KeyConditionExpression" ], str ):
205
+ del table_input ["ExpressionAttributeValues" ]
157
206
else :
158
207
# Pass the original string through.
159
208
# The table will accept the string as-is.
160
209
pass
161
210
if "FilterExpression" in table_input :
162
211
if table_input ["FilterExpression" ] in known_filter_expression_string_to_condition_map :
163
212
# Turn the query into the resource-formatted query
164
- print (f"Converting { table_input ['FilterExpression' ]= } to { known_filter_expression_string_to_condition_map [table_input ['FilterExpression' ]]= } " )
165
213
table_input ["FilterExpression" ] = known_filter_expression_string_to_condition_map [table_input ["FilterExpression" ]]
214
+ # boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
215
+ # if the FilterExpression is not a string.
216
+ # If the FilterExpression was replaced, remove the now-useless ExpressionAttributeValues.
217
+ if "ExpressionAttributeValues" in table_input and not isinstance (table_input ["FilterExpression" ], str ):
218
+ del table_input ["ExpressionAttributeValues" ]
166
219
else :
167
220
# Pass the original string through.
168
221
# The table will accept the string as-is.
@@ -178,23 +231,33 @@ def transact_write_items(self, **kwargs):
178
231
raise NotImplementedError ("transact_write_items not supported on table interface; remove tests calling this" )
179
232
180
233
def query (self , ** kwargs ):
234
+ print (f'{ kwargs = } ' )
181
235
table_input = self ._client_shape_to_resource_shape_converter .query_request (kwargs )
236
+ print (f'{ table_input = } ' )
182
237
# To exhaustively test Tables,
183
238
# convert the string-based KeyConditionExpression and FilterExpression
184
239
# into the boto3.conditions.Key and boto3.conditions.Attr resource-formatted queries.
185
240
if "KeyConditionExpression" in table_input :
186
241
if table_input ["KeyConditionExpression" ] in known_key_condition_expression_string_to_condition_map :
187
- print (f"Converting { table_input ['KeyConditionExpression' ]= } to { known_key_condition_expression_string_to_condition_map [table_input ['KeyConditionExpression' ]]= } " )
188
242
table_input ["KeyConditionExpression" ] = known_key_condition_expression_string_to_condition_map [table_input ["KeyConditionExpression" ]]
243
+ # boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
244
+ # if the KeyConditionExpression is not a string.
245
+ # If the KeyConditionExpression was replaced, remove the now-useless ExpressionAttributeValues.
246
+ if "ExpressionAttributeValues" in table_input and not isinstance (table_input ["KeyConditionExpression" ], str ):
247
+ del table_input ["ExpressionAttributeValues" ]
189
248
else :
190
249
# Pass the original string through.
191
250
# The table will accept the string as-is.
192
251
pass
193
252
if "FilterExpression" in table_input :
194
253
if table_input ["FilterExpression" ] in known_filter_expression_string_to_condition_map :
195
254
# Turn the query into the resource-formatted query
196
- print (f"Converting { table_input ['FilterExpression' ]= } to { known_filter_expression_string_to_condition_map [table_input ['FilterExpression' ]]= } " )
197
255
table_input ["FilterExpression" ] = known_filter_expression_string_to_condition_map [table_input ["FilterExpression" ]]
256
+ # boto3 Conditions cannot accept any externally-provided ExpressionAttributeValues
257
+ # if the FilterExpression is not a string.
258
+ # If the FilterExpression was replaced, remove the now-useless ExpressionAttributeValues.
259
+ if "ExpressionAttributeValues" in table_input and not isinstance (table_input ["FilterExpression" ], str ):
260
+ del table_input ["ExpressionAttributeValues" ]
198
261
else :
199
262
# Pass the original string through.
200
263
# The table will accept the string as-is.
@@ -228,7 +291,7 @@ def CreateInterceptedDDBClient(dafny_encryption_config):
228
291
# If needed, >1 table could be supported by setting up an EncryptedTablesManager
229
292
raise ValueError (">1 table not supported" )
230
293
# For TestVectors, use local DynamoDB endpoint
231
- table = boto3 .resource ('dynamodb' , endpoint_url = "http://localhost:8000" ).Table (table_config_names [0 ])
294
+ table = boto3 .resource ('dynamodb' ).Table (table_config_names [0 ])
232
295
encrypted_table = EncryptedTable (table = table , encryption_config = native_encryption_config )
233
296
wrapped_encrypted_table = DynamoDBClientWrapperForDynamoDBTable (table = encrypted_table , client = boto3_client )
234
297
return aws_cryptography_internal_dynamodb .internaldafny .extern .Com_Amazonaws_Dynamodb .default__ .DynamoDBClient (wrapped_encrypted_table )
0 commit comments