Add Dynamodb2 global indexes. Closes #329.
This commit is contained in:
parent
09fe37da7e
commit
82f19952dd
@ -122,7 +122,7 @@ class Item(object):
|
|||||||
|
|
||||||
class Table(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.name = table_name
|
||||||
self.attr = attr
|
self.attr = attr
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
@ -143,6 +143,7 @@ class Table(object):
|
|||||||
self.throughput = throughput
|
self.throughput = throughput
|
||||||
self.throughput["NumberOfDecreasesToday"] = 0
|
self.throughput["NumberOfDecreasesToday"] = 0
|
||||||
self.indexes = indexes
|
self.indexes = indexes
|
||||||
|
self.global_indexes = global_indexes if global_indexes else []
|
||||||
self.created_at = datetime.datetime.now()
|
self.created_at = datetime.datetime.now()
|
||||||
self.items = defaultdict(dict)
|
self.items = defaultdict(dict)
|
||||||
|
|
||||||
@ -158,6 +159,7 @@ class Table(object):
|
|||||||
'KeySchema': self.schema,
|
'KeySchema': self.schema,
|
||||||
'ItemCount': len(self),
|
'ItemCount': len(self),
|
||||||
'CreationDateTime': unix_time(self.created_at),
|
'CreationDateTime': unix_time(self.created_at),
|
||||||
|
'GlobalSecondaryIndexes': [index for index in self.global_indexes],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
@ -171,6 +173,24 @@ class Table(object):
|
|||||||
count += 1
|
count += 1
|
||||||
return count
|
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):
|
def put_item(self, item_attrs):
|
||||||
hash_value = DynamoType(item_attrs.get(self.hash_key_attr))
|
hash_value = DynamoType(item_attrs.get(self.hash_key_attr))
|
||||||
if self.has_range_key:
|
if self.has_range_key:
|
||||||
@ -293,12 +313,21 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
return None
|
return None
|
||||||
return table.put_item(item_attrs)
|
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)
|
table = self.tables.get(table_name)
|
||||||
if not table:
|
if not table:
|
||||||
return None, None
|
return None, None
|
||||||
else:
|
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):
|
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):
|
if table.hash_key_attr not in keys or (table.has_range_key and table.range_key_attr not in keys):
|
||||||
|
@ -99,10 +99,12 @@ class DynamoHandler(BaseResponse):
|
|||||||
# getting attribute definition
|
# getting attribute definition
|
||||||
attr = body["AttributeDefinitions"]
|
attr = body["AttributeDefinitions"]
|
||||||
# getting the indexes
|
# getting the indexes
|
||||||
|
global_indexes = body.get("GlobalSecondaryIndexes", [])
|
||||||
table = dynamodb_backend2.create_table(table_name,
|
table = dynamodb_backend2.create_table(table_name,
|
||||||
schema=key_schema,
|
schema=key_schema,
|
||||||
throughput=throughput,
|
throughput=throughput,
|
||||||
attr=attr)
|
attr=attr,
|
||||||
|
global_indexes=global_indexes)
|
||||||
return dynamo_json_dump(table.describe)
|
return dynamo_json_dump(table.describe)
|
||||||
|
|
||||||
def delete_table(self):
|
def delete_table(self):
|
||||||
@ -216,13 +218,14 @@ class DynamoHandler(BaseResponse):
|
|||||||
|
|
||||||
def query(self):
|
def query(self):
|
||||||
name = self.body['TableName']
|
name = self.body['TableName']
|
||||||
keys = self.body['KeyConditions']
|
key_conditions = self.body['KeyConditions']
|
||||||
hash_key_name, range_key_name = dynamodb_backend2.get_table_keys_name(name)
|
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:
|
if hash_key_name is None:
|
||||||
er = "'com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
|
er = "'com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
|
||||||
return self.error(er)
|
return self.error(er)
|
||||||
hash_key = keys[hash_key_name]['AttributeValueList'][0]
|
hash_key = key_conditions[hash_key_name]['AttributeValueList'][0]
|
||||||
if len(keys) == 1:
|
if len(key_conditions) == 1:
|
||||||
range_comparison = None
|
range_comparison = None
|
||||||
range_values = []
|
range_values = []
|
||||||
else:
|
else:
|
||||||
@ -230,7 +233,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
er = "com.amazon.coral.validate#ValidationException"
|
er = "com.amazon.coral.validate#ValidationException"
|
||||||
return self.error(er)
|
return self.error(er)
|
||||||
else:
|
else:
|
||||||
range_condition = keys[range_key_name]
|
range_condition = key_conditions[range_key_name]
|
||||||
if range_condition:
|
if range_condition:
|
||||||
range_comparison = range_condition['ComparisonOperator']
|
range_comparison = range_condition['ComparisonOperator']
|
||||||
range_values = range_condition['AttributeValueList']
|
range_values = range_condition['AttributeValueList']
|
||||||
|
@ -48,7 +48,7 @@ def test_create_table():
|
|||||||
},
|
},
|
||||||
'TableName': 'messages',
|
'TableName': 'messages',
|
||||||
'TableSizeBytes': 0,
|
'TableSizeBytes': 0,
|
||||||
'TableStatus': 'ACTIVE'
|
'TableStatus': 'ACTIVE',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.describe_table('messages').should.equal(expected)
|
conn.describe_table('messages').should.equal(expected)
|
||||||
|
@ -7,10 +7,8 @@ from moto import mock_dynamodb2
|
|||||||
from boto.exception import JSONResponseError
|
from boto.exception import JSONResponseError
|
||||||
from tests.helpers import requires_boto_gte
|
from tests.helpers import requires_boto_gte
|
||||||
try:
|
try:
|
||||||
from boto.dynamodb2.fields import HashKey
|
from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey
|
||||||
from boto.dynamodb2.fields import RangeKey
|
from boto.dynamodb2.table import Item, Table
|
||||||
from boto.dynamodb2.table import Table
|
|
||||||
from boto.dynamodb2.table import Item
|
|
||||||
from boto.dynamodb2.exceptions import ValidationException
|
from boto.dynamodb2.exceptions import ValidationException
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -53,7 +51,8 @@ def test_create_table():
|
|||||||
{'KeyType': 'HASH', 'AttributeName': 'forum_name'},
|
{'KeyType': 'HASH', 'AttributeName': 'forum_name'},
|
||||||
{'KeyType': 'RANGE', 'AttributeName': 'subject'}
|
{'KeyType': 'RANGE', 'AttributeName': 'subject'}
|
||||||
],
|
],
|
||||||
'ItemCount': 0, 'CreationDateTime': 1326499200.0
|
'ItemCount': 0, 'CreationDateTime': 1326499200.0,
|
||||||
|
'GlobalSecondaryIndexes': [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
table.describe().should.equal(expected)
|
table.describe().should.equal(expected)
|
||||||
@ -445,3 +444,92 @@ def test_get_key_fields():
|
|||||||
table = create_table()
|
table = create_table()
|
||||||
kf = table.get_key_fields()
|
kf = table.get_key_fields()
|
||||||
kf.should.equal(['forum_name', 'subject'])
|
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)
|
||||||
|
@ -43,7 +43,8 @@ def test_create_table():
|
|||||||
'KeySchema': [
|
'KeySchema': [
|
||||||
{'KeyType': 'HASH', 'AttributeName': 'forum_name'}
|
{'KeyType': 'HASH', 'AttributeName': 'forum_name'}
|
||||||
],
|
],
|
||||||
'ItemCount': 0, 'CreationDateTime': 1326499200.0
|
'ItemCount': 0, 'CreationDateTime': 1326499200.0,
|
||||||
|
'GlobalSecondaryIndexes': [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn = boto.dynamodb2.connect_to_region(
|
conn = boto.dynamodb2.connect_to_region(
|
||||||
|
Loading…
Reference in New Issue
Block a user