def test_validation_of_if_not_exists_with_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" }, "b": { "N": "3" } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=update_expression_values, item=item, table=table, ).validate() dynamo_value = get_set_action_value(validated_ast) assert dynamo_value == DynamoType({"N": "3"})
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"}), range_key=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"}), range_key=None, attrs={ "id": {"S": "foo2"}, "s": {"SS": ["value1", "value2", "value3", "value5"]}, }, ) assert expected_item == item
def test_valid_update_expression(table): update_expression = "set forum_desc=:Desc, forum_type=:NewType" update_expression_values = { ":Desc": { "S": "AmazingForum" }, ":NewType": { "S": "BASIC" }, } update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "forum_name"}), range_key=DynamoType({"S": "forum_type"}), attrs={"forum_name": { "S": "hello" }}, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=update_expression_values, item=item, table=table, ).validate()
def test_validation_of_subraction_operation(table): update_expression = "SET ri = :val - :val2" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "a": { "N": "3" }, "b": { "N": "4" } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":val": { "N": "1" }, ":val2": { "N": "3" } }, item=item, table=table, ).validate() dynamo_value = get_set_action_value(validated_ast) assert dynamo_value == DynamoType({"N": "-2"})
def test_validation_set_path_does_not_need_to_be_resolvable_when_setting_a_new_attribute( table, ): """If this step just passes we are happy enough""" update_expression = "set d=a" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "foo2" }, "a": { "N": "3" } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, table=table, ).validate() dynamo_value = get_set_action_value(validated_ast) assert dynamo_value == DynamoType({"N": "3"})
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"}), range_key=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"}), range_key=None, attrs={"id": {"S": "foo2"}, "s": {"N": "15"}}, ) assert expected_item == item assert False except IncorrectDataType: assert True
def test_validation_of_update_expression_with_keyword(table): try: update_expression = "SET myNum = path + :val" update_expression_values = {":val": {"N": "3"}} update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "path": { "N": "3" } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=update_expression_values, item=item, table=table, ).validate() assert False, "No exception raised" except AttributeIsReservedKeyword as e: assert e.keyword == "path"
def test_validation_of_update_expression_with_attribute_name_that_is_not_defined( update_expression, table): """ When an update expression tries to get an attribute name that is not provided it must throw an exception. An error occurred (ValidationException) when calling the UpdateItem operation: Invalid UpdateExpression: An expression attribute name used in the document path is not defined; attribute name: #c """ try: update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "path": { "N": "3" } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names={ "#b": "ok" }, expression_attribute_values=None, item=item, table=table, ).validate() assert False, "No exception raised" except ExpressionAttributeNameNotDefined as e: assert e.not_defined_attribute_name == "#c"
def test_validation_of_update_expression_with_attribute_that_does_not_exist_in_item( table, ): """ When an update expression tries to get an attribute that does not exist it must throw the appropriate exception. An error occurred (ValidationException) when calling the UpdateItem operation: The provided expression refers to an attribute that does not exist in the item """ try: update_expression = "SET a = nonexistent" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "path": { "N": "3" } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, table=table, ).validate() assert False, "No exception raised" except AttributeDoesNotExist: assert True
def test_cannot_index_into_a_string(table): """ Must error out: The document path provided in the update expression is invalid for update' """ try: update_expression = "set itemstr[1]=:Item" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "foo2" }, "itemstr": { "S": "somestring" } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":Item": { "S": "string_update" } }, item=item, table=table, ).validate() assert False, "Must raise exception" except InvalidUpdateExpressionInvalidDocumentPath: assert True
def test_validation_set_path_does_not_need_to_be_resolvable_but_must_be_creatable_when_setting_a_new_attribute( table, ): try: update_expression = "set d.e=a" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "foo2" }, "a": { "N": "3" } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, table=table, ).validate() assert False, "Must raise exception" except InvalidUpdateExpressionInvalidDocumentPath: assert True
def test_validation_of_if_not_exists_not_existing_invalid_replace_value(table): try: update_expression = "SET a = if_not_exists(b, a.c)" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "a": { "S": "A" } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values=None, item=item, table=table, ).validate() assert False, "No exception raised" except AttributeDoesNotExist: assert True
def test_execution_of_remove_in_map(table): update_expression = "Remove itemmap.itemlist[1].foo11" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=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, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() expected_item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": {"S": "foo2"}, "itemmap": { "M": { "itemlist": { "L": [ {"M": {"foo00": {"S": "bar1"}, "foo01": {"S": "bar2"}}}, {"M": {"foo10": {"S": "bar1"}}}, ] } } }, }, ) assert expected_item == item
def test_validation_homogeneous_list_append_function(table): update_expression = "SET ri = list_append(ri, :vals)" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "ri": { "L": [{ "S": "i1" }, { "S": "i2" }] } }, ) validated_ast = UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":vals": { "L": [{ "S": "i3" }, { "S": "i4" }] } }, item=item, table=table, ).validate() dynamo_value = get_set_action_value(validated_ast) assert dynamo_value == DynamoType( {"L": [{ "S": "i1" }, { "S": "i2" }, { "S": "i3" }, { "S": "i4" }]})
def execute(self, item): value_to_add = self.get_action_value() if isinstance(value_to_add, DynamoType): if value_to_add.is_set(): try: current_string_set = self.get_item_at_end_of_path(item) except ProvidedKeyDoesNotExist: current_string_set = DynamoType({value_to_add.type: []}) SetExecutor.set( item_part_to_modify_with_set=self. get_item_before_end_of_path(item), element_to_set=self.get_element_to_action(), value_to_set=current_string_set, expression_attribute_names=self. expression_attribute_names, ) assert isinstance(current_string_set, DynamoType) if not current_string_set.type == value_to_add.type: raise IncorrectDataType() # Sets are implemented as list for value in value_to_add.value: if value in current_string_set.value: continue else: current_string_set.value.append(value) elif value_to_add.type == DDBType.NUMBER: try: existing_value = self.get_item_at_end_of_path(item) except ProvidedKeyDoesNotExist: existing_value = DynamoType({DDBType.NUMBER: "0"}) assert isinstance(existing_value, DynamoType) if not existing_value.type == DDBType.NUMBER: raise IncorrectDataType() new_value = existing_value + value_to_add SetExecutor.set( item_part_to_modify_with_set=self. get_item_before_end_of_path(item), element_to_set=self.get_element_to_action(), value_to_set=new_value, expression_attribute_names=self.expression_attribute_names, ) else: raise IncorrectDataType()
def replace_expression_attribute_value_with_value(self, node): """A node representing an Expression Attribute Value. Resolve and replace value""" assert isinstance(node, ExpressionAttributeValue) attribute_value_name = node.get_value_name() try: target = self.expression_attribute_values[attribute_value_name] except KeyError: raise ExpressionAttributeValueNotDefined( attribute_value=attribute_value_name) return DDBTypedValue(DynamoType(target))
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
def test_execution_of_if_not_exists_not_existing_value(table): update_expression = "SET a = if_not_exists(b, a)" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={"id": {"S": "1"}, "a": {"S": "A"}}, ) 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"}), range_key=None, attrs={"id": {"S": "1"}, "a": {"S": "A"}}, ) assert expected_item == item
def test_execution_of_remove(table): update_expression = "Remove a" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=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"}), range_key=None, attrs={"id": {"S": "1"}, "b": {"N": "4"}}, ) assert expected_item == item
def test_sum_with_incompatible_types(table): """ Must error out: Invalid UpdateExpression: Incorrect operand type for operator or function; operator or function: +, operand type: S' Returns: """ try: update_expression = "SET ri = :val + :val2" update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "ri": { "L": [{ "S": "i1" }, { "S": "i2" }] } }, ) UpdateExpressionValidator( update_expression_ast, expression_attribute_names=None, expression_attribute_values={ ":val": { "S": "N" }, ":val2": { "N": "3" } }, item=item, table=table, ).validate() except IncorrectOperandType as e: assert e.operand_type == "S" assert e.operator_or_function == "+"
def test_execution_of_delete_element_from_a_string_attribute(table): """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"}), range_key=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, table=table, ).validate() UpdateExpressionExecutor(validated_ast, item, None).execute() assert False, "Must raise exception" except IncorrectDataType: assert True
def test_validation_of_a_set_statement_with_incorrect_passed_value( update_expression, table): """ By running permutations it shows that values are replaced prior to resolving attributes. An error occurred (ValidationException) when calling the UpdateItem operation: Invalid UpdateExpression: An expression attribute value used in expression is not defined; attribute value: :val2 """ update_expression_ast = UpdateExpressionParser.make(update_expression) item = Item( hash_key=DynamoType({"S": "id"}), range_key=None, attrs={ "id": { "S": "1" }, "b": { "N": "3" } }, ) try: UpdateExpressionValidator( update_expression_ast, expression_attribute_names={ "#b": "ok" }, expression_attribute_values={ ":val": { "N": "3" } }, item=item, table=table, ).validate() except ExpressionAttributeValueNotDefined as e: assert e.attribute_value == ":val2"
def test_execution_of__delete_element_from_set_invalid_value( expression_attribute_values, unexpected_data_type, table ): """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"}), range_key=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, table=table, ).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