def test_execution_of_delete_element_from_a_string_attribute(): """A delete statement must use a value of type SS in order to delete elements from a set.""" update_expression = "delete s :value" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "S": "5" }, }, ) try: validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":value": { "SS": ["value2"] } }, item=item, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() assert False, "Must raise exception" except IncorrectDataType: assert True
def test_execution_of_if_not_exists_with_non_existing_attribute_should_return_value( table, ): update_expression = "SET a = if_not_exists(b, :val)" update_expression_values = {":val": {"N": "4"}} update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={"id": { "S": "1" }}, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=update_expression_values, item=item, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "a": { "N": "4" } }, ) assert expected_item == item
def test_execution_of__delete_element_from_set_invalid_value( expression_attribute_values, unexpected_data_type): """A delete statement must use a value of type SS in order to delete elements from a set.""" update_expression = "delete s :value" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "SS": ["value1", "value2", "value3"] }, }, ) try: validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=expression_attribute_values, item=item, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() assert False, "Must raise exception" except IncorrectOperandType as e: assert e.operator_or_function == "operator: DELETE" assert e.operand_type == unexpected_data_type
def test_execution_of_add_set_to_a_number(table): update_expression = "add s :value" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "N": "5" }, }, ) try: validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":value": { "SS": ["s1"] } }, item=item, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "N": "15" } }, ) assert expected_item == item assert False except IncorrectDataType: assert True
def test_execution_of_if_not_exists_with_existing_attribute_should_return_attribute( ): update_expression = "SET a = if_not_exists(b, a)" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "1" }, "a": { "S": "A" }, "b": { "S": "B" } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "1" }, "a": { "S": "B" }, "b": { "S": "B" } }, ) assert expected_item == item
def test_execution_of_sum_operation(table): update_expression = "SET a = a + b" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "1" }, "a": { "N": "3" }, "b": { "N": "4" } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "1" }, "a": { "N": "7" }, "b": { "N": "4" } }, ) assert expected_item == item
def test_execution_of_add_to_a_set(table): update_expression = "ADD s :value" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "SS": ["value1", "value2", "value3"] }, }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":value": { "SS": ["value2", "value5"] } }, item=item, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "SS": ["value1", "value2", "value3", "value5"] }, }, ) assert expected_item == item
def test_execution_of_add_number(): update_expression = "add s :value" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "N": "5" }, }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":value": { "N": "10" } }, item=item, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "s": { "N": "15" } }, ) assert expected_item == item
def update_item( self, table_name, key, update_expression, expression_attribute_names, expression_attribute_values, attribute_updates=None, expected=None, condition_expression=None, ): table = self.get_table(table_name) # Support spaces between operators in an update expression # E.g. `a = b + c` -> `a=b+c` if update_expression: # Parse expression to get validation errors update_expression_ast = UpdateExpressionParser.make( update_expression) update_expression = re.sub(r"\s*([=\+-])\s*", "\\1", update_expression) if all([table.hash_key_attr in key, table.range_key_attr in key]): # Covers cases where table has hash and range keys, ``key`` param # will be a dict hash_value = DynamoType(key[table.hash_key_attr]) range_value = DynamoType(key[table.range_key_attr]) elif table.hash_key_attr in key: # Covers tables that have a range key where ``key`` param is a dict hash_value = DynamoType(key[table.hash_key_attr]) range_value = None else: # Covers other cases hash_value = DynamoType(key) range_value = None item = table.get_item(hash_value, range_value) orig_item = copy.deepcopy(item) if not expected: expected = {} if not get_expected(expected).expr(item): raise ConditionalCheckFailed condition_op = get_filter_expression( condition_expression, expression_attribute_names, expression_attribute_values, ) if not condition_op.expr(item): raise ConditionalCheckFailed # Update does not fail on new items, so create one if item is None: data = {table.hash_key_attr: {hash_value.type: hash_value.value}} if range_value: data.update({ table.range_key_attr: { range_value.type: range_value.value } }) table.put_item(data) item = table.get_item(hash_value, range_value) if update_expression: validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=expression_attribute_names, expression_attribute_values=expression_attribute_values, item=item, ).validate() try: UpdateExpressionExecutor(validated_ast, item, expression_attribute_names).execute() except ItemSizeTooLarge: raise ItemSizeToUpdateTooLarge() else: item.update_with_attribute_updates(attribute_updates) if table.stream_shard is not None: table.stream_shard.add(orig_item, item) return item
def test_execution_of_remove_in_list(): update_expression = "Remove itemmap.itemlist[1]" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "itemmap": { "M": { "itemlist": { "L": [ { "M": { "foo00": { "S": "bar1" }, "foo01": { "S": "bar2" } } }, { "M": { "foo10": { "S": "bar1" }, "foo11": { "S": "bar2" } } }, ] } } }, }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), hash_key_type="TYPE", range_key=None, range_key_type=None, attrs={ "id": { "S": "foo2" }, "itemmap": { "M": { "itemlist": { "L": [ { "M": { "foo00": { "S": "bar1" }, "foo01": { "S": "bar2" } } }, ] } } }, }, ) assert expected_item == item
def test_execution_of_delete_element_from_set(table, attr_name): expression_attribute_names = {"#placeholder": "s"} update_expression = "delete {} :value".format(attr_name) update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "foo2" }, "s": { "SS": ["value1", "value2", "value3"] } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=expression_attribute_names, expression_attribute_values={ ":value": { "SS": ["value2", "value5"] } }, item=item, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, expression_attribute_names).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "foo2" }, "s": { "SS": ["value1", "value3"] }, }, ) assert expected_item == item # delete last elements update_expression = "delete {} :value".format(attr_name) update_expression_ast = UpdateExpressionParser.make(update_expression) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=expression_attribute_names, expression_attribute_values={ ":value": { "SS": ["value1", "value3"] } }, item=item, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, expression_attribute_names).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={"id": { "S": "foo2" }}, ) assert expected_item == item