Merge pull request #1938 from kgutwin/i-1937-dynamodb2-update-attributes

Correct behavior of ReturnValues parameter to put_item and update_item
This commit is contained in:
Steve Pulec 2018-12-28 20:36:00 -05:00 committed by GitHub
commit f82d4bb619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 130 additions and 3 deletions

View File

@ -194,6 +194,11 @@ class DynamoHandler(BaseResponse):
def put_item(self):
name = self.body['TableName']
item = self.body['Item']
return_values = self.body.get('ReturnValues', 'NONE')
if return_values not in ('ALL_OLD', 'NONE'):
er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
return self.error(er, 'Return values set to invalid value')
if has_empty_keys_or_values(item):
return get_empty_str_error()
@ -204,6 +209,13 @@ class DynamoHandler(BaseResponse):
else:
expected = None
if return_values == 'ALL_OLD':
existing_item = self.dynamodb_backend.get_item(name, item)
if existing_item:
existing_attributes = existing_item.to_json()['Attributes']
else:
existing_attributes = {}
# Attempt to parse simple ConditionExpressions into an Expected
# expression
if not expected:
@ -239,6 +251,10 @@ class DynamoHandler(BaseResponse):
'TableName': name,
'CapacityUnits': 1
}
if return_values == 'ALL_OLD':
item_dict['Attributes'] = existing_attributes
else:
item_dict.pop('Attributes', None)
return dynamo_json_dump(item_dict)
else:
er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException'
@ -523,7 +539,11 @@ class DynamoHandler(BaseResponse):
def delete_item(self):
name = self.body['TableName']
keys = self.body['Key']
return_values = self.body.get('ReturnValues', '')
return_values = self.body.get('ReturnValues', 'NONE')
if return_values not in ('ALL_OLD', 'NONE'):
er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
return self.error(er, 'Return values set to invalid value')
table = self.dynamodb_backend.get_table(name)
if not table:
er = 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException'
@ -538,9 +558,9 @@ class DynamoHandler(BaseResponse):
return dynamo_json_dump(item_dict)
def update_item(self):
name = self.body['TableName']
key = self.body['Key']
return_values = self.body.get('ReturnValues', 'NONE')
update_expression = self.body.get('UpdateExpression')
attribute_updates = self.body.get('AttributeUpdates')
expression_attribute_names = self.body.get(
@ -548,6 +568,15 @@ class DynamoHandler(BaseResponse):
expression_attribute_values = self.body.get(
'ExpressionAttributeValues', {})
existing_item = self.dynamodb_backend.get_item(name, key)
if existing_item:
existing_attributes = existing_item.to_json()['Attributes']
else:
existing_attributes = {}
if return_values not in ('NONE', 'ALL_OLD', 'ALL_NEW', 'UPDATED_OLD',
'UPDATED_NEW'):
er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
return self.error(er, 'Return values set to invalid value')
if has_empty_keys_or_values(expression_attribute_values):
return get_empty_str_error()
@ -602,8 +631,26 @@ class DynamoHandler(BaseResponse):
'TableName': name,
'CapacityUnits': 0.5
}
if not existing_item:
unchanged_attributes = {
k for k in existing_attributes.keys()
if existing_attributes[k] == item_dict['Attributes'].get(k)
}
changed_attributes = set(existing_attributes.keys()).union(item_dict['Attributes'].keys()).difference(unchanged_attributes)
if return_values == 'NONE':
item_dict['Attributes'] = {}
elif return_values == 'ALL_OLD':
item_dict['Attributes'] = existing_attributes
elif return_values == 'UPDATED_OLD':
item_dict['Attributes'] = {
k: v for k, v in existing_attributes.items()
if k in changed_attributes
}
elif return_values == 'UPDATED_NEW':
item_dict['Attributes'] = {
k: v for k, v in item_dict['Attributes'].items()
if k in changed_attributes
}
return dynamo_json_dump(item_dict)

View File

@ -1000,6 +1000,11 @@ def test_delete_item():
response = table.scan()
assert response['Count'] == 2
# Test ReturnValues validation
with assert_raises(ClientError) as ex:
table.delete_item(Key={'client': 'client1', 'app': 'app1'},
ReturnValues='ALL_NEW')
# Test deletion and returning old value
response = table.delete_item(Key={'client': 'client1', 'app': 'app1'}, ReturnValues='ALL_OLD')
response['Attributes'].should.contain('client')
@ -1246,6 +1251,81 @@ def test_update_if_not_exists():
assert resp['Items'][0]['created_at'] == 123
# https://github.com/spulec/moto/issues/1937
@mock_dynamodb2
def test_update_return_attributes():
dynamodb = boto3.client('dynamodb', region_name='us-east-1')
dynamodb.create_table(
TableName='moto-test',
KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}],
ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1}
)
def update(col, to, rv):
return dynamodb.update_item(
TableName='moto-test',
Key={'id': {'S': 'foo'}},
AttributeUpdates={col: {'Value': {'S': to}, 'Action': 'PUT'}},
ReturnValues=rv
)
r = update('col1', 'val1', 'ALL_NEW')
assert r['Attributes'] == {'id': {'S': 'foo'}, 'col1': {'S': 'val1'}}
r = update('col1', 'val2', 'ALL_OLD')
assert r['Attributes'] == {'id': {'S': 'foo'}, 'col1': {'S': 'val1'}}
r = update('col2', 'val3', 'UPDATED_NEW')
assert r['Attributes'] == {'col2': {'S': 'val3'}}
r = update('col2', 'val4', 'UPDATED_OLD')
assert r['Attributes'] == {'col2': {'S': 'val3'}}
r = update('col1', 'val5', 'NONE')
assert r['Attributes'] == {}
with assert_raises(ClientError) as ex:
r = update('col1', 'val6', 'WRONG')
@mock_dynamodb2
def test_put_return_attributes():
dynamodb = boto3.client('dynamodb', region_name='us-east-1')
dynamodb.create_table(
TableName='moto-test',
KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}],
ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1}
)
r = dynamodb.put_item(
TableName='moto-test',
Item={'id': {'S': 'foo'}, 'col1': {'S': 'val1'}},
ReturnValues='NONE'
)
assert 'Attributes' not in r
r = dynamodb.put_item(
TableName='moto-test',
Item={'id': {'S': 'foo'}, 'col1': {'S': 'val2'}},
ReturnValues='ALL_OLD'
)
assert r['Attributes'] == {'id': {'S': 'foo'}, 'col1': {'S': 'val1'}}
with assert_raises(ClientError) as ex:
dynamodb.put_item(
TableName='moto-test',
Item={'id': {'S': 'foo'}, 'col1': {'S': 'val3'}},
ReturnValues='ALL_NEW'
)
ex.exception.response['Error']['Code'].should.equal('ValidationException')
ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
ex.exception.response['Error']['Message'].should.equal('Return values set to invalid value')
@mock_dynamodb2
def test_query_global_secondary_index_when_created_via_update_table_resource():
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')