Make EQ conditions work reliably in DynamoDB.
The AWS API represents a set object as a list of values. Internally moto also represents a set as a list. This means that when we do value comparisons, the order of the values can cause a set equality test to fail.
This commit is contained in:
parent
850496f29a
commit
0b15bb13b6
@ -66,6 +66,8 @@ class DynamoType(object):
|
|||||||
return int(self.value)
|
return int(self.value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return float(self.value)
|
return float(self.value)
|
||||||
|
elif self.is_set():
|
||||||
|
return set(self.value)
|
||||||
else:
|
else:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
@ -509,15 +511,12 @@ class Table(BaseModel):
|
|||||||
elif 'Value' in val and DynamoType(val['Value']).value != current_attr[key].value:
|
elif 'Value' in val and DynamoType(val['Value']).value != current_attr[key].value:
|
||||||
raise ValueError("The conditional request failed")
|
raise ValueError("The conditional request failed")
|
||||||
elif 'ComparisonOperator' in val:
|
elif 'ComparisonOperator' in val:
|
||||||
comparison_func = get_comparison_func(
|
|
||||||
val['ComparisonOperator'])
|
|
||||||
dynamo_types = [
|
dynamo_types = [
|
||||||
DynamoType(ele) for ele in
|
DynamoType(ele) for ele in
|
||||||
val.get("AttributeValueList", [])
|
val.get("AttributeValueList", [])
|
||||||
]
|
]
|
||||||
for t in dynamo_types:
|
if not current_attr[key].compare(val['ComparisonOperator'], dynamo_types):
|
||||||
if not comparison_func(current_attr[key].value, t.value):
|
raise ValueError('The conditional request failed')
|
||||||
raise ValueError('The conditional request failed')
|
|
||||||
if range_value:
|
if range_value:
|
||||||
self.items[hash_value][range_value] = item
|
self.items[hash_value][range_value] = item
|
||||||
else:
|
else:
|
||||||
@ -946,15 +945,12 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
elif 'Value' in val and DynamoType(val['Value']).value != item_attr[key].value:
|
elif 'Value' in val and DynamoType(val['Value']).value != item_attr[key].value:
|
||||||
raise ValueError("The conditional request failed")
|
raise ValueError("The conditional request failed")
|
||||||
elif 'ComparisonOperator' in val:
|
elif 'ComparisonOperator' in val:
|
||||||
comparison_func = get_comparison_func(
|
|
||||||
val['ComparisonOperator'])
|
|
||||||
dynamo_types = [
|
dynamo_types = [
|
||||||
DynamoType(ele) for ele in
|
DynamoType(ele) for ele in
|
||||||
val.get("AttributeValueList", [])
|
val.get("AttributeValueList", [])
|
||||||
]
|
]
|
||||||
for t in dynamo_types:
|
if not item_attr[key].compare(val['ComparisonOperator'], dynamo_types):
|
||||||
if not comparison_func(item_attr[key].value, t.value):
|
raise ValueError('The conditional request failed')
|
||||||
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:
|
||||||
|
@ -750,6 +750,47 @@ def test_boto3_update_item_conditions_pass_because_expect_exists_by_compare_to_n
|
|||||||
returned_item = table.get_item(Key={'username': 'johndoe'})
|
returned_item = table.get_item(Key={'username': 'johndoe'})
|
||||||
assert dict(returned_item)['Item']['foo'].should.equal("baz")
|
assert dict(returned_item)['Item']['foo'].should.equal("baz")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_boto3_update_settype_item_with_conditions():
|
||||||
|
class OrderedSet(set):
|
||||||
|
"""A set with predictable iteration order"""
|
||||||
|
def __init__(self, values):
|
||||||
|
super(OrderedSet, self).__init__(values)
|
||||||
|
self.__ordered_values = values
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__ordered_values)
|
||||||
|
|
||||||
|
table = _create_user_table()
|
||||||
|
table.put_item(Item={'username': 'johndoe'})
|
||||||
|
table.update_item(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=:new_value',
|
||||||
|
ExpressionAttributeValues={
|
||||||
|
':new_value': OrderedSet(['hello', 'world']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
table.update_item(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=:new_value',
|
||||||
|
ExpressionAttributeValues={
|
||||||
|
':new_value': set(['baz']),
|
||||||
|
},
|
||||||
|
Expected={
|
||||||
|
'foo': {
|
||||||
|
'ComparisonOperator': 'EQ',
|
||||||
|
'AttributeValueList': [
|
||||||
|
OrderedSet(['world', 'hello']), # Opposite order to original
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
returned_item = table.get_item(Key={'username': 'johndoe'})
|
||||||
|
assert dict(returned_item)['Item']['foo'].should.equal(set(['baz']))
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_boto3_put_item_conditions_pass():
|
def test_boto3_put_item_conditions_pass():
|
||||||
table = _create_user_table()
|
table = _create_user_table()
|
||||||
|
Loading…
Reference in New Issue
Block a user