diff --git a/moto/dynamodb2/models/__init__.py b/moto/dynamodb2/models/__init__.py index 86f2f932d..c72cee52f 100644 --- a/moto/dynamodb2/models/__init__.py +++ b/moto/dynamodb2/models/__init__.py @@ -1407,6 +1407,22 @@ class DynamoDBBackend(BaseBackend): # Update does not fail on new items, so create one if item is None: + if update_expression: + # Validate AST before creating anything + item = Item( + hash_value, + table.hash_key_type, + range_value, + table.range_key_type, + attrs={}, + ) + UpdateExpressionValidator( + update_expression_ast, + expression_attribute_names=expression_attribute_names, + expression_attribute_values=expression_attribute_values, + item=item, + table=table, + ).validate() data = {table.hash_key_attr: {hash_value.type: hash_value.value}} if range_value: data.update( diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 6b581e9ec..01984df87 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -6245,3 +6245,35 @@ def test_describe_endpoints(region): }, ] ) + + +@mock_dynamodb2 +def test_update_non_existing_item_raises_error_and_does_not_contain_item_afterwards(): + """ + https://github.com/spulec/moto/issues/3729 + Exception is raised, but item was persisted anyway + Happened because we would create a placeholder, before validating/executing the UpdateExpression + :return: + """ + name = "TestTable" + conn = boto3.client("dynamodb", region_name="us-west-2") + hkey = "primary_partition_key" + conn.create_table( + TableName=name, + KeySchema=[{"AttributeName": hkey, "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": hkey, "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + update_expression = { + "Key": {hkey: "some_identification_string"}, + "UpdateExpression": "set #AA.#AB = :aa", + "ExpressionAttributeValues": {":aa": "abc"}, + "ExpressionAttributeNames": {"#AA": "some_dict", "#AB": "key1"}, + "ConditionExpression": "attribute_not_exists(#AA.#AB)", + } + table = boto3.resource("dynamodb", region_name="us-west-2").Table(name) + with pytest.raises(ClientError) as err: + table.update_item(**update_expression) + err.value.response["Error"]["Code"].should.equal("ValidationException") + + conn.scan(TableName=name)["Items"].should.have.length_of(0)