@@ -109,6 +109,24 @@ def get_index_hash_keyname(self, index_name):
109
109
if schema_key .get (KEY_TYPE ) == HASH :
110
110
return schema_key .get (ATTR_NAME )
111
111
112
+ def get_index_range_keyname (self , index_name ):
113
+ """
114
+ Returns the name of the hash key for a given index
115
+ """
116
+ global_indexes = self .data .get (GLOBAL_SECONDARY_INDEXES )
117
+ local_indexes = self .data .get (LOCAL_SECONDARY_INDEXES )
118
+ indexes = []
119
+ if local_indexes :
120
+ indexes += local_indexes
121
+ if global_indexes :
122
+ indexes += global_indexes
123
+ for index in indexes :
124
+ if index .get (INDEX_NAME ) == index_name :
125
+ for schema_key in index .get (KEY_SCHEMA ):
126
+ if schema_key .get (KEY_TYPE ) == RANGE :
127
+ return schema_key .get (ATTR_NAME )
128
+ return None
129
+
112
130
def get_item_attribute_map (self , attributes , item_key = ITEM , pythonic_key = True ):
113
131
"""
114
132
Builds up a dynamodb compatible AttributeValue map
@@ -1031,6 +1049,7 @@ def get_item(self,
1031
1049
1032
1050
def rate_limited_scan (self ,
1033
1051
table_name ,
1052
+ filter_condition = None ,
1034
1053
attributes_to_get = None ,
1035
1054
page_size = None ,
1036
1055
limit = None ,
@@ -1051,6 +1070,7 @@ def rate_limited_scan(self,
1051
1070
limit the rate of the scan. 'ProvisionedThroughputExceededException' is also handled and retried.
1052
1071
1053
1072
:param table_name: Name of the table to perform scan on.
1073
+ :param filter_condition: Condition used to restrict the scan results
1054
1074
:param attributes_to_get: A list of attributes to return.
1055
1075
:param page_size: Page size of the scan to DynamoDB
1056
1076
:param limit: Used to limit the number of results returned
@@ -1094,6 +1114,7 @@ def rate_limited_scan(self,
1094
1114
try :
1095
1115
data = self .scan (
1096
1116
table_name ,
1117
+ filter_condition = filter_condition ,
1097
1118
attributes_to_get = attributes_to_get ,
1098
1119
exclusive_start_key = last_evaluated_key ,
1099
1120
limit = page_size ,
@@ -1177,6 +1198,7 @@ def rate_limited_scan(self,
1177
1198
1178
1199
def scan (self ,
1179
1200
table_name ,
1201
+ filter_condition = None ,
1180
1202
attributes_to_get = None ,
1181
1203
limit = None ,
1182
1204
conditional_operator = None ,
@@ -1189,10 +1211,15 @@ def scan(self,
1189
1211
"""
1190
1212
Performs the scan operation
1191
1213
"""
1214
+ self ._check_condition ('filter_condition' , filter_condition , scan_filter , conditional_operator )
1215
+
1192
1216
operation_kwargs = {TABLE_NAME : table_name }
1193
1217
name_placeholders = {}
1194
1218
expression_attribute_values = {}
1195
1219
1220
+ if filter_condition is not None :
1221
+ filter_expression = filter_condition .serialize (name_placeholders , expression_attribute_values )
1222
+ operation_kwargs [FILTER_EXPRESSION ] = filter_expression
1196
1223
if attributes_to_get is not None :
1197
1224
projection_expression = create_projection_expression (attributes_to_get , name_placeholders )
1198
1225
operation_kwargs [PROJECTION_EXPRESSION ] = projection_expression
@@ -1226,6 +1253,8 @@ def scan(self,
1226
1253
def query (self ,
1227
1254
table_name ,
1228
1255
hash_key ,
1256
+ range_key_condition = None ,
1257
+ filter_condition = None ,
1229
1258
attributes_to_get = None ,
1230
1259
consistent_read = False ,
1231
1260
exclusive_start_key = None ,
@@ -1240,6 +1269,9 @@ def query(self,
1240
1269
"""
1241
1270
Performs the Query operation and returns the result
1242
1271
"""
1272
+ self ._check_condition ('range_key_condition' , range_key_condition , key_conditions , conditional_operator )
1273
+ self ._check_condition ('filter_condition' , filter_condition , query_filters , conditional_operator )
1274
+
1243
1275
operation_kwargs = {TABLE_NAME : table_name }
1244
1276
name_placeholders = {}
1245
1277
expression_attribute_values = {}
@@ -1251,10 +1283,21 @@ def query(self,
1251
1283
hash_keyname = tbl .get_index_hash_keyname (index_name )
1252
1284
if not hash_keyname :
1253
1285
raise ValueError ("No hash key attribute for index: {0}" .format (index_name ))
1286
+ range_keyname = tbl .get_index_range_keyname (index_name )
1254
1287
else :
1255
1288
hash_keyname = tbl .hash_keyname
1289
+ range_keyname = tbl .range_keyname
1290
+
1291
+ key_condition = self ._get_condition (table_name , hash_keyname , '__eq__' , hash_key )
1292
+ if range_key_condition is not None :
1293
+ if range_key_condition .is_valid_range_key_condition (range_keyname ):
1294
+ key_condition = key_condition & range_key_condition
1295
+ elif filter_condition is None :
1296
+ # Try to gracefully handle the case where a user passed in a filter as a range key condition
1297
+ (filter_condition , range_key_condition ) = (range_key_condition , None )
1298
+ else :
1299
+ raise ValueError ("{0} is not a valid range key condition" .format (range_key_condition ))
1256
1300
1257
- key_condition_expression = self ._get_condition (table_name , hash_keyname , '__eq__' , hash_key )
1258
1301
if key_conditions is None or len (key_conditions ) == 0 :
1259
1302
pass # No comparisons on sort key
1260
1303
elif len (key_conditions ) > 1 :
@@ -1267,11 +1310,21 @@ def query(self,
1267
1310
operator = KEY_CONDITION_OPERATOR_MAP [operator ]
1268
1311
values = condition .get (ATTR_VALUE_LIST )
1269
1312
sort_key_expression = self ._get_condition (table_name , key , operator , * values )
1270
- key_condition_expression = key_condition_expression & sort_key_expression
1313
+ key_condition = key_condition & sort_key_expression
1271
1314
1272
- operation_kwargs [KEY_CONDITION_EXPRESSION ] = key_condition_expression .serialize (
1315
+ operation_kwargs [KEY_CONDITION_EXPRESSION ] = key_condition .serialize (
1273
1316
name_placeholders , expression_attribute_values )
1274
-
1317
+ if filter_condition is not None :
1318
+ filter_expression = filter_condition .serialize (name_placeholders , expression_attribute_values )
1319
+ # FilterExpression does not allow key attributes. Check for hash and range key name placeholders
1320
+ hash_key_placeholder = name_placeholders .get (hash_keyname )
1321
+ range_key_placeholder = range_keyname and name_placeholders .get (range_keyname )
1322
+ if (
1323
+ hash_key_placeholder in filter_expression or
1324
+ (range_key_placeholder and range_key_placeholder in filter_expression )
1325
+ ):
1326
+ raise ValueError ("'filter_condition' cannot contain key attributes" )
1327
+ operation_kwargs [FILTER_EXPRESSION ] = filter_expression
1275
1328
if attributes_to_get :
1276
1329
projection_expression = create_projection_expression (attributes_to_get , name_placeholders )
1277
1330
operation_kwargs [PROJECTION_EXPRESSION ] = projection_expression
@@ -1383,7 +1436,7 @@ def _check_condition(self, name, condition, expected_or_filter, conditional_oper
1383
1436
if condition is not None :
1384
1437
if not isinstance (condition , Condition ):
1385
1438
raise ValueError ("'{0}' must be an instance of Condition" .format (name ))
1386
- if expected_or_filter is not None or conditional_operator is not None :
1439
+ if expected_or_filter or conditional_operator is not None :
1387
1440
raise ValueError ("Legacy conditional parameters cannot be used with condition expressions" )
1388
1441
1389
1442
@staticmethod
0 commit comments