From 99556620a95da8fbfc7bca71e1a904f5f54f08be Mon Sep 17 00:00:00 2001 From: Brian Pandola Date: Wed, 14 Oct 2020 08:32:42 -0700 Subject: [PATCH] Fix: Empty sets not removed from item after UpdateExpression (#3386) DynamoDB does not support empty sets. If the last item in a set is deleted as part of an UpdateExpression, the attribute must be removed. Ref: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html Fixes #3296 --- moto/dynamodb2/parsing/executors.py | 7 ++++++ tests/test_dynamodb2/test_dynamodb.py | 34 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/moto/dynamodb2/parsing/executors.py b/moto/dynamodb2/parsing/executors.py index 2f2f2bb8..76642542 100644 --- a/moto/dynamodb2/parsing/executors.py +++ b/moto/dynamodb2/parsing/executors.py @@ -161,6 +161,13 @@ class DeleteExecutor(NodeExecutor): # DynamoDB does not mind if value is not present pass + # DynamoDB does not support empty sets. If we've deleted + # the last item in the set, we have to remove the attribute. + if not string_set_list: + element = self.get_element_to_action() + container = self.get_item_before_end_of_path(item) + container.pop(element.get_attribute_name()) + class RemoveExecutor(NodeExecutor): def execute(self, item): diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index e2dd744e..06dfec01 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -5445,3 +5445,37 @@ def test_lsi_projection_type_keys_only(): items[0].should.equal( {"partitionKey": "pk-1", "sortKey": "sk-1", "lsiK1SortKey": "lsi-sk"} ) + + +@mock_dynamodb2 +def test_set_attribute_is_dropped_if_empty_after_update_expression(): + table_name, item_key, set_item = "test-table", "test-id", "test-data" + client = boto3.client("dynamodb", region_name="us-east-1") + client.create_table( + TableName=table_name, + KeySchema=[{"AttributeName": "customer", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "customer", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + client.update_item( + TableName=table_name, + Key={"customer": {"S": item_key}}, + UpdateExpression="ADD orders :order", + ExpressionAttributeValues={":order": {"SS": [set_item]}}, + ) + resp = client.scan(TableName=table_name, ProjectionExpression="customer, orders") + item = resp["Items"][0] + item.should.have.key("customer") + item.should.have.key("orders") + + client.update_item( + TableName=table_name, + Key={"customer": {"S": item_key}}, + UpdateExpression="DELETE orders :order", + ExpressionAttributeValues={":order": {"SS": [set_item]}}, + ) + resp = client.scan(TableName=table_name, ProjectionExpression="customer, orders") + item = resp["Items"][0] + item.should.have.key("customer") + item.should_not.have.key("orders")