From 3cbaed97344a617c55b2123e1829b7c0021c3df1 Mon Sep 17 00:00:00 2001 From: Ian Auld Date: Wed, 13 Jan 2016 15:27:02 -0800 Subject: [PATCH 1/5] Added support for local senondary indexes in DynamoDB2 --- moto/dynamodb2/responses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 2be0dda8..dbf9191e 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -100,12 +100,14 @@ class DynamoHandler(BaseResponse): attr = body["AttributeDefinitions"] # getting the indexes global_indexes = body.get("GlobalSecondaryIndexes", []) + local_secondary_indexes = body.get("LocalSecondaryIndexes", []) table = dynamodb_backend2.create_table(table_name, schema=key_schema, throughput=throughput, attr=attr, - global_indexes=global_indexes) + global_indexes=global_indexes, + indexes=local_secondary_indexes) if table is not None: return dynamo_json_dump(table.describe) else: From 9400cc3ba4aaf047cbee9fe91ea461b9d66f159d Mon Sep 17 00:00:00 2001 From: Ian Auld Date: Thu, 14 Jan 2016 11:30:18 -0800 Subject: [PATCH 2/5] Added LocalSecondaryIndexes to describe property --- moto/dynamodb2/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 77b1434c..43059265 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -184,6 +184,7 @@ class Table(object): 'ItemCount': len(self), 'CreationDateTime': unix_time(self.created_at), 'GlobalSecondaryIndexes': [index for index in self.global_indexes], + 'LocalSecondaryIndexes': [index for index in self.indexes] } } return results From 582db74deed584055e3b3ead2bb102c1c88a6bd8 Mon Sep 17 00:00:00 2001 From: Ian Auld Date: Thu, 14 Jan 2016 11:30:50 -0800 Subject: [PATCH 3/5] Added test for creating a table with a local index. --- .../test_dynamodb_table_with_range_key.py | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py index a90f06ca..a0a91d7f 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py @@ -11,8 +11,9 @@ from moto import mock_dynamodb2 from boto.exception import JSONResponseError from tests.helpers import requires_boto_gte try: - from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey + from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey, AllIndex from boto.dynamodb2.table import Item, Table + from boto.dynamodb2.types import STRING from boto.dynamodb2.exceptions import ValidationException from boto.dynamodb2.exceptions import ConditionalCheckFailedException except ImportError: @@ -30,6 +31,30 @@ def create_table(): return table +def create_table_with_local_indexes(): + table = Table.create( + 'messages', + schema=[ + HashKey('forum_name'), + RangeKey('subject'), + ], + throughput={ + 'read': 10, + 'write': 10, + }, + indexes=[ + AllIndex( + 'recipient_timestamp_index', + parts=[ + HashKey('forum_name', data_type=STRING), + RangeKey('timestamp'), + ] + ) + ] + ) + return table + + def iterate_results(res): for i in res: pass @@ -63,6 +88,48 @@ def test_create_table(): table.describe().should.equal(expected) +@requires_boto_gte("2.9") +@mock_dynamodb2 +@freeze_time("2012-01-14") +def test_create_table(): + table = create_table_with_local_indexes() + expected = { + 'Table': { + 'AttributeDefinitions': [ + {'AttributeName': 'forum_name', 'AttributeType': 'S'}, + {'AttributeName': 'subject', 'AttributeType': 'S'}, + {'AttributeName': 'timestamp', 'AttributeType': 'S'} + ], + 'ProvisionedThroughput': { + 'NumberOfDecreasesToday': 0, + 'WriteCapacityUnits': 10, + 'ReadCapacityUnits': 10, + }, + 'TableSizeBytes': 0, + 'TableName': 'messages', + 'TableStatus': 'ACTIVE', + 'KeySchema': [ + {'KeyType': 'HASH', 'AttributeName': 'forum_name'}, + {'KeyType': 'RANGE', 'AttributeName': 'subject'} + ], + 'LocalSecondaryIndexes': [ + { + 'IndexName': 'recipient_timestamp_index', + 'KeySchema': [ + {'AttributeName': 'forum_name', 'KeyType': 'HASH'}, + {'AttributeName': 'timestamp', 'KeyType': 'RANGE'} + ], + 'Projection': {'ProjectionType': 'ALL'} + } + ], + 'ItemCount': 0, + 'CreationDateTime': 1326499200.0, + 'GlobalSecondaryIndexes': [], + } + } + table.describe().should.equal(expected) + + @requires_boto_gte("2.9") @mock_dynamodb2 def test_delete_table(): From 06af5365eded8ca5bd1ca55de75746bd131b3dac Mon Sep 17 00:00:00 2001 From: Ian Auld Date: Thu, 14 Jan 2016 14:25:04 -0800 Subject: [PATCH 4/5] Updated tests --- .../test_dynamodb_table_with_range_key.py | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py index a0a91d7f..bcda68f6 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py @@ -13,7 +13,7 @@ from tests.helpers import requires_boto_gte try: from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey, AllIndex from boto.dynamodb2.table import Item, Table - from boto.dynamodb2.types import STRING + from boto.dynamodb2.types import STRING, NUMBER from boto.dynamodb2.exceptions import ValidationException from boto.dynamodb2.exceptions import ConditionalCheckFailedException except ImportError: @@ -44,10 +44,10 @@ def create_table_with_local_indexes(): }, indexes=[ AllIndex( - 'recipient_timestamp_index', + 'threads_index', parts=[ HashKey('forum_name', data_type=STRING), - RangeKey('timestamp'), + RangeKey('threads', data_type=NUMBER), ] ) ] @@ -81,6 +81,7 @@ def test_create_table(): {'KeyType': 'HASH', 'AttributeName': 'forum_name'}, {'KeyType': 'RANGE', 'AttributeName': 'subject'} ], + 'LocalSecondaryIndexes': [], 'ItemCount': 0, 'CreationDateTime': 1326499200.0, 'GlobalSecondaryIndexes': [], } @@ -91,14 +92,14 @@ def test_create_table(): @requires_boto_gte("2.9") @mock_dynamodb2 @freeze_time("2012-01-14") -def test_create_table(): +def test_create_table_with_local_index(): table = create_table_with_local_indexes() expected = { 'Table': { 'AttributeDefinitions': [ {'AttributeName': 'forum_name', 'AttributeType': 'S'}, {'AttributeName': 'subject', 'AttributeType': 'S'}, - {'AttributeName': 'timestamp', 'AttributeType': 'S'} + {'AttributeName': 'threads', 'AttributeType': 'N'} ], 'ProvisionedThroughput': { 'NumberOfDecreasesToday': 0, @@ -114,10 +115,10 @@ def test_create_table(): ], 'LocalSecondaryIndexes': [ { - 'IndexName': 'recipient_timestamp_index', + 'IndexName': 'threads_index', 'KeySchema': [ {'AttributeName': 'forum_name', 'KeyType': 'HASH'}, - {'AttributeName': 'timestamp', 'KeyType': 'RANGE'} + {'AttributeName': 'threads', 'KeyType': 'RANGE'} ], 'Projection': {'ProjectionType': 'ALL'} } @@ -637,6 +638,26 @@ def test_query_with_global_indexes(): list(results).should.have.length_of(0) +@mock_dynamodb2 +def test_query_with_local_indexes(): + table = create_table_with_local_indexes() + item_data = { + 'forum_name': 'Cool Forum', + 'subject': 'Check this out!', + 'version': '1', + 'threads': 1, + 'status': 'inactive' + } + item = Item(table, item_data) + item.save(overwrite=True) + + item['version'] = '2' + item.save(overwrite=True) + # Revisit this query once support for QueryFilter is added + results = table.query(forum_name__eq='Cool Forum', index='threads_index') + list(results).should.have.length_of(1) + + @mock_dynamodb2 def test_reverse_query(): conn = boto.dynamodb2.layer1.DynamoDBConnection() From dd6cc305cd84552316ce02f016dd385d3702b364 Mon Sep 17 00:00:00 2001 From: Ian Auld Date: Thu, 14 Jan 2016 14:51:47 -0800 Subject: [PATCH 5/5] Updated more tests. --- tests/test_dynamodb2/test_dynamodb_table_without_range_key.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py index 6baeb8a1..36aac9a8 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py @@ -48,6 +48,7 @@ def test_create_table(): ], 'ItemCount': 0, 'CreationDateTime': 1326499200.0, 'GlobalSecondaryIndexes': [], + 'LocalSecondaryIndexes': [] } } conn = boto.dynamodb2.connect_to_region(