From 96c2506fd416db910002178cdcad00e113ef2d93 Mon Sep 17 00:00:00 2001 From: Ber Zoidberg Date: Tue, 11 Jun 2019 22:38:15 -0700 Subject: [PATCH 1/6] 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 6bcde41b2..2f6575354 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 1880c7cab..d882b14a6 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/6] 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 d882b14a6..7d3ccee8b 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/6] 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 2f6575354..4b7175bbd 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/6] 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 7d3ccee8b..e5056def0 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/6] 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 e5056def0..b2209d990 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') From 71e86ab41737b0659486f7d95c053370a57f388e Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sat, 5 Oct 2019 11:33:34 +0100 Subject: [PATCH 6/6] #1834 - Bugfix when removing item in double nested maps --- moto/dynamodb2/models.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 3f525f3ad..c06014488 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -197,15 +197,18 @@ class Item(BaseModel): # 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'] + 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) + last_val_type = list(last_val.keys()) + if last_val_type and last_val_type[0] == 'M': + last_val['M'].pop(key_parts[-1], None) + else: + last_val.pop(key_parts[-1], None) elif action == 'SET': key, value = value.split("=", 1) key = key.strip()