Add Dynamodb2 global indexes. Closes #329.

This commit is contained in:
Steve Pulec 2015-03-14 15:02:43 -04:00
parent 09fe37da7e
commit 82f19952dd
5 changed files with 137 additions and 16 deletions

View File

@ -122,7 +122,7 @@ class Item(object):
class Table(object):
def __init__(self, table_name, schema=None, attr=None, throughput=None, indexes=None):
def __init__(self, table_name, schema=None, attr=None, throughput=None, indexes=None, global_indexes=None):
self.name = table_name
self.attr = attr
self.schema = schema
@ -143,6 +143,7 @@ class Table(object):
self.throughput = throughput
self.throughput["NumberOfDecreasesToday"] = 0
self.indexes = indexes
self.global_indexes = global_indexes if global_indexes else []
self.created_at = datetime.datetime.now()
self.items = defaultdict(dict)
@ -158,6 +159,7 @@ class Table(object):
'KeySchema': self.schema,
'ItemCount': len(self),
'CreationDateTime': unix_time(self.created_at),
'GlobalSecondaryIndexes': [index for index in self.global_indexes],
}
}
return results
@ -171,6 +173,24 @@ class Table(object):
count += 1
return count
@property
def hash_key_names(self):
keys = [self.hash_key_attr]
for index in self.global_indexes:
for key in index['KeySchema']:
if key['KeyType'] == 'HASH':
keys.append(key['AttributeName'])
return keys
@property
def range_key_names(self):
keys = [self.range_key_attr]
for index in self.global_indexes:
for key in index['KeySchema']:
if key['KeyType'] == 'RANGE':
keys.append(key['AttributeName'])
return keys
def put_item(self, item_attrs):
hash_value = DynamoType(item_attrs.get(self.hash_key_attr))
if self.has_range_key:
@ -293,12 +313,21 @@ class DynamoDBBackend(BaseBackend):
return None
return table.put_item(item_attrs)
def get_table_keys_name(self, table_name):
def get_table_keys_name(self, table_name, keys):
"""
Given a set of keys, extracts the key and range key
"""
table = self.tables.get(table_name)
if not table:
return None, None
else:
return table.hash_key_attr, table.range_key_attr
hash_key = range_key = None
for key in keys:
if key in table.hash_key_names:
hash_key = key
elif key in table.range_key_names:
range_key = key
return hash_key, range_key
def get_keys_value(self, table, keys):
if table.hash_key_attr not in keys or (table.has_range_key and table.range_key_attr not in keys):

View File

@ -99,10 +99,12 @@ class DynamoHandler(BaseResponse):
# getting attribute definition
attr = body["AttributeDefinitions"]
# getting the indexes
global_indexes = body.get("GlobalSecondaryIndexes", [])
table = dynamodb_backend2.create_table(table_name,
schema=key_schema,
throughput=throughput,
attr=attr)
attr=attr,
global_indexes=global_indexes)
return dynamo_json_dump(table.describe)
def delete_table(self):
@ -216,13 +218,14 @@ class DynamoHandler(BaseResponse):
def query(self):
name = self.body['TableName']
keys = self.body['KeyConditions']
hash_key_name, range_key_name = dynamodb_backend2.get_table_keys_name(name)
key_conditions = self.body['KeyConditions']
hash_key_name, range_key_name = dynamodb_backend2.get_table_keys_name(name, key_conditions.keys())
# hash_key_name, range_key_name = dynamodb_backend2.get_table_keys_name(name)
if hash_key_name is None:
er = "'com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
return self.error(er)
hash_key = keys[hash_key_name]['AttributeValueList'][0]
if len(keys) == 1:
hash_key = key_conditions[hash_key_name]['AttributeValueList'][0]
if len(key_conditions) == 1:
range_comparison = None
range_values = []
else:
@ -230,7 +233,7 @@ class DynamoHandler(BaseResponse):
er = "com.amazon.coral.validate#ValidationException"
return self.error(er)
else:
range_condition = keys[range_key_name]
range_condition = key_conditions[range_key_name]
if range_condition:
range_comparison = range_condition['ComparisonOperator']
range_values = range_condition['AttributeValueList']

View File

@ -48,7 +48,7 @@ def test_create_table():
},
'TableName': 'messages',
'TableSizeBytes': 0,
'TableStatus': 'ACTIVE'
'TableStatus': 'ACTIVE',
}
}
conn.describe_table('messages').should.equal(expected)

View File

@ -7,10 +7,8 @@ from moto import mock_dynamodb2
from boto.exception import JSONResponseError
from tests.helpers import requires_boto_gte
try:
from boto.dynamodb2.fields import HashKey
from boto.dynamodb2.fields import RangeKey
from boto.dynamodb2.table import Table
from boto.dynamodb2.table import Item
from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey
from boto.dynamodb2.table import Item, Table
from boto.dynamodb2.exceptions import ValidationException
except ImportError:
pass
@ -53,7 +51,8 @@ def test_create_table():
{'KeyType': 'HASH', 'AttributeName': 'forum_name'},
{'KeyType': 'RANGE', 'AttributeName': 'subject'}
],
'ItemCount': 0, 'CreationDateTime': 1326499200.0
'ItemCount': 0, 'CreationDateTime': 1326499200.0,
'GlobalSecondaryIndexes': [],
}
}
table.describe().should.equal(expected)
@ -445,3 +444,92 @@ def test_get_key_fields():
table = create_table()
kf = table.get_key_fields()
kf.should.equal(['forum_name', 'subject'])
@mock_dynamodb2
def test_create_with_global_indexes():
conn = boto.dynamodb2.layer1.DynamoDBConnection()
Table.create('messages', schema=[
HashKey('subject'),
RangeKey('version'),
], global_indexes=[
GlobalAllIndex('topic-created_at-index',
parts=[
HashKey('topic'),
RangeKey('created_at', data_type='N')
],
throughput={
'read': 6,
'write': 1
}
),
])
table_description = conn.describe_table("messages")
table_description['Table']["GlobalSecondaryIndexes"].should.equal([
{
"IndexName": "topic-created_at-index",
"KeySchema": [
{
"AttributeName": "topic",
"KeyType": "HASH"
},
{
"AttributeName": "created_at",
"KeyType": "RANGE"
},
],
"Projection": {
"ProjectionType": "ALL"
},
"ProvisionedThroughput": {
"ReadCapacityUnits": 6,
"WriteCapacityUnits": 1,
}
}
])
@mock_dynamodb2
def test_query_with_global_indexes():
table = Table.create('messages', schema=[
HashKey('subject'),
RangeKey('version'),
], global_indexes=[
GlobalAllIndex('topic-created_at-index',
parts=[
HashKey('topic'),
RangeKey('created_at', data_type='N')
],
throughput={
'read': 6,
'write': 1
}
),
GlobalAllIndex('status-created_at-index',
parts=[
HashKey('status'),
RangeKey('created_at', data_type='N')
],
throughput={
'read': 2,
'write': 1
}
)
])
item_data = {
'subject': 'Check this out!',
'version': '1',
'created_at': 0,
'status': 'inactive'
}
item = Item(table, item_data)
item.save(overwrite=True)
item['version'] = '2'
item.save(overwrite=True)
results = table.query(status__eq='active')
list(results).should.have.length_of(0)

View File

@ -43,7 +43,8 @@ def test_create_table():
'KeySchema': [
{'KeyType': 'HASH', 'AttributeName': 'forum_name'}
],
'ItemCount': 0, 'CreationDateTime': 1326499200.0
'ItemCount': 0, 'CreationDateTime': 1326499200.0,
'GlobalSecondaryIndexes': [],
}
}
conn = boto.dynamodb2.connect_to_region(