more comprehensive table scanning and querying
This commit is contained in:
parent
ad4b6c4ee2
commit
3e6f802a20
@ -6,13 +6,13 @@ COMPARISON_FUNCS = {
|
|||||||
'LT': 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,
|
'GE': lambda item_value, test_value: item_value >= test_value,
|
||||||
'GT': 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,
|
'NULL': lambda item_value, test_value: item_value is None,
|
||||||
'NOT_NULL': lambda item_value: item_value is not None,
|
'NOT_NULL': lambda item_value, test_value: item_value is not None,
|
||||||
'CONTAINS': lambda item_value, test_value: test_value in item_value,
|
'CONTAINS': lambda item_value, test_value: test_value in item_value,
|
||||||
'NOT_CONTAINS': lambda item_value, test_value: test_value not 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),
|
'BEGINS_WITH': lambda item_value, test_value: item_value.startswith(test_value),
|
||||||
'IN': lambda item_value, test_value: item_value in 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],
|
'BETWEEN': lambda item_value, lower_test_value, upper_test_value: lower_test_value <= item_value <= upper_test_value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,14 +97,14 @@ class Table(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def query(self, hash_key, range_comparison, range_value):
|
def query(self, hash_key, range_comparison, range_values):
|
||||||
results = []
|
results = []
|
||||||
last_page = True # Once pagination is implemented, change this
|
last_page = True # Once pagination is implemented, change this
|
||||||
|
|
||||||
possible_results = self.items.get(hash_key, [])
|
possible_results = self.items.get(hash_key, [])
|
||||||
comparison_func = get_comparison_func(range_comparison)
|
comparison_func = get_comparison_func(range_comparison)
|
||||||
for result in possible_results.values():
|
for result in possible_results.values():
|
||||||
if comparison_func(result.range_key, range_value):
|
if comparison_func(result.range_key, *range_values):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
return results, last_page
|
return results, last_page
|
||||||
|
|
||||||
@ -121,12 +121,24 @@ class Table(object):
|
|||||||
for result in self.all_items():
|
for result in self.all_items():
|
||||||
scanned_count += 1
|
scanned_count += 1
|
||||||
passes_all_conditions = True
|
passes_all_conditions = True
|
||||||
for attribute_name, (comparison_operator, comparison_value) in filters.iteritems():
|
for attribute_name, (comparison_operator, comparison_values) in filters.iteritems():
|
||||||
comparison_func = get_comparison_func(comparison_operator)
|
comparison_func = get_comparison_func(comparison_operator)
|
||||||
attribute_value = result.attrs[attribute_name].values()[0]
|
|
||||||
if not comparison_func(attribute_value, comparison_value):
|
attribute = result.attrs.get(attribute_name)
|
||||||
|
if attribute:
|
||||||
|
# Attribute found
|
||||||
|
attribute_value = attribute.values()[0]
|
||||||
|
if not comparison_func(attribute_value, *comparison_values):
|
||||||
passes_all_conditions = False
|
passes_all_conditions = False
|
||||||
break
|
break
|
||||||
|
elif comparison_operator == 'NULL':
|
||||||
|
# Comparison is NULL and we don't have the attribute
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# No attribute found and comparison is no NULL. This item fails
|
||||||
|
passes_all_conditions = False
|
||||||
|
break
|
||||||
|
|
||||||
if passes_all_conditions:
|
if passes_all_conditions:
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
@ -172,12 +184,12 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
|
|
||||||
return table.get_item(hash_key, range_key)
|
return table.get_item(hash_key, range_key)
|
||||||
|
|
||||||
def query(self, table_name, hash_key, range_comparison, range_value):
|
def query(self, table_name, hash_key, range_comparison, range_values):
|
||||||
table = self.tables.get(table_name)
|
table = self.tables.get(table_name)
|
||||||
if not table:
|
if not table:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return table.query(hash_key, range_comparison, range_value)
|
return table.query(hash_key, range_comparison, range_values)
|
||||||
|
|
||||||
def scan(self, table_name, filters):
|
def scan(self, table_name, filters):
|
||||||
table = self.tables.get(table_name)
|
table = self.tables.get(table_name)
|
||||||
|
@ -2,6 +2,7 @@ import json
|
|||||||
|
|
||||||
from moto.core.utils import headers_to_dict
|
from moto.core.utils import headers_to_dict
|
||||||
from .models import dynamodb_backend
|
from .models import dynamodb_backend
|
||||||
|
from .utils import values_from_dynamo_types
|
||||||
|
|
||||||
|
|
||||||
class DynamoHandler(object):
|
class DynamoHandler(object):
|
||||||
@ -128,8 +129,9 @@ class DynamoHandler(object):
|
|||||||
hash_key = body['HashKeyValue'].values()[0]
|
hash_key = body['HashKeyValue'].values()[0]
|
||||||
range_condition = body['RangeKeyCondition']
|
range_condition = body['RangeKeyCondition']
|
||||||
range_comparison = range_condition['ComparisonOperator']
|
range_comparison = range_condition['ComparisonOperator']
|
||||||
range_value = range_condition['AttributeValueList'][0].values()[0]
|
range_values = values_from_dynamo_types(range_condition['AttributeValueList'])
|
||||||
items, last_page = dynamodb_backend.query(name, hash_key, range_comparison, range_value)
|
|
||||||
|
items, last_page = dynamodb_backend.query(name, hash_key, range_comparison, range_values)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"Count": len(items),
|
"Count": len(items),
|
||||||
@ -152,8 +154,11 @@ class DynamoHandler(object):
|
|||||||
for attribute_name, scan_filter in scan_filters.iteritems():
|
for attribute_name, scan_filter in scan_filters.iteritems():
|
||||||
# Keys are attribute names. Values are tuples of (comparison, comparison_value)
|
# Keys are attribute names. Values are tuples of (comparison, comparison_value)
|
||||||
comparison_operator = scan_filter["ComparisonOperator"]
|
comparison_operator = scan_filter["ComparisonOperator"]
|
||||||
comparison_value = scan_filter["AttributeValueList"][0].values()[0]
|
if scan_filter.get("AttributeValueList"):
|
||||||
filters[attribute_name] = (comparison_operator, comparison_value)
|
comparison_values = values_from_dynamo_types(scan_filter.get("AttributeValueList"))
|
||||||
|
else:
|
||||||
|
comparison_values = [None]
|
||||||
|
filters[attribute_name] = (comparison_operator, comparison_values)
|
||||||
|
|
||||||
items, scanned_count, last_page = dynamodb_backend.scan(name, filters)
|
items, scanned_count, last_page = dynamodb_backend.scan(name, filters)
|
||||||
|
|
||||||
|
@ -5,3 +5,18 @@ def unix_time(dt):
|
|||||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||||
delta = dt - epoch
|
delta = dt - epoch
|
||||||
return delta.total_seconds()
|
return delta.total_seconds()
|
||||||
|
|
||||||
|
|
||||||
|
def value_from_dynamo_type(dynamo_type):
|
||||||
|
"""
|
||||||
|
Dynamo return attributes like {"S": "AttributeValue1"}.
|
||||||
|
This function takes that value and returns "AttributeValue1".
|
||||||
|
|
||||||
|
# TODO eventually this should be smarted to actually read the type of
|
||||||
|
the attribute
|
||||||
|
"""
|
||||||
|
return dynamo_type.values()[0]
|
||||||
|
|
||||||
|
|
||||||
|
def values_from_dynamo_types(dynamo_types):
|
||||||
|
return [value_from_dynamo_type(dynamo_type) for dynamo_type in dynamo_types]
|
||||||
|
@ -224,6 +224,15 @@ def test_query():
|
|||||||
results = table.query(hash_key='the-key', range_key_condition=condition.GT('9999'))
|
results = table.query(hash_key='the-key', range_key_condition=condition.GT('9999'))
|
||||||
results.response['Items'].should.have.length_of(0)
|
results.response['Items'].should.have.length_of(0)
|
||||||
|
|
||||||
|
results = table.query(hash_key='the-key', range_key_condition=condition.CONTAINS('12'))
|
||||||
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
|
results = table.query(hash_key='the-key', range_key_condition=condition.BEGINS_WITH('7'))
|
||||||
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
|
results = table.query(hash_key='the-key', range_key_condition=condition.BETWEEN('567', '890'))
|
||||||
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb
|
@mock_dynamodb
|
||||||
def test_scan():
|
def test_scan():
|
||||||
@ -249,6 +258,13 @@ def test_scan():
|
|||||||
)
|
)
|
||||||
item.put()
|
item.put()
|
||||||
|
|
||||||
|
item_data = {
|
||||||
|
'Body': 'http://url_to_lolcat.gif',
|
||||||
|
'SentBy': 'User B',
|
||||||
|
'ReceivedTime': '12/9/2011 11:36:03 PM',
|
||||||
|
'Ids': {1, 2, 3},
|
||||||
|
'PK': 7,
|
||||||
|
}
|
||||||
item = table.new_item(
|
item = table.new_item(
|
||||||
hash_key='the-key',
|
hash_key='the-key',
|
||||||
range_key='789',
|
range_key='789',
|
||||||
@ -257,12 +273,26 @@ def test_scan():
|
|||||||
item.put()
|
item.put()
|
||||||
|
|
||||||
results = table.scan(scan_filter={'SentBy': condition.EQ('User B')})
|
results = table.scan(scan_filter={'SentBy': condition.EQ('User B')})
|
||||||
results.response['Items'].should.have.length_of(0)
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
results = table.scan(scan_filter={'Body': condition.BEGINS_WITH('http')})
|
results = table.scan(scan_filter={'Body': condition.BEGINS_WITH('http')})
|
||||||
results.response['Items'].should.have.length_of(3)
|
results.response['Items'].should.have.length_of(3)
|
||||||
|
|
||||||
|
results = table.scan(scan_filter={'Ids': condition.CONTAINS(2)})
|
||||||
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
|
results = table.scan(scan_filter={'Ids': condition.NOT_NULL()})
|
||||||
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
|
results = table.scan(scan_filter={'Ids': condition.NULL()})
|
||||||
|
results.response['Items'].should.have.length_of(2)
|
||||||
|
|
||||||
|
results = table.scan(scan_filter={'PK': condition.BETWEEN(8, 9)})
|
||||||
|
results.response['Items'].should.have.length_of(0)
|
||||||
|
|
||||||
|
results = table.scan(scan_filter={'PK': condition.BETWEEN(5, 8)})
|
||||||
|
results.response['Items'].should.have.length_of(1)
|
||||||
|
|
||||||
|
|
||||||
# Batch read
|
# Batch read
|
||||||
# Batch write
|
# Batch write
|
||||||
# scan
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user