add dynamodb scanning

This commit is contained in:
Steve Pulec 2013-03-13 10:11:13 -04:00
parent 58ac6c44b0
commit ad4b6c4ee2
4 changed files with 114 additions and 73 deletions

View File

@ -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

View File

@ -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:

View File

@ -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]

View File

@ -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