From 96c2506fd416db910002178cdcad00e113ef2d93 Mon Sep 17 00:00:00 2001 From: Ber Zoidberg Date: Tue, 11 Jun 2019 22:38:15 -0700 Subject: [PATCH 1/5] Fix DynamoDB UpdateExpression support for REMOVE on nested maps --- moto/dynamodb2/models.py | 23 +++++++++++++++ .../test_dynamodb_table_without_range_key.py | 29 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 6bcde41b..2f657535 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -149,6 +149,29 @@ class Item(BaseModel): value = re.sub(r'{0}\b'.format(k), v, value) if action == "REMOVE": + key = value + if '.' not in key: + self.attrs.pop(value, None) + else: + # Handle nested dict updates + key_parts = key.split('.') + attr = key_parts.pop(0) + if attr not in self.attrs: + raise ValueError + + last_val = self.attrs[attr].value + for key_part in key_parts[:-1]: + # Hack but it'll do, traverses into a dict + last_val_type = list(last_val.keys()) + if last_val_type and last_val_type[0] == 'M': + last_val = last_val['M'] + + if key_part not in last_val: + last_val[key_part] = {'M': {}} + + last_val = last_val[key_part] + + last_val.pop(key_parts[-1], None) self.attrs.pop(value, None) elif action == 'SET': key, value = value.split("=", 1) 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 1880c7ca..d882b14a 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py @@ -450,6 +450,35 @@ def test_update_item_remove(): }) +@mock_dynamodb2_deprecated +def test_update_item_nested_remove(): + conn = boto.dynamodb2.connect_to_region("us-east-1") + table = Table.create('messages', schema=[ + HashKey('username') + ]) + + data = { + 'username': "steve", + 'Meta': { + 'FullName': 'Steve Urkel' + } + } + table.put_item(data=data) + key_map = { + 'username': {"S": "steve"} + } + + # Then remove the SentBy field + conn.update_item("messages", key_map, + update_expression="REMOVE Meta.FullName") + + returned_item = table.get_item(username="steve") + dict(returned_item).should.equal({ + 'username': "steve", + 'Meta': {} + }) + + @mock_dynamodb2_deprecated def test_update_item_set(): conn = boto.dynamodb2.connect_to_region("us-east-1") From 26ae13b7152299b051c16d3fac67d57128d1d0d9 Mon Sep 17 00:00:00 2001 From: Ber Zoidberg Date: Tue, 11 Jun 2019 22:41:56 -0700 Subject: [PATCH 2/5] Fix copypasta error in comment --- tests/test_dynamodb2/test_dynamodb_table_without_range_key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d882b14a..7d3ccee8 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py @@ -468,7 +468,7 @@ def test_update_item_nested_remove(): 'username': {"S": "steve"} } - # Then remove the SentBy field + # Then remove the Meta.FullName field conn.update_item("messages", key_map, update_expression="REMOVE Meta.FullName") From fee3800c41d60522a7f082f4879595e348da0ad8 Mon Sep 17 00:00:00 2001 From: Ber Zoidberg Date: Tue, 11 Jun 2019 22:44:56 -0700 Subject: [PATCH 3/5] remove extra space --- moto/dynamodb2/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 2f657535..4b7175bb 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -149,7 +149,7 @@ class Item(BaseModel): value = re.sub(r'{0}\b'.format(k), v, value) if action == "REMOVE": - key = value + key = value if '.' not in key: self.attrs.pop(value, None) else: From 997556f7bb049931da8e85654587709041f337d6 Mon Sep 17 00:00:00 2001 From: Ber Zoidberg Date: Wed, 12 Jun 2019 08:06:37 -0700 Subject: [PATCH 4/5] improve test coverage --- .../test_dynamodb_table_without_range_key.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) 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 7d3ccee8..e5056def 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py @@ -479,6 +479,41 @@ def test_update_item_nested_remove(): }) +@mock_dynamodb2_deprecated +def test_update_item_nested_remove(): + conn = boto.dynamodb2.connect_to_region("us-east-1") + table = Table.create('messages', schema=[ + HashKey('username') + ]) + + data = { + 'username': "steve", + 'Meta': { + 'Name': { + 'First': 'Steve', + 'Last': 'Urkel' + } + } + } + table.put_item(data=data) + key_map = { + 'username': {"S": "steve"} + } + + # Then remove the Meta.FullName field + conn.update_item("messages", key_map, + update_expression="REMOVE Meta.Name.First") + + returned_item = table.get_item(username="steve") + dict(returned_item).should.equal({ + 'username': "steve", + 'Meta': { + 'Name': { + 'Last': 'Urkel' + } + } + }) + @mock_dynamodb2_deprecated def test_update_item_set(): conn = boto.dynamodb2.connect_to_region("us-east-1") From 6717cba2865f6ed1a7c13d0386ecd5c75fef10e8 Mon Sep 17 00:00:00 2001 From: Ber Zoidberg Date: Wed, 12 Jun 2019 08:12:15 -0700 Subject: [PATCH 5/5] test name fix --- tests/test_dynamodb2/test_dynamodb_table_without_range_key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e5056def..b2209d99 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py @@ -480,7 +480,7 @@ def test_update_item_nested_remove(): @mock_dynamodb2_deprecated -def test_update_item_nested_remove(): +def test_update_item_double_nested_remove(): conn = boto.dynamodb2.connect_to_region("us-east-1") table = Table.create('messages', schema=[ HashKey('username')