Support Expected in dynamoDB updateItem
This commit is contained in:
parent
e079fab9e3
commit
be07fbda52
@ -634,7 +634,8 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
|
|
||||||
return table.scan(scan_filters, limit, exclusive_start_key)
|
return table.scan(scan_filters, limit, exclusive_start_key)
|
||||||
|
|
||||||
def update_item(self, table_name, key, update_expression, attribute_updates, expression_attribute_names, expression_attribute_values):
|
def update_item(self, table_name, key, update_expression, attribute_updates, expression_attribute_names,
|
||||||
|
expression_attribute_values, expected=None):
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
|
|
||||||
if all([table.hash_key_attr in key, table.range_key_attr in key]):
|
if all([table.hash_key_attr in key, table.range_key_attr in key]):
|
||||||
@ -652,6 +653,34 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
range_value = None
|
range_value = None
|
||||||
|
|
||||||
item = table.get_item(hash_value, range_value)
|
item = table.get_item(hash_value, range_value)
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
item_attr = {}
|
||||||
|
elif hasattr(item, 'attrs'):
|
||||||
|
item_attr = item.attrs
|
||||||
|
else:
|
||||||
|
item_attr = item
|
||||||
|
|
||||||
|
if not expected:
|
||||||
|
expected = {}
|
||||||
|
|
||||||
|
for key, val in expected.items():
|
||||||
|
if 'Exists' in val and val['Exists'] is False:
|
||||||
|
if key in item_attr:
|
||||||
|
raise ValueError("The conditional request failed")
|
||||||
|
elif key not in item_attr:
|
||||||
|
raise ValueError("The conditional request failed")
|
||||||
|
elif 'Value' in val and DynamoType(val['Value']).value != item_attr[key].value:
|
||||||
|
raise ValueError("The conditional request failed")
|
||||||
|
elif 'ComparisonOperator' in val:
|
||||||
|
comparison_func = get_comparison_func(
|
||||||
|
val['ComparisonOperator'])
|
||||||
|
dynamo_types = [DynamoType(ele) for ele in val[
|
||||||
|
"AttributeValueList"]]
|
||||||
|
for t in dynamo_types:
|
||||||
|
if not comparison_func(item_attr[key].value, t.value):
|
||||||
|
raise ValueError('The conditional request failed')
|
||||||
|
|
||||||
# Update does not fail on new items, so create one
|
# Update does not fail on new items, so create one
|
||||||
if item is None:
|
if item is None:
|
||||||
data = {
|
data = {
|
||||||
|
@ -207,7 +207,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
try:
|
try:
|
||||||
result = dynamodb_backend2.put_item(
|
result = dynamodb_backend2.put_item(
|
||||||
name, item, expected, overwrite)
|
name, item, expected, overwrite)
|
||||||
except Exception:
|
except ValueError:
|
||||||
er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException'
|
er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException'
|
||||||
return self.error(er)
|
return self.error(er)
|
||||||
|
|
||||||
@ -474,14 +474,46 @@ class DynamoHandler(BaseResponse):
|
|||||||
'ExpressionAttributeValues', {})
|
'ExpressionAttributeValues', {})
|
||||||
existing_item = dynamodb_backend2.get_item(name, key)
|
existing_item = dynamodb_backend2.get_item(name, key)
|
||||||
|
|
||||||
|
if 'Expected' in self.body:
|
||||||
|
expected = self.body['Expected']
|
||||||
|
else:
|
||||||
|
expected = None
|
||||||
|
|
||||||
|
# Attempt to parse simple ConditionExpressions into an Expected
|
||||||
|
# expression
|
||||||
|
if not expected:
|
||||||
|
condition_expression = self.body.get('ConditionExpression')
|
||||||
|
if condition_expression and 'OR' not in condition_expression:
|
||||||
|
cond_items = [c.strip()
|
||||||
|
for c in condition_expression.split('AND')]
|
||||||
|
|
||||||
|
if cond_items:
|
||||||
|
expected = {}
|
||||||
|
exists_re = re.compile('^attribute_exists\((.*)\)$')
|
||||||
|
not_exists_re = re.compile(
|
||||||
|
'^attribute_not_exists\((.*)\)$')
|
||||||
|
|
||||||
|
for cond in cond_items:
|
||||||
|
exists_m = exists_re.match(cond)
|
||||||
|
not_exists_m = not_exists_re.match(cond)
|
||||||
|
if exists_m:
|
||||||
|
expected[exists_m.group(1)] = {'Exists': True}
|
||||||
|
elif not_exists_m:
|
||||||
|
expected[not_exists_m.group(1)] = {'Exists': False}
|
||||||
|
|
||||||
# Support spaces between operators in an update expression
|
# Support spaces between operators in an update expression
|
||||||
# E.g. `a = b + c` -> `a=b+c`
|
# E.g. `a = b + c` -> `a=b+c`
|
||||||
if update_expression:
|
if update_expression:
|
||||||
update_expression = re.sub(
|
update_expression = re.sub(
|
||||||
'\s*([=\+-])\s*', '\\1', update_expression)
|
'\s*([=\+-])\s*', '\\1', update_expression)
|
||||||
|
|
||||||
item = dynamodb_backend2.update_item(
|
try:
|
||||||
name, key, update_expression, attribute_updates, expression_attribute_names, expression_attribute_values)
|
item = dynamodb_backend2.update_item(
|
||||||
|
name, key, update_expression, attribute_updates, expression_attribute_names, expression_attribute_values,
|
||||||
|
expected)
|
||||||
|
except ValueError:
|
||||||
|
er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException'
|
||||||
|
return self.error(er)
|
||||||
|
|
||||||
item_dict = item.to_json()
|
item_dict = item.to_json()
|
||||||
item_dict['ConsumedCapacityUnits'] = 0.5
|
item_dict['ConsumedCapacityUnits'] = 0.5
|
||||||
|
@ -608,6 +608,61 @@ def test_boto3_put_item_conditions_fails():
|
|||||||
}
|
}
|
||||||
}).should.throw(botocore.client.ClientError)
|
}).should.throw(botocore.client.ClientError)
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_boto3_update_item_conditions_fails():
|
||||||
|
table = _create_user_table()
|
||||||
|
table.put_item(Item={'username': 'johndoe', 'foo': 'baz'})
|
||||||
|
table.update_item.when.called_with(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=bar',
|
||||||
|
Expected={
|
||||||
|
'foo': {
|
||||||
|
'Value': 'bar',
|
||||||
|
}
|
||||||
|
}).should.throw(botocore.client.ClientError)
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_boto3_update_item_conditions_fails_because_expect_not_exists():
|
||||||
|
table = _create_user_table()
|
||||||
|
table.put_item(Item={'username': 'johndoe', 'foo': 'baz'})
|
||||||
|
table.update_item.when.called_with(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=bar',
|
||||||
|
Expected={
|
||||||
|
'foo': {
|
||||||
|
'Exists': False
|
||||||
|
}
|
||||||
|
}).should.throw(botocore.client.ClientError)
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_boto3_update_item_conditions_pass():
|
||||||
|
table = _create_user_table()
|
||||||
|
table.put_item(Item={'username': 'johndoe', 'foo': 'bar'})
|
||||||
|
table.update_item(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=baz',
|
||||||
|
Expected={
|
||||||
|
'foo': {
|
||||||
|
'Value': 'bar',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
returned_item = table.get_item(Key={'username': 'johndoe'})
|
||||||
|
assert dict(returned_item)['Item']['foo'].should.equal("baz")
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_boto3_update_item_conditions_pass_because_expext_not_exists():
|
||||||
|
table = _create_user_table()
|
||||||
|
table.put_item(Item={'username': 'johndoe', 'foo': 'bar'})
|
||||||
|
table.update_item(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=baz',
|
||||||
|
Expected={
|
||||||
|
'whatever': {
|
||||||
|
'Exists': False,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
returned_item = table.get_item(Key={'username': 'johndoe'})
|
||||||
|
assert dict(returned_item)['Item']['foo'].should.equal("baz")
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_boto3_put_item_conditions_pass():
|
def test_boto3_put_item_conditions_pass():
|
||||||
|
Loading…
Reference in New Issue
Block a user