#847 - DynamoDB - Implement list_append feature when updating
This commit is contained in:
parent
b927ec99b5
commit
730c4be1a3
@ -9,6 +9,7 @@ import uuid
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
from botocore.exceptions import ParamValidationError
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.utils import unix_time
|
from moto.core.utils import unix_time
|
||||||
@ -302,6 +303,8 @@ class Item(BaseModel):
|
|||||||
attr, list_index = attribute_is_list(key.split('.')[0])
|
attr, list_index = attribute_is_list(key.split('.')[0])
|
||||||
# If value not exists, changes value to a default if needed, else its the same as it was
|
# If value not exists, changes value to a default if needed, else its the same as it was
|
||||||
value = self._get_default(value)
|
value = self._get_default(value)
|
||||||
|
# If operation == list_append, get the original value and append it
|
||||||
|
value = self._get_appended_list(value, expression_attribute_values)
|
||||||
|
|
||||||
if type(value) != DynamoType:
|
if type(value) != DynamoType:
|
||||||
if value in expression_attribute_values:
|
if value in expression_attribute_values:
|
||||||
@ -370,6 +373,18 @@ class Item(BaseModel):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError('{} update action not yet supported'.format(action))
|
raise NotImplementedError('{} update action not yet supported'.format(action))
|
||||||
|
|
||||||
|
def _get_appended_list(self, value, expression_attribute_values):
|
||||||
|
if type(value) != DynamoType:
|
||||||
|
list_append_re = re.match('list_append\\((.+),(.+)\\)', value)
|
||||||
|
if list_append_re:
|
||||||
|
new_value = expression_attribute_values[list_append_re.group(2).strip()]
|
||||||
|
old_list = self.attrs[list_append_re.group(1)]
|
||||||
|
if not old_list.is_list():
|
||||||
|
raise ParamValidationError
|
||||||
|
old_list.value.extend(new_value['L'])
|
||||||
|
value = old_list
|
||||||
|
return value
|
||||||
|
|
||||||
def _get_default(self, value):
|
def _get_default(self, value):
|
||||||
if value.startswith('if_not_exists'):
|
if value.startswith('if_not_exists'):
|
||||||
# Function signature
|
# Function signature
|
||||||
|
@ -11,7 +11,7 @@ import requests
|
|||||||
from moto import mock_dynamodb2, mock_dynamodb2_deprecated
|
from moto import mock_dynamodb2, mock_dynamodb2_deprecated
|
||||||
from moto.dynamodb2 import dynamodb_backend2
|
from moto.dynamodb2 import dynamodb_backend2
|
||||||
from boto.exception import JSONResponseError
|
from boto.exception import JSONResponseError
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, ParamValidationError
|
||||||
from tests.helpers import requires_boto_gte
|
from tests.helpers import requires_boto_gte
|
||||||
import tests.backport_assert_raises
|
import tests.backport_assert_raises
|
||||||
|
|
||||||
@ -2734,6 +2734,13 @@ def test_item_size_is_under_400KB():
|
|||||||
Item={'id': {'S': 'foo'}, 'itemlist': {'L': [{'M': {'item1': {'S': large_item}}}]}})
|
Item={'id': {'S': 'foo'}, 'itemlist': {'L': [{'M': {'item1': {'S': large_item}}}]}})
|
||||||
|
|
||||||
|
|
||||||
|
def assert_failure_due_to_item_size(func, **kwargs):
|
||||||
|
with assert_raises(ClientError) as ex:
|
||||||
|
func(**kwargs)
|
||||||
|
ex.exception.response['Error']['Code'].should.equal('ValidationException')
|
||||||
|
ex.exception.response['Error']['Message'].should.equal('Item size has exceeded the maximum allowed size')
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
|
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
|
||||||
def test_hash_key_cannot_use_begins_with_operations():
|
def test_hash_key_cannot_use_begins_with_operations():
|
||||||
@ -2759,11 +2766,75 @@ def test_hash_key_cannot_use_begins_with_operations():
|
|||||||
ex.exception.response['Error']['Message'].should.equal('Query key condition not supported')
|
ex.exception.response['Error']['Message'].should.equal('Query key condition not supported')
|
||||||
|
|
||||||
|
|
||||||
def assert_failure_due_to_item_size(func, **kwargs):
|
@mock_dynamodb2
|
||||||
with assert_raises(ClientError) as ex:
|
def test_update_supports_complex_expression_attribute_values():
|
||||||
func(**kwargs)
|
client = boto3.client('dynamodb')
|
||||||
ex.exception.response['Error']['Code'].should.equal('ValidationException')
|
|
||||||
ex.exception.response['Error']['Message'].should.equal('Item size has exceeded the maximum allowed size')
|
client.create_table(AttributeDefinitions=[{'AttributeName': 'SHA256', 'AttributeType': 'S'}],
|
||||||
|
TableName='TestTable',
|
||||||
|
KeySchema=[{'AttributeName': 'SHA256', 'KeyType': 'HASH'}],
|
||||||
|
ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5})
|
||||||
|
|
||||||
|
client.update_item(TableName='TestTable',
|
||||||
|
Key={'SHA256': {'S': 'sha-of-file'}},
|
||||||
|
UpdateExpression=('SET MD5 = :md5,'
|
||||||
|
'MyStringSet = :string_set,'
|
||||||
|
'MyMap = :map' ),
|
||||||
|
ExpressionAttributeValues={':md5': {'S': 'md5-of-file'},
|
||||||
|
':string_set': {'SS': ['string1', 'string2']},
|
||||||
|
':map': {'M': {'EntryKey': {'SS': ['thing1', 'thing2']}}}})
|
||||||
|
result = client.get_item(TableName='TestTable', Key={'SHA256': {'S': 'sha-of-file'}})['Item']
|
||||||
|
result.should.equal({u'MyStringSet': {u'SS': [u'string1', u'string2']},
|
||||||
|
'MyMap': {u'M': {u'EntryKey': {u'SS': [u'thing1', u'thing2']}}},
|
||||||
|
'SHA256': {u'S': u'sha-of-file'},
|
||||||
|
'MD5': {u'S': u'md5-of-file'}})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_update_supports_list_append():
|
||||||
|
client = boto3.client('dynamodb')
|
||||||
|
|
||||||
|
client.create_table(AttributeDefinitions=[{'AttributeName': 'SHA256', 'AttributeType': 'S'}],
|
||||||
|
TableName='TestTable',
|
||||||
|
KeySchema=[{'AttributeName': 'SHA256', 'KeyType': 'HASH'}],
|
||||||
|
ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5})
|
||||||
|
client.put_item(TableName='TestTable',
|
||||||
|
Item={'SHA256': {'S': 'sha-of-file'}, 'crontab': {'L': [{'S': 'bar1'}]}})
|
||||||
|
|
||||||
|
# Update item using list_append expression
|
||||||
|
client.update_item(TableName='TestTable',
|
||||||
|
Key={'SHA256': {'S': 'sha-of-file'}},
|
||||||
|
UpdateExpression="SET crontab = list_append(crontab, :i)",
|
||||||
|
ExpressionAttributeValues={':i': {'L': [{'S': 'bar2'}]}})
|
||||||
|
|
||||||
|
# Verify item is appended to the existing list
|
||||||
|
result = client.get_item(TableName='TestTable', Key={'SHA256': {'S': 'sha-of-file'}})['Item']
|
||||||
|
result.should.equal({'SHA256': {'S': 'sha-of-file'},
|
||||||
|
'crontab': {'L': [{'S': 'bar1'}, {'S': 'bar2'}]}})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_update_catches_invalid_list_append_operation():
|
||||||
|
client = boto3.client('dynamodb')
|
||||||
|
|
||||||
|
client.create_table(AttributeDefinitions=[{'AttributeName': 'SHA256', 'AttributeType': 'S'}],
|
||||||
|
TableName='TestTable',
|
||||||
|
KeySchema=[{'AttributeName': 'SHA256', 'KeyType': 'HASH'}],
|
||||||
|
ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5})
|
||||||
|
client.put_item(TableName='TestTable',
|
||||||
|
Item={'SHA256': {'S': 'sha-of-file'}, 'crontab': {'L': [{'S': 'bar1'}]}})
|
||||||
|
|
||||||
|
# Update item using invalid list_append expression
|
||||||
|
with assert_raises(ParamValidationError) as ex:
|
||||||
|
client.update_item(TableName='TestTable',
|
||||||
|
Key={'SHA256': {'S': 'sha-of-file'}},
|
||||||
|
UpdateExpression="SET crontab = list_append(crontab, :i)",
|
||||||
|
ExpressionAttributeValues={':i': [{'S': 'bar2'}]})
|
||||||
|
|
||||||
|
# Verify correct error is returned
|
||||||
|
ex.exception.message.should.equal("Parameter validation failed:\n"
|
||||||
|
"Invalid type for parameter ExpressionAttributeValues."
|
||||||
|
":i, value: [{u'S': u'bar2'}], type: <type 'list'>, valid types: <type 'dict'>")
|
||||||
|
|
||||||
|
|
||||||
def _create_user_table():
|
def _create_user_table():
|
||||||
|
Loading…
Reference in New Issue
Block a user