diff --git a/moto/dynamodb/comparisons.py b/moto/dynamodb/comparisons.py index 18ab79f42..3bbbeae3c 100644 --- a/moto/dynamodb/comparisons.py +++ b/moto/dynamodb/comparisons.py @@ -1,76 +1,20 @@ +# TODO add tests for all of these COMPARISON_FUNCS = { 'EQ': lambda item_value, test_value: item_value == test_value, - 'GT': lambda item_value, test_value: item_value > test_value + 'NE': lambda item_value, test_value: item_value != test_value, + 'LE': lambda item_value, test_value: item_value <= test_value, + 'LT': lambda item_value, test_value: item_value < test_value, + 'GE': lambda item_value, test_value: item_value >= test_value, + 'GT': lambda item_value, test_value: item_value > test_value, + 'NULL': lambda item_value: item_value is None, + 'NOT_NULL': lambda item_value: item_value is not None, + 'CONTAINS': lambda item_value, test_value: test_value in item_value, + 'NOT_CONTAINS': lambda item_value, test_value: test_value not in item_value, + 'BEGINS_WITH': lambda item_value, test_value: item_value.startswith(test_value), + 'IN': lambda item_value, test_value: item_value in test_value, + 'BETWEEN': lambda item_value, test_values: test_values[0] <= item_value <= test_values[1], } def get_comparison_func(range_comparison): return COMPARISON_FUNCS.get(range_comparison) - -# class EQ(ConditionOneArg): - -# pass - - -# class NE(ConditionOneArg): - -# pass - - -# class LE(ConditionOneArg): - -# pass - - -# class LT(ConditionOneArg): - -# pass - - -# class GE(ConditionOneArg): - -# pass - - -# class GT(ConditionOneArg): - -# pass - - -# class NULL(ConditionNoArgs): - -# pass - - -# class NOT_NULL(ConditionNoArgs): - -# pass - - -# class CONTAINS(ConditionOneArg): - -# pass - - -# class NOT_CONTAINS(ConditionOneArg): - -# pass - - -# class BEGINS_WITH(ConditionOneArg): - -# pass - - -# class IN(ConditionOneArg): - -# pass - - -# class BEGINS_WITH(ConditionOneArg): - -# pass - -# class BETWEEN(ConditionTwoArgs): - -# pass diff --git a/moto/dynamodb/models.py b/moto/dynamodb/models.py index bc1362e4d..5dac91f21 100644 --- a/moto/dynamodb/models.py +++ b/moto/dynamodb/models.py @@ -108,6 +108,30 @@ class Table(object): results.append(result) return results, last_page + def all_items(self): + for hash_set in self.items.values(): + for item in hash_set.values(): + yield item + + def scan(self, filters): + results = [] + scanned_count = 0 + last_page = True # Once pagination is implemented, change this + + for result in self.all_items(): + scanned_count += 1 + passes_all_conditions = True + for attribute_name, (comparison_operator, comparison_value) in filters.iteritems(): + comparison_func = get_comparison_func(comparison_operator) + attribute_value = result.attrs[attribute_name].values()[0] + if not comparison_func(attribute_value, comparison_value): + passes_all_conditions = False + break + if passes_all_conditions: + results.append(result) + + return results, scanned_count, last_page + def delete_item(self, hash_key, range_key): try: return self.items[hash_key].pop(range_key) @@ -155,6 +179,13 @@ class DynamoDBBackend(BaseBackend): return table.query(hash_key, range_comparison, range_value) + def scan(self, table_name, filters): + table = self.tables.get(table_name) + if not table: + return None + + return table.scan(filters) + def delete_item(self, table_name, hash_key, range_key): table = self.tables.get(table_name) if not table: diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py index 0442c0764..a6dd95812 100644 --- a/moto/dynamodb/responses.py +++ b/moto/dynamodb/responses.py @@ -144,6 +144,33 @@ class DynamoHandler(object): } return json.dumps(result) + def Scan(self, uri, body, headers): + name = body['TableName'] + + filters = {} + scan_filters = body['ScanFilter'] + for attribute_name, scan_filter in scan_filters.iteritems(): + # Keys are attribute names. Values are tuples of (comparison, comparison_value) + comparison_operator = scan_filter["ComparisonOperator"] + comparison_value = scan_filter["AttributeValueList"][0].values()[0] + filters[attribute_name] = (comparison_operator, comparison_value) + + items, scanned_count, last_page = dynamodb_backend.scan(name, filters) + + result = { + "Count": len(items), + "Items": [item.attrs for item in items], + "ConsumedCapacityUnits": 1, + "ScannedCount": scanned_count + } + + if not last_page: + result["LastEvaluatedKey"] = { + "HashKeyElement": items[-1].hash_key, + "RangeKeyElement": items[-1].range_key, + } + return json.dumps(result) + def DeleteItem(self, uri, body, headers): name = body['TableName'] hash_key = body['Key']['HashKeyElement'].values()[0] diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py index 4a9ea5106..ef34359c8 100644 --- a/tests/test_dynamodb/test_dynamodb.py +++ b/tests/test_dynamodb/test_dynamodb.py @@ -5,7 +5,7 @@ from freezegun import freeze_time from moto import mock_dynamodb from moto.dynamodb import dynamodb_backend -from boto.dynamodb.condition import GT +from boto.dynamodb import condition from boto.exception import DynamoDBResponseError @@ -215,15 +215,54 @@ def test_query(): ) item.put() - results = table.query(hash_key='the-key', range_key_condition=GT('1')) + results = table.query(hash_key='the-key', range_key_condition=condition.GT('1')) results.response['Items'].should.have.length_of(3) - results = table.query(hash_key='the-key', range_key_condition=GT('234')) + results = table.query(hash_key='the-key', range_key_condition=condition.GT('234')) results.response['Items'].should.have.length_of(2) - results = table.query(hash_key='the-key', range_key_condition=GT('9999')) + results = table.query(hash_key='the-key', range_key_condition=condition.GT('9999')) results.response['Items'].should.have.length_of(0) + +@mock_dynamodb +def test_scan(): + conn = boto.connect_dynamodb() + table = create_table(conn) + + item_data = { + 'Body': 'http://url_to_lolcat.gif', + 'SentBy': 'User A', + 'ReceivedTime': '12/9/2011 11:36:03 PM', + } + item = table.new_item( + hash_key='the-key', + range_key='456', + attrs=item_data, + ) + item.put() + + item = table.new_item( + hash_key='the-key', + range_key='123', + attrs=item_data, + ) + item.put() + + item = table.new_item( + hash_key='the-key', + range_key='789', + attrs=item_data, + ) + item.put() + + results = table.scan(scan_filter={'SentBy': condition.EQ('User B')}) + results.response['Items'].should.have.length_of(0) + + results = table.scan(scan_filter={'Body': condition.BEGINS_WITH('http')}) + results.response['Items'].should.have.length_of(3) + + # Batch read # Batch write # scan