diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 0a48c277a..f4e61ca9f 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -135,7 +135,9 @@ class Item(BaseModel): assert len(parts) % 2 == 0, "Mismatched operators and values in update expression: '{}'".format(update_expression) for action, valstr in zip(parts[:-1:2], parts[1::2]): action = action.upper() - values = valstr.split(',') + + # "Should" retain arguments in side (...) + values = re.split(r',(?![^(]*\))', valstr) for value in values: # A Real value value = value.lstrip(":").rstrip(",").strip() @@ -145,9 +147,23 @@ class Item(BaseModel): if action == "REMOVE": self.attrs.pop(value, None) elif action == 'SET': - key, value = value.split("=") + key, value = value.split("=", 1) key = key.strip() value = value.strip() + + # If not exists, changes value to a default if needed, else its the same as it was + if value.startswith('if_not_exists'): + # Function signature + match = re.match(r'.*if_not_exists\((?P.+),\s*(?P.+)\).*', value) + if not match: + raise TypeError + + path, value = match.groups() + + # If it already exists, get its value so we dont overwrite it + if path in self.attrs: + value = self.attrs[path].cast_value + if value in expression_attribute_values: value = DynamoType(expression_attribute_values[value]) else: diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 05daf23aa..d7c5b5843 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -1067,3 +1067,71 @@ def test_update_item_on_map(): resp = table.scan() resp['Items'][0]['body'].should.equal({'nested': {'data': 'new_value'}}) + + +# https://github.com/spulec/moto/issues/1358 +@mock_dynamodb2 +def test_update_if_not_exists(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123' + }) + + table.update_item(Key={ + 'forum_name': 'the-key', + 'subject': '123' + }, + UpdateExpression='SET created_at = if_not_exists(created_at, :created_at)', + ExpressionAttributeValues={ + ':created_at': 123 + } + ) + + resp = table.scan() + assert resp['Items'][0]['created_at'] == 123 + + table.update_item(Key={ + 'forum_name': 'the-key', + 'subject': '123' + }, + UpdateExpression='SET created_at = if_not_exists(created_at, :created_at)', + ExpressionAttributeValues={ + ':created_at': 456 + } + ) + + resp = table.scan() + # Still the original value + assert resp['Items'][0]['created_at'] == 123