diff --git a/include/mg_procedure.h b/include/mg_procedure.h
index 93b9b865f..00bd5942d 100644
--- a/include/mg_procedure.h
+++ b/include/mg_procedure.h
@@ -29,9 +29,12 @@ enum MGP_NODISCARD mgp_error {
   MGP_ERROR_INSUFFICIENT_BUFFER,
   MGP_ERROR_OUT_OF_RANGE,
   MGP_ERROR_LOGIC_ERROR,
-  MGP_ERROR_NON_EXISTENT_OBJECT,
+  MGP_ERROR_DELETED_OBJECT,
   MGP_ERROR_INVALID_ARGUMENT,
   MGP_ERROR_KEY_ALREADY_EXISTS,
+  MGP_ERROR_IMMUTABLE_OBJECT,
+  MGP_ERROR_VALUE_CONVERSION,
+  MGP_ERROR_SERIALIZATION_ERROR,
 };
 ///@}
 
@@ -208,93 +211,93 @@ enum mgp_error mgp_value_make_path(struct mgp_path *val, struct mgp_value **resu
 
 /// Get the type of the value contained in mgp_value.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_type(const struct mgp_value *val, enum mgp_value_type *result);
+enum mgp_error mgp_value_get_type(struct mgp_value *val, enum mgp_value_type *result);
 
 /// Result is non-zero if the given mgp_value represents `null`.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_null(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_null(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a boolean.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_bool(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_bool(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores an integer.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_int(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_int(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a double floating-point.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_double(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_double(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a character string.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_string(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_string(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a list of values.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_list(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_list(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a map of values.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_map(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_map(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a vertex.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_vertex(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_vertex(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores an edge.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_edge(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_edge(struct mgp_value *val, int *result);
 
 /// Result is non-zero if the given mgp_value stores a path.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_is_path(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_is_path(struct mgp_value *val, int *result);
 
 /// Get the contained boolean value.
 /// Non-zero values represent `true`, while zero represents `false`.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_bool(const struct mgp_value *val, int *result);
+enum mgp_error mgp_value_get_bool(struct mgp_value *val, int *result);
 
 /// Get the contained integer.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_int(const struct mgp_value *val, int64_t *result);
+enum mgp_error mgp_value_get_int(struct mgp_value *val, int64_t *result);
 
 /// Get the contained double floating-point.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_double(const struct mgp_value *val, double *result);
+enum mgp_error mgp_value_get_double(struct mgp_value *val, double *result);
 
 /// Get the contained character string.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_string(const struct mgp_value *val, const char **result);
+enum mgp_error mgp_value_get_string(struct mgp_value *val, const char **result);
 
 /// Get the contained list of values.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_list(const struct mgp_value *val, const struct mgp_list **result);
+enum mgp_error mgp_value_get_list(struct mgp_value *val, struct mgp_list **result);
 
-/// Return the contained map of values.
+/// Get the contained map of values.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_map(const struct mgp_value *val, const struct mgp_map **result);
+enum mgp_error mgp_value_get_map(struct mgp_value *val, struct mgp_map **result);
 
 /// Get the contained vertex.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_vertex(const struct mgp_value *val, const struct mgp_vertex **result);
+enum mgp_error mgp_value_get_vertex(struct mgp_value *val, struct mgp_vertex **result);
 
 /// Get the contained edge.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_edge(const struct mgp_value *val, const struct mgp_edge **result);
+enum mgp_error mgp_value_get_edge(struct mgp_value *val, struct mgp_edge **result);
 
 /// Get the contained path.
 /// Result is undefined if mgp_value does not contain the expected type.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_value_get_path(const struct mgp_value *val, const struct mgp_path **result);
+enum mgp_error mgp_value_get_path(struct mgp_value *val, struct mgp_path **result);
 
 /// Create an empty list with given capacity.
 /// You need to free the created instance with mgp_list_destroy.
@@ -313,7 +316,7 @@ void mgp_list_destroy(struct mgp_list *list);
 /// original value.
 /// Return MGP_ERROR_INSUFFICIENT_BUFFER if there's no capacity.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
-enum mgp_error mgp_list_append(struct mgp_list *list, const struct mgp_value *val);
+enum mgp_error mgp_list_append(struct mgp_list *list, struct mgp_value *val);
 
 /// Append a copy of mgp_value to mgp_list increasing capacity if needed.
 /// The list copies the given value and therefore does not take ownership of the
@@ -323,20 +326,20 @@ enum mgp_error mgp_list_append(struct mgp_list *list, const struct mgp_value *va
 /// memory and any references to them will be invalid.
 /// Return MGP_ERROR_INSUFFICIENT_BUFFER if there's no capacity.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
-enum mgp_error mgp_list_append_extend(struct mgp_list *list, const struct mgp_value *val);
+enum mgp_error mgp_list_append_extend(struct mgp_list *list, struct mgp_value *val);
 
 /// Get the number of elements stored in mgp_list.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_list_size(const struct mgp_list *list, size_t *result);
+enum mgp_error mgp_list_size(struct mgp_list *list, size_t *result);
 
 /// Get the total number of elements for which there's already allocated
 /// memory in mgp_list.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_list_capacity(const struct mgp_list *list, size_t *result);
+enum mgp_error mgp_list_capacity(struct mgp_list *list, size_t *result);
 
 /// Get the element in mgp_list at given position.
 /// MGP_ERROR_OUT_OF_RANGE is returned if the index is not within mgp_list_size.
-enum mgp_error mgp_list_at(const struct mgp_list *list, size_t index, const struct mgp_value **result);
+enum mgp_error mgp_list_at(struct mgp_list *list, size_t index, struct mgp_value **result);
 
 /// Create an empty map of character strings to mgp_value instances.
 /// You need to free the created instance with mgp_map_destroy.
@@ -353,24 +356,24 @@ void mgp_map_destroy(struct mgp_map *map);
 /// you still need to free their memory explicitly.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate for insertion.
 /// Return MGP_ERROR_KEY_ALREADY_EXISTS if a previous mapping already exists.
-enum mgp_error mgp_map_insert(struct mgp_map *map, const char *key, const struct mgp_value *value);
+enum mgp_error mgp_map_insert(struct mgp_map *map, const char *key, struct mgp_value *value);
 
 /// Get the number of items stored in mgp_map.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_map_size(const struct mgp_map *map, size_t *result);
+enum mgp_error mgp_map_size(struct mgp_map *map, size_t *result);
 
 /// Get the mapped mgp_value to the given character string.
 /// Result is NULL if no mapping exists.
-enum mgp_error mgp_map_at(const struct mgp_map *map, const char *key, const struct mgp_value **result);
+enum mgp_error mgp_map_at(struct mgp_map *map, const char *key, struct mgp_value **result);
 
 /// An item in the mgp_map.
 struct mgp_map_item;
 
 /// Get the key of the mapped item.
-enum mgp_error mgp_map_item_key(const struct mgp_map_item *item, const char **result);
+enum mgp_error mgp_map_item_key(struct mgp_map_item *item, const char **result);
 
 /// Get the value of the mapped item.
-enum mgp_error mgp_map_item_value(const struct mgp_map_item *item, const struct mgp_value **result);
+enum mgp_error mgp_map_item_value(struct mgp_map_item *item, struct mgp_value **result);
 
 /// An iterator over the items in mgp_map.
 struct mgp_map_items_iterator;
@@ -379,7 +382,7 @@ struct mgp_map_items_iterator;
 /// The resulting mgp_map_items_iterator needs to be deallocated with
 /// mgp_map_items_iterator_destroy.
 /// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map_items_iterator.
-enum mgp_error mgp_map_iter_items(const struct mgp_map *map, struct mgp_memory *memory,
+enum mgp_error mgp_map_iter_items(struct mgp_map *map, struct mgp_memory *memory,
                                   struct mgp_map_items_iterator **result);
 
 /// Deallocate memory used by mgp_map_items_iterator.
@@ -393,24 +396,23 @@ void mgp_map_items_iterator_destroy(struct mgp_map_items_iterator *it);
 /// as the value before, and use them after invoking
 /// mgp_map_items_iterator_next.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_map_items_iterator_get(const struct mgp_map_items_iterator *it, const struct mgp_map_item **result);
+enum mgp_error mgp_map_items_iterator_get(struct mgp_map_items_iterator *it, struct mgp_map_item **result);
 
 /// Advance the iterator to the next item stored in map and return it.
 /// The previous pointer obtained through mgp_map_items_iterator_get will
 /// be invalidated, but the pointers to key and value will remain valid.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_map_items_iterator_next(struct mgp_map_items_iterator *it, const struct mgp_map_item **result);
+enum mgp_error mgp_map_items_iterator_next(struct mgp_map_items_iterator *it, struct mgp_map_item **result);
 
 /// Create a path with the copy of the given starting vertex.
 /// You need to free the created instance with mgp_path_destroy.
 /// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path.
-enum mgp_error mgp_path_make_with_start(const struct mgp_vertex *vertex, struct mgp_memory *memory,
-                                        struct mgp_path **result);
+enum mgp_error mgp_path_make_with_start(struct mgp_vertex *vertex, struct mgp_memory *memory, struct mgp_path **result);
 
 /// Copy a mgp_path.
 /// Returned pointer must be freed with mgp_path_destroy.
 /// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path.
-enum mgp_error mgp_path_copy(const struct mgp_path *path, struct mgp_memory *memory, struct mgp_path **result);
+enum mgp_error mgp_path_copy(struct mgp_path *path, struct mgp_memory *memory, struct mgp_path **result);
 
 /// Free the memory used by the given mgp_path and contained vertices and edges.
 void mgp_path_destroy(struct mgp_path *path);
@@ -423,24 +425,24 @@ void mgp_path_destroy(struct mgp_path *path);
 /// edge, as continued from the current last vertex.
 /// Return MGP_ERROR_LOGIC_ERROR if the current last vertex in the path is not part of the given edge.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for path extension.
-enum mgp_error mgp_path_expand(struct mgp_path *path, const struct mgp_edge *edge);
+enum mgp_error mgp_path_expand(struct mgp_path *path, struct mgp_edge *edge);
 
 /// Get the number of edges in a mgp_path.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_path_size(const struct mgp_path *path, size_t *result);
+enum mgp_error mgp_path_size(struct mgp_path *path, size_t *result);
 
 /// Get the vertex from a path at given index.
 /// The valid index range is [0, mgp_path_size].
 /// MGP_ERROR_OUT_OF_RANGE is returned if index is out of range.
-enum mgp_error mgp_path_vertex_at(const struct mgp_path *path, size_t index, const struct mgp_vertex **result);
+enum mgp_error mgp_path_vertex_at(struct mgp_path *path, size_t index, struct mgp_vertex **result);
 
 /// Get the edge from a path at given index.
 /// The valid index range is [0, mgp_path_size - 1].
 /// MGP_ERROR_OUT_OF_RANGE is returned if index is out of range.
-enum mgp_error mgp_path_edge_at(const struct mgp_path *path, size_t index, const struct mgp_edge **result);
+enum mgp_error mgp_path_edge_at(struct mgp_path *path, size_t index, struct mgp_edge **result);
 
 /// Result is non-zero if given paths are equal, otherwise 0.
-enum mgp_error mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2, int *result);
+enum mgp_error mgp_path_equal(struct mgp_path *p1, struct mgp_path *p2, int *result);
 
 ///@}
 
@@ -468,7 +470,7 @@ enum mgp_error mgp_result_new_record(struct mgp_result *res, struct mgp_result_r
 /// Return MGP_ERROR_OUT_OF_RANGE if there is no field named `field_name`.
 /// Return MGP_ERROR_LOGIC_ERROR `val` does not satisfy the type of the field name `field_name`.
 enum mgp_error mgp_result_record_insert(struct mgp_result_record *record, const char *field_name,
-                                        const struct mgp_value *val);
+                                        struct mgp_value *val);
 ///@}
 
 /// @name Graph Constructs
@@ -497,21 +499,21 @@ struct mgp_property {
   /// Name (key) of a property as a NULL terminated string.
   const char *name;
   /// Value of the referenced property.
-  const struct mgp_value *value;
+  struct mgp_value *value;
 };
 
 /// Get the current property pointed to by the iterator.
 /// When the mgp_properties_iterator_next is invoked, the previous
 /// mgp_property is invalidated and its value must not be used.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_properties_iterator_get(const struct mgp_properties_iterator *it,
-                                           const struct mgp_property **result);
+enum mgp_error mgp_properties_iterator_get(struct mgp_properties_iterator *it, struct mgp_property **result);
 
 /// Advance the iterator to the next property and return it.
 /// The previous mgp_property obtained through mgp_properties_iterator_get
 /// will be invalidated, and you must not use its value.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_properties_iterator_next(struct mgp_properties_iterator *it, const struct mgp_property **result);
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_property.
+enum mgp_error mgp_properties_iterator_next(struct mgp_properties_iterator *it, struct mgp_property **result);
 
 /// Iterator over edges of a vertex.
 struct mgp_edges_iterator;
@@ -527,70 +529,116 @@ struct mgp_vertex_id {
 /// Get the ID of given vertex.
 /// The ID is only valid for a single query execution, you should never store it
 /// globally in a query module.
-enum mgp_error mgp_vertex_get_id(const struct mgp_vertex *v, struct mgp_vertex_id *result);
+enum mgp_error mgp_vertex_get_id(struct mgp_vertex *v, struct mgp_vertex_id *result);
+
+/// Result is non-zero if the vertex can be modified.
+/// The mutability of the vertex is the same as the graph which it is part of. If a vertex is immutable, then edges
+/// cannot be added or removed, properties and labels cannot be set or removed and all of the returned edges will be
+/// immutable also.
+/// Current implementation always returns without errors.
+enum mgp_error mgp_vertex_underlying_graph_is_mutable(struct mgp_vertex *v, int *result);
+
+/// Set the value of a property on a vertex.
+/// When the value is `null`, then the property is removed from the vertex.
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
+/// Return MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
+enum mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name,
+                                       struct mgp_value *property_value);
+
+/// Add the label to the vertex.
+/// If the vertex already has the label, this function does nothing.
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the label.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
+enum mgp_error mgp_vertex_add_label(struct mgp_vertex *v, struct mgp_label label);
+
+/// Remove the label from the vertex.
+/// If the vertex doesn't have the label, this function does nothing.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable.
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction.
+enum mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, struct mgp_label label);
 
 /// Copy a mgp_vertex.
 /// Resulting pointer must be freed with mgp_vertex_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex.
-enum mgp_error mgp_vertex_copy(const struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_vertex **result);
+enum mgp_error mgp_vertex_copy(struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_vertex **result);
 
 /// Free the memory used by a mgp_vertex.
 void mgp_vertex_destroy(struct mgp_vertex *v);
 
 /// Result is non-zero if given vertices are equal, otherwise 0.
-enum mgp_error mgp_vertex_equal(const struct mgp_vertex *v1, const struct mgp_vertex *v2, int *result);
+enum mgp_error mgp_vertex_equal(struct mgp_vertex *v1, struct mgp_vertex *v2, int *result);
 
 /// Get the number of labels a given vertex has.
-enum mgp_error mgp_vertex_labels_count(const struct mgp_vertex *v, size_t *result);
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_labels_count(struct mgp_vertex *v, size_t *result);
 
 /// Get mgp_label in mgp_vertex at given index.
 /// Return MGP_ERROR_OUT_OF_RANGE if the index is out of range.
-enum mgp_error mgp_vertex_label_at(const struct mgp_vertex *v, size_t index, struct mgp_label *result);
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_label_at(struct mgp_vertex *v, size_t index, struct mgp_label *result);
 
 /// Result is non-zero if the given vertex has the given label.
-enum mgp_error mgp_vertex_has_label(const struct mgp_vertex *v, struct mgp_label label, int *result);
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_has_label(struct mgp_vertex *v, struct mgp_label label, int *result);
 
 /// Result is non-zero if the given vertex has a label with given name.
-enum mgp_error mgp_vertex_has_label_named(const struct mgp_vertex *v, const char *label_name, int *result);
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_has_label_named(struct mgp_vertex *v, const char *label_name, int *result);
 
 /// Get a copy of a vertex property mapped to a given name.
 /// Resulting value must be freed with mgp_value_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
-enum mgp_error mgp_vertex_get_property(const struct mgp_vertex *v, const char *property_name, struct mgp_memory *memory,
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_get_property(struct mgp_vertex *v, const char *property_name, struct mgp_memory *memory,
                                        struct mgp_value **result);
 
 /// Start iterating over properties stored in the given vertex.
 /// The resulting mgp_properties_iterator needs to be deallocated with
 /// mgp_properties_iterator_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator.
-enum mgp_error mgp_vertex_iter_properties(const struct mgp_vertex *v, struct mgp_memory *memory,
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_iter_properties(struct mgp_vertex *v, struct mgp_memory *memory,
                                           struct mgp_properties_iterator **result);
 
 /// Start iterating over inbound edges of the given vertex.
 /// The resulting mgp_edges_iterator needs to be deallocated with
 /// mgp_edges_iterator_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator.
-enum mgp_error mgp_vertex_iter_in_edges(const struct mgp_vertex *v, struct mgp_memory *memory,
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_iter_in_edges(struct mgp_vertex *v, struct mgp_memory *memory,
                                         struct mgp_edges_iterator **result);
 
 /// Start iterating over outbound edges of the given vertex.
-/// The returned mgp_edges_iterator needs to be deallocated with
+/// The resulting mgp_edges_iterator needs to be deallocated with
 /// mgp_edges_iterator_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator.
-enum mgp_error mgp_vertex_iter_out_edges(const struct mgp_vertex *v, struct mgp_memory *memory,
+/// Return MGP_ERROR_DELETED_OBJECT if `v` has been removed.
+enum mgp_error mgp_vertex_iter_out_edges(struct mgp_vertex *v, struct mgp_memory *memory,
                                          struct mgp_edges_iterator **result);
 
+/// Result is non-zero if the edges returned by this iterator can be modified.
+/// The mutability of the mgp_edges_iterator is the same as the graph which it belongs to.
+/// Current implementation always returns without errors.
+enum mgp_error mgp_edges_iterator_underlying_graph_is_mutable(struct mgp_edges_iterator *it, int *result);
+
 /// Get the current edge pointed to by the iterator.
 /// When the mgp_edges_iterator_next is invoked, the previous
 /// mgp_edge is invalidated and its value must not be used.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_edges_iterator_get(const struct mgp_edges_iterator *it, const struct mgp_edge **result);
+enum mgp_error mgp_edges_iterator_get(struct mgp_edges_iterator *it, struct mgp_edge **result);
 
 /// Advance the iterator to the next edge and return it.
 /// The previous mgp_edge obtained through mgp_edges_iterator_get
 /// will be invalidated, and you must not use its value.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_edges_iterator_next(struct mgp_edges_iterator *it, const struct mgp_edge **result);
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge.
+enum mgp_error mgp_edges_iterator_next(struct mgp_edges_iterator *it, struct mgp_edge **result);
 
 /// ID of an edge; valid during a single query execution.
 struct mgp_edge_id {
@@ -600,75 +648,133 @@ struct mgp_edge_id {
 /// Get the ID of given edge.
 /// The ID is only valid for a single query execution, you should never store it
 /// globally in a query module.
-enum mgp_error mgp_edge_get_id(const struct mgp_edge *e, struct mgp_edge_id *result);
+enum mgp_error mgp_edge_get_id(struct mgp_edge *e, struct mgp_edge_id *result);
+
+/// Result is non-zero if the edge can be modified.
+/// The mutability of the edge is the same as the graph which it is part of. If an edge is immutable, properties cannot
+/// be set or removed and all of the returned vertices will be immutable also.
+/// Current implementation always returns without errors.
+enum mgp_error mgp_edge_underlying_graph_is_mutable(struct mgp_edge *e, int *result);
 
 /// Copy a mgp_edge.
 /// Resulting pointer must be freed with mgp_edge_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge.
-enum mgp_error mgp_edge_copy(const struct mgp_edge *e, struct mgp_memory *memory, struct mgp_edge **result);
+enum mgp_error mgp_edge_copy(struct mgp_edge *e, struct mgp_memory *memory, struct mgp_edge **result);
 
 /// Free the memory used by a mgp_edge.
 void mgp_edge_destroy(struct mgp_edge *e);
 
 /// Result is non-zero if given edges are equal, otherwise 0.
-enum mgp_error mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2, int *result);
+enum mgp_error mgp_edge_equal(struct mgp_edge *e1, struct mgp_edge *e2, int *result);
 
 /// Get the type of the given edge.
-enum mgp_error mgp_edge_get_type(const struct mgp_edge *e, struct mgp_edge_type *result);
+enum mgp_error mgp_edge_get_type(struct mgp_edge *e, struct mgp_edge_type *result);
 
 /// Get the source vertex of the given edge.
+/// Resulting vertex is valid until the edge is valid and it must not be used afterwards.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_edge_get_from(const struct mgp_edge *e, const struct mgp_vertex **result);
+enum mgp_error mgp_edge_get_from(struct mgp_edge *e, struct mgp_vertex **result);
 
 /// Get the destination vertex of the given edge.
+/// Resulting vertex is valid until the edge is valid and it must not be used afterwards.
 /// Current implementation always returns without errors.
-enum mgp_error mgp_edge_get_to(const struct mgp_edge *e, const struct mgp_vertex **result);
+enum mgp_error mgp_edge_get_to(struct mgp_edge *e, struct mgp_vertex **result);
 
 /// Get a copy of a edge property mapped to a given name.
 /// Resulting value must be freed with mgp_value_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value.
-enum mgp_error mgp_edge_get_property(const struct mgp_edge *e, const char *property_name, struct mgp_memory *memory,
+/// Return MGP_ERROR_DELETED_OBJECT if `e` has been removed.
+enum mgp_error mgp_edge_get_property(struct mgp_edge *e, const char *property_name, struct mgp_memory *memory,
                                      struct mgp_value **result);
 
+/// Set the value of a property on an edge.
+/// When the value is `null`, then the property is removed from the edge.
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `e` is immutable.
+/// Return MGP_ERROR_DELETED_OBJECT if `e` has been removed.
+/// Return MGP_ERROR_LOGIC_ERROR if properties on edges are disabled.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `e` has been modified by another transaction.
+/// Return MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path.
+enum mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, struct mgp_value *property_value);
+
 /// Start iterating over properties stored in the given edge.
 /// Resulting mgp_properties_iterator needs to be deallocated with
 /// mgp_properties_iterator_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator.
-enum mgp_error mgp_edge_iter_properties(const struct mgp_edge *e, struct mgp_memory *memory,
+/// Return MGP_ERROR_DELETED_OBJECT if `e` has been removed.
+enum mgp_error mgp_edge_iter_properties(struct mgp_edge *e, struct mgp_memory *memory,
                                         struct mgp_properties_iterator **result);
 
 /// State of the graph database.
 struct mgp_graph;
 
-/// Return the vertex corresponding to given ID.
+/// Get the vertex corresponding to given ID, or NULL if no such vertex exists.
 /// Resulting vertex must be freed using mgp_vertex_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the vertex or if ID is not valid.
-enum mgp_error mgp_graph_get_vertex_by_id(const struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory,
+enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory,
                                           struct mgp_vertex **result);
 
+/// Result is non-zero if the graph can be modified.
+/// If a graph is immutable, then vertices cannot be added or removed, and all of the returned vertices will be
+/// immutable also.
+/// Current implementation always returns without errors.
+enum mgp_error mgp_graph_is_mutable(struct mgp_graph *graph, int *result);
+
+/// Add a new vertex to the graph.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex.
+enum mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_vertex **result);
+
+/// Remove a vertex from the graph.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
+/// Return MGP_ERROR_LOGIC_ERROR if `vertex` has edges.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction.
+enum mgp_error mgp_graph_remove_vertex(struct mgp_graph *graph, struct mgp_vertex *vertex);
+
+/// Add a new directed edge between the two vertices with the specified label.
+/// NULL is returned if the the edge creation fails for any reason.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge.
+/// Return MGP_ERROR_DELETED_OBJECT if `from` or `to` has been removed.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `from` or `to` has been modified by another transaction.
+enum mgp_error mgp_graph_create_edge(struct mgp_graph *graph, struct mgp_vertex *from, struct mgp_vertex *to,
+                                     struct mgp_edge_type type, struct mgp_memory *memory, struct mgp_edge **result);
+
+/// Remove an edge from the graph.
+/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
+/// Return MGP_ERROR_LOGIC_ERROR if `vertex` has edges.
+/// Return MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by another
+/// transaction.
+enum mgp_error mgp_graph_remove_edge(struct mgp_graph *graph, struct mgp_edge *edge);
+
 /// Iterator over vertices.
 struct mgp_vertices_iterator;
 
 /// Free the memory used by a mgp_vertices_iterator.
 void mgp_vertices_iterator_destroy(struct mgp_vertices_iterator *it);
 
-/// Start iterating over vertices of the given graph.
 /// Resulting mgp_vertices_iterator needs to be deallocated with mgp_vertices_iterator_destroy.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertices_iterator.
-enum mgp_error mgp_graph_iter_vertices(const struct mgp_graph *g, struct mgp_memory *memory,
+enum mgp_error mgp_graph_iter_vertices(struct mgp_graph *g, struct mgp_memory *memory,
                                        struct mgp_vertices_iterator **result);
 
+/// Result is non-zero if the vertices returned by this iterator can be modified.
+/// The mutability of the mgp_vertices_iterator is the same as the graph which it belongs to.
+/// Current implementation always returns without errors.
+enum mgp_error mgp_vertices_iterator_underlying_graph_is_mutable(struct mgp_vertices_iterator *it, int *result);
+
 /// Get the current vertex pointed to by the iterator.
 /// When the mgp_vertices_iterator_next is invoked, the previous
 /// mgp_vertex is invalidated and its value must not be used.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_vertices_iterator_get(const struct mgp_vertices_iterator *it, const struct mgp_vertex **result);
+enum mgp_error mgp_vertices_iterator_get(struct mgp_vertices_iterator *it, struct mgp_vertex **result);
 
 /// Advance the iterator to the next vertex and return it.
 /// The previous mgp_vertex obtained through mgp_vertices_iterator_get
 /// will be invalidated, and you must not use its value.
 /// Result is NULL if the end of the iteration has been reached.
-enum mgp_error mgp_vertices_iterator_next(struct mgp_vertices_iterator *it, const struct mgp_vertex **result);
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex.
+enum mgp_error mgp_vertices_iterator_next(struct mgp_vertices_iterator *it, struct mgp_vertex **result);
 ///@}
 
 /// @name Type System
@@ -687,29 +793,29 @@ struct mgp_type;
 ///
 /// The ANY type is the parent type of all types.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_any(const struct mgp_type **result);
+enum mgp_error mgp_type_any(struct mgp_type **result);
 
 /// Get the type representing boolean values.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_bool(const struct mgp_type **result);
+enum mgp_error mgp_type_bool(struct mgp_type **result);
 
 /// Get the type representing character string values.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_string(const struct mgp_type **result);
+enum mgp_error mgp_type_string(struct mgp_type **result);
 
 /// Get the type representing integer values.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_int(const struct mgp_type **result);
+enum mgp_error mgp_type_int(struct mgp_type **result);
 
 /// Get the type representing floating-point values.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_float(const struct mgp_type **result);
+enum mgp_error mgp_type_float(struct mgp_type **result);
 
 /// Get the type representing any number value.
 ///
 /// This is the parent type for numeric types, i.e. INTEGER and FLOAT.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_number(const struct mgp_type **result);
+enum mgp_error mgp_type_number(struct mgp_type **result);
 
 /// Get the type representing map values.
 ///
@@ -721,35 +827,35 @@ enum mgp_error mgp_type_number(const struct mgp_type **result);
 /// @sa mgp_type_node
 /// @sa mgp_type_relationship
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_map(const struct mgp_type **result);
+enum mgp_error mgp_type_map(struct mgp_type **result);
 
 /// Get the type representing graph node values.
 ///
 /// Since a node contains a map of properties, the node itself is also of MAP
 /// type.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_node(const struct mgp_type **result);
+enum mgp_error mgp_type_node(struct mgp_type **result);
 
 /// Get the type representing graph relationship values.
 ///
 /// Since a relationship contains a map of properties, the relationship itself
 /// is also of MAP type.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_relationship(const struct mgp_type **result);
+enum mgp_error mgp_type_relationship(struct mgp_type **result);
 
 /// Get the type representing a graph path (walk) from one node to another.
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_path(const struct mgp_type **result);
+enum mgp_error mgp_type_path(struct mgp_type **result);
 
 /// Build a type representing a list of values of given `element_type`.
 ///
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_list(const struct mgp_type *element_type, const struct mgp_type **result);
+enum mgp_error mgp_type_list(struct mgp_type *element_type, struct mgp_type **result);
 
 /// Build a type representing either a `null` value or a value of given `type`.
 ///
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type.
-enum mgp_error mgp_type_nullable(const struct mgp_type *type, const struct mgp_type **result);
+enum mgp_error mgp_type_nullable(struct mgp_type *type, struct mgp_type **result);
 ///@}
 
 /// @name Query Module & Procedures
@@ -772,15 +878,14 @@ struct mgp_module;
 /// Describes a procedure of a query module.
 struct mgp_proc;
 
-/// Entry-point for a query module procedure, invoked through openCypher.
+/// Entry-point for a query module read procedure, invoked through openCypher.
 ///
 /// Passed in arguments will not live longer than the callback's execution.
 /// Therefore, you must not store them globally or use the passed in mgp_memory
 /// to allocate global resources.
-typedef void (*mgp_proc_cb)(const struct mgp_list *, const struct mgp_graph *, struct mgp_result *,
-                            struct mgp_memory *);
+typedef void (*mgp_proc_cb)(struct mgp_list *, struct mgp_graph *, struct mgp_result *, struct mgp_memory *);
 
-/// Register a read-only procedure with a module.
+/// Register a read-only procedure to a module.
 ///
 /// The `name` must be a sequence of digits, underscores, lowercase and
 /// uppercase Latin letters. The name must begin with a non-digit character.
@@ -793,6 +898,17 @@ typedef void (*mgp_proc_cb)(const struct mgp_list *, const struct mgp_graph *, s
 enum mgp_error mgp_module_add_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb,
                                              struct mgp_proc **result);
 
+/// Register a read-only procedure to a module.
+///
+/// The `name` must be a valid identifier, following the same rules as the
+/// procedure`name` in mgp_module_add_read_procedure.
+///
+/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc.
+/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name.
+/// RETURN MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered.
+enum mgp_error mgp_module_add_write_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb,
+                                              struct mgp_proc **result);
+
 /// Add a required argument to a procedure.
 ///
 /// The order of adding arguments will correspond to the order the procedure
@@ -807,7 +923,7 @@ enum mgp_error mgp_module_add_read_procedure(struct mgp_module *module, const ch
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument.
 /// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name.
 /// RETURN MGP_ERROR_LOGIC_ERROR if the procedure already has any optional argument.
-enum mgp_error mgp_proc_add_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type);
+enum mgp_error mgp_proc_add_arg(struct mgp_proc *proc, const char *name, struct mgp_type *type);
 
 /// Add an optional argument with a default value to a procedure.
 ///
@@ -831,8 +947,8 @@ enum mgp_error mgp_proc_add_arg(struct mgp_proc *proc, const char *name, const s
 /// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name.
 /// RETURN MGP_ERROR_OUT_OF_RANGE if `default_value` is a graph element (vertex, edge or path).
 /// RETURN MGP_ERROR_LOGIC_ERROR if `default_value` does not satisfy `type`.
-enum mgp_error mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type,
-                                    const struct mgp_value *default_value);
+enum mgp_error mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, struct mgp_type *type,
+                                    struct mgp_value *default_value);
 
 /// Add a result field to a procedure.
 ///
@@ -845,7 +961,7 @@ enum mgp_error mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, con
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument.
 /// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name.
 /// RETURN MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added.
-enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type);
+enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, struct mgp_type *type);
 
 /// Add a result field to a procedure and mark it as deprecated.
 ///
@@ -855,7 +971,7 @@ enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, cons
 /// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument.
 /// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name.
 /// RETURN MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added.
-enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type);
+enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, struct mgp_type *type);
 ///@}
 
 /// @name Execution
@@ -873,7 +989,7 @@ enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char
 /// their code in order to determine whether they should abort or not. Note that
 /// this mechanism is purely cooperative and depends on the procedure doing the
 /// checking and aborting on its own.
-int mgp_must_abort(const struct mgp_graph *graph);
+int mgp_must_abort(struct mgp_graph *graph);
 
 /// @}
 
@@ -892,37 +1008,36 @@ struct mgp_messages;
 /// Payload is not null terminated and not a string but rather a byte array.
 /// You need to call mgp_message_payload_size() first, to read the size of
 /// the payload.
-enum mgp_error mgp_message_payload(const struct mgp_message *message, const char **result);
+enum mgp_error mgp_message_payload(struct mgp_message *message, const char **result);
 
-/// Return the payload size
-enum mgp_error mgp_message_payload_size(const struct mgp_message *message, size_t *result);
+/// Get the payload size
+enum mgp_error mgp_message_payload_size(struct mgp_message *message, size_t *result);
 
-/// Return the name of topic
-enum mgp_error mgp_message_topic_name(const struct mgp_message *message, const char **result);
+/// Get the name of topic
+enum mgp_error mgp_message_topic_name(struct mgp_message *message, const char **result);
 
-/// Return the key of mgp_message as a byte array
-enum mgp_error mgp_message_key(const struct mgp_message *message, const char **result);
+/// Get the key of mgp_message as a byte array
+enum mgp_error mgp_message_key(struct mgp_message *message, const char **result);
 
-/// Return the key size of mgp_message
-enum mgp_error mgp_message_key_size(const struct mgp_message *message, size_t *result);
+/// Get the key size of mgp_message
+enum mgp_error mgp_message_key_size(struct mgp_message *message, size_t *result);
 
-/// Return the timestamp of mgp_message as a byte array
-enum mgp_error mgp_message_timestamp(const struct mgp_message *message, int64_t *result);
+/// Get the timestamp of mgp_message as a byte array
+enum mgp_error mgp_message_timestamp(struct mgp_message *message, int64_t *result);
 
-/// Return the number of messages contained in the mgp_messages list
+/// Get the number of messages contained in the mgp_messages list
 /// Current implementation always returns without errors.
-enum mgp_error mgp_messages_size(const struct mgp_messages *message, size_t *result);
+enum mgp_error mgp_messages_size(struct mgp_messages *message, size_t *result);
 
-/// Return the message from a messages list at given index
-enum mgp_error mgp_messages_at(const struct mgp_messages *message, size_t index, const struct mgp_message **result);
+/// Get the message from a messages list at given index
+enum mgp_error mgp_messages_at(struct mgp_messages *message, size_t index, struct mgp_message **result);
 
 /// Entry-point for a module transformation, invoked through a stream transformation.
 ///
 /// Passed in arguments will not live longer than the callback's execution.
 /// Therefore, you must not store them globally or use the passed in mgp_memory
 /// to allocate global resources.
-typedef void (*mgp_trans_cb)(const struct mgp_messages *, const struct mgp_graph *, struct mgp_result *,
-                             struct mgp_memory *);
+typedef void (*mgp_trans_cb)(struct mgp_messages *, struct mgp_graph *, struct mgp_result *, struct mgp_memory *);
 
 /// Register a transformation with a module.
 ///
diff --git a/query_modules/example.c b/query_modules/example.c
index c64a44a5f..1a4dfb8c0 100644
--- a/query_modules/example.c
+++ b/query_modules/example.c
@@ -16,7 +16,7 @@
 //   CALL example.procedure(1, 2) YIELD args, result;
 //   CALL example.procedure(1) YIELD args, result;
 // Naturally, you may pass in different arguments or yield less fields.
-static void procedure(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result,
+static void procedure(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result,
                       struct mgp_memory *memory) {
   size_t args_size = 0;
   if (mgp_list_size(args, &args_size) != MGP_ERROR_NO_ERROR) {
@@ -27,7 +27,7 @@ static void procedure(const struct mgp_list *args, const struct mgp_graph *graph
     goto error_something_went_wrong;
   }
   for (size_t i = 0; i < args_size; ++i) {
-    const struct mgp_value *value = NULL;
+    struct mgp_value *value = NULL;
     if (mgp_list_at(args, i, &value) != MGP_ERROR_NO_ERROR) {
       goto error_free_list;
     }
@@ -69,6 +69,11 @@ error_something_went_wrong:
   return;
 }
 
+static void write_procedure(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result,
+                            struct mgp_memory *memory) {
+  // TODO(antaljanosbenjamin): Finish this example
+}
+
 // Each module needs to define mgp_init_module function.
 // Here you can register multiple procedures your module supports.
 int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
@@ -76,11 +81,11 @@ int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
   if (mgp_module_add_read_procedure(module, "procedure", procedure, &proc) != MGP_ERROR_NO_ERROR) {
     return 1;
   }
-  const struct mgp_type *any_type = NULL;
+  struct mgp_type *any_type = NULL;
   if (mgp_type_any(&any_type) != MGP_ERROR_NO_ERROR) {
     return 1;
   }
-  const struct mgp_type *nullable_any_type = NULL;
+  struct mgp_type *nullable_any_type = NULL;
   if (mgp_type_nullable(any_type, &nullable_any_type) != MGP_ERROR_NO_ERROR) {
     return 1;
   }
@@ -97,14 +102,14 @@ int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
     return 1;
   }
   mgp_value_destroy(null_value);
-  const struct mgp_type *string = NULL;
+  struct mgp_type *string = NULL;
   if (mgp_type_string(&string) != MGP_ERROR_NO_ERROR) {
     return 1;
   }
   if (mgp_proc_add_result(proc, "result", string) != MGP_ERROR_NO_ERROR) {
     return 1;
   }
-  const struct mgp_type *list_of_anything = NULL;
+  struct mgp_type *list_of_anything = NULL;
   if (mgp_type_list(nullable_any_type, &list_of_anything) != MGP_ERROR_NO_ERROR) {
     return 1;
   }
diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp
index 492624a05..89c8847ac 100644
--- a/src/query/plan/operator.cpp
+++ b/src/query/plan/operator.cpp
@@ -22,6 +22,7 @@
 #include "query/interpret/eval.hpp"
 #include "query/path.hpp"
 #include "query/plan/scoped_profile.hpp"
+#include "query/procedure/cypher_types.hpp"
 #include "query/procedure/mg_procedure_impl.hpp"
 #include "query/procedure/module.hpp"
 #include "storage/v2/property_value.hpp"
diff --git a/src/query/procedure/cypher_type_ptr.hpp b/src/query/procedure/cypher_type_ptr.hpp
new file mode 100644
index 000000000..010c3955a
--- /dev/null
+++ b/src/query/procedure/cypher_type_ptr.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <functional>
+#include <memory>
+
+namespace query::procedure {
+class CypherType;
+using CypherTypePtr = std::unique_ptr<CypherType, std::function<void(CypherType *)>>;
+}  // namespace query::procedure
\ No newline at end of file
diff --git a/src/query/procedure/cypher_types.hpp b/src/query/procedure/cypher_types.hpp
index 33ef5cc56..e7a41100d 100644
--- a/src/query/procedure/cypher_types.hpp
+++ b/src/query/procedure/cypher_types.hpp
@@ -7,7 +7,8 @@
 #include <memory>
 #include <string_view>
 
-#include "query/procedure/mg_procedure_helpers.hpp"
+#include "query/procedure/cypher_type_ptr.hpp"
+#include "query/procedure/mg_procedure_impl.hpp"
 #include "query/typed_value.hpp"
 #include "utils/memory.hpp"
 #include "utils/pmr/string.hpp"
@@ -43,15 +44,13 @@ class CypherType {
   virtual const NullableType *AsNullableType() const { return nullptr; }
 };
 
-using CypherTypePtr = std::unique_ptr<CypherType, std::function<void(CypherType *)>>;
-
 // Simple Types
 
 class AnyType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "ANY"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return !CallBool(mgp_value_is_null, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type != MGP_VALUE_TYPE_NULL; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return !value.IsNull(); }
 };
@@ -60,7 +59,7 @@ class BoolType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "BOOLEAN"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_bool, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_BOOL; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsBool(); }
 };
@@ -69,7 +68,7 @@ class StringType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "STRING"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_string, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_STRING; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsString(); }
 };
@@ -78,7 +77,7 @@ class IntType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "INTEGER"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_int, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_INT; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsInt(); }
 };
@@ -87,7 +86,7 @@ class FloatType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "FLOAT"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_double, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_DOUBLE; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsDouble(); }
 };
@@ -97,7 +96,7 @@ class NumberType : public CypherType {
   std::string_view GetPresentableName() const override { return "NUMBER"; }
 
   bool SatisfiesType(const mgp_value &value) const override {
-    return CallBool(mgp_value_is_int, &value) || CallBool(mgp_value_is_double, &value);
+    return value.type == MGP_VALUE_TYPE_INT || value.type == MGP_VALUE_TYPE_DOUBLE;
   }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsInt() || value.IsDouble(); }
@@ -107,7 +106,7 @@ class NodeType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "NODE"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_vertex, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_VERTEX; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsVertex(); }
 };
@@ -116,7 +115,7 @@ class RelationshipType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "RELATIONSHIP"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_edge, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_EDGE; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsEdge(); }
 };
@@ -125,7 +124,7 @@ class PathType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "PATH"; }
 
-  bool SatisfiesType(const mgp_value &value) const override { return CallBool(mgp_value_is_path, &value); }
+  bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_PATH; }
 
   bool SatisfiesType(const query::TypedValue &value) const override { return value.IsPath(); }
 };
@@ -142,8 +141,7 @@ class MapType : public CypherType {
   std::string_view GetPresentableName() const override { return "MAP"; }
 
   bool SatisfiesType(const mgp_value &value) const override {
-    return CallBool(mgp_value_is_map, &value) || CallBool(mgp_value_is_vertex, &value) ||
-           CallBool(mgp_value_is_edge, &value);
+    return value.type == MGP_VALUE_TYPE_MAP || value.type == MGP_VALUE_TYPE_VERTEX || value.type == MGP_VALUE_TYPE_EDGE;
   }
 
   bool SatisfiesType(const query::TypedValue &value) const override {
@@ -168,13 +166,13 @@ class ListType : public CypherType {
   std::string_view GetPresentableName() const override { return presentable_name_; }
 
   bool SatisfiesType(const mgp_value &value) const override {
-    if (!CallBool(mgp_value_is_list, &value)) {
+    if (value.type != MGP_VALUE_TYPE_LIST) {
       return false;
     }
-    auto *list = Call<const mgp_list *>(mgp_value_get_list, &value);
-    const auto list_size = Call<size_t>(mgp_list_size, list);
+    auto *list = value.list_v;
+    const auto list_size = list->elems.size();
     for (size_t i = 0; i < list_size; ++i) {
-      if (!element_type_->SatisfiesType(*Call<const mgp_value *>(mgp_list_at, list, i))) {
+      if (!element_type_->SatisfiesType(list->elems[i])) {
         return false;
       };
     }
@@ -235,7 +233,7 @@ class NullableType : public CypherType {
   std::string_view GetPresentableName() const override { return presentable_name_; }
 
   bool SatisfiesType(const mgp_value &value) const override {
-    return CallBool(mgp_value_is_null, &value) || type_->SatisfiesType(value);
+    return value.type == MGP_VALUE_TYPE_NULL || type_->SatisfiesType(value);
   }
 
   bool SatisfiesType(const query::TypedValue &value) const override {
diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp
index efd45be1c..d09078a9f 100644
--- a/src/query/procedure/mg_procedure_impl.cpp
+++ b/src/query/procedure/mg_procedure_impl.cpp
@@ -5,13 +5,18 @@
 #include <cstring>
 #include <exception>
 #include <memory>
+#include <optional>
 #include <regex>
 #include <stdexcept>
 #include <type_traits>
 #include <utility>
 
+#include "mg_procedure.h"
 #include "module.hpp"
+#include "query/procedure/cypher_types.hpp"
 #include "query/procedure/mg_procedure_helpers.hpp"
+#include "storage/v2/property_value.hpp"
+#include "storage/v2/view.hpp"
 #include "utils/algorithm.hpp"
 #include "utils/concepts.hpp"
 #include "utils/logging.hpp"
@@ -76,7 +81,7 @@ void MgpFreeImpl(utils::MemoryResource &memory, void *const p) noexcept {
     spdlog::error("Unexpected throw during the release of memory for query modules");
   }
 }
-struct NonexistentObjectException : public utils::BasicException {
+struct DeletedObjectException : public utils::BasicException {
   using utils::BasicException::BasicException;
 };
 
@@ -88,6 +93,18 @@ struct InsufficientBufferException : public utils::BasicException {
   using utils::BasicException::BasicException;
 };
 
+struct ImmutableObjectException : public utils::BasicException {
+  using utils::BasicException::BasicException;
+};
+
+struct ValueConversionException : public utils::BasicException {
+  using utils::BasicException::BasicException;
+};
+
+struct SerializationException : public utils::BasicException {
+  using utils::BasicException::BasicException;
+};
+
 template <typename TFunc, typename TReturn>
 concept ReturnsType = std::same_as<std::invoke_result_t<TFunc>, TReturn>;
 
@@ -110,15 +127,24 @@ template <typename TFunc, typename... Args>
   static_assert(sizeof...(args) <= 1, "WrapExceptions should have only one or zero parameter!");
   try {
     WrapExceptionsHelper(std::forward<TFunc>(func), std::forward<Args>(args)...);
-  } catch (const NonexistentObjectException &neoe) {
-    spdlog::error("Nonexistent object error during mg API call: {}", neoe.what());
-    return MGP_ERROR_NON_EXISTENT_OBJECT;
+  } catch (const DeletedObjectException &neoe) {
+    spdlog::error("Deleted object error during mg API call: {}", neoe.what());
+    return MGP_ERROR_DELETED_OBJECT;
   } catch (const KeyAlreadyExistsException &kaee) {
     spdlog::error("Key already exists error during mg API call: {}", kaee.what());
     return MGP_ERROR_KEY_ALREADY_EXISTS;
   } catch (const InsufficientBufferException &ibe) {
     spdlog::error("Insufficient buffer error during mg API call: {}", ibe.what());
     return MGP_ERROR_INSUFFICIENT_BUFFER;
+  } catch (const ImmutableObjectException &ioe) {
+    spdlog::error("Immutable object error during mg API call: {}", ioe.what());
+    return MGP_ERROR_IMMUTABLE_OBJECT;
+  } catch (const ValueConversionException &vce) {
+    spdlog::error("Value converion error during mg API call: {}", vce.what());
+    return MGP_ERROR_VALUE_CONVERSION;
+  } catch (const SerializationException &se) {
+    spdlog::error("Serialization error during mg API call: {}", se.what());
+    return MGP_ERROR_SERIALIZATION_ERROR;
   } catch (const std::bad_alloc &bae) {
     spdlog::error("Memory allocation error during mg API call: {}", bae.what());
     return MGP_ERROR_UNABLE_TO_ALLOCATE;
@@ -143,6 +169,12 @@ template <typename TFunc, typename... Args>
   }
   return MGP_ERROR_NO_ERROR;
 }
+
+bool MgpGraphIsMutable(const mgp_graph &graph) noexcept { return graph.view == storage::View::NEW; }
+
+bool MgpVertexIsMutable(const mgp_vertex &vertex) { return MgpGraphIsMutable(*vertex.graph); }
+
+bool MgpEdgeIsMutable(const mgp_edge &edge) { return MgpVertexIsMutable(edge.from); }
 }  // namespace
 
 mgp_error mgp_alloc(mgp_memory *memory, size_t size_in_bytes, void **result) {
@@ -234,19 +266,19 @@ mgp_value_type FromTypedValueType(query::TypedValue::Type type) {
 }
 
 query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memory) {
-  switch (Call<mgp_value_type>(mgp_value_get_type, &val)) {
+  switch (val.type) {
     case MGP_VALUE_TYPE_NULL:
       return query::TypedValue(memory);
     case MGP_VALUE_TYPE_BOOL:
-      return query::TypedValue(CallBool(mgp_value_get_bool, &val), memory);
+      return query::TypedValue(val.bool_v, memory);
     case MGP_VALUE_TYPE_INT:
-      return query::TypedValue(Call<int64_t>(mgp_value_get_int, &val), memory);
+      return query::TypedValue(val.int_v, memory);
     case MGP_VALUE_TYPE_DOUBLE:
-      return query::TypedValue(Call<double>(mgp_value_get_double, &val), memory);
+      return query::TypedValue(val.double_v, memory);
     case MGP_VALUE_TYPE_STRING:
-      return query::TypedValue(Call<const char *>(mgp_value_get_string, &val), memory);
+      return query::TypedValue(val.string_v, memory);
     case MGP_VALUE_TYPE_LIST: {
-      const auto *list = Call<const mgp_list *>(mgp_value_get_list, &val);
+      const auto *list = val.list_v;
       query::TypedValue::TVector tv_list(memory);
       tv_list.reserve(list->elems.size());
       for (const auto &elem : list->elems) {
@@ -255,7 +287,7 @@ query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memo
       return query::TypedValue(std::move(tv_list));
     }
     case MGP_VALUE_TYPE_MAP: {
-      const auto *map = Call<const mgp_map *>(mgp_value_get_map, &val);
+      const auto *map = val.map_v;
       query::TypedValue::TMap tv_map(memory);
       for (const auto &item : map->items) {
         tv_map.emplace(item.first, ToTypedValue(item.second, memory));
@@ -263,11 +295,11 @@ query::TypedValue ToTypedValue(const mgp_value &val, utils::MemoryResource *memo
       return query::TypedValue(std::move(tv_map));
     }
     case MGP_VALUE_TYPE_VERTEX:
-      return query::TypedValue(Call<const mgp_vertex *>(mgp_value_get_vertex, &val)->impl, memory);
+      return query::TypedValue(val.vertex_v->impl, memory);
     case MGP_VALUE_TYPE_EDGE:
-      return query::TypedValue(Call<const mgp_edge *>(mgp_value_get_edge, &val)->impl, memory);
+      return query::TypedValue(val.edge_v->impl, memory);
     case MGP_VALUE_TYPE_PATH: {
-      const auto *path = Call<const mgp_path *>(mgp_value_get_path, &val);
+      const auto *path = val.path_v;
       MG_ASSERT(!path->vertices.empty());
       MG_ASSERT(path->vertices.size() == path->edges.size() + 1);
       query::Path tv_path(path->vertices[0].impl, memory);
@@ -320,7 +352,7 @@ mgp_value::mgp_value(mgp_path *val, utils::MemoryResource *m) noexcept
   MG_ASSERT(val->GetMemoryResource() == m, "Unable to take ownership of a pointer with different allocator.");
 }
 
-mgp_value::mgp_value(const query::TypedValue &tv, const mgp_graph *graph, utils::MemoryResource *m)
+mgp_value::mgp_value(const query::TypedValue &tv, mgp_graph *graph, utils::MemoryResource *m)
     : type(FromTypedValueType(tv.type())), memory(m) {
   switch (type) {
     case MGP_VALUE_TYPE_NULL:
@@ -600,6 +632,10 @@ mgp_value::mgp_value(mgp_value &&other, utils::MemoryResource *m) : type(other.t
 
 mgp_value::~mgp_value() noexcept { DeleteValueMember(this); }
 
+mgp_edge *mgp_edge::Copy(const mgp_edge &edge, mgp_memory &memory) {
+  return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph);
+}
+
 void mgp_value_destroy(mgp_value *val) { DeleteRawMgpObject(val); }
 
 mgp_error mgp_value_make_null(mgp_memory *memory, mgp_value **result) {
@@ -637,18 +673,18 @@ namespace {
 mgp_value_type MgpValueGetType(const mgp_value &val) noexcept { return val.type; }
 }  // namespace
 
-mgp_error mgp_value_get_type(const mgp_value *val, mgp_value_type *result) {
+mgp_error mgp_value_get_type(mgp_value *val, mgp_value_type *result) {
   static_assert(noexcept(MgpValueGetType(*val)));
   *result = MgpValueGetType(*val);
   return MGP_ERROR_NO_ERROR;
 }
 
 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
-#define DEFINE_MGP_VALUE_IS(type_lowercase, type_uppercase)                    \
-  mgp_error mgp_value_is_##type_lowercase(const mgp_value *val, int *result) { \
-    static_assert(noexcept(MgpValueGetType(*val)));                            \
-    *result = MgpValueGetType(*val) == MGP_VALUE_TYPE_##type_uppercase;        \
-    return MGP_ERROR_NO_ERROR;                                                 \
+#define DEFINE_MGP_VALUE_IS(type_lowercase, type_uppercase)              \
+  mgp_error mgp_value_is_##type_lowercase(mgp_value *val, int *result) { \
+    static_assert(noexcept(MgpValueGetType(*val)));                      \
+    *result = MgpValueGetType(*val) == MGP_VALUE_TYPE_##type_uppercase;  \
+    return MGP_ERROR_NO_ERROR;                                           \
   }
 
 DEFINE_MGP_VALUE_IS(null, NULL)
@@ -662,29 +698,29 @@ DEFINE_MGP_VALUE_IS(vertex, VERTEX)
 DEFINE_MGP_VALUE_IS(edge, EDGE)
 DEFINE_MGP_VALUE_IS(path, PATH)
 
-mgp_error mgp_value_get_bool(const mgp_value *val, int *result) {
+mgp_error mgp_value_get_bool(mgp_value *val, int *result) {
   *result = val->bool_v ? 1 : 0;
   return MGP_ERROR_NO_ERROR;
 }
-mgp_error mgp_value_get_int(const mgp_value *val, int64_t *result) {
+mgp_error mgp_value_get_int(mgp_value *val, int64_t *result) {
   *result = val->int_v;
   return MGP_ERROR_NO_ERROR;
 }
-mgp_error mgp_value_get_double(const mgp_value *val, double *result) {
+mgp_error mgp_value_get_double(mgp_value *val, double *result) {
   *result = val->double_v;
   return MGP_ERROR_NO_ERROR;
 }
-mgp_error mgp_value_get_string(const mgp_value *val, const char **result) {
+mgp_error mgp_value_get_string(mgp_value *val, const char **result) {
   static_assert(noexcept(val->string_v.c_str()));
   *result = val->string_v.c_str();
   return MGP_ERROR_NO_ERROR;
 }
 
 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
-#define DEFINE_MGP_VALUE_GET(type)                                                  \
-  mgp_error mgp_value_get_##type(const mgp_value *val, const mgp_##type **result) { \
-    *result = val->type##_v;                                                        \
-    return MGP_ERROR_NO_ERROR;                                                      \
+#define DEFINE_MGP_VALUE_GET(type)                                      \
+  mgp_error mgp_value_get_##type(mgp_value *val, mgp_##type **result) { \
+    *result = val->type##_v;                                            \
+    return MGP_ERROR_NO_ERROR;                                          \
   }
 
 DEFINE_MGP_VALUE_GET(list)
@@ -709,7 +745,7 @@ namespace {
 void MgpListAppendExtend(mgp_list &list, const mgp_value &value) { list.elems.push_back(value); }
 }  // namespace
 
-mgp_error mgp_list_append(mgp_list *list, const mgp_value *val) {
+mgp_error mgp_list_append(mgp_list *list, mgp_value *val) {
   return WrapExceptions([list, val] {
     if (Call<size_t>(mgp_list_size, list) >= Call<size_t>(mgp_list_capacity, list)) {
       throw InsufficientBufferException{
@@ -719,23 +755,23 @@ mgp_error mgp_list_append(mgp_list *list, const mgp_value *val) {
   });
 }
 
-mgp_error mgp_list_append_extend(mgp_list *list, const mgp_value *val) {
+mgp_error mgp_list_append_extend(mgp_list *list, mgp_value *val) {
   return WrapExceptions([list, val] { MgpListAppendExtend(*list, *val); });
 }
 
-mgp_error mgp_list_size(const mgp_list *list, size_t *result) {
+mgp_error mgp_list_size(mgp_list *list, size_t *result) {
   static_assert(noexcept(list->elems.size()));
   *result = list->elems.size();
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_list_capacity(const mgp_list *list, size_t *result) {
+mgp_error mgp_list_capacity(mgp_list *list, size_t *result) {
   static_assert(noexcept(list->elems.capacity()));
   *result = list->elems.capacity();
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_list_at(const mgp_list *list, size_t i, const mgp_value **result) {
+mgp_error mgp_list_at(mgp_list *list, size_t i, mgp_value **result) {
   return WrapExceptions(
       [list, i] {
         if (i >= Call<size_t>(mgp_list_size, list)) {
@@ -752,7 +788,7 @@ mgp_error mgp_map_make_empty(mgp_memory *memory, mgp_map **result) {
 
 void mgp_map_destroy(mgp_map *map) { DeleteRawMgpObject(map); }
 
-mgp_error mgp_map_insert(mgp_map *map, const char *key, const mgp_value *value) {
+mgp_error mgp_map_insert(mgp_map *map, const char *key, mgp_value *value) {
   return WrapExceptions([&] {
     auto emplace_result = map->items.emplace(key, *value);
     if (!emplace_result.second) {
@@ -761,15 +797,15 @@ mgp_error mgp_map_insert(mgp_map *map, const char *key, const mgp_value *value)
   });
 }
 
-mgp_error mgp_map_size(const mgp_map *map, size_t *result) {
+mgp_error mgp_map_size(mgp_map *map, size_t *result) {
   static_assert(noexcept(map->items.size()));
   *result = map->items.size();
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_map_at(const mgp_map *map, const char *key, const mgp_value **result) {
+mgp_error mgp_map_at(mgp_map *map, const char *key, mgp_value **result) {
   return WrapExceptions(
-      [&map, &key]() -> const mgp_value * {
+      [&map, &key]() -> mgp_value * {
         auto found_it = map->items.find(key);
         if (found_it == map->items.end()) {
           return nullptr;
@@ -783,7 +819,7 @@ mgp_error mgp_map_item_key(mgp_map_item *item, const char **result) {
   return WrapExceptions([&item] { return item->key; }, result);
 }
 
-mgp_error mgp_map_item_value(const mgp_map_item *item, const mgp_value **result) {
+mgp_error mgp_map_item_value(mgp_map_item *item, mgp_value **result) {
   return WrapExceptions([item] { return item->value; }, result);
 }
 
@@ -793,9 +829,9 @@ mgp_error mgp_map_iter_items(mgp_map *map, mgp_memory *memory, mgp_map_items_ite
 
 void mgp_map_items_iterator_destroy(mgp_map_items_iterator *it) { DeleteRawMgpObject(it); }
 
-mgp_error mgp_map_items_iterator_get(const mgp_map_items_iterator *it, const mgp_map_item **result) {
+mgp_error mgp_map_items_iterator_get(mgp_map_items_iterator *it, mgp_map_item **result) {
   return WrapExceptions(
-      [it]() -> const mgp_map_item * {
+      [it]() -> mgp_map_item * {
         if (it->current_it == it->map->items.end()) {
           return nullptr;
         };
@@ -804,9 +840,9 @@ mgp_error mgp_map_items_iterator_get(const mgp_map_items_iterator *it, const mgp
       result);
 }
 
-mgp_error mgp_map_items_iterator_next(mgp_map_items_iterator *it, const mgp_map_item **result) {
+mgp_error mgp_map_items_iterator_next(mgp_map_items_iterator *it, mgp_map_item **result) {
   return WrapExceptions(
-      [it]() -> const mgp_map_item * {
+      [it]() -> mgp_map_item * {
         if (it->current_it == it->map->items.end()) {
           return nullptr;
         }
@@ -820,7 +856,7 @@ mgp_error mgp_map_items_iterator_next(mgp_map_items_iterator *it, const mgp_map_
       result);
 }
 
-mgp_error mgp_path_make_with_start(const mgp_vertex *vertex, mgp_memory *memory, mgp_path **result) {
+mgp_error mgp_path_make_with_start(mgp_vertex *vertex, mgp_memory *memory, mgp_path **result) {
   return WrapExceptions(
       [vertex, memory]() -> mgp_path * {
         auto path = NewMgpObject<mgp_path>(memory);
@@ -833,7 +869,7 @@ mgp_error mgp_path_make_with_start(const mgp_vertex *vertex, mgp_memory *memory,
       result);
 }
 
-mgp_error mgp_path_copy(const mgp_path *path, mgp_memory *memory, mgp_path **result) {
+mgp_error mgp_path_copy(mgp_path *path, mgp_memory *memory, mgp_path **result) {
   return WrapExceptions(
       [path, memory] {
         MG_ASSERT(Call<size_t>(mgp_path_size, path) == path->vertices.size() - 1, "Invalid mgp_path");
@@ -844,17 +880,17 @@ mgp_error mgp_path_copy(const mgp_path *path, mgp_memory *memory, mgp_path **res
 
 void mgp_path_destroy(mgp_path *path) { DeleteRawMgpObject(path); }
 
-mgp_error mgp_path_expand(mgp_path *path, const mgp_edge *edge) {
+mgp_error mgp_path_expand(mgp_path *path, mgp_edge *edge) {
   return WrapExceptions([path, edge] {
     MG_ASSERT(Call<size_t>(mgp_path_size, path) == path->vertices.size() - 1, "Invalid mgp_path");
     // Check that the both the last vertex on path and dst_vertex are endpoints of
     // the given edge.
-    const auto *src_vertex = &path->vertices.back();
-    const mgp_vertex *dst_vertex = nullptr;
-    if (CallBool(mgp_vertex_equal, Call<const mgp_vertex *>(mgp_edge_get_to, edge), src_vertex) != 0) {
-      dst_vertex = Call<const mgp_vertex *>(mgp_edge_get_from, edge);
-    } else if (CallBool(mgp_vertex_equal, Call<const mgp_vertex *>(mgp_edge_get_from, edge), src_vertex)) {
-      dst_vertex = Call<const mgp_vertex *>(mgp_edge_get_to, edge);
+    auto *src_vertex = &path->vertices.back();
+    mgp_vertex *dst_vertex{nullptr};
+    if (edge->to == *src_vertex) {
+      dst_vertex = &edge->from;
+    } else if (edge->from == *src_vertex) {
+      dst_vertex = &edge->to;
     } else {
       // edge is not a continuation on src_vertex
       throw std::logic_error{"The current last vertex in the path is not part of the given edge."};
@@ -873,12 +909,12 @@ namespace {
 size_t MgpPathSize(const mgp_path &path) noexcept { return path.edges.size(); }
 }  // namespace
 
-mgp_error mgp_path_size(const mgp_path *path, size_t *result) {
+mgp_error mgp_path_size(mgp_path *path, size_t *result) {
   *result = MgpPathSize(*path);
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_path_vertex_at(const mgp_path *path, size_t i, const mgp_vertex **result) {
+mgp_error mgp_path_vertex_at(mgp_path *path, size_t i, mgp_vertex **result) {
   return WrapExceptions(
       [path, i] {
         const auto path_size = Call<size_t>(mgp_path_size, path);
@@ -891,7 +927,7 @@ mgp_error mgp_path_vertex_at(const mgp_path *path, size_t i, const mgp_vertex **
       result);
 }
 
-mgp_error mgp_path_edge_at(const mgp_path *path, size_t i, const mgp_edge **result) {
+mgp_error mgp_path_edge_at(mgp_path *path, size_t i, mgp_edge **result) {
   return WrapExceptions(
       [path, i] {
         const auto path_size = Call<size_t>(mgp_path_size, path);
@@ -904,7 +940,7 @@ mgp_error mgp_path_edge_at(const mgp_path *path, size_t i, const mgp_edge **resu
       result);
 }
 
-mgp_error mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2, int *result) {
+mgp_error mgp_path_equal(mgp_path *p1, mgp_path *p2, int *result) {
   return WrapExceptions(
       [p1, p2] {
         const auto p1_size = MgpPathSize(*p1);
@@ -914,15 +950,15 @@ mgp_error mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2, i
         if (p1_size != p2_size) {
           return 0;
         }
-        const auto *start1 = Call<const mgp_vertex *>(mgp_path_vertex_at, p1, 0);
-        const auto *start2 = Call<const mgp_vertex *>(mgp_path_vertex_at, p2, 0);
+        const auto *start1 = Call<mgp_vertex *>(mgp_path_vertex_at, p1, 0);
+        const auto *start2 = Call<mgp_vertex *>(mgp_path_vertex_at, p2, 0);
         static_assert(noexcept(start1->impl == start2->impl));
         if (*start1 != *start2) {
           return 0;
         }
         for (size_t i = 0; i < p1_size; ++i) {
-          const auto *e1 = Call<const mgp_edge *>(mgp_path_edge_at, p1, i);
-          const auto *e2 = Call<const mgp_edge *>(mgp_path_edge_at, p2, i);
+          const auto *e1 = Call<mgp_edge *>(mgp_path_edge_at, p1, i);
+          const auto *e2 = Call<mgp_edge *>(mgp_path_edge_at, p2, i);
           if (*e1 != *e2) {
             return 0;
           }
@@ -953,7 +989,7 @@ mgp_error mgp_result_new_record(mgp_result *res, mgp_result_record **result) {
       result);
 }
 
-mgp_error mgp_result_record_insert(mgp_result_record *record, const char *field_name, const mgp_value *val) {
+mgp_error mgp_result_record_insert(mgp_result_record *record, const char *field_name, mgp_value *val) {
   return WrapExceptions([=] {
     auto *memory = record->values.get_allocator().GetMemoryResource();
     // Validate field_name & val satisfy the procedure's result signature.
@@ -975,9 +1011,9 @@ mgp_error mgp_result_record_insert(mgp_result_record *record, const char *field_
 
 void mgp_properties_iterator_destroy(mgp_properties_iterator *it) { DeleteRawMgpObject(it); }
 
-mgp_error mgp_properties_iterator_get(const mgp_properties_iterator *it, const mgp_property **result) {
+mgp_error mgp_properties_iterator_get(mgp_properties_iterator *it, mgp_property **result) {
   return WrapExceptions(
-      [it]() -> const mgp_property * {
+      [it]() -> mgp_property * {
         if (it->current) {
           return &it->property;
         };
@@ -986,7 +1022,7 @@ mgp_error mgp_properties_iterator_get(const mgp_properties_iterator *it, const m
       result);
 }
 
-mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, const mgp_property **result) {
+mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property **result) {
   // Incrementing the iterator either for on-disk or in-memory
   // storage, so perhaps the underlying thing can throw.
   // Both copying TypedValue and/or string from PropertyName may fail to
@@ -995,7 +1031,7 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, const mgp_pr
   // Hopefully iterator comparison doesn't throw, but wrap the whole thing in
   // try ... catch just to be sure.
   return WrapExceptions(
-      [it]() -> const mgp_property * {
+      [it]() -> mgp_property * {
         if (it->current_it == it->pvs.end()) {
           MG_ASSERT(!it->current,
                     "Iteration is already done, so it->current should "
@@ -1018,36 +1054,158 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, const mgp_pr
       result);
 }
 
-mgp_error mgp_vertex_get_id(const mgp_vertex *v, mgp_vertex_id *result) {
+mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) {
   return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result);
 }
 
-mgp_error mgp_vertex_copy(const mgp_vertex *v, mgp_memory *memory, mgp_vertex **result) {
+mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) {
+  return mgp_graph_is_mutable(v->graph, result);
+}
+
+namespace {
+storage::PropertyValue ToPropertyValue(const mgp_value &value);
+
+storage::PropertyValue ToPropertyValue(const mgp_list &list) {
+  storage::PropertyValue result{std::vector<storage::PropertyValue>{}};
+  auto &result_list = result.ValueList();
+  for (const auto &value : list.elems) {
+    result_list.push_back(ToPropertyValue(value));
+  }
+  return result;
+}
+
+storage::PropertyValue ToPropertyValue(const mgp_map &map) {
+  storage::PropertyValue result{std::map<std::string, storage::PropertyValue>{}};
+  auto &result_map = result.ValueMap();
+  for (const auto &[key, value] : map.items) {
+    result_map.insert_or_assign(std::string{key}, ToPropertyValue(value));
+  }
+  return result;
+}
+
+storage::PropertyValue ToPropertyValue(const mgp_value &value) {
+  switch (value.type) {
+    case MGP_VALUE_TYPE_NULL:
+      return storage::PropertyValue{};
+    case MGP_VALUE_TYPE_BOOL:
+      return storage::PropertyValue{value.bool_v};
+    case MGP_VALUE_TYPE_INT:
+      return storage::PropertyValue{value.int_v};
+    case MGP_VALUE_TYPE_DOUBLE:
+      return storage::PropertyValue{value.double_v};
+    case MGP_VALUE_TYPE_STRING:
+      return storage::PropertyValue{std::string{value.string_v}};
+    case MGP_VALUE_TYPE_LIST:
+      return ToPropertyValue(*value.list_v);
+    case MGP_VALUE_TYPE_MAP:
+      return ToPropertyValue(*value.map_v);
+    case MGP_VALUE_TYPE_VERTEX:
+      throw ValueConversionException{"A vertex is not a valid property value! "};
+    case MGP_VALUE_TYPE_EDGE:
+      throw ValueConversionException{"An edge is not a valid property value!"};
+    case MGP_VALUE_TYPE_PATH:
+      throw ValueConversionException{"A path is not a valid property value!"};
+  }
+}
+}  // namespace
+
+mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name, mgp_value *property_value) {
+  return WrapExceptions([=] {
+    if (!MgpVertexIsMutable(*v)) {
+      throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
+    }
+    const auto result =
+        v->impl.SetProperty(v->graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
+
+    if (result.HasError()) {
+      switch (result.GetError()) {
+        case storage::Error::DELETED_OBJECT:
+          throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"};
+        case storage::Error::NONEXISTENT_OBJECT:
+          LOG_FATAL("Query modules mustn't have access to nonexistent objects when setting a property of a vertex!");
+        case storage::Error::PROPERTIES_DISABLED:
+        case storage::Error::VERTEX_HAS_EDGES:
+          LOG_FATAL("Unexpected error when setting a property of a vertex.");
+        case storage::Error::SERIALIZATION_ERROR:
+          throw SerializationException{"Cannot serialize setting a property of a vertex."};
+      }
+    }
+  });
+}
+
+mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
+  return WrapExceptions([=] {
+    if (!MgpVertexIsMutable(*v)) {
+      throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
+    }
+    const auto result = v->impl.AddLabel(v->graph->impl->NameToLabel(label.name));
+
+    if (result.HasError()) {
+      switch (result.GetError()) {
+        case storage::Error::DELETED_OBJECT:
+          throw DeletedObjectException{"Cannot add a label to a deleted vertex!"};
+        case storage::Error::NONEXISTENT_OBJECT:
+          LOG_FATAL("Query modules mustn't have access to nonexistent objects when adding a label to a vertex!");
+        case storage::Error::PROPERTIES_DISABLED:
+        case storage::Error::VERTEX_HAS_EDGES:
+          LOG_FATAL("Unexpected error when adding a label to a vertex.");
+        case storage::Error::SERIALIZATION_ERROR:
+          throw SerializationException{"Cannot serialize adding a label to a vertex."};
+      }
+    }
+  });
+}
+
+mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
+  return WrapExceptions([=] {
+    if (!MgpVertexIsMutable(*v)) {
+      throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
+    }
+    const auto result = v->impl.RemoveLabel(v->graph->impl->NameToLabel(label.name));
+
+    if (result.HasError()) {
+      switch (result.GetError()) {
+        case storage::Error::DELETED_OBJECT:
+          throw DeletedObjectException{"Cannot remove a label from a deleted vertex!"};
+        case storage::Error::NONEXISTENT_OBJECT:
+          LOG_FATAL("Query modules mustn't have access to nonexistent objects when removing a label from a vertex!");
+        case storage::Error::PROPERTIES_DISABLED:
+        case storage::Error::VERTEX_HAS_EDGES:
+          LOG_FATAL("Unexpected error when removing a label from a vertex.");
+        case storage::Error::SERIALIZATION_ERROR:
+          throw SerializationException{"Cannot serialize removing a label from a vertex."};
+      }
+    }
+  });
+}
+
+mgp_error mgp_vertex_copy(mgp_vertex *v, mgp_memory *memory, mgp_vertex **result) {
   return WrapExceptions([v, memory] { return NewRawMgpObject<mgp_vertex>(memory, *v); }, result);
 }
 
 void mgp_vertex_destroy(mgp_vertex *v) { DeleteRawMgpObject(v); }
 
-mgp_error mgp_vertex_equal(const mgp_vertex *v1, const mgp_vertex *v2, int *result) {
+mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) {
   // NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression)
   static_assert(noexcept(*result = *v1 == *v2 ? 1 : 0));
   *result = *v1 == *v2 ? 1 : 0;
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_vertex_labels_count(const mgp_vertex *v, size_t *result) {
+mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) {
   return WrapExceptions(
       [v]() -> size_t {
         auto maybe_labels = v->impl.Labels(v->graph->view);
         if (maybe_labels.HasError()) {
           switch (maybe_labels.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get the labels of a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get the labels of nonexistent vertex"};
+              LOG_FATAL("Query modules mustn't have access to nonexistent objects when getting vertex labels!");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting vertex labels.");
+              LOG_FATAL("Unexpected error when getting vertex labels.");
           }
         }
         return maybe_labels->size();
@@ -1055,7 +1213,7 @@ mgp_error mgp_vertex_labels_count(const mgp_vertex *v, size_t *result) {
       result);
 }
 
-mgp_error mgp_vertex_label_at(const mgp_vertex *v, size_t i, mgp_label *result) {
+mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
   return WrapExceptions(
       [v, i]() -> const char * {
         // TODO: Maybe it's worth caching this in mgp_vertex.
@@ -1063,12 +1221,13 @@ mgp_error mgp_vertex_label_at(const mgp_vertex *v, size_t i, mgp_label *result)
         if (maybe_labels.HasError()) {
           switch (maybe_labels.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get a label of a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get a label of nonexistent vertex"};
+              LOG_FATAL("Query modules mustn't have access to nonexistent objects when getting a label of a vertex!");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting a label of a vertex.");
+              LOG_FATAL("Unexpected error when getting a label of a vertex.");
           }
         }
         if (i >= maybe_labels->size()) {
@@ -1084,7 +1243,7 @@ mgp_error mgp_vertex_label_at(const mgp_vertex *v, size_t i, mgp_label *result)
       &result->name);
 }
 
-mgp_error mgp_vertex_has_label_named(const mgp_vertex *v, const char *name, int *result) {
+mgp_error mgp_vertex_has_label_named(mgp_vertex *v, const char *name, int *result) {
   return WrapExceptions(
       [v, name] {
         storage::LabelId label;
@@ -1094,24 +1253,27 @@ mgp_error mgp_vertex_has_label_named(const mgp_vertex *v, const char *name, int
         if (maybe_has_label.HasError()) {
           switch (maybe_has_label.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot check the existence of a label on a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot check the existence of a label on nonexistent vertex"};
+              LOG_FATAL(
+                  "Query modules mustn't have access to nonexistent objects when checking the existence of a label on "
+                  "a vertex!");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when checking the existence of a label on a vertex.");
+              LOG_FATAL("Unexpected error when checking the existence of a label on a vertex.");
           }
         }
-        return *maybe_has_label;
+        return *maybe_has_label ? 1 : 0;
       },
       result);
 }
 
-mgp_error mgp_vertex_has_label(const mgp_vertex *v, mgp_label label, int *result) {
+mgp_error mgp_vertex_has_label(mgp_vertex *v, mgp_label label, int *result) {
   return mgp_vertex_has_label_named(v, label.name, result);
 }
 
-mgp_error mgp_vertex_get_property(const mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) {
+mgp_error mgp_vertex_get_property(mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) {
   return WrapExceptions(
       [v, name, memory]() -> mgp_value * {
         const auto &key = v->graph->impl->NameToProperty(name);
@@ -1119,12 +1281,14 @@ mgp_error mgp_vertex_get_property(const mgp_vertex *v, const char *name, mgp_mem
         if (maybe_prop.HasError()) {
           switch (maybe_prop.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get a property of a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get a property of nonexistent vertex"};
+              LOG_FATAL(
+                  "Query modules mustn't have access to nonexistent objects when getting a property of a vertex.");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting a property of a vertex.");
+              LOG_FATAL("Unexpected error when getting a property of a vertex.");
           }
         }
         return NewRawMgpObject<mgp_value>(memory, std::move(*maybe_prop));
@@ -1132,7 +1296,7 @@ mgp_error mgp_vertex_get_property(const mgp_vertex *v, const char *name, mgp_mem
       result);
 }
 
-mgp_error mgp_vertex_iter_properties(const mgp_vertex *v, mgp_memory *memory, mgp_properties_iterator **result) {
+mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_properties_iterator **result) {
   // NOTE: This copies the whole properties into the iterator.
   // TODO: Think of a good way to avoid the copy which doesn't just rely on some
   // assumption that storage may return a pointer to the property store. This
@@ -1143,12 +1307,14 @@ mgp_error mgp_vertex_iter_properties(const mgp_vertex *v, mgp_memory *memory, mg
         if (maybe_props.HasError()) {
           switch (maybe_props.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get the properties of a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get the properties of nonexistent vertex"};
+              LOG_FATAL(
+                  "Query modules mustn't have access to nonexistent objects when getting the properties of a vertex.");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting the properties of a vertex.");
+              LOG_FATAL("Unexpected error when getting the properties of a vertex.");
           }
         }
         return NewRawMgpObject<mgp_properties_iterator>(memory, v->graph, std::move(*maybe_props));
@@ -1158,7 +1324,7 @@ mgp_error mgp_vertex_iter_properties(const mgp_vertex *v, mgp_memory *memory, mg
 
 void mgp_edges_iterator_destroy(mgp_edges_iterator *it) { DeleteRawMgpObject(it); }
 
-mgp_error mgp_vertex_iter_in_edges(const mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
+mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
   return WrapExceptions(
       [v, memory] {
         auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
@@ -1168,12 +1334,15 @@ mgp_error mgp_vertex_iter_in_edges(const mgp_vertex *v, mgp_memory *memory, mgp_
         if (maybe_edges.HasError()) {
           switch (maybe_edges.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get the inbound edges of a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get the inbound edges of nonexistent vertex"};
+              LOG_FATAL(
+                  "Query modules mustn't have access to nonexistent objects when getting the inbound edges of a "
+                  "vertex.");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting the inbound edges of a vertex.");
+              LOG_FATAL("Unexpected error when getting the inbound edges of a vertex.");
           }
         }
         it->in.emplace(std::move(*maybe_edges));
@@ -1187,7 +1356,7 @@ mgp_error mgp_vertex_iter_in_edges(const mgp_vertex *v, mgp_memory *memory, mgp_
       result);
 }
 
-mgp_error mgp_vertex_iter_out_edges(const mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
+mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
   return WrapExceptions(
       [v, memory] {
         auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
@@ -1197,12 +1366,15 @@ mgp_error mgp_vertex_iter_out_edges(const mgp_vertex *v, mgp_memory *memory, mgp
         if (maybe_edges.HasError()) {
           switch (maybe_edges.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get the outbound edges of a deleted vertex!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get the outbound edges of nonexistent vertex"};
+              LOG_FATAL(
+                  "Query modules mustn't have access to nonexistent objects when getting the outbound edges of a "
+                  "vertex.");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting the outbound edges of a vertex.");
+              LOG_FATAL("Unexpected error when getting the outbound edges of a vertex.");
           }
         }
         it->out.emplace(std::move(*maybe_edges));
@@ -1216,9 +1388,13 @@ mgp_error mgp_vertex_iter_out_edges(const mgp_vertex *v, mgp_memory *memory, mgp
       result);
 }
 
-mgp_error mgp_edges_iterator_get(const mgp_edges_iterator *it, const mgp_edge **result) {
+mgp_error mgp_edges_iterator_underlying_graph_is_mutable(mgp_edges_iterator *it, int *result) {
+  return mgp_vertex_underlying_graph_is_mutable(&it->source_vertex, result);
+}
+
+mgp_error mgp_edges_iterator_get(mgp_edges_iterator *it, mgp_edge **result) {
   return WrapExceptions(
-      [it]() -> const mgp_edge * {
+      [it]() -> mgp_edge * {
         if (it->current_e.has_value()) {
           return &*it->current_e;
         }
@@ -1227,11 +1403,11 @@ mgp_error mgp_edges_iterator_get(const mgp_edges_iterator *it, const mgp_edge **
       result);
 }
 
-mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, const mgp_edge **result) {
+mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
   return WrapExceptions(
       [it] {
         MG_ASSERT(it->in || it->out);
-        auto next = [&](auto *impl_it, const auto &end) -> const mgp_edge * {
+        auto next = [&](auto *impl_it, const auto &end) -> mgp_edge * {
           if (*impl_it == end) {
             MG_ASSERT(!it->current_e,
                       "Iteration is already done, so it->current_e "
@@ -1253,24 +1429,28 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, const mgp_edge **resul
       result);
 }
 
-mgp_error mgp_edge_get_id(const mgp_edge *e, mgp_edge_id *result) {
+mgp_error mgp_edge_get_id(mgp_edge *e, mgp_edge_id *result) {
   return WrapExceptions([e] { return mgp_edge_id{.as_int = e->impl.Gid().AsInt()}; }, result);
 }
 
-mgp_error mgp_edge_copy(const mgp_edge *e, mgp_memory *memory, mgp_edge **result) {
-  return WrapExceptions([e, memory] { return NewRawMgpObject<mgp_edge>(memory, e->impl, e->from.graph); }, result);
+mgp_error mgp_edge_underlying_graph_is_mutable(mgp_edge *e, int *result) {
+  return mgp_vertex_underlying_graph_is_mutable(&e->from, result);
+}
+
+mgp_error mgp_edge_copy(mgp_edge *e, mgp_memory *memory, mgp_edge **result) {
+  return WrapExceptions([e, memory] { return mgp_edge::Copy(*e, *memory); }, result);
 }
 
 void mgp_edge_destroy(mgp_edge *e) { DeleteRawMgpObject(e); }
 
-mgp_error mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2, int *result) {
+mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) {
   // NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression)
   static_assert(noexcept(*result = *e1 == *e2 ? 1 : 0));
   *result = *e1 == *e2 ? 1 : 0;
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_edge_get_type(const mgp_edge *e, mgp_edge_type *result) {
+mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) {
   return WrapExceptions(
       [e] {
         const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType());
@@ -1282,17 +1462,17 @@ mgp_error mgp_edge_get_type(const mgp_edge *e, mgp_edge_type *result) {
       &result->name);
 }
 
-mgp_error mgp_edge_get_from(const mgp_edge *e, const mgp_vertex **result) {
+mgp_error mgp_edge_get_from(mgp_edge *e, mgp_vertex **result) {
   *result = &e->from;
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_edge_get_to(const mgp_edge *e, const mgp_vertex **result) {
+mgp_error mgp_edge_get_to(mgp_edge *e, mgp_vertex **result) {
   *result = &e->to;
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_edge_get_property(const mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) {
+mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) {
   return WrapExceptions(
       [e, name, memory] {
         const auto &key = e->from.graph->impl->NameToProperty(name);
@@ -1301,12 +1481,13 @@ mgp_error mgp_edge_get_property(const mgp_edge *e, const char *name, mgp_memory
         if (maybe_prop.HasError()) {
           switch (maybe_prop.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get a property of a deleted edge!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get a property of nonexistent edge"};
+              LOG_FATAL("Query modules mustn't have access to nonexistent objects when getting a property of an edge.");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting a property of an edge.");
+              LOG_FATAL("Unexpected error when getting a property of an edge.");
           }
         }
         return NewRawMgpObject<mgp_value>(memory, std::move(*maybe_prop));
@@ -1314,7 +1495,32 @@ mgp_error mgp_edge_get_property(const mgp_edge *e, const char *name, mgp_memory
       result);
 }
 
-mgp_error mgp_edge_iter_properties(const mgp_edge *e, mgp_memory *memory, mgp_properties_iterator **result) {
+mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, mgp_value *property_value) {
+  return WrapExceptions([=] {
+    if (!MgpEdgeIsMutable(*e)) {
+      throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
+    }
+    const auto result =
+        e->impl.SetProperty(e->from.graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
+
+    if (result.HasError()) {
+      switch (result.GetError()) {
+        case storage::Error::DELETED_OBJECT:
+          throw DeletedObjectException{"Cannot set the properties of a deleted edge!"};
+        case storage::Error::NONEXISTENT_OBJECT:
+          LOG_FATAL("Query modules mustn't have access to nonexistent objects when setting a property of an edge!");
+        case storage::Error::PROPERTIES_DISABLED:
+          throw std::logic_error{"Cannot set the properties of edges, because properties on edges are disabled!"};
+        case storage::Error::VERTEX_HAS_EDGES:
+          LOG_FATAL("Unexpected error when setting a property of an edge.");
+        case storage::Error::SERIALIZATION_ERROR:
+          throw SerializationException{"Cannot serialize setting a property of an edge."};
+      }
+    }
+  });
+}
+
+mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properties_iterator **result) {
   // NOTE: This copies the whole properties into iterator.
   // TODO: Think of a good way to avoid the copy which doesn't just rely on some
   // assumption that storage may return a pointer to the property store. This
@@ -1326,12 +1532,14 @@ mgp_error mgp_edge_iter_properties(const mgp_edge *e, mgp_memory *memory, mgp_pr
         if (maybe_props.HasError()) {
           switch (maybe_props.GetError()) {
             case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot get the properties of a deleted edge!"};
             case storage::Error::NONEXISTENT_OBJECT:
-              throw NonexistentObjectException{"Cannot get the properties of nonexistent edge"};
+              LOG_FATAL(
+                  "Query modules mustn't have access to nonexistent objects when getting the properties of an edge.");
             case storage::Error::PROPERTIES_DISABLED:
             case storage::Error::VERTEX_HAS_EDGES:
             case storage::Error::SERIALIZATION_ERROR:
-              MG_ASSERT(false, "Unexpected error when getting the properties of an edge.");
+              LOG_FATAL("Unexpected error when getting the properties of an edge.");
           }
         }
         return NewRawMgpObject<mgp_properties_iterator>(memory, e->from.graph, std::move(*maybe_props));
@@ -1339,8 +1547,7 @@ mgp_error mgp_edge_iter_properties(const mgp_edge *e, mgp_memory *memory, mgp_pr
       result);
 }
 
-mgp_error mgp_graph_get_vertex_by_id(const mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory,
-                                     mgp_vertex **result) {
+mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) {
   return WrapExceptions(
       [graph, id, memory]() -> mgp_vertex * {
         auto maybe_vertex = graph->impl->FindVertex(storage::Gid::FromInt(id.as_int), graph->view);
@@ -1352,15 +1559,109 @@ mgp_error mgp_graph_get_vertex_by_id(const mgp_graph *graph, mgp_vertex_id id, m
       result);
 }
 
+mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) {
+  *result = MgpGraphIsMutable(*graph) ? 1 : 0;
+  return MGP_ERROR_NO_ERROR;
+};
+
+mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, mgp_vertex **result) {
+  return WrapExceptions(
+      [=] {
+        if (!MgpGraphIsMutable(*graph)) {
+          throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
+        }
+
+        auto vertex = graph->impl->InsertVertex();
+        return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
+      },
+      result);
+}
+
+mgp_error mgp_graph_remove_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
+  return WrapExceptions([=] {
+    if (!MgpGraphIsMutable(*graph)) {
+      throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
+    }
+    const auto result = graph->impl->RemoveVertex(&vertex->impl);
+
+    if (result.HasError()) {
+      switch (result.GetError()) {
+        case storage::Error::NONEXISTENT_OBJECT:
+          LOG_FATAL("Query modules mustn't have access to nonexistent objects when removing a vertex!");
+        case storage::Error::DELETED_OBJECT:
+        case storage::Error::PROPERTIES_DISABLED:
+          LOG_FATAL("Unexpected error when removing a vertex.");
+        case storage::Error::VERTEX_HAS_EDGES:
+          throw std::logic_error{"Cannot remove a vertex that has edges!"};
+        case storage::Error::SERIALIZATION_ERROR:
+          throw SerializationException{"Cannot serialize removing a vertex."};
+      }
+    }
+  });
+}
+
+mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
+                                mgp_memory *memory, mgp_edge **result) {
+  return WrapExceptions(
+      [=] {
+        if (!MgpGraphIsMutable(*graph)) {
+          throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"};
+        }
+
+        auto edge = graph->impl->InsertEdge(&from->impl, &to->impl, from->graph->impl->NameToEdgeType(type.name));
+        if (edge.HasError()) {
+          switch (edge.GetError()) {
+            case storage::Error::DELETED_OBJECT:
+              throw DeletedObjectException{"Cannot add an edge to a deleted vertex!"};
+            case storage::Error::NONEXISTENT_OBJECT:
+              LOG_FATAL("Query modules mustn't have access to nonexistent objects when creating an edge!");
+            case storage::Error::PROPERTIES_DISABLED:
+            case storage::Error::VERTEX_HAS_EDGES:
+              LOG_FATAL("Unexpected error when creating an edge.");
+            case storage::Error::SERIALIZATION_ERROR:
+              throw SerializationException{"Cannot serialize creating an edge."};
+          }
+        }
+        return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
+      },
+      result);
+}
+
+mgp_error mgp_graph_remove_edge(struct mgp_graph *graph, mgp_edge *edge) {
+  return WrapExceptions([=] {
+    if (!MgpGraphIsMutable(*graph)) {
+      throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
+    }
+    const auto result = graph->impl->RemoveEdge(&edge->impl);
+
+    if (result.HasError()) {
+      switch (result.GetError()) {
+        case storage::Error::NONEXISTENT_OBJECT:
+          LOG_FATAL("Query modules mustn't have access to nonexistent objects when removing an edge!");
+        case storage::Error::DELETED_OBJECT:
+        case storage::Error::PROPERTIES_DISABLED:
+        case storage::Error::VERTEX_HAS_EDGES:
+          LOG_FATAL("Unexpected error when removing an edge.");
+        case storage::Error::SERIALIZATION_ERROR:
+          throw SerializationException{"Cannot serialize removing an edge."};
+      }
+    }
+  });
+}
+
 void mgp_vertices_iterator_destroy(mgp_vertices_iterator *it) { DeleteRawMgpObject(it); }
 
-mgp_error mgp_graph_iter_vertices(const mgp_graph *graph, mgp_memory *memory, mgp_vertices_iterator **result) {
+mgp_error mgp_graph_iter_vertices(mgp_graph *graph, mgp_memory *memory, mgp_vertices_iterator **result) {
   return WrapExceptions([graph, memory] { return NewRawMgpObject<mgp_vertices_iterator>(memory, graph); }, result);
 }
 
-mgp_error mgp_vertices_iterator_get(const mgp_vertices_iterator *it, const mgp_vertex **result) {
+mgp_error mgp_vertices_iterator_underlying_graph_is_mutable(mgp_vertices_iterator *it, int *result) {
+  return mgp_graph_is_mutable(it->graph, result);
+}
+
+mgp_error mgp_vertices_iterator_get(mgp_vertices_iterator *it, mgp_vertex **result) {
   return WrapExceptions(
-      [it]() -> const mgp_vertex * {
+      [it]() -> mgp_vertex * {
         if (it->current_v.has_value()) {
           return &*it->current_v;
         }
@@ -1369,9 +1670,9 @@ mgp_error mgp_vertices_iterator_get(const mgp_vertices_iterator *it, const mgp_v
       result);
 }
 
-mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, const mgp_vertex **result) {
+mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **result) {
   return WrapExceptions(
-      [it]() -> const mgp_vertex * {
+      [it]() -> mgp_vertex * {
         if (it->current_it == it->vertices.end()) {
           MG_ASSERT(!it->current_v,
                     "Iteration is already done, so it->current_v "
@@ -1401,7 +1702,7 @@ void NoOpCypherTypeDeleter(CypherType * /*type*/) {}
 
 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
 #define DEFINE_MGP_TYPE_GETTER(cypher_type_name, mgp_type_name)                            \
-  mgp_error mgp_type_##mgp_type_name(const mgp_type **result) {                            \
+  mgp_error mgp_type_##mgp_type_name(mgp_type **result) {                                  \
     return WrapExceptions(                                                                 \
         [] {                                                                               \
           static cypher_type_name##Type impl;                                              \
@@ -1422,11 +1723,11 @@ DEFINE_MGP_TYPE_GETTER(Node, node);
 DEFINE_MGP_TYPE_GETTER(Relationship, relationship);
 DEFINE_MGP_TYPE_GETTER(Path, path);
 
-mgp_error mgp_type_list(const mgp_type *type, const mgp_type **result) {
+mgp_error mgp_type_list(mgp_type *type, mgp_type **result) {
   return WrapExceptions(
       [type] {
         // Maps `type` to corresponding instance of ListType.
-        static utils::pmr::map<const mgp_type *, mgp_type> gListTypes(utils::NewDeleteResource());
+        static utils::pmr::map<mgp_type *, mgp_type> gListTypes(utils::NewDeleteResource());
         static utils::SpinLock lock;
         std::lock_guard<utils::SpinLock> guard(lock);
         auto found_it = gListTypes.find(type);
@@ -1444,11 +1745,11 @@ mgp_error mgp_type_list(const mgp_type *type, const mgp_type **result) {
       result);
 }
 
-mgp_error mgp_type_nullable(const mgp_type *type, const mgp_type **result) {
+mgp_error mgp_type_nullable(mgp_type *type, mgp_type **result) {
   return WrapExceptions(
       [type] {
         // Maps `type` to corresponding instance of NullableType.
-        static utils::pmr::map<const mgp_type *, mgp_type> gNullableTypes(utils::NewDeleteResource());
+        static utils::pmr::map<mgp_type *, mgp_type> gNullableTypes(utils::NewDeleteResource());
         static utils::SpinLock lock;
         std::lock_guard<utils::SpinLock> guard(lock);
         auto found_it = gNullableTypes.find(type);
@@ -1462,24 +1763,30 @@ mgp_error mgp_type_nullable(const mgp_type *type, const mgp_type **result) {
       result);
 }
 
-mgp_error mgp_module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb, mgp_proc **result) {
-  return WrapExceptions(
-      [module, name, cb]() -> mgp_proc * {
-        if (!IsValidIdentifierName(name)) {
-          throw std::invalid_argument{fmt::format("Invalid procedure name: {}", name)};
-        }
-        if (module->procedures.find(name) != module->procedures.end()) {
-          throw std::logic_error{fmt::format("Procedure already exists with name '{}'", name)};
-        };
+namespace {
+mgp_proc *mgp_module_add_procedure(mgp_module *module, const char *name, mgp_proc_cb cb, bool is_write_procedure) {
+  if (!IsValidIdentifierName(name)) {
+    throw std::invalid_argument{fmt::format("Invalid procedure name: {}", name)};
+  }
+  if (module->procedures.find(name) != module->procedures.end()) {
+    throw std::logic_error{fmt::format("Procedure already exists with name '{}'", name)};
+  };
 
-        auto *memory = module->procedures.get_allocator().GetMemoryResource();
-        // May throw std::bad_alloc, std::length_error
-        return &module->procedures.emplace(name, mgp_proc(name, cb, memory)).first->second;
-      },
-      result);
+  auto *memory = module->procedures.get_allocator().GetMemoryResource();
+  // May throw std::bad_alloc, std::length_error
+  return &module->procedures.emplace(name, mgp_proc(name, cb, memory, is_write_procedure)).first->second;
+}
+}  // namespace
+
+mgp_error mgp_module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb, mgp_proc **result) {
+  return WrapExceptions([=] { return mgp_module_add_procedure(module, name, cb, false); }, result);
 }
 
-mgp_error mgp_proc_add_arg(mgp_proc *proc, const char *name, const mgp_type *type) {
+mgp_error mgp_module_add_write_procedure(mgp_module *module, const char *name, mgp_proc_cb cb, mgp_proc **result) {
+  return WrapExceptions([=] { return mgp_module_add_procedure(module, name, cb, true); }, result);
+}
+
+mgp_error mgp_proc_add_arg(mgp_proc *proc, const char *name, mgp_type *type) {
   return WrapExceptions([=] {
     if (!IsValidIdentifierName(name)) {
       throw std::invalid_argument{fmt::format("Invalid argument name for procedure '{}': {}", proc->name, name)};
@@ -1492,7 +1799,7 @@ mgp_error mgp_proc_add_arg(mgp_proc *proc, const char *name, const mgp_type *typ
   });
 }
 
-mgp_error mgp_proc_add_opt_arg(mgp_proc *proc, const char *name, const mgp_type *type, const mgp_value *default_value) {
+mgp_error mgp_proc_add_opt_arg(mgp_proc *proc, const char *name, mgp_type *type, mgp_value *default_value) {
   return WrapExceptions([=] {
     if (!IsValidIdentifierName(name)) {
       throw std::invalid_argument{fmt::format("Invalid argument name for procedure '{}': {}", proc->name, name)};
@@ -1531,7 +1838,7 @@ template <typename T>
 concept ModuleProperties = utils::SameAsAnyOf<T, mgp_proc, mgp_trans>;
 
 template <ModuleProperties T>
-mgp_error AddResultToProp(T *prop, const char *name, const mgp_type *type, bool is_deprecated) noexcept {
+mgp_error AddResultToProp(T *prop, const char *name, mgp_type *type, bool is_deprecated) noexcept {
   return WrapExceptions([=] {
     if (!IsValidIdentifierName(name)) {
       throw std::invalid_argument{fmt::format("Invalid result name for procedure '{}': {}", prop->name, name)};
@@ -1546,24 +1853,24 @@ mgp_error AddResultToProp(T *prop, const char *name, const mgp_type *type, bool
 
 }  // namespace
 
-mgp_error mgp_proc_add_result(mgp_proc *proc, const char *name, const mgp_type *type) {
+mgp_error mgp_proc_add_result(mgp_proc *proc, const char *name, mgp_type *type) {
   return AddResultToProp(proc, name, type, false);
 }
 
 mgp_error MgpTransAddFixedResult(mgp_trans *trans) noexcept {
-  if (const auto err = AddResultToProp(trans, "query", Call<const mgp_type *>(mgp_type_string), false);
+  if (const auto err = AddResultToProp(trans, "query", Call<mgp_type *>(mgp_type_string), false);
       err != MGP_ERROR_NO_ERROR) {
     return err;
   }
-  return AddResultToProp(trans, "parameters",
-                         Call<const mgp_type *>(mgp_type_nullable, Call<const mgp_type *>(mgp_type_map)), false);
+  return AddResultToProp(trans, "parameters", Call<mgp_type *>(mgp_type_nullable, Call<mgp_type *>(mgp_type_map)),
+                         false);
 }
 
-mgp_error mgp_proc_add_deprecated_result(mgp_proc *proc, const char *name, const mgp_type *type) {
+mgp_error mgp_proc_add_deprecated_result(mgp_proc *proc, const char *name, mgp_type *type) {
   return AddResultToProp(proc, name, type, true);
 }
 
-int mgp_must_abort(const mgp_graph *graph) {
+int mgp_must_abort(mgp_graph *graph) {
   MG_ASSERT(graph->ctx);
   static_assert(noexcept(query::MustAbort(*graph->ctx)));
   return query::MustAbort(*graph->ctx) ? 1 : 0;
@@ -1638,37 +1945,37 @@ bool IsValidIdentifierName(const char *name) {
 
 }  // namespace query::procedure
 
-mgp_error mgp_message_payload(const mgp_message *message, const char **result) {
+mgp_error mgp_message_payload(mgp_message *message, const char **result) {
   return WrapExceptions([message] { return message->msg->Payload().data(); }, result);
 }
 
-mgp_error mgp_message_payload_size(const mgp_message *message, size_t *result) {
+mgp_error mgp_message_payload_size(mgp_message *message, size_t *result) {
   return WrapExceptions([message] { return message->msg->Payload().size(); }, result);
 }
 
-mgp_error mgp_message_topic_name(const mgp_message *message, const char **result) {
+mgp_error mgp_message_topic_name(mgp_message *message, const char **result) {
   return WrapExceptions([message] { return message->msg->TopicName().data(); }, result);
 }
 
-mgp_error mgp_message_key(const mgp_message *message, const char **result) {
+mgp_error mgp_message_key(mgp_message *message, const char **result) {
   return WrapExceptions([message] { return message->msg->Key().data(); }, result);
 }
 
-mgp_error mgp_message_key_size(const struct mgp_message *message, size_t *result) {
+mgp_error mgp_message_key_size(mgp_message *message, size_t *result) {
   return WrapExceptions([message] { return message->msg->Key().size(); }, result);
 }
 
-mgp_error mgp_message_timestamp(const mgp_message *message, int64_t *result) {
+mgp_error mgp_message_timestamp(mgp_message *message, int64_t *result) {
   return WrapExceptions([message] { return message->msg->Timestamp(); }, result);
 }
 
-mgp_error mgp_messages_size(const mgp_messages *messages, size_t *result) {
+mgp_error mgp_messages_size(mgp_messages *messages, size_t *result) {
   static_assert(noexcept(messages->messages.size()));
   *result = messages->messages.size();
   return MGP_ERROR_NO_ERROR;
 }
 
-mgp_error mgp_messages_at(const mgp_messages *messages, size_t index, const mgp_message **result) {
+mgp_error mgp_messages_at(mgp_messages *messages, size_t index, mgp_message **result) {
   return WrapExceptions(
       [messages, index] {
         if (index >= Call<size_t>(mgp_messages_size, messages)) {
diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp
index 8ea1f7fbd..1dc436ef1 100644
--- a/src/query/procedure/mg_procedure_impl.hpp
+++ b/src/query/procedure/mg_procedure_impl.hpp
@@ -11,7 +11,7 @@
 #include "integrations/kafka/consumer.hpp"
 #include "query/context.hpp"
 #include "query/db_accessor.hpp"
-#include "query/procedure/cypher_types.hpp"
+#include "query/procedure/cypher_type_ptr.hpp"
 #include "query/typed_value.hpp"
 #include "storage/v2/view.hpp"
 #include "utils/memory.hpp"
@@ -56,7 +56,7 @@ struct mgp_value {
   /// Construct by copying query::TypedValue using utils::MemoryResource.
   /// mgp_graph is needed to construct mgp_vertex and mgp_edge.
   /// @throw std::bad_alloc
-  mgp_value(const query::TypedValue &, const mgp_graph *, utils::MemoryResource *);
+  mgp_value(const query::TypedValue &, mgp_graph *, utils::MemoryResource *);
 
   /// Construct by copying storage::PropertyValue using utils::MemoryResource.
   /// @throw std::bad_alloc
@@ -165,13 +165,13 @@ struct mgp_map {
 
 struct mgp_map_item {
   const char *key;
-  const mgp_value *value;
+  mgp_value *value;
 };
 
 struct mgp_map_items_iterator {
   using allocator_type = utils::Allocator<mgp_map_items_iterator>;
 
-  mgp_map_items_iterator(const mgp_map *map, utils::MemoryResource *memory)
+  mgp_map_items_iterator(mgp_map *map, utils::MemoryResource *memory)
       : memory(memory), map(map), current_it(map->items.begin()) {
     if (current_it != map->items.end()) {
       current.key = current_it->first.c_str();
@@ -189,7 +189,7 @@ struct mgp_map_items_iterator {
   utils::MemoryResource *GetMemoryResource() const { return memory; }
 
   utils::MemoryResource *memory;
-  const mgp_map *map;
+  mgp_map *map;
   decltype(map->items.begin()) current_it;
   mgp_map_item current;
 };
@@ -204,7 +204,7 @@ struct mgp_vertex {
   // have everything noexcept here.
   static_assert(std::is_nothrow_copy_constructible_v<query::VertexAccessor>);
 
-  mgp_vertex(query::VertexAccessor v, const mgp_graph *graph, utils::MemoryResource *memory) noexcept
+  mgp_vertex(query::VertexAccessor v, mgp_graph *graph, utils::MemoryResource *memory) noexcept
       : memory(memory), impl(v), graph(graph) {}
 
   mgp_vertex(const mgp_vertex &other, utils::MemoryResource *memory) noexcept
@@ -230,7 +230,7 @@ struct mgp_vertex {
 
   utils::MemoryResource *memory;
   query::VertexAccessor impl;
-  const mgp_graph *graph;
+  mgp_graph *graph;
 };
 
 struct mgp_edge {
@@ -243,7 +243,9 @@ struct mgp_edge {
   // have everything noexcept here.
   static_assert(std::is_nothrow_copy_constructible_v<query::EdgeAccessor>);
 
-  mgp_edge(const query::EdgeAccessor &impl, const mgp_graph *graph, utils::MemoryResource *memory) noexcept
+  static mgp_edge *Copy(const mgp_edge &edge, mgp_memory &memory);
+
+  mgp_edge(const query::EdgeAccessor &impl, mgp_graph *graph, utils::MemoryResource *memory) noexcept
       : memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {}
 
   mgp_edge(const mgp_edge &other, utils::MemoryResource *memory) noexcept
@@ -334,20 +336,20 @@ struct mgp_properties_iterator {
   // need to be visible in method definitions.
 
   utils::MemoryResource *memory;
-  const mgp_graph *graph;
+  mgp_graph *graph;
   std::remove_reference_t<decltype(*std::declval<query::VertexAccessor>().Properties(graph->view))> pvs;
   decltype(pvs.begin()) current_it;
   std::optional<std::pair<utils::pmr::string, mgp_value>> current;
   mgp_property property{nullptr, nullptr};
 
   // Construct with no properties.
-  explicit mgp_properties_iterator(const mgp_graph *graph, utils::MemoryResource *memory)
+  explicit mgp_properties_iterator(mgp_graph *graph, utils::MemoryResource *memory)
       : memory(memory), graph(graph), current_it(pvs.begin()) {}
 
   // May throw who the #$@! knows what because PropertyValueStore doesn't
   // document what it throws, and it may surely throw some piece of !@#$
   // exception because it's built on top of STL and other libraries.
-  mgp_properties_iterator(const mgp_graph *graph, decltype(pvs) pvs, utils::MemoryResource *memory)
+  mgp_properties_iterator(mgp_graph *graph, decltype(pvs) pvs, utils::MemoryResource *memory)
       : memory(memory), graph(graph), pvs(std::move(pvs)), current_it(this->pvs.begin()) {
     if (current_it != this->pvs.end()) {
       current.emplace(utils::pmr::string(graph->impl->PropertyToName(current_it->first), memory),
@@ -408,7 +410,7 @@ struct mgp_vertices_iterator {
   using allocator_type = utils::Allocator<mgp_vertices_iterator>;
 
   /// @throw anything VerticesIterable may throw
-  mgp_vertices_iterator(const mgp_graph *graph, utils::MemoryResource *memory)
+  mgp_vertices_iterator(mgp_graph *graph, utils::MemoryResource *memory)
       : memory(memory), graph(graph), vertices(graph->impl->Vertices(graph->view)), current_it(vertices.begin()) {
     if (current_it != vertices.end()) {
       current_v.emplace(*current_it, graph, memory);
@@ -418,7 +420,7 @@ struct mgp_vertices_iterator {
   utils::MemoryResource *GetMemoryResource() const { return memory; }
 
   utils::MemoryResource *memory;
-  const mgp_graph *graph;
+  mgp_graph *graph;
   decltype(graph->impl->Vertices(graph->view)) vertices;
   decltype(vertices.begin()) current_it;
   std::optional<mgp_vertex> current_v;
@@ -433,14 +435,24 @@ struct mgp_proc {
 
   /// @throw std::bad_alloc
   /// @throw std::length_error
-  mgp_proc(const char *name, mgp_proc_cb cb, utils::MemoryResource *memory)
-      : name(name, memory), cb(cb), args(memory), opt_args(memory), results(memory) {}
+  mgp_proc(const char *name, mgp_proc_cb cb, utils::MemoryResource *memory, bool is_write_procedure)
+      : name(name, memory),
+        cb(cb),
+        args(memory),
+        opt_args(memory),
+        results(memory),
+        is_write_procedure(is_write_procedure) {}
 
   /// @throw std::bad_alloc
   /// @throw std::length_error
-  mgp_proc(const char *name, std::function<void(const mgp_list *, const mgp_graph *, mgp_result *, mgp_memory *)> cb,
-           utils::MemoryResource *memory)
-      : name(name, memory), cb(cb), args(memory), opt_args(memory), results(memory) {}
+  mgp_proc(const char *name, std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)> cb,
+           utils::MemoryResource *memory, bool is_write_procedure)
+      : name(name, memory),
+        cb(cb),
+        args(memory),
+        opt_args(memory),
+        results(memory),
+        is_write_procedure(is_write_procedure) {}
 
   /// @throw std::bad_alloc
   /// @throw std::length_error
@@ -449,14 +461,16 @@ struct mgp_proc {
         cb(other.cb),
         args(other.args, memory),
         opt_args(other.opt_args, memory),
-        results(other.results, memory) {}
+        results(other.results, memory),
+        is_write_procedure(other.is_write_procedure) {}
 
   mgp_proc(mgp_proc &&other, utils::MemoryResource *memory)
       : name(std::move(other.name), memory),
         cb(std::move(other.cb)),
         args(std::move(other.args), memory),
         opt_args(std::move(other.opt_args), memory),
-        results(std::move(other.results), memory) {}
+        results(std::move(other.results), memory),
+        is_write_procedure(other.is_write_procedure) {}
 
   mgp_proc(const mgp_proc &other) = default;
   mgp_proc(mgp_proc &&other) = default;
@@ -469,13 +483,14 @@ struct mgp_proc {
   /// Name of the procedure.
   utils::pmr::string name;
   /// Entry-point for the procedure.
-  std::function<void(const mgp_list *, const mgp_graph *, mgp_result *, mgp_memory *)> cb;
+  std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)> cb;
   /// Required, positional arguments as a (name, type) pair.
   utils::pmr::vector<std::pair<utils::pmr::string, const query::procedure::CypherType *>> args;
   /// Optional positional arguments as a (name, type, default_value) tuple.
   utils::pmr::vector<std::tuple<utils::pmr::string, const query::procedure::CypherType *, query::TypedValue>> opt_args;
   /// Fields this procedure returns, as a (name -> (type, is_deprecated)) map.
   utils::pmr::map<utils::pmr::string, std::pair<const query::procedure::CypherType *, bool>> results;
+  bool is_write_procedure{false};
 };
 
 struct mgp_trans {
@@ -488,8 +503,7 @@ struct mgp_trans {
 
   /// @throw std::bad_alloc
   /// @throw std::length_error
-  mgp_trans(const char *name,
-            std::function<void(const mgp_messages *, const mgp_graph *, mgp_result *, mgp_memory *)> cb,
+  mgp_trans(const char *name, std::function<void(mgp_messages *, mgp_graph *, mgp_result *, mgp_memory *)> cb,
             utils::MemoryResource *memory)
       : name(name, memory), cb(cb), results(memory) {}
 
@@ -512,7 +526,7 @@ struct mgp_trans {
   /// Name of the transformation.
   utils::pmr::string name;
   /// Entry-point for the transformation.
-  std::function<void(const mgp_messages *, const mgp_graph *, mgp_result *, mgp_memory *)> cb;
+  std::function<void(mgp_messages *, mgp_graph *, mgp_result *, mgp_memory *)> cb;
   /// Fields this transformation returns.
   utils::pmr::map<utils::pmr::string, std::pair<const query::procedure::CypherType *, bool>> results;
 };
diff --git a/src/query/procedure/module.cpp b/src/query/procedure/module.cpp
index d692157ae..a9777ceee 100644
--- a/src/query/procedure/module.cpp
+++ b/src/query/procedure/module.cpp
@@ -9,6 +9,7 @@ extern "C" {
 
 #include "fmt/format.h"
 #include "py/py.hpp"
+#include "query/procedure/mg_procedure_helpers.hpp"
 #include "query/procedure/py_module.hpp"
 #include "utils/file.hpp"
 #include "utils/logging.hpp"
@@ -91,16 +92,16 @@ void RegisterMgLoad(ModuleRegistry *module_registry, utils::RWLock *lock, Builti
     }
     lock->lock_shared();
   };
-  auto load_all_cb = [module_registry, with_unlock_shared](const mgp_list * /*args*/, const mgp_graph * /*graph*/,
+  auto load_all_cb = [module_registry, with_unlock_shared](mgp_list * /*args*/, mgp_graph * /*graph*/,
                                                            mgp_result * /*result*/, mgp_memory * /*memory*/) {
     with_unlock_shared([&]() { module_registry->UnloadAndLoadModulesFromDirectories(); });
   };
-  mgp_proc load_all("load_all", load_all_cb, utils::NewDeleteResource());
+  mgp_proc load_all("load_all", load_all_cb, utils::NewDeleteResource(), false);
   module->AddProcedure("load_all", std::move(load_all));
-  auto load_cb = [module_registry, with_unlock_shared](const mgp_list *args, const mgp_graph * /*graph*/,
-                                                       mgp_result *result, mgp_memory * /*memory*/) {
+  auto load_cb = [module_registry, with_unlock_shared](mgp_list *args, mgp_graph * /*graph*/, mgp_result *result,
+                                                       mgp_memory * /*memory*/) {
     MG_ASSERT(Call<size_t>(mgp_list_size, args) == 1U, "Should have been type checked already");
-    const auto *arg = Call<const mgp_value *>(mgp_list_at, args, 0);
+    auto *arg = Call<mgp_value *>(mgp_list_at, args, 0);
     MG_ASSERT(CallBool(mgp_value_is_string, arg), "Should have been type checked already");
     bool succ = false;
     with_unlock_shared([&]() {
@@ -115,15 +116,15 @@ void RegisterMgLoad(ModuleRegistry *module_registry, utils::RWLock *lock, Builti
       MG_ASSERT(mgp_result_set_error_msg(result, "Failed to (re)load the module.") == MGP_ERROR_NO_ERROR);
     }
   };
-  mgp_proc load("load", load_cb, utils::NewDeleteResource());
-  MG_ASSERT(mgp_proc_add_arg(&load, "module_name", Call<const mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
+  mgp_proc load("load", load_cb, utils::NewDeleteResource(), false);
+  MG_ASSERT(mgp_proc_add_arg(&load, "module_name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
   module->AddProcedure("load", std::move(load));
 }
 
 void RegisterMgProcedures(
     // We expect modules to be sorted by name.
     const std::map<std::string, std::unique_ptr<Module>, std::less<>> *all_modules, BuiltinModule *module) {
-  auto procedures_cb = [all_modules](const mgp_list * /*args*/, const mgp_graph * /*graph*/, mgp_result *result,
+  auto procedures_cb = [all_modules](mgp_list * /*args*/, mgp_graph * /*graph*/, mgp_result *result,
                                      mgp_memory *memory) {
     // Iterating over all_modules assumes that the standard mechanism of custom
     // procedure invocations takes the ModuleRegistry::lock_ with READ access.
@@ -178,16 +179,15 @@ void RegisterMgProcedures(
       }
     }
   };
-  mgp_proc procedures("procedures", procedures_cb, utils::NewDeleteResource());
-  MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<const mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
-  MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call<const mgp_type *>(mgp_type_string)) ==
-            MGP_ERROR_NO_ERROR);
+  mgp_proc procedures("procedures", procedures_cb, utils::NewDeleteResource(), false);
+  MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
+  MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
   module->AddProcedure("procedures", std::move(procedures));
 }
 
 void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Module>, std::less<>> *all_modules,
                                BuiltinModule *module) {
-  auto procedures_cb = [all_modules](const mgp_list * /*unused*/, const mgp_graph * /*unused*/, mgp_result *result,
+  auto procedures_cb = [all_modules](mgp_list * /*unused*/, mgp_graph * /*unused*/, mgp_result *result,
                                      mgp_memory *memory) {
     for (const auto &[module_name, module] : *all_modules) {
       // Return the results in sorted order by module and by transformation.
@@ -225,8 +225,8 @@ void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Modul
       }
     }
   };
-  mgp_proc procedures("transformations", procedures_cb, utils::NewDeleteResource());
-  MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<const mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
+  mgp_proc procedures("transformations", procedures_cb, utils::NewDeleteResource(), false);
+  MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
   module->AddProcedure("transformations", std::move(procedures));
 }
 
diff --git a/src/query/procedure/py_module.cpp b/src/query/procedure/py_module.cpp
index 0562e0083..9d71f5130 100644
--- a/src/query/procedure/py_module.cpp
+++ b/src/query/procedure/py_module.cpp
@@ -41,7 +41,7 @@ PyObject *DisallowPickleAndCopy(PyObject *self, PyObject *Py_UNUSED(ignored)) {
 // clang-format off
 struct PyGraph {
   PyObject_HEAD
-  const mgp_graph *graph;
+  mgp_graph *graph;
   mgp_memory *memory;
 };
 // clang-format on
@@ -54,7 +54,7 @@ struct PyVerticesIterator {
 };
 // clang-format on
 
-PyObject *MakePyVertex(const mgp_vertex &vertex, PyGraph *py_graph);
+PyObject *MakePyVertex(mgp_vertex &vertex, PyGraph *py_graph);
 
 void PyVerticesIteratorDealloc(PyVerticesIterator *self) {
   MG_ASSERT(self->it);
@@ -71,7 +71,7 @@ PyObject *PyVerticesIteratorGet(PyVerticesIterator *self, PyObject *Py_UNUSED(ig
   MG_ASSERT(self->it);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *vertex = Call<const mgp_vertex *>(mgp_vertices_iterator_get, self->it);
+  auto *vertex = Call<mgp_vertex *>(mgp_vertices_iterator_get, self->it);
   if (!vertex) Py_RETURN_NONE;
   return MakePyVertex(*vertex, self->py_graph);
 }
@@ -80,7 +80,7 @@ PyObject *PyVerticesIteratorNext(PyVerticesIterator *self, PyObject *Py_UNUSED(i
   MG_ASSERT(self->it);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *vertex = Call<const mgp_vertex *>(mgp_vertices_iterator_next, self->it);
+  auto *vertex = Call<mgp_vertex *>(mgp_vertices_iterator_next, self->it);
   if (!vertex) Py_RETURN_NONE;
   return MakePyVertex(*vertex, self->py_graph);
 }
@@ -114,7 +114,7 @@ struct PyEdgesIterator {
 };
 // clang-format on
 
-PyObject *MakePyEdge(const mgp_edge &edge, PyGraph *py_graph);
+PyObject *MakePyEdge(mgp_edge &edge, PyGraph *py_graph);
 
 void PyEdgesIteratorDealloc(PyEdgesIterator *self) {
   MG_ASSERT(self->it);
@@ -131,7 +131,7 @@ PyObject *PyEdgesIteratorGet(PyEdgesIterator *self, PyObject *Py_UNUSED(ignored)
   MG_ASSERT(self->it);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *edge = Call<const mgp_edge *>(mgp_edges_iterator_get, self->it);
+  auto *edge = Call<mgp_edge *>(mgp_edges_iterator_get, self->it);
   if (!edge) Py_RETURN_NONE;
   return MakePyEdge(*edge, self->py_graph);
 }
@@ -140,7 +140,7 @@ PyObject *PyEdgesIteratorNext(PyEdgesIterator *self, PyObject *Py_UNUSED(ignored
   MG_ASSERT(self->it);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *edge = Call<const mgp_edge *>(mgp_edges_iterator_next, self->it);
+  auto *edge = Call<mgp_edge *>(mgp_edges_iterator_next, self->it);
   if (!edge) Py_RETURN_NONE;
   return MakePyEdge(*edge, self->py_graph);
 }
@@ -241,7 +241,7 @@ static PyTypeObject PyGraphType = {
 };
 // clang-format on
 
-PyObject *MakePyGraph(const mgp_graph *graph, mgp_memory *memory) {
+PyObject *MakePyGraph(mgp_graph *graph, mgp_memory *memory) {
   MG_ASSERT(!graph || (graph && memory));
   auto *py_graph = PyObject_New(PyGraph, &PyGraphType);
   if (!py_graph) return nullptr;
@@ -253,7 +253,7 @@ PyObject *MakePyGraph(const mgp_graph *graph, mgp_memory *memory) {
 // clang-format off
 struct PyCypherType {
   PyObject_HEAD
-  const mgp_type *type;
+  mgp_type *type;
 };
 // clang-format on
 
@@ -267,7 +267,7 @@ static PyTypeObject PyCypherTypeType = {
 };
 // clang-format on
 
-PyObject *MakePyCypherType(const mgp_type *type) {
+PyObject *MakePyCypherType(mgp_type *type) {
   MG_ASSERT(type);
   auto *py_type = PyObject_New(PyCypherType, &PyCypherTypeType);
   if (!py_type) return nullptr;
@@ -291,7 +291,7 @@ PyObject *PyQueryProcAddArg(PyQueryProc *self, PyObject *args) {
     PyErr_SetString(PyExc_TypeError, "Expected a _mgp.Type.");
     return nullptr;
   }
-  const auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
+  auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
   if (mgp_proc_add_arg(self->proc, name, type) != MGP_ERROR_NO_ERROR) {
     PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_arg.");
     return nullptr;
@@ -309,7 +309,7 @@ PyObject *PyQueryProcAddOptArg(PyQueryProc *self, PyObject *args) {
     PyErr_SetString(PyExc_TypeError, "Expected a _mgp.Type.");
     return nullptr;
   }
-  const auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
+  auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
   mgp_memory memory{self->proc->opt_args.get_allocator().GetMemoryResource()};
   mgp_value *value{nullptr};
   try {
@@ -346,7 +346,7 @@ PyObject *PyQueryProcAddResult(PyQueryProc *self, PyObject *args) {
     PyErr_SetString(PyExc_TypeError, "Expected a _mgp.Type.");
     return nullptr;
   }
-  const auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
+  auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
   if (mgp_proc_add_result(self->proc, name, type) != MGP_ERROR_NO_ERROR) {
     PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_result.");
     return nullptr;
@@ -363,7 +363,7 @@ PyObject *PyQueryProcAddDeprecatedResult(PyQueryProc *self, PyObject *args) {
     PyErr_SetString(PyExc_TypeError, "Expected a _mgp.Type.");
     return nullptr;
   }
-  const auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
+  auto *type = reinterpret_cast<PyCypherType *>(py_type)->type;
   if (const auto err = mgp_proc_add_deprecated_result(self->proc, name, type); err != MGP_ERROR_NO_ERROR) {
     PyErr_SetString(PyExc_ValueError, "Invalid call to mgp_proc_add_deprecated_result.");
     return nullptr;
@@ -404,13 +404,13 @@ struct PyQueryModule {
 
 struct PyMessages {
   PyObject_HEAD;
-  const mgp_messages *messages;
+  mgp_messages *messages;
   mgp_memory *memory;
 };
 
 struct PyMessage {
   PyObject_HEAD;
-  const mgp_message *message;
+  mgp_message *message;
   const PyMessages *messages;
   mgp_memory *memory;
 };
@@ -529,8 +529,8 @@ PyObject *PyMessagesGetMessageAt(PyMessages *self, PyObject *args) {
   MG_ASSERT(self->memory);
   int64_t id = 0;
   if (!PyArg_ParseTuple(args, "l", &id)) return nullptr;
-  if (id < 0) return nullptr;
-  const auto *message = Call<const mgp_message *>(mgp_messages_at, self->messages, id);
+  if (id < 0 || id >= self->messages->messages.size()) return nullptr;
+  auto *message = &self->messages->messages[id];
   // NOLINTNEXTLINE
   auto *py_message = PyObject_New(PyMessage, &PyMessageType);
   if (!py_message) {
@@ -574,7 +574,7 @@ static PyTypeObject PyMessagesType = {
     .tp_methods = PyMessagesMethods,
 };
 
-PyObject *MakePyMessages(const mgp_messages *msgs, mgp_memory *memory) {
+PyObject *MakePyMessages(mgp_messages *msgs, mgp_memory *memory) {
   MG_ASSERT(!msgs || (msgs && memory));
   // NOLINTNEXTLINE
   auto *py_messages = PyObject_New(PyMessages, &PyMessagesType);
@@ -584,14 +584,14 @@ PyObject *MakePyMessages(const mgp_messages *msgs, mgp_memory *memory) {
   return reinterpret_cast<PyObject *>(py_messages);
 }
 
-py::Object MgpListToPyTuple(const mgp_list *list, PyGraph *py_graph) {
+py::Object MgpListToPyTuple(mgp_list *list, PyGraph *py_graph) {
   MG_ASSERT(list);
   MG_ASSERT(py_graph);
-  const auto len = Call<size_t>(mgp_list_size, list);
+  const auto len = list->elems.size();
   py::Object py_tuple(PyTuple_New(len));
   if (!py_tuple) return nullptr;
   for (size_t i = 0; i < len; ++i) {
-    auto elem = MgpValueToPyObject(*Call<const mgp_value *>(mgp_list_at, list, i), py_graph);
+    auto elem = MgpValueToPyObject(list->elems[i], py_graph);
     if (!elem) return nullptr;
     // Explicitly convert `py_tuple`, which is `py::Object`, via static_cast.
     // Then the macro will cast it to `PyTuple *`.
@@ -600,7 +600,7 @@ py::Object MgpListToPyTuple(const mgp_list *list, PyGraph *py_graph) {
   return py_tuple;
 }
 
-py::Object MgpListToPyTuple(const mgp_list *list, PyObject *py_graph) {
+py::Object MgpListToPyTuple(mgp_list *list, PyObject *py_graph) {
   if (Py_TYPE(py_graph) != &PyGraphType) {
     PyErr_SetString(PyExc_TypeError, "Expected a _mgp.Graph.");
     return nullptr;
@@ -689,7 +689,7 @@ std::optional<py::ExceptionInfo> AddMultipleRecordsFromPython(mgp_result *result
   return std::nullopt;
 }
 
-void CallPythonProcedure(const py::Object &py_cb, const mgp_list *args, const mgp_graph *graph, mgp_result *result,
+void CallPythonProcedure(const py::Object &py_cb, mgp_list *args, mgp_graph *graph, mgp_result *result,
                          mgp_memory *memory) {
   auto gil = py::EnsureGIL();
 
@@ -769,8 +769,8 @@ void CallPythonProcedure(const py::Object &py_cb, const mgp_list *args, const mg
   }
 }
 
-void CallPythonTransformation(const py::Object &py_cb, const mgp_messages *msgs, const mgp_graph *graph,
-                              mgp_result *result, mgp_memory *memory) {
+void CallPythonTransformation(const py::Object &py_cb, mgp_messages *msgs, mgp_graph *graph, mgp_result *result,
+                              mgp_memory *memory) {
   auto gil = py::EnsureGIL();
 
   auto error_to_msg = [](const std::optional<py::ExceptionInfo> &exc_info) -> std::optional<std::string> {
@@ -868,10 +868,10 @@ PyObject *PyQueryModuleAddReadProcedure(PyQueryModule *self, PyObject *cb) {
   auto *memory = self->module->procedures.get_allocator().GetMemoryResource();
   mgp_proc proc(
       name,
-      [py_cb](const mgp_list *args, const mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
+      [py_cb](mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
         CallPythonProcedure(py_cb, args, graph, result, memory);
       },
-      memory);
+      memory, false);
   const auto &[proc_it, did_insert] = self->module->procedures.emplace(name, std::move(proc));
   if (!did_insert) {
     PyErr_SetString(PyExc_ValueError, "Already registered a procedure with the same name.");
@@ -900,7 +900,7 @@ PyObject *PyQueryModuleAddTransformation(PyQueryModule *self, PyObject *cb) {
   auto *memory = self->module->transformations.get_allocator().GetMemoryResource();
   mgp_trans trans(
       name,
-      [py_cb](const mgp_messages *msgs, const mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
+      [py_cb](mgp_messages *msgs, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
         CallPythonTransformation(py_cb, msgs, graph, result, memory);
       },
       memory);
@@ -946,7 +946,7 @@ PyObject *PyMgpModuleTypeNullable(PyObject *mod, PyObject *obj) {
     return nullptr;
   }
   auto *py_type = reinterpret_cast<PyCypherType *>(obj);
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_nullable, py_type->type));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_nullable, py_type->type));
 }
 
 PyObject *PyMgpModuleTypeList(PyObject *mod, PyObject *obj) {
@@ -955,47 +955,47 @@ PyObject *PyMgpModuleTypeList(PyObject *mod, PyObject *obj) {
     return nullptr;
   }
   auto *py_type = reinterpret_cast<PyCypherType *>(obj);
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_list, py_type->type));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_list, py_type->type));
 }
 
 PyObject *PyMgpModuleTypeAny(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_any));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_any));
 }
 
 PyObject *PyMgpModuleTypeBool(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_bool));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_bool));
 }
 
 PyObject *PyMgpModuleTypeString(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_string));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_string));
 }
 
 PyObject *PyMgpModuleTypeInt(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_int));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_int));
 }
 
 PyObject *PyMgpModuleTypeFloat(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_float));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_float));
 }
 
 PyObject *PyMgpModuleTypeNumber(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_number));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_number));
 }
 
 PyObject *PyMgpModuleTypeMap(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_map));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_map));
 }
 
 PyObject *PyMgpModuleTypeNode(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_node));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_node));
 }
 
 PyObject *PyMgpModuleTypeRelationship(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_relationship));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_relationship));
 }
 
 PyObject *PyMgpModuleTypePath(PyObject * /*mod*/, PyObject *Py_UNUSED(ignored)) {
-  return MakePyCypherType(Call<const mgp_type *>(mgp_type_path));
+  return MakePyCypherType(Call<mgp_type *>(mgp_type_path));
 }
 
 static PyMethodDef PyMgpModuleMethods[] = {
@@ -1051,7 +1051,7 @@ PyObject *PyPropertiesIteratorGet(PyPropertiesIterator *self, PyObject *Py_UNUSE
   MG_ASSERT(self->it);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *property = Call<const mgp_property *>(mgp_properties_iterator_get, self->it);
+  auto *property = Call<mgp_property *>(mgp_properties_iterator_get, self->it);
   if (!property) Py_RETURN_NONE;
   py::Object py_name(PyUnicode_FromString(property->name));
   if (!py_name) return nullptr;
@@ -1064,7 +1064,7 @@ PyObject *PyPropertiesIteratorNext(PyPropertiesIterator *self, PyObject *Py_UNUS
   MG_ASSERT(self->it);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *property = Call<const mgp_property *>(mgp_properties_iterator_next, self->it);
+  auto *property = Call<mgp_property *>(mgp_properties_iterator_next, self->it);
   if (!property) Py_RETURN_NONE;
   py::Object py_name(PyUnicode_FromString(property->name));
   if (!py_name) return nullptr;
@@ -1114,9 +1114,8 @@ PyObject *PyEdgeFromVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
   MG_ASSERT(self->edge);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *vertex = Call<const mgp_vertex *>(mgp_edge_get_from, self->edge);
-  MG_ASSERT(vertex);
-  return MakePyVertex(*vertex, self->py_graph);
+  auto &vertex = self->edge->from;
+  return MakePyVertex(&vertex, self->py_graph);
 }
 
 PyObject *PyEdgeToVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
@@ -1124,9 +1123,8 @@ PyObject *PyEdgeToVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
   MG_ASSERT(self->edge);
   MG_ASSERT(self->py_graph);
   MG_ASSERT(self->py_graph->graph);
-  const auto *vertex = Call<const mgp_vertex *>(mgp_edge_get_to, self->edge);
-  MG_ASSERT(vertex);
-  return MakePyVertex(*vertex, self->py_graph);
+  auto &vertex = self->edge->to;
+  return MakePyVertex(vertex, self->py_graph);
 }
 
 void PyEdgeDealloc(PyEdge *self) {
@@ -1232,10 +1230,11 @@ static PyTypeObject PyEdgeType = {
 ///
 /// The created instance references an existing `_mgp.Graph` instance, which
 /// marks the execution context.
-PyObject *MakePyEdge(const mgp_edge &edge, PyGraph *py_graph) {
+PyObject *MakePyEdge(mgp_edge &edge, PyGraph *py_graph) {
   MG_ASSERT(py_graph);
   MG_ASSERT(py_graph->graph && py_graph->memory);
   mgp_edge *edge_copy{nullptr};
+  // TODO(antaljanosbenjamin)
   if (const auto err = mgp_edge_copy(&edge, py_graph->memory, &edge_copy); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
     PyErr_SetString(PyExc_MemoryError, "Unable to allocate mgp_edge.");
     return nullptr;
@@ -1492,7 +1491,7 @@ PyObject *MakePyVertex(mgp_vertex *vertex, PyGraph *py_graph) {
   return reinterpret_cast<PyObject *>(py_vertex);
 }
 
-PyObject *MakePyVertex(const mgp_vertex &vertex, PyGraph *py_graph) {
+PyObject *MakePyVertex(mgp_vertex &vertex, PyGraph *py_graph) {
   MG_ASSERT(py_graph);
   MG_ASSERT(py_graph->graph && py_graph->memory);
 
@@ -1590,7 +1589,7 @@ PyObject *PyPathVertexAt(PyPath *self, PyObject *args) {
   if (!PyArg_ParseTuple(args, "n", &i)) {
     return nullptr;
   }
-  const mgp_vertex *vertex{nullptr};
+  mgp_vertex *vertex{nullptr};
   if (const auto err = mgp_path_vertex_at(self->path, i, &vertex); err == MGP_ERROR_OUT_OF_RANGE) {
     PyErr_SetString(PyExc_IndexError, "Index is out of range.");
     return nullptr;
@@ -1610,7 +1609,7 @@ PyObject *PyPathEdgeAt(PyPath *self, PyObject *args) {
   if (!PyArg_ParseTuple(args, "n", &i)) {
     return nullptr;
   }
-  const mgp_edge *edge{nullptr};
+  mgp_edge *edge{nullptr};
   if (const auto err = mgp_path_edge_at(self->path, i, &edge); err == MGP_ERROR_OUT_OF_RANGE) {
     PyErr_SetString(PyExc_IndexError, "Index is out of range.");
     return nullptr;
@@ -1661,7 +1660,7 @@ PyObject *MakePyPath(mgp_path *path, PyGraph *py_graph) {
   return reinterpret_cast<PyObject *>(py_path);
 }
 
-PyObject *MakePyPath(const mgp_path &path, PyGraph *py_graph) {
+PyObject *MakePyPath(mgp_path &path, PyGraph *py_graph) {
   MG_ASSERT(py_graph);
   MG_ASSERT(py_graph->graph && py_graph->memory);
   mgp_path *path_copy{nullptr};
@@ -1783,22 +1782,22 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyObject *py_graph) {
 }
 
 py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) {
-  switch (Call<mgp_value_type>(mgp_value_get_type, &value)) {
+  switch (value.type) {
     case MGP_VALUE_TYPE_NULL:
       Py_INCREF(Py_None);
       return py::Object(Py_None);
     case MGP_VALUE_TYPE_BOOL:
-      return py::Object(PyBool_FromLong(CallBool(mgp_value_get_bool, &value)));
+      return py::Object(PyBool_FromLong(value.bool_v));
     case MGP_VALUE_TYPE_INT:
-      return py::Object(PyLong_FromLongLong(Call<int64_t>(mgp_value_get_int, &value)));
+      return py::Object(PyLong_FromLongLong(value.int_v));
     case MGP_VALUE_TYPE_DOUBLE:
-      return py::Object(PyFloat_FromDouble(Call<double>(mgp_value_get_double, &value)));
+      return py::Object(PyFloat_FromDouble(value.double_v));
     case MGP_VALUE_TYPE_STRING:
-      return py::Object(PyUnicode_FromString(Call<const char *>(mgp_value_get_string, &value)));
+      return py::Object(PyUnicode_FromString(value.string_v.c_str()));
     case MGP_VALUE_TYPE_LIST:
-      return MgpListToPyTuple(Call<const mgp_list *>(mgp_value_get_list, &value), py_graph);
+      return MgpListToPyTuple(value.list_v, py_graph);
     case MGP_VALUE_TYPE_MAP: {
-      const auto *map = Call<const mgp_map *>(mgp_value_get_map, &value);
+      auto *map = value.map_v;
       py::Object py_dict(PyDict_New());
       if (!py_dict) {
         return nullptr;
@@ -1816,21 +1815,21 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) {
     case MGP_VALUE_TYPE_VERTEX: {
       py::Object py_mgp(PyImport_ImportModule("mgp"));
       if (!py_mgp) return nullptr;
-      const auto *v = Call<const mgp_vertex *>(mgp_value_get_vertex, &value);
+      auto *v = value.vertex_v;
       py::Object py_vertex(reinterpret_cast<PyObject *>(MakePyVertex(*v, py_graph)));
       return py_mgp.CallMethod("Vertex", py_vertex);
     }
     case MGP_VALUE_TYPE_EDGE: {
       py::Object py_mgp(PyImport_ImportModule("mgp"));
       if (!py_mgp) return nullptr;
-      const auto *e = Call<const mgp_edge *>(mgp_value_get_edge, &value);
+      auto *e = value.edge_v;
       py::Object py_edge(reinterpret_cast<PyObject *>(MakePyEdge(*e, py_graph)));
       return py_mgp.CallMethod("Edge", py_edge);
     }
     case MGP_VALUE_TYPE_PATH: {
       py::Object py_mgp(PyImport_ImportModule("mgp"));
       if (!py_mgp) return nullptr;
-      const auto *p = Call<const mgp_path *>(mgp_value_get_path, &value);
+      auto *p = value.path_v;
       py::Object py_path(reinterpret_cast<PyObject *>(MakePyPath(*p, py_graph)));
       return py_mgp.CallMethod("Path", py_path);
     }
diff --git a/src/query/procedure/py_module.hpp b/src/query/procedure/py_module.hpp
index 29ed38e20..8d32c95ab 100644
--- a/src/query/procedure/py_module.hpp
+++ b/src/query/procedure/py_module.hpp
@@ -50,7 +50,7 @@ mgp_value *PyObjectToMgpValue(PyObject *, mgp_memory *);
 PyObject *PyInitMgpModule();
 
 /// Create an instance of _mgp.Graph class.
-PyObject *MakePyGraph(const mgp_graph *, mgp_memory *);
+PyObject *MakePyGraph(mgp_graph *, mgp_memory *);
 
 /// Import a module with given name in the context of mgp_module.
 ///
diff --git a/src/utils/result.hpp b/src/utils/result.hpp
index d93bd91d0..1c008cd50 100644
--- a/src/utils/result.hpp
+++ b/src/utils/result.hpp
@@ -10,6 +10,8 @@ namespace utils {
 template <class TError, class TValue = void>
 class [[nodiscard]] BasicResult final {
  public:
+  using ErrorType = TError;
+  using ValueType = TValue;
   BasicResult(const TValue &value) : value_(value) {}
   BasicResult(TValue &&value) noexcept : value_(std::move(value)) {}
   BasicResult(const TError &error) : error_(error) {}
@@ -96,6 +98,8 @@ class [[nodiscard]] BasicResult final {
 template <class TError>
 class [[nodiscard]] BasicResult<TError, void> final {
  public:
+  using ErrorType = TError;
+  using ValueType = void;
   BasicResult() = default;
   BasicResult(const TError &error) : error_(error) {}
   BasicResult(TError &&error) noexcept : error_(std::move(error)) {}
diff --git a/tests/e2e/memory/procedures/global_memory_limit_proc.c b/tests/e2e/memory/procedures/global_memory_limit_proc.c
index 95f7318f3..f54202ead 100644
--- a/tests/e2e/memory/procedures/global_memory_limit_proc.c
+++ b/tests/e2e/memory/procedures/global_memory_limit_proc.c
@@ -55,7 +55,7 @@ int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
   const enum mgp_error error_proc_err = mgp_module_add_read_procedure(module, "error", error, &error_proc);
   if (error_proc_err != MGP_ERROR_NO_ERROR) return 1;
 
-  const struct mgp_type *string_type = NULL;
+  struct mgp_type *string_type = NULL;
   const enum mgp_error string_type_err = mgp_type_string(&string_type);
   if (string_type_err != MGP_ERROR_NO_ERROR) return 1;
   if (mgp_proc_add_result(error_proc, "error_result", string_type) != MGP_ERROR_NO_ERROR) return 1;
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 610b43ee2..94bebd8d3 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -44,6 +44,12 @@ function(_add_unit_test test_cpp custom_main)
 endfunction(_add_unit_test)
 
 
+# Test utilities
+
+add_library(storage_test_utils storage_test_utils.cpp)
+target_link_libraries(storage_test_utils mg-storage-v2)
+
+
 # Test integrations-kafka
 
 add_library(kafka-mock STATIC kafka_mock.cpp)
@@ -132,6 +138,10 @@ target_include_directories(${test_prefix}query_procedure_mgp_module PRIVATE ${CM
 add_unit_test_with_custom_main(query_procedure_py_module.cpp)
 target_link_libraries(${test_prefix}query_procedure_py_module mg-query)
 target_include_directories(${test_prefix}query_procedure_py_module PRIVATE ${CMAKE_SOURCE_DIR}/include)
+
+add_unit_test(query_procedures_mgp_graph.cpp)
+target_link_libraries(${test_prefix}query_procedures_mgp_graph mg-query storage_test_utils)
+target_include_directories(${test_prefix}query_procedures_mgp_graph PRIVATE ${CMAKE_SOURCE_DIR}/include)
 # END query/procedure
 
 add_unit_test(query_profile.cpp)
@@ -275,7 +285,7 @@ add_unit_test(property_value_v2.cpp)
 target_link_libraries(${test_prefix}property_value_v2 mg-utils)
 
 add_unit_test(storage_v2.cpp)
-target_link_libraries(${test_prefix}storage_v2 mg-storage-v2)
+target_link_libraries(${test_prefix}storage_v2 mg-storage-v2 storage_test_utils)
 
 add_unit_test(storage_v2_constraints.cpp)
 target_link_libraries(${test_prefix}storage_v2_constraints mg-storage-v2)
diff --git a/tests/unit/mgp_kafka_c_api.cpp b/tests/unit/mgp_kafka_c_api.cpp
index 8e1396075..ee4e0467c 100644
--- a/tests/unit/mgp_kafka_c_api.cpp
+++ b/tests/unit/mgp_kafka_c_api.cpp
@@ -133,11 +133,11 @@ class MgpApiTest : public ::testing::Test {
 };
 
 TEST_F(MgpApiTest, TestAllMgpKafkaCApi) {
-  const mgp_messages &messages = Messages();
+  mgp_messages &messages = Messages();
   EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_messages_size, &messages), expected.size());
 
   for (int i = 0; i < expected.size(); ++i) {
-    const auto *message = EXPECT_MGP_NO_ERROR(const mgp_message *, mgp_messages_at, &messages, i);
+    auto *message = EXPECT_MGP_NO_ERROR(mgp_message *, mgp_messages_at, &messages, i);
     // Test for key and key size. Key size is always 1 in this test.
     EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_message_key_size, message), 1);
     EXPECT_EQ(*EXPECT_MGP_NO_ERROR(const char *, mgp_message_key, message), expected[i].key);
diff --git a/tests/unit/mgp_trans_c_api.cpp b/tests/unit/mgp_trans_c_api.cpp
index 150dfcc4e..95085b2e7 100644
--- a/tests/unit/mgp_trans_c_api.cpp
+++ b/tests/unit/mgp_trans_c_api.cpp
@@ -5,8 +5,7 @@
 #include "test_utils.hpp"
 
 TEST(MgpTransTest, TestMgpTransApi) {
-  constexpr auto no_op_cb = [](const mgp_messages *msg, const mgp_graph *graph, mgp_result *result,
-                               mgp_memory *memory) {};
+  constexpr auto no_op_cb = [](mgp_messages *msg, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {};
   mgp_module module(utils::NewDeleteResource());
   // If this is false, then mgp_module_add_transformation()
   // correctly calls IsValidIdentifier(). We don't need to test
diff --git a/tests/unit/query_procedure_mgp_module.cpp b/tests/unit/query_procedure_mgp_module.cpp
index f01596bf4..312241a8f 100644
--- a/tests/unit/query_procedure_mgp_module.cpp
+++ b/tests/unit/query_procedure_mgp_module.cpp
@@ -1,5 +1,6 @@
 #include <gtest/gtest.h>
 
+#include <functional>
 #include <sstream>
 #include <string_view>
 
@@ -7,7 +8,7 @@
 
 #include "test_utils.hpp"
 
-static void DummyCallback(const mgp_list *, const mgp_graph *, mgp_result *, mgp_memory *) {}
+static void DummyCallback(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *) {}
 
 TEST(Module, InvalidProcedureRegistration) {
   mgp_module module(utils::NewDeleteResource());
@@ -61,36 +62,33 @@ TEST(Module, ProcedureSignature) {
   mgp_module module(utils::NewDeleteResource());
   auto *proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_read_procedure, &module, "proc", &DummyCallback);
   CheckSignature(proc, "proc() :: ()");
-  EXPECT_EQ(mgp_proc_add_arg(proc, "arg1", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)), MGP_ERROR_NO_ERROR);
+  EXPECT_EQ(mgp_proc_add_arg(proc, "arg1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)), MGP_ERROR_NO_ERROR);
   CheckSignature(proc, "proc(arg1 :: NUMBER) :: ()");
-  EXPECT_EQ(
-      mgp_proc_add_opt_arg(
-          proc, "opt1",
-          EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)),
-          test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
-      MGP_ERROR_NO_ERROR);
-  CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: ()");
-  EXPECT_EQ(mgp_proc_add_result(proc, "res1",
-                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list,
-                                                    EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int))),
+  EXPECT_EQ(mgp_proc_add_opt_arg(
+                proc, "opt1",
+                EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
+                test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
             MGP_ERROR_NO_ERROR);
+  CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: ()");
+  EXPECT_EQ(
+      mgp_proc_add_result(
+          proc, "res1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int))),
+      MGP_ERROR_NO_ERROR);
   CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)");
-  EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)),
-            MGP_ERROR_LOGIC_ERROR);
+  EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)), MGP_ERROR_LOGIC_ERROR);
   CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)");
-  EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)), MGP_ERROR_LOGIC_ERROR);
+  EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)), MGP_ERROR_LOGIC_ERROR);
   CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)");
-  EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)),
+  EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string)),
             MGP_ERROR_NO_ERROR);
   CheckSignature(proc,
                  "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: "
                  "(res1 :: LIST OF INTEGER, DEPRECATED res2 :: STRING)");
-  EXPECT_EQ(mgp_proc_add_result(proc, "res2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)),
-            MGP_ERROR_LOGIC_ERROR);
-  EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res1", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)),
+  EXPECT_EQ(mgp_proc_add_result(proc, "res2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)), MGP_ERROR_LOGIC_ERROR);
+  EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
             MGP_ERROR_LOGIC_ERROR);
   EXPECT_EQ(
-      mgp_proc_add_opt_arg(proc, "opt2", EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
+      mgp_proc_add_opt_arg(proc, "opt2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
                            test_utils::CreateValueOwningPtr(
                                EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_string, "string=\"value\"", &memory))
                                .get()),
@@ -105,11 +103,23 @@ TEST(Module, ProcedureSignatureOnlyOptArg) {
   mgp_memory memory{utils::NewDeleteResource()};
   mgp_module module(utils::NewDeleteResource());
   auto *proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_read_procedure, &module, "proc", &DummyCallback);
-  EXPECT_EQ(
-      mgp_proc_add_opt_arg(
-          proc, "opt1",
-          EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)),
-          test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
-      MGP_ERROR_NO_ERROR);
+  EXPECT_EQ(mgp_proc_add_opt_arg(
+                proc, "opt1",
+                EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
+                test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
+            MGP_ERROR_NO_ERROR);
   CheckSignature(proc, "proc(opt1 = Null :: ANY?) :: ()");
 }
+
+TEST(Module, ReadWriteProcedures) {
+  mgp_module module(utils::NewDeleteResource());
+  auto *read_proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_read_procedure, &module, "read", &DummyCallback);
+  EXPECT_FALSE(read_proc->is_write_procedure);
+  auto *write_proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_write_procedure, &module, "write", &DummyCallback);
+  EXPECT_TRUE(write_proc->is_write_procedure);
+  mgp_proc read_proc_with_function{"dummy_name",
+                                   std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)>{
+                                       [](mgp_list *, mgp_graph *, mgp_result *, mgp_memory *) {}},
+                                   utils::NewDeleteResource(), false};
+  EXPECT_FALSE(read_proc_with_function.is_write_procedure);
+}
diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp
index 3c1a46120..493b169d4 100644
--- a/tests/unit/query_procedure_mgp_type.cpp
+++ b/tests/unit/query_procedure_mgp_type.cpp
@@ -4,68 +4,68 @@
 
 #include <gtest/gtest.h>
 
+#include "query/procedure/cypher_types.hpp"
 #include "query/procedure/mg_procedure_impl.hpp"
 
 #include "test_utils.hpp"
 
 TEST(CypherType, PresentableNameSimpleTypes) {
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)->impl->GetPresentableName(), "ANY");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool)->impl->GetPresentableName(), "BOOLEAN");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)->impl->GetPresentableName(), "STRING");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int)->impl->GetPresentableName(), "INTEGER");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float)->impl->GetPresentableName(), "FLOAT");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)->impl->GetPresentableName(), "NUMBER");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)->impl->GetPresentableName(), "MAP");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node)->impl->GetPresentableName(), "NODE");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship)->impl->GetPresentableName(), "RELATIONSHIP");
-  EXPECT_EQ(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)->impl->GetPresentableName(), "PATH");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)->impl->GetPresentableName(), "ANY");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool)->impl->GetPresentableName(), "BOOLEAN");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string)->impl->GetPresentableName(), "STRING");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int)->impl->GetPresentableName(), "INTEGER");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float)->impl->GetPresentableName(), "FLOAT");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)->impl->GetPresentableName(), "NUMBER");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)->impl->GetPresentableName(), "MAP");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node)->impl->GetPresentableName(), "NODE");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship)->impl->GetPresentableName(), "RELATIONSHIP");
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)->impl->GetPresentableName(), "PATH");
 }
 
 TEST(CypherType, PresentableNameCompositeTypes) {
-  const mgp_type *any_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any);
+  mgp_type *any_type = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any);
   {
-    const auto *nullable_any = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, any_type);
+    auto *nullable_any = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, any_type);
     EXPECT_EQ(nullable_any->impl->GetPresentableName(), "ANY?");
   }
   {
-    const auto *nullable_any =
-        EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable,
-                            EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable,
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, any_type)));
+    auto *nullable_any =
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable,
+                            EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable,
+                                                EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, any_type)));
     EXPECT_EQ(nullable_any->impl->GetPresentableName(), "ANY?");
   }
   {
-    const auto *nullable_list = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable,
-                                                    EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, any_type));
+    auto *nullable_list =
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, any_type));
     EXPECT_EQ(nullable_list->impl->GetPresentableName(), "LIST? OF ANY");
   }
   {
-    const auto *list_of_int =
-        EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int));
+    auto *list_of_int = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int));
     EXPECT_EQ(list_of_int->impl->GetPresentableName(), "LIST OF INTEGER");
   }
   {
-    const auto *list_of_nullable_path = EXPECT_MGP_NO_ERROR(
-        const mgp_type *, mgp_type_list,
-        EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)));
+    auto *list_of_nullable_path = EXPECT_MGP_NO_ERROR(
+        mgp_type *, mgp_type_list,
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)));
     EXPECT_EQ(list_of_nullable_path->impl->GetPresentableName(), "LIST OF PATH?");
   }
   {
-    const auto *list_of_list_of_map = EXPECT_MGP_NO_ERROR(
-        const mgp_type *, mgp_type_list,
-        EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)));
+    auto *list_of_list_of_map = EXPECT_MGP_NO_ERROR(
+        mgp_type *, mgp_type_list,
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)));
     EXPECT_EQ(list_of_list_of_map->impl->GetPresentableName(), "LIST OF LIST OF MAP");
   }
   {
-    const auto *nullable_list_of_nullable_list_of_nullable_string = EXPECT_MGP_NO_ERROR(
-        const mgp_type *, mgp_type_nullable,
+    auto *nullable_list_of_nullable_list_of_nullable_string = EXPECT_MGP_NO_ERROR(
+        mgp_type *, mgp_type_nullable,
         EXPECT_MGP_NO_ERROR(
-            const mgp_type *, mgp_type_list,
+            mgp_type *, mgp_type_list,
             EXPECT_MGP_NO_ERROR(
-                const mgp_type *, mgp_type_nullable,
-                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list,
-                                    EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable,
-                                                        EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string))))));
+                mgp_type *, mgp_type_nullable,
+                EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list,
+                                    EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable,
+                                                        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string))))));
     EXPECT_EQ(nullable_list_of_nullable_list_of_nullable_string->impl->GetPresentableName(),
               "LIST? OF LIST? OF STRING?");
   }
@@ -76,24 +76,19 @@ TEST(CypherType, NullSatisfiesType) {
   {
     auto *mgp_null = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory);
     const query::TypedValue tv_null;
-    std::vector<const mgp_type *> primitive_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-                                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)};
-    for (const auto *primitive_type : primitive_types) {
-      for (const auto *type :
-           {primitive_type, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, primitive_type),
-            EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list,
-                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, primitive_type))}) {
+    std::vector<mgp_type *> primitive_types{
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any),          EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool),
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number),
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),          EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)};
+    for (auto *primitive_type : primitive_types) {
+      for (auto *type : {primitive_type, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, primitive_type),
+                         EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list,
+                                             EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, primitive_type))}) {
         EXPECT_FALSE(type->impl->SatisfiesType(*mgp_null));
         EXPECT_FALSE(type->impl->SatisfiesType(tv_null));
-        const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type);
+        auto *null_type = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, type);
         EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_null));
         EXPECT_TRUE(null_type->impl->SatisfiesType(tv_null));
       }
@@ -103,26 +98,25 @@ TEST(CypherType, NullSatisfiesType) {
 }
 
 static void CheckSatisfiesTypesAndNullable(const mgp_value *mgp_val, const query::TypedValue &tv,
-                                           const std::vector<const mgp_type *> &types) {
-  for (const auto *type : types) {
+                                           const std::vector<mgp_type *> &types) {
+  for (auto *type : types) {
     EXPECT_TRUE(type->impl->SatisfiesType(*mgp_val)) << type->impl->GetPresentableName();
     EXPECT_TRUE(type->impl->SatisfiesType(tv));
-    const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type);
+    auto *null_type = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, type);
     EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_val)) << null_type->impl->GetPresentableName();
     EXPECT_TRUE(null_type->impl->SatisfiesType(tv));
   }
 }
 
 static void CheckNotSatisfiesTypesAndListAndNullable(const mgp_value *mgp_val, const query::TypedValue &tv,
-                                                     const std::vector<const mgp_type *> &elem_types) {
-  for (const auto *elem_type : elem_types) {
-    for (const auto *type :
-         {elem_type, EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, elem_type),
-          EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list,
-                              EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, elem_type))}) {
+                                                     const std::vector<mgp_type *> &elem_types) {
+  for (auto *elem_type : elem_types) {
+    for (auto *type : {elem_type, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, elem_type),
+                       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list,
+                                           EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, elem_type))}) {
       EXPECT_FALSE(type->impl->SatisfiesType(*mgp_val)) << type->impl->GetPresentableName();
       EXPECT_FALSE(type->impl->SatisfiesType(tv));
-      const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type);
+      auto *null_type = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, type);
       EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_val)) << null_type->impl->GetPresentableName();
       EXPECT_FALSE(null_type->impl->SatisfiesType(tv));
     }
@@ -135,14 +129,13 @@ TEST(CypherType, BoolSatisfiesType) {
   const query::TypedValue tv_bool(true);
   CheckSatisfiesTypesAndNullable(
       mgp_bool, tv_bool,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_bool, tv_bool,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_bool);
 }
 
@@ -152,15 +145,14 @@ TEST(CypherType, IntSatisfiesType) {
   const query::TypedValue tv_int(42);
   CheckSatisfiesTypesAndNullable(
       mgp_int, tv_int,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_int, tv_int,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_int);
 }
 
@@ -170,15 +162,14 @@ TEST(CypherType, DoubleSatisfiesType) {
   const query::TypedValue tv_double(42.0);
   CheckSatisfiesTypesAndNullable(
       mgp_double, tv_double,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_double, tv_double,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_double);
 }
 
@@ -188,14 +179,13 @@ TEST(CypherType, StringSatisfiesType) {
   const query::TypedValue tv_string("text");
   CheckSatisfiesTypesAndNullable(
       mgp_string, tv_string,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_string, tv_string,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_string);
 }
 
@@ -211,14 +201,13 @@ TEST(CypherType, MapSatisfiesType) {
   const query::TypedValue tv_map(std::map<std::string, query::TypedValue>{{"key", query::TypedValue(42)}});
   CheckSatisfiesTypesAndNullable(
       mgp_map_v, tv_map,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_map_v, tv_map,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_map_v);
 }
 
@@ -235,15 +224,14 @@ TEST(CypherType, VertexSatisfiesType) {
   const query::TypedValue tv_vertex(vertex);
   CheckSatisfiesTypesAndNullable(
       mgp_vertex_v, tv_vertex,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_vertex_v, tv_vertex,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_vertex_v);
 }
 
@@ -259,16 +247,16 @@ TEST(CypherType, EdgeSatisfiesType) {
   mgp_graph graph{&dba, storage::View::NEW};
   auto *mgp_edge_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_edge, alloc.new_object<mgp_edge>(edge, &graph));
   const query::TypedValue tv_edge(edge);
-  CheckSatisfiesTypesAndNullable(mgp_edge_v, tv_edge,
-                                 {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any),
-                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-                                  EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map)});
+  CheckSatisfiesTypesAndNullable(
+      mgp_edge_v, tv_edge,
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_edge_v, tv_edge,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_edge_v);
 }
 
@@ -293,24 +281,23 @@ TEST(CypherType, PathSatisfiesType) {
   const query::TypedValue tv_path(query::Path(v1, edge, v2));
   CheckSatisfiesTypesAndNullable(
       mgp_path_v, tv_path,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_path_v, tv_path,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship)});
   mgp_value_destroy(mgp_path_v);
 }
 
-static std::vector<const mgp_type *> MakeListTypes(const std::vector<const mgp_type *> &element_types) {
-  std::vector<const mgp_type *> list_types;
+static std::vector<mgp_type *> MakeListTypes(const std::vector<mgp_type *> &element_types) {
+  std::vector<mgp_type *> list_types;
   list_types.reserve(2U * element_types.size());
-  for (const auto *type : element_types) {
-    list_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, type));
-    list_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list,
-                                             EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type)));
+  for (auto *type : element_types) {
+    list_types.push_back(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, type));
+    list_types.push_back(
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, type)));
   }
   return list_types;
 }
@@ -321,18 +308,14 @@ TEST(CypherType, EmptyListSatisfiesType) {
   auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list);
   query::TypedValue tv_list(std::vector<query::TypedValue>{});
   // Empty List satisfies all list element types
-  std::vector<const mgp_type *> primitive_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)};
+  std::vector<mgp_type *> primitive_types{
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any),          EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),          EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)};
   auto all_types = MakeListTypes(primitive_types);
-  all_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any));
+  all_types.push_back(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any));
   CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, all_types);
   mgp_value_destroy(mgp_list_v);
 }
@@ -350,18 +333,17 @@ TEST(CypherType, ListOfIntSatisfiesType) {
             test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, i, &memory)).get()),
         MGP_ERROR_NO_ERROR);
     tv_list.ValueList().emplace_back(i);
-    auto valid_types = MakeListTypes({EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any),
-                                      EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-                                      EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number)});
-    valid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any));
+    auto valid_types =
+        MakeListTypes({EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+                       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)});
+    valid_types.push_back(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any));
     CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types);
     CheckNotSatisfiesTypesAndListAndNullable(
         mgp_list_v, tv_list,
-        {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-         EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-         EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-         EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-         EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+        {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+         EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),
+         EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship),
+         EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   }
   mgp_value_destroy(mgp_list_v);
 }
@@ -386,18 +368,17 @@ TEST(CypherType, ListOfIntAndBoolSatisfiesType) {
           test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 1, &memory)).get()),
       MGP_ERROR_NO_ERROR);
   tv_list.ValueList().emplace_back(true);
-  auto valid_types = MakeListTypes({EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)});
-  valid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any));
+  auto valid_types = MakeListTypes({EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)});
+  valid_types.push_back(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any));
   CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types);
   // All other types will not be satisfied
   CheckNotSatisfiesTypesAndListAndNullable(
       mgp_list_v, tv_list,
-      {EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-       EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)});
+      {EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship),
+       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)});
   mgp_value_destroy(mgp_list_v);
 }
 
@@ -412,32 +393,28 @@ TEST(CypherType, ListOfNullSatisfiesType) {
       MGP_ERROR_NO_ERROR);
   tv_list.ValueList().emplace_back();
   // List with Null satisfies all nullable list element types
-  std::vector<const mgp_type *> primitive_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_bool),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_string),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_int),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_float),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_number),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_map),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_node),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_relationship),
-                                                EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_path)};
-  std::vector<const mgp_type *> valid_types{EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_any)};
+  std::vector<mgp_type *> primitive_types{
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any),          EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),       EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_float),        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map),          EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_node),
+      EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_relationship), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)};
+  std::vector<mgp_type *> valid_types{EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)};
   valid_types.reserve(1U + primitive_types.size());
-  for (const auto *elem_type : primitive_types) {
-    valid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list,
-                                              EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, elem_type)));
+  for (auto *elem_type : primitive_types) {
+    valid_types.push_back(
+        EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, elem_type)));
   }
   CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types);
-  std::vector<const mgp_type *> invalid_types;
+  std::vector<mgp_type *> invalid_types;
   invalid_types.reserve(primitive_types.size());
-  for (const auto *elem_type : primitive_types) {
-    invalid_types.push_back(EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_list, elem_type));
+  for (auto *elem_type : primitive_types) {
+    invalid_types.push_back(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, elem_type));
   }
-  for (const auto *type : invalid_types) {
+  for (auto *type : invalid_types) {
     EXPECT_FALSE(type->impl->SatisfiesType(*mgp_list_v)) << type->impl->GetPresentableName();
     EXPECT_FALSE(type->impl->SatisfiesType(tv_list));
-    const auto *null_type = EXPECT_MGP_NO_ERROR(const mgp_type *, mgp_type_nullable, type);
+    auto *null_type = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, type);
     EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_list_v)) << null_type->impl->GetPresentableName();
     EXPECT_FALSE(null_type->impl->SatisfiesType(tv_list));
   }
diff --git a/tests/unit/query_procedure_py_module.cpp b/tests/unit/query_procedure_py_module.cpp
index 9602c2972..aee93adb8 100644
--- a/tests/unit/query_procedure_py_module.cpp
+++ b/tests/unit/query_procedure_py_module.cpp
@@ -122,10 +122,10 @@ TEST(PyModule, PyVertex) {
   ASSERT_TRUE(new_vertex_value);
   ASSERT_NE(new_vertex_value, vertex_value);  // Pointer compare.
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_vertex, new_vertex_value), 1);
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_equal,
-                                EXPECT_MGP_NO_ERROR(const mgp_vertex *, mgp_value_get_vertex, vertex_value),
-                                EXPECT_MGP_NO_ERROR(const mgp_vertex *, mgp_value_get_vertex, new_vertex_value)),
-            1);
+  ASSERT_EQ(
+      EXPECT_MGP_NO_ERROR(int, mgp_vertex_equal, EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_value_get_vertex, vertex_value),
+                          EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_value_get_vertex, new_vertex_value)),
+      1);
   // Clean up.
   mgp_value_destroy(new_vertex_value);
   mgp_value_destroy(vertex_value);
@@ -157,10 +157,10 @@ TEST(PyModule, PyEdge) {
   auto *edges_it = EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, start_v, &memory);
   ASSERT_TRUE(edges_it);
   auto *edge = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy,
-                                   EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_get, edges_it), &memory);
+                                   EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, edges_it), &memory);
   auto *edge_value = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_edge, edge);
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_next, edges_it), nullptr);
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_get, edges_it), nullptr);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_next, edges_it), nullptr);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, edges_it), nullptr);
   mgp_edges_iterator_destroy(edges_it);
   mgp_vertex_destroy(start_v);
   // Initialize the Python graph object.
@@ -177,10 +177,9 @@ TEST(PyModule, PyEdge) {
   ASSERT_TRUE(new_edge_value);
   ASSERT_NE(new_edge_value, edge_value);  // Pointer compare.
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_edge, new_edge_value), 1);
-  ASSERT_EQ(
-      EXPECT_MGP_NO_ERROR(int, mgp_edge_equal, EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_value_get_edge, edge_value),
-                          EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_value_get_edge, new_edge_value)),
-      1);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_equal, EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_value_get_edge, edge_value),
+                                EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_value_get_edge, new_edge_value)),
+            1);
   // Clean up.
   mgp_value_destroy(new_edge_value);
   mgp_value_destroy(edge_value);
@@ -206,8 +205,8 @@ TEST(PyModule, PyPath) {
   ASSERT_TRUE(path);
   auto *edges_it = EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, start_v, &memory);
   ASSERT_TRUE(edges_it);
-  for (const auto *edge = EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_get, edges_it); edge != nullptr;
-       edge = EXPECT_MGP_NO_ERROR(const mgp_edge *, mgp_edges_iterator_next, edges_it)) {
+  for (auto *edge = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, edges_it); edge != nullptr;
+       edge = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_next, edges_it)) {
     ASSERT_EQ(mgp_path_expand(path, edge), MGP_ERROR_NO_ERROR);
   }
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_path_size, path), 1);
@@ -227,10 +226,9 @@ TEST(PyModule, PyPath) {
   ASSERT_TRUE(new_path_value);
   ASSERT_NE(new_path_value, path_value);  // Pointer compare.
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_path, new_path_value), 1);
-  ASSERT_EQ(
-      EXPECT_MGP_NO_ERROR(int, mgp_path_equal, EXPECT_MGP_NO_ERROR(const mgp_path *, mgp_value_get_path, path_value),
-                          EXPECT_MGP_NO_ERROR(const mgp_path *, mgp_value_get_path, new_path_value)),
-      1);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_path_equal, EXPECT_MGP_NO_ERROR(mgp_path *, mgp_value_get_path, path_value),
+                                EXPECT_MGP_NO_ERROR(mgp_path *, mgp_value_get_path, new_path_value)),
+            1);
   mgp_value_destroy(new_path_value);
   mgp_value_destroy(path_value);
   ASSERT_FALSE(dba.Commit().HasError());
@@ -244,52 +242,40 @@ TEST(PyModule, PyObjectToMgpValue) {
   auto *value = query::procedure::PyObjectToMgpValue(py_value.Ptr(), &memory);
 
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_list, value), 1);
-  auto *list1 = EXPECT_MGP_NO_ERROR(const mgp_list *, mgp_value_get_list, value);
+  auto *list1 = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_value_get_list, value);
   EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_list_size, list1), 5);
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 0)),
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 0)), 1);
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 0)),
             1);
-  EXPECT_EQ(
-      EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 0)),
-      1);
-  ASSERT_EQ(
-      EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 1)), 1);
-  EXPECT_EQ(
-      EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 1)),
-      1.0);
-  ASSERT_EQ(
-      EXPECT_MGP_NO_ERROR(int, mgp_value_is_string, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 2)), 1);
-  EXPECT_STREQ(EXPECT_MGP_NO_ERROR(const char *, mgp_value_get_string,
-                                   EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 2)),
-               "one");
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_list, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 3)),
-            1);
-  auto *list2 = EXPECT_MGP_NO_ERROR(const mgp_list *, mgp_value_get_list,
-                                    EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 3));
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 1)), 1);
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 1)),
+            1.0);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_string, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 2)), 1);
+  EXPECT_STREQ(
+      EXPECT_MGP_NO_ERROR(const char *, mgp_value_get_string, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 2)),
+      "one");
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_list, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 3)), 1);
+  auto *list2 =
+      EXPECT_MGP_NO_ERROR(mgp_list *, mgp_value_get_list, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 3));
   EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_list_size, list2), 3);
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 0)),
-            1);
-  EXPECT_EQ(
-      EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 0)),
-      2);
-  ASSERT_EQ(
-      EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 1)), 1);
-  EXPECT_EQ(
-      EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 1)),
-      2.0);
-  ASSERT_EQ(
-      EXPECT_MGP_NO_ERROR(int, mgp_value_is_string, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 2)), 1);
-  EXPECT_STREQ(EXPECT_MGP_NO_ERROR(const char *, mgp_value_get_string,
-                                   EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list2, 2)),
-               "two");
-  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_map, EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 4)),
-            1);
-  auto *map = EXPECT_MGP_NO_ERROR(const mgp_map *, mgp_value_get_map,
-                                  EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_list_at, list1, 4));
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list2, 0)), 1);
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list2, 0)),
+            2);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list2, 1)), 1);
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list2, 1)),
+            2.0);
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_string, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list2, 2)), 1);
+  EXPECT_STREQ(
+      EXPECT_MGP_NO_ERROR(const char *, mgp_value_get_string, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list2, 2)),
+      "two");
+  ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_map, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 4)), 1);
+  auto *map =
+      EXPECT_MGP_NO_ERROR(mgp_map *, mgp_value_get_map, EXPECT_MGP_NO_ERROR(mgp_value *, mgp_list_at, list1, 4));
   EXPECT_EQ(EXPECT_MGP_NO_ERROR(size_t, mgp_map_size, map), 2);
-  const mgp_value *v1 = EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_map_at, map, "three");
+  mgp_value *v1 = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_map_at, map, "three");
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_int, v1), 1);
   EXPECT_EQ(EXPECT_MGP_NO_ERROR(int64_t, mgp_value_get_int, v1), 3);
-  const mgp_value *v2 = EXPECT_MGP_NO_ERROR(const mgp_value *, mgp_map_at, map, "four");
+  mgp_value *v2 = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_map_at, map, "four");
   ASSERT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_value_is_double, v2), 1);
   EXPECT_EQ(EXPECT_MGP_NO_ERROR(double, mgp_value_get_double, v2), 4.0);
   mgp_value_destroy(value);
diff --git a/tests/unit/query_procedures_mgp_graph.cpp b/tests/unit/query_procedures_mgp_graph.cpp
new file mode 100644
index 000000000..0462a76bb
--- /dev/null
+++ b/tests/unit/query_procedures_mgp_graph.cpp
@@ -0,0 +1,591 @@
+#include <algorithm>
+#include <iterator>
+#include <list>
+#include <memory>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "mg_procedure.h"
+#include "query/db_accessor.hpp"
+#include "query/procedure/mg_procedure_impl.hpp"
+#include "storage/v2/id_types.hpp"
+#include "storage/v2/property_value.hpp"
+#include "storage/v2/storage.hpp"
+#include "storage/v2/vertex_accessor.hpp"
+#include "storage/v2/view.hpp"
+#include "storage_test_utils.hpp"
+#include "test_utils.hpp"
+#include "utils/memory.hpp"
+
+#define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, MGP_ERROR_NO_ERROR)
+
+namespace {
+struct MgpEdgeDeleter {
+  void operator()(mgp_edge *e) {
+    if (e != nullptr) {
+      mgp_edge_destroy(e);
+    }
+  }
+};
+
+struct MgpEdgesIteratorDeleter {
+  void operator()(mgp_edges_iterator *it) {
+    if (it != nullptr) {
+      mgp_edges_iterator_destroy(it);
+    }
+  }
+};
+
+struct MgpVertexDeleter {
+  void operator()(mgp_vertex *v) {
+    if (v != nullptr) {
+      mgp_vertex_destroy(v);
+    }
+  }
+};
+
+struct MgpVerticesIteratorDeleter {
+  void operator()(mgp_vertices_iterator *it) {
+    if (it != nullptr) {
+      mgp_vertices_iterator_destroy(it);
+    }
+  }
+};
+
+struct MgpValueDeleter {
+  void operator()(mgp_value *v) {
+    if (v != nullptr) {
+      mgp_value_destroy(v);
+    }
+  }
+};
+
+using MgpEdgePtr = std::unique_ptr<mgp_edge, MgpEdgeDeleter>;
+using MgpEdgesIteratorPtr = std::unique_ptr<mgp_edges_iterator, MgpEdgesIteratorDeleter>;
+using MgpVertexPtr = std::unique_ptr<mgp_vertex, MgpVertexDeleter>;
+using MgpVerticesIteratorPtr = std::unique_ptr<mgp_vertices_iterator, MgpVerticesIteratorDeleter>;
+using MgpValuePtr = std::unique_ptr<mgp_value, MgpValueDeleter>;
+
+template <typename TMaybeIterable>
+size_t CountMaybeIterables(TMaybeIterable &&maybe_iterable) {
+  if (maybe_iterable.HasError()) {
+    ADD_FAILURE() << static_cast<std::underlying_type_t<typename TMaybeIterable::ErrorType>>(maybe_iterable.GetError());
+    return 0;
+  }
+  auto &iterable = maybe_iterable.GetValue();
+  return std::distance(iterable.begin(), iterable.end());
+}
+
+void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, const size_t number_of_edges_between) {
+  EXPECT_EQ(CountMaybeIterables(from->impl.InEdges(storage::View::NEW)), 0);
+  EXPECT_EQ(CountMaybeIterables(from->impl.OutEdges(storage::View::NEW)), number_of_edges_between);
+  EXPECT_EQ(CountMaybeIterables(to->impl.InEdges(storage::View::NEW)), number_of_edges_between);
+  EXPECT_EQ(CountMaybeIterables(to->impl.OutEdges(storage::View::NEW)), 0);
+}
+}  // namespace
+
+struct MgpGraphTest : public ::testing::Test {
+  mgp_graph CreateGraph(const storage::View view = storage::View::NEW) {
+    // the execution context can be null as it shouldn't be used in these tests
+    return mgp_graph{&CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION), view, nullptr};
+  }
+
+  std::array<storage::Gid, 2> CreateEdge() {
+    std::array<storage::Gid, 2> vertex_ids{};
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    for (auto i = 0; i < 2; ++i) {
+      vertex_ids[i] = accessor.InsertVertex().Gid();
+    }
+    auto from = accessor.FindVertex(vertex_ids[0], storage::View::NEW);
+    auto to = accessor.FindVertex(vertex_ids[1], storage::View::NEW);
+    EXPECT_TRUE(accessor.InsertEdge(&from.value(), &to.value(), accessor.NameToEdgeType("EDGE")).HasValue());
+
+    EXPECT_FALSE(accessor.Commit().HasError());
+
+    return vertex_ids;
+  }
+
+  void GetFirstOutEdge(mgp_graph &graph, storage::Gid vertex_id, MgpEdgePtr &edge) {
+    MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
+                                          mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+    ASSERT_NE(from, nullptr);
+
+    MgpEdgesIteratorPtr it{EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)};
+    ASSERT_NE(it, nullptr);
+    auto *edge_from_it = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, it.get());
+    ASSERT_NE(edge_from_it, nullptr);
+    // Copy is used to get a non const pointer because mgp_edges_iterator_get_mutable doesn't work with immutable graph
+    edge.reset(EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &memory));
+    ASSERT_NE(edge, nullptr);
+  }
+
+  query::DbAccessor &CreateDbAccessor(const storage::IsolationLevel isolationLevel) {
+    accessors_.push_back(storage.Access(isolationLevel));
+    db_accessors_.emplace_back(&accessors_.back());
+    return db_accessors_.back();
+  }
+
+  storage::Storage storage;
+  mgp_memory memory{utils::NewDeleteResource()};
+
+ private:
+  std::list<storage::Storage::Accessor> accessors_;
+  std::list<query::DbAccessor> db_accessors_;
+};
+
+TEST_F(MgpGraphTest, IsMutable) {
+  mgp_graph immutable_graph = CreateGraph(storage::View::OLD);
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_graph_is_mutable, &immutable_graph), 0);
+  mgp_graph mutable_graph = CreateGraph(storage::View::NEW);
+  EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_graph_is_mutable, &mutable_graph), 0);
+}
+
+TEST_F(MgpGraphTest, CreateVertex) {
+  mgp_graph graph = CreateGraph();
+  auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 0);
+  MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &memory)};
+  EXPECT_NE(vertex, nullptr);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
+  const auto vertex_id = EXPECT_MGP_NO_ERROR(mgp_vertex_id, mgp_vertex_get_id, vertex.get());
+  EXPECT_TRUE(
+      read_uncommited_accessor.FindVertex(storage::Gid::FromInt(vertex_id.as_int), storage::View::NEW).has_value());
+}
+
+TEST_F(MgpGraphTest, RemoveVertex) {
+  storage::Gid vertex_id{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    const auto vertex = accessor.InsertVertex();
+    vertex_id = vertex.Gid();
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  mgp_graph graph = CreateGraph();
+  auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
+  MgpVertexPtr vertex{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+  EXPECT_NE(vertex, nullptr);
+  EXPECT_SUCCESS(mgp_graph_remove_vertex(&graph, vertex.get()));
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 0);
+}
+
+TEST_F(MgpGraphTest, CreateRemoveWithImmutableGraph) {
+  storage::Gid vertex_id{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    const auto vertex = accessor.InsertVertex();
+    vertex_id = vertex.Gid();
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
+
+  mgp_graph immutable_graph = CreateGraph(storage::View::OLD);
+  mgp_vertex *raw_vertex{nullptr};
+  EXPECT_EQ(mgp_graph_create_vertex(&immutable_graph, &memory, &raw_vertex), MGP_ERROR_IMMUTABLE_OBJECT);
+  MgpVertexPtr created_vertex{raw_vertex};
+  EXPECT_EQ(created_vertex, nullptr);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
+  MgpVertexPtr vertex_to_remove{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &immutable_graph,
+                                                    mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+  ASSERT_NE(vertex_to_remove, nullptr);
+  EXPECT_EQ(mgp_graph_remove_vertex(&immutable_graph, vertex_to_remove.get()), MGP_ERROR_IMMUTABLE_OBJECT);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
+}
+
+TEST_F(MgpGraphTest, VerticesIterator) {
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    accessor.InsertVertex();
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto check_vertices_iterator = [this](const storage::View view) {
+    mgp_graph graph = CreateGraph(view);
+    MgpVerticesIteratorPtr vertices_iter{
+        EXPECT_MGP_NO_ERROR(mgp_vertices_iterator *, mgp_graph_iter_vertices, &graph, &memory)};
+    ASSERT_NE(vertices_iter, nullptr);
+    EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_vertices_iterator_get, vertices_iter.get());
+    if (view == storage::View::NEW) {
+      EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertices_iterator_underlying_graph_is_mutable, vertices_iter.get()), 0);
+    } else {
+      EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertices_iterator_underlying_graph_is_mutable, vertices_iter.get()), 0);
+    }
+  };
+  {
+    SCOPED_TRACE("View::OLD");
+    check_vertices_iterator(storage::View::OLD);
+  }
+  {
+    SCOPED_TRACE("View::NEW");
+    check_vertices_iterator(storage::View::NEW);
+  }
+}
+
+TEST_F(MgpGraphTest, VertexIsMutable) {
+  auto graph = CreateGraph(storage::View::NEW);
+  MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &memory)};
+  ASSERT_NE(vertex.get(), nullptr);
+  EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, vertex.get()), 0);
+  graph.view = storage::View::OLD;
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, vertex.get()), 0);
+}
+
+TEST_F(MgpGraphTest, VertexSetProperty) {
+  constexpr std::string_view property_to_update{"to_update"};
+  constexpr std::string_view property_to_set{"to_set"};
+  storage::Gid vertex_id{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    auto vertex = accessor.InsertVertex();
+    vertex_id = vertex.Gid();
+    const auto result = vertex.SetProperty(accessor.NameToProperty(property_to_update), storage::PropertyValue(42));
+    ASSERT_TRUE(result.HasValue());
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+  EXPECT_EQ(CountVertices(read_uncommited_accessor, storage::View::NEW), 1);
+
+  mgp_graph graph = CreateGraph(storage::View::NEW);
+  MgpVertexPtr vertex{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+  ASSERT_NE(vertex, nullptr);
+
+  auto vertex_acc = read_uncommited_accessor.FindVertex(vertex_id, storage::View::NEW);
+  ASSERT_TRUE(vertex_acc.has_value());
+  const auto property_id_to_update = read_uncommited_accessor.NameToProperty(property_to_update);
+
+  {
+    SCOPED_TRACE("Update the property");
+    constexpr int64_t numerical_value_to_update_to{69};
+    MgpValuePtr value_to_update_to{
+        EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, numerical_value_to_update_to, &memory)};
+    ASSERT_NE(value_to_update_to, nullptr);
+    EXPECT_SUCCESS(mgp_vertex_set_property(vertex.get(), property_to_update.data(), value_to_update_to.get()));
+
+    const auto maybe_prop = vertex_acc->GetProperty(property_id_to_update, storage::View::NEW);
+    ASSERT_TRUE(maybe_prop.HasValue());
+    EXPECT_EQ(*maybe_prop, storage::PropertyValue{numerical_value_to_update_to});
+  }
+  {
+    SCOPED_TRACE("Remove the property");
+    MgpValuePtr null_value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)};
+    ASSERT_NE(null_value, nullptr);
+    EXPECT_SUCCESS(mgp_vertex_set_property(vertex.get(), property_to_update.data(), null_value.get()));
+
+    const auto maybe_prop = vertex_acc->GetProperty(property_id_to_update, storage::View::NEW);
+    ASSERT_TRUE(maybe_prop.HasValue());
+    EXPECT_EQ(*maybe_prop, storage::PropertyValue{});
+  }
+  {
+    SCOPED_TRACE("Add a property");
+    constexpr double numerical_value_to_set{3.5};
+    MgpValuePtr value_to_set{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, numerical_value_to_set, &memory)};
+    ASSERT_NE(value_to_set, nullptr);
+    EXPECT_SUCCESS(mgp_vertex_set_property(vertex.get(), property_to_set.data(), value_to_set.get()));
+    const auto maybe_prop =
+        vertex_acc->GetProperty(read_uncommited_accessor.NameToProperty(property_to_set), storage::View::NEW);
+    ASSERT_TRUE(maybe_prop.HasValue());
+    EXPECT_EQ(*maybe_prop, storage::PropertyValue{numerical_value_to_set});
+  }
+}
+
+TEST_F(MgpGraphTest, VertexAddLabel) {
+  constexpr std::string_view label = "test_label";
+  storage::Gid vertex_id{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    const auto vertex = accessor.InsertVertex();
+    vertex_id = vertex.Gid();
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+
+  mgp_graph graph = CreateGraph(storage::View::NEW);
+  MgpVertexPtr vertex{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+  EXPECT_SUCCESS(mgp_vertex_add_label(vertex.get(), mgp_label{label.data()}));
+
+  auto check_label = [&]() {
+    EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0);
+
+    auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+    const auto maybe_vertex = read_uncommited_accessor.FindVertex(vertex_id, storage::View::NEW);
+    ASSERT_TRUE(maybe_vertex.has_value());
+    const auto label_ids = maybe_vertex->Labels(storage::View::NEW);
+    ASSERT_TRUE(label_ids.HasValue());
+    EXPECT_THAT(*label_ids, ::testing::ContainerEq(std::vector{read_uncommited_accessor.NameToLabel(label)}));
+  };
+  ASSERT_NO_FATAL_FAILURE(check_label());
+  EXPECT_SUCCESS(mgp_vertex_add_label(vertex.get(), mgp_label{label.data()}));
+  ASSERT_NO_FATAL_FAILURE(check_label());
+}
+
+TEST_F(MgpGraphTest, VertexRemoveLabel) {
+  constexpr std::string_view label = "test_label";
+  storage::Gid vertex_id{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    auto vertex = accessor.InsertVertex();
+    const auto result = vertex.AddLabel(accessor.NameToLabel(label));
+    ASSERT_TRUE(result.HasValue());
+    ASSERT_TRUE(*result);
+    vertex_id = vertex.Gid();
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+
+  mgp_graph graph = CreateGraph(storage::View::NEW);
+  MgpVertexPtr vertex{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+  EXPECT_SUCCESS(mgp_vertex_remove_label(vertex.get(), mgp_label{label.data()}));
+
+  auto check_label = [&]() {
+    EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0);
+
+    auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+    const auto maybe_vertex = read_uncommited_accessor.FindVertex(vertex_id, storage::View::NEW);
+    ASSERT_TRUE(maybe_vertex.has_value());
+    const auto label_ids = maybe_vertex->Labels(storage::View::NEW);
+    ASSERT_TRUE(label_ids.HasValue());
+    EXPECT_EQ(label_ids->size(), 0);
+  };
+  ASSERT_NO_FATAL_FAILURE(check_label());
+  EXPECT_SUCCESS(mgp_vertex_remove_label(vertex.get(), mgp_label{label.data()}));
+  ASSERT_NO_FATAL_FAILURE(check_label());
+}
+
+TEST_F(MgpGraphTest, ModifyImmutableVertex) {
+  constexpr std::string_view label_to_remove{"label_to_remove"};
+  storage::Gid vertex_id{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    auto vertex = accessor.InsertVertex();
+    vertex_id = vertex.Gid();
+    ASSERT_TRUE(vertex.AddLabel(accessor.NameToLabel(label_to_remove)).HasValue());
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto graph = CreateGraph(storage::View::OLD);
+  MgpVertexPtr vertex{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)};
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, vertex.get()), 0);
+
+  EXPECT_EQ(mgp_vertex_add_label(vertex.get(), mgp_label{"label"}), MGP_ERROR_IMMUTABLE_OBJECT);
+  EXPECT_EQ(mgp_vertex_remove_label(vertex.get(), mgp_label{label_to_remove.data()}), MGP_ERROR_IMMUTABLE_OBJECT);
+  MgpValuePtr value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 4, &memory)};
+  EXPECT_EQ(mgp_vertex_set_property(vertex.get(), "property", value.get()), MGP_ERROR_IMMUTABLE_OBJECT);
+}
+
+TEST_F(MgpGraphTest, CreateRemoveEdge) {
+  std::array<storage::Gid, 2> vertex_ids{};
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    for (auto i = 0; i < 2; ++i) {
+      vertex_ids[i] = accessor.InsertVertex().Gid();
+    }
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto graph = CreateGraph();
+  MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
+                                        mgp_vertex_id{vertex_ids[0].AsInt()}, &memory)};
+  MgpVertexPtr to{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
+                                      mgp_vertex_id{vertex_ids[1].AsInt()}, &memory)};
+  ASSERT_NE(from, nullptr);
+  ASSERT_NE(to, nullptr);
+  CheckEdgeCountBetween(from, to, 0);
+  MgpEdgePtr edge{EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_graph_create_edge, &graph, from.get(), to.get(),
+                                      mgp_edge_type{"EDGE"}, &memory)};
+  CheckEdgeCountBetween(from, to, 1);
+  ASSERT_NE(edge, nullptr);
+  EXPECT_SUCCESS(mgp_graph_remove_edge(&graph, edge.get()));
+  CheckEdgeCountBetween(from, to, 0);
+}
+
+TEST_F(MgpGraphTest, CreateRemoveEdgeWithImmutableGraph) {
+  storage::Gid from_id;
+  storage::Gid to_id;
+  {
+    auto accessor = CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    auto from = accessor.InsertVertex();
+    auto to = accessor.InsertVertex();
+    from_id = from.Gid();
+    to_id = to.Gid();
+    ASSERT_TRUE(accessor.InsertEdge(&from, &to, accessor.NameToEdgeType("EDGE_TYPE_TO_REMOVE")).HasValue());
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto graph = CreateGraph(storage::View::OLD);
+  MgpVertexPtr from{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{from_id.AsInt()}, &memory)};
+  MgpVertexPtr to{
+      EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{to_id.AsInt()}, &memory)};
+  ASSERT_NE(from, nullptr);
+  ASSERT_NE(to, nullptr);
+  CheckEdgeCountBetween(from, to, 1);
+  mgp_edge *edge{nullptr};
+  EXPECT_EQ(
+      mgp_graph_create_edge(&graph, from.get(), to.get(), mgp_edge_type{"NEWLY_CREATED_EDGE_TYPE"}, &memory, &edge),
+      MGP_ERROR_IMMUTABLE_OBJECT);
+  CheckEdgeCountBetween(from, to, 1);
+
+  MgpEdgesIteratorPtr edges_it{
+      EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)};
+  auto *edge_from_it = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, edges_it.get());
+  ASSERT_NE(edge_from_it, nullptr);
+  EXPECT_EQ(mgp_graph_remove_edge(&graph, edge_from_it), MGP_ERROR_IMMUTABLE_OBJECT);
+  MgpEdgePtr edge_copy_of_immutable{EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &memory)};
+  EXPECT_EQ(mgp_graph_remove_edge(&graph, edge_copy_of_immutable.get()), MGP_ERROR_IMMUTABLE_OBJECT);
+  CheckEdgeCountBetween(from, to, 1);
+}
+
+TEST_F(MgpGraphTest, EdgeIsMutable) {
+  const auto vertex_ids = CreateEdge();
+  auto graph = CreateGraph();
+  MgpEdgePtr edge{};
+  ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, vertex_ids[0], edge));
+  EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()), 0);
+  graph.view = storage::View::OLD;
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()), 0);
+}
+
+TEST_F(MgpGraphTest, MutableFromTo) {
+  storage::Gid from_vertex_id{};
+  {
+    const auto vertex_ids = CreateEdge();
+    from_vertex_id = vertex_ids[0];
+  }
+  auto check_edges_iterator = [this, from_vertex_id](const storage::View view) {
+    mgp_graph graph = CreateGraph(view);
+
+    MgpEdgePtr edge{};
+    ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, from_vertex_id, edge));
+    auto *from = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_edge_get_from, edge.get());
+    auto *to = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_edge_get_from, edge.get());
+    auto check_is_mutable = [&edge, from, to](bool is_mutable) {
+      EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()) != 0, is_mutable);
+      EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, from) != 0, is_mutable);
+      EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, to) != 0, is_mutable);
+    };
+    if (view == storage::View::NEW) {
+      check_is_mutable(true);
+    } else {
+      check_is_mutable(false);
+    }
+  };
+  {
+    SCOPED_TRACE("View::OLD");
+    check_edges_iterator(storage::View::OLD);
+  }
+  {
+    SCOPED_TRACE("View::NEW");
+    check_edges_iterator(storage::View::NEW);
+  }
+}
+
+TEST_F(MgpGraphTest, EdgesIterator) {
+  storage::Gid from_vertex_id{};
+  {
+    const auto vertex_ids = CreateEdge();
+    from_vertex_id = vertex_ids[0];
+  }
+  auto check_edges_iterator = [this, from_vertex_id](const storage::View view) {
+    mgp_graph graph = CreateGraph(view);
+
+    MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
+                                          mgp_vertex_id{from_vertex_id.AsInt()}, &memory)};
+    MgpEdgesIteratorPtr iter{EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)};
+    auto *edge = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, iter.get());
+    auto check_is_mutable = [&edge, &iter](bool is_mutable) {
+      EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edges_iterator_underlying_graph_is_mutable, iter.get()) != 0, is_mutable);
+      EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge) != 0, is_mutable);
+    };
+    if (view == storage::View::NEW) {
+      check_is_mutable(true);
+    } else {
+      check_is_mutable(false);
+    }
+  };
+  {
+    SCOPED_TRACE("View::OLD");
+    check_edges_iterator(storage::View::OLD);
+  }
+  {
+    SCOPED_TRACE("View::NEW");
+    check_edges_iterator(storage::View::NEW);
+  }
+}
+
+TEST_F(MgpGraphTest, EdgeSetProperty) {
+  constexpr std::string_view property_to_update{"to_update"};
+  constexpr std::string_view property_to_set{"to_set"};
+
+  storage::Gid from_vertex_id{};
+  auto get_edge = [&from_vertex_id](storage::Storage::Accessor &accessor) -> storage::EdgeAccessor {
+    auto from = accessor.FindVertex(from_vertex_id, storage::View::NEW);
+    return from->OutEdges(storage::View::NEW).GetValue().front();
+  };
+  {
+    const auto vertex_ids = CreateEdge();
+    from_vertex_id = vertex_ids[0];
+    auto accessor = storage.Access(storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    auto edge = get_edge(accessor);
+    const auto result = edge.SetProperty(accessor.NameToProperty(property_to_update), storage::PropertyValue(42));
+    ASSERT_TRUE(result.HasValue());
+    ASSERT_FALSE(accessor.Commit().HasError());
+  }
+  auto read_uncommited_accessor = storage.Access(storage::IsolationLevel::READ_UNCOMMITTED);
+
+  mgp_graph graph = CreateGraph(storage::View::NEW);
+  MgpEdgePtr edge;
+  ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, from_vertex_id, edge));
+  const auto edge_acc = get_edge(read_uncommited_accessor);
+
+  const auto property_id_to_update = read_uncommited_accessor.NameToProperty(property_to_update);
+
+  {
+    SCOPED_TRACE("Update the property");
+    constexpr int64_t numerical_value_to_update_to{69};
+    MgpValuePtr value_to_update_to{
+        EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, numerical_value_to_update_to, &memory)};
+    ASSERT_NE(value_to_update_to, nullptr);
+    EXPECT_SUCCESS(mgp_edge_set_property(edge.get(), property_to_update.data(), value_to_update_to.get()));
+
+    const auto maybe_prop = edge_acc.GetProperty(property_id_to_update, storage::View::NEW);
+    ASSERT_TRUE(maybe_prop.HasValue());
+    EXPECT_EQ(*maybe_prop, storage::PropertyValue{numerical_value_to_update_to});
+  }
+  {
+    SCOPED_TRACE("Remove the property");
+    MgpValuePtr null_value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)};
+    ASSERT_NE(null_value, nullptr);
+    EXPECT_SUCCESS(mgp_edge_set_property(edge.get(), property_to_update.data(), null_value.get()));
+
+    const auto maybe_prop = edge_acc.GetProperty(property_id_to_update, storage::View::NEW);
+    ASSERT_TRUE(maybe_prop.HasValue());
+    EXPECT_EQ(*maybe_prop, storage::PropertyValue{});
+  }
+  {
+    SCOPED_TRACE("Add a property");
+    constexpr double numerical_value_to_set{3.5};
+    MgpValuePtr value_to_set{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, numerical_value_to_set, &memory)};
+    ASSERT_NE(value_to_set, nullptr);
+    EXPECT_SUCCESS(mgp_edge_set_property(edge.get(), property_to_set.data(), value_to_set.get()));
+    const auto maybe_prop =
+        edge_acc.GetProperty(read_uncommited_accessor.NameToProperty(property_to_set), storage::View::NEW);
+    ASSERT_TRUE(maybe_prop.HasValue());
+    EXPECT_EQ(*maybe_prop, storage::PropertyValue{numerical_value_to_set});
+  }
+}
+
+TEST_F(MgpGraphTest, EdgeSetPropertyWithImmutableGraph) {
+  storage::Gid from_vertex_id{};
+  {
+    const auto vertex_ids = CreateEdge();
+    from_vertex_id = vertex_ids[0];
+  }
+  auto graph = CreateGraph(storage::View::OLD);
+  MgpEdgePtr edge;
+  ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, from_vertex_id, edge));
+  MgpValuePtr value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 65, &memory)};
+  EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()), 0);
+  EXPECT_EQ(mgp_edge_set_property(edge.get(), "property", value.get()), MGP_ERROR_IMMUTABLE_OBJECT);
+}
diff --git a/tests/unit/storage_test_utils.cpp b/tests/unit/storage_test_utils.cpp
new file mode 100644
index 000000000..4f7f656f5
--- /dev/null
+++ b/tests/unit/storage_test_utils.cpp
@@ -0,0 +1,9 @@
+#include "storage_test_utils.hpp"
+
+size_t CountVertices(storage::Storage::Accessor &storage_accessor, storage::View view) {
+  auto vertices = storage_accessor.Vertices(view);
+  size_t count = 0U;
+  for (auto it = vertices.begin(); it != vertices.end(); ++it, ++count)
+    ;
+  return count;
+}
\ No newline at end of file
diff --git a/tests/unit/storage_test_utils.hpp b/tests/unit/storage_test_utils.hpp
new file mode 100644
index 000000000..90aee2afa
--- /dev/null
+++ b/tests/unit/storage_test_utils.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "storage/v2/storage.hpp"
+#include "storage/v2/view.hpp"
+
+size_t CountVertices(storage::Storage::Accessor &storage_accessor, storage::View view);
\ No newline at end of file
diff --git a/tests/unit/storage_v2.cpp b/tests/unit/storage_v2.cpp
index db9082bd7..ba6af920f 100644
--- a/tests/unit/storage_v2.cpp
+++ b/tests/unit/storage_v2.cpp
@@ -6,16 +6,10 @@
 #include "storage/v2/property_value.hpp"
 #include "storage/v2/storage.hpp"
 #include "storage/v2/vertex_accessor.hpp"
+#include "storage_test_utils.hpp"
 
 using testing::UnorderedElementsAre;
 
-size_t CountVertices(storage::Storage::Accessor *storage_accessor, storage::View view) {
-  auto vertices = storage_accessor->Vertices(view);
-  size_t count = 0U;
-  for (auto it = vertices.begin(); it != vertices.end(); ++it) ++count;
-  return count;
-}
-
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageV2, Commit) {
   storage::Storage store;
@@ -25,17 +19,17 @@ TEST(StorageV2, Commit) {
     auto vertex = acc.CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
     ASSERT_FALSE(acc.Commit().HasError());
   }
   {
     auto acc = store.Access();
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 1U);
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
     acc.Abort();
   }
   {
@@ -45,21 +39,21 @@ TEST(StorageV2, Commit) {
 
     auto res = acc.DeleteVertex(&*vertex);
     ASSERT_FALSE(res.HasError());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
 
     acc.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
 
     ASSERT_FALSE(acc.Commit().HasError());
   }
   {
     auto acc = store.Access();
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.Abort();
   }
 }
@@ -73,17 +67,17 @@ TEST(StorageV2, Abort) {
     auto vertex = acc.CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
     acc.Abort();
   }
   {
     auto acc = store.Access();
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.Abort();
   }
 }
@@ -99,18 +93,18 @@ TEST(StorageV2, AdvanceCommandCommit) {
     auto vertex1 = acc.CreateVertex();
     gid1 = vertex1.Gid();
     ASSERT_FALSE(acc.FindVertex(gid1, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
 
     acc.AdvanceCommand();
 
     auto vertex2 = acc.CreateVertex();
     gid2 = vertex2.Gid();
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 1U);
     ASSERT_TRUE(acc.FindVertex(gid2, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 2U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 2U);
 
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::OLD).has_value());
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::NEW).has_value());
@@ -123,8 +117,8 @@ TEST(StorageV2, AdvanceCommandCommit) {
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::NEW).has_value());
     ASSERT_TRUE(acc.FindVertex(gid2, storage::View::OLD).has_value());
     ASSERT_TRUE(acc.FindVertex(gid2, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 2U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 2U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 2U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 2U);
     acc.Abort();
   }
 }
@@ -140,18 +134,18 @@ TEST(StorageV2, AdvanceCommandAbort) {
     auto vertex1 = acc.CreateVertex();
     gid1 = vertex1.Gid();
     ASSERT_FALSE(acc.FindVertex(gid1, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
 
     acc.AdvanceCommand();
 
     auto vertex2 = acc.CreateVertex();
     gid2 = vertex2.Gid();
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 1U);
     ASSERT_TRUE(acc.FindVertex(gid2, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 2U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 2U);
 
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::OLD).has_value());
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::NEW).has_value());
@@ -164,8 +158,8 @@ TEST(StorageV2, AdvanceCommandAbort) {
     ASSERT_FALSE(acc.FindVertex(gid1, storage::View::NEW).has_value());
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::OLD).has_value());
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.Abort();
   }
 }
@@ -181,26 +175,26 @@ TEST(StorageV2, SnapshotIsolation) {
   auto gid = vertex.Gid();
 
   ASSERT_FALSE(acc2.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
-  EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 0U);
   ASSERT_FALSE(acc2.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 1U);
-  EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 0U);
 
   ASSERT_FALSE(acc1.Commit().HasError());
 
   ASSERT_FALSE(acc2.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 0U);
   ASSERT_FALSE(acc2.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 0U);
 
   acc2.Abort();
 
   auto acc3 = store.Access();
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::OLD), 1U);
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::NEW), 1U);
   acc3.Abort();
 }
 
@@ -214,25 +208,25 @@ TEST(StorageV2, AccessorMove) {
     gid = vertex.Gid();
 
     ASSERT_FALSE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
 
     storage::Storage::Accessor moved(std::move(acc));
 
     ASSERT_FALSE(moved.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&moved, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(moved, storage::View::OLD), 0U);
     ASSERT_TRUE(moved.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&moved, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(moved, storage::View::NEW), 1U);
 
     ASSERT_FALSE(moved.Commit().HasError());
   }
   {
     auto acc = store.Access();
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 1U);
     ASSERT_TRUE(acc.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
     acc.Abort();
   }
 }
@@ -250,9 +244,9 @@ TEST(StorageV2, VertexDeleteCommit) {
     auto vertex = acc2.CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc2.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 0U);
     ASSERT_TRUE(acc2.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 1U);
     ASSERT_FALSE(acc2.Commit().HasError());
   }
 
@@ -261,31 +255,31 @@ TEST(StorageV2, VertexDeleteCommit) {
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
 
   // Check whether the vertex exists in transaction 3
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::OLD), 1U);
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::NEW), 1U);
 
   // Delete the vertex in transaction 4
   {
     auto vertex = acc4.FindVertex(gid, storage::View::NEW);
     ASSERT_TRUE(vertex);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::NEW), 1U);
 
     auto res = acc4.DeleteVertex(&*vertex);
     ASSERT_TRUE(res.HasValue());
-    EXPECT_EQ(CountVertices(&acc4, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::NEW), 0U);
 
     acc4.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc4, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::NEW), 0U);
 
     ASSERT_FALSE(acc4.Commit().HasError());
   }
@@ -294,21 +288,21 @@ TEST(StorageV2, VertexDeleteCommit) {
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
 
   // Check whether the vertex exists in transaction 3
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::OLD), 1U);
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::NEW), 1U);
 
   // Check whether the vertex exists in transaction 5
   ASSERT_FALSE(acc5.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc5, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc5, storage::View::OLD), 0U);
   ASSERT_FALSE(acc5.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc5, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc5, storage::View::NEW), 0U);
 }
 
 // NOLINTNEXTLINE(hicpp-special-member-functions)
@@ -324,9 +318,9 @@ TEST(StorageV2, VertexDeleteAbort) {
     auto vertex = acc2.CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc2.FindVertex(gid, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 0U);
     ASSERT_TRUE(acc2.FindVertex(gid, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 1U);
     ASSERT_FALSE(acc2.Commit().HasError());
   }
 
@@ -335,31 +329,31 @@ TEST(StorageV2, VertexDeleteAbort) {
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
 
   // Check whether the vertex exists in transaction 3
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::OLD), 1U);
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::NEW), 1U);
 
   // Delete the vertex in transaction 4, but abort the transaction
   {
     auto vertex = acc4.FindVertex(gid, storage::View::NEW);
     ASSERT_TRUE(vertex);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::NEW), 1U);
 
     auto res = acc4.DeleteVertex(&*vertex);
     ASSERT_TRUE(res.HasValue());
-    EXPECT_EQ(CountVertices(&acc4, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::NEW), 0U);
 
     acc4.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc4, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc4, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc4, storage::View::NEW), 0U);
 
     acc4.Abort();
   }
@@ -369,37 +363,37 @@ TEST(StorageV2, VertexDeleteAbort) {
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
 
   // Check whether the vertex exists in transaction 3
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::OLD), 1U);
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::NEW), 1U);
 
   // Check whether the vertex exists in transaction 5
   ASSERT_TRUE(acc5.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc5, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc5, storage::View::OLD), 1U);
   ASSERT_TRUE(acc5.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc5, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc5, storage::View::NEW), 1U);
 
   // Delete the vertex in transaction 6
   {
     auto vertex = acc6.FindVertex(gid, storage::View::NEW);
     ASSERT_TRUE(vertex);
-    EXPECT_EQ(CountVertices(&acc6, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc6, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc6, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc6, storage::View::NEW), 1U);
 
     auto res = acc6.DeleteVertex(&*vertex);
     ASSERT_TRUE(res.HasValue());
-    EXPECT_EQ(CountVertices(&acc6, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc6, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc6, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc6, storage::View::NEW), 0U);
 
     acc6.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc6, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc6, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc6, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc6, storage::View::NEW), 0U);
 
     ASSERT_FALSE(acc6.Commit().HasError());
   }
@@ -408,27 +402,27 @@ TEST(StorageV2, VertexDeleteAbort) {
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
   ASSERT_FALSE(acc1.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
 
   // Check whether the vertex exists in transaction 3
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::OLD), 1U);
   ASSERT_TRUE(acc3.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc3, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc3, storage::View::NEW), 1U);
 
   // Check whether the vertex exists in transaction 5
   ASSERT_TRUE(acc5.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc5, storage::View::OLD), 1U);
+  EXPECT_EQ(CountVertices(acc5, storage::View::OLD), 1U);
   ASSERT_TRUE(acc5.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc5, storage::View::NEW), 1U);
+  EXPECT_EQ(CountVertices(acc5, storage::View::NEW), 1U);
 
   // Check whether the vertex exists in transaction 7
   ASSERT_FALSE(acc7.FindVertex(gid, storage::View::OLD).has_value());
-  EXPECT_EQ(CountVertices(&acc7, storage::View::OLD), 0U);
+  EXPECT_EQ(CountVertices(acc7, storage::View::OLD), 0U);
   ASSERT_FALSE(acc7.FindVertex(gid, storage::View::NEW).has_value());
-  EXPECT_EQ(CountVertices(&acc7, storage::View::NEW), 0U);
+  EXPECT_EQ(CountVertices(acc7, storage::View::NEW), 0U);
 
   // Commit all accessors
   ASSERT_FALSE(acc1.Commit().HasError());
@@ -457,44 +451,44 @@ TEST(StorageV2, VertexDeleteSerializationError) {
   {
     auto vertex = acc1.FindVertex(gid, storage::View::OLD);
     ASSERT_TRUE(vertex);
-    EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 1U);
 
     {
       auto res = acc1.DeleteVertex(&*vertex);
       ASSERT_TRUE(res.HasValue());
       ASSERT_TRUE(res.GetValue());
-      EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 1U);
-      EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+      EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 1U);
+      EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
     }
 
     {
       auto res = acc1.DeleteVertex(&*vertex);
       ASSERT_TRUE(res.HasValue());
       ASSERT_FALSE(res.GetValue());
-      EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 1U);
-      EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+      EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 1U);
+      EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
     }
 
     acc1.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc1, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc1, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc1, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc1, storage::View::NEW), 0U);
   }
 
   // Delete vertex in accessor 2
   {
     auto vertex = acc2.FindVertex(gid, storage::View::OLD);
     ASSERT_TRUE(vertex);
-    EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 1U);
     auto res = acc2.DeleteVertex(&*vertex);
     ASSERT_TRUE(res.HasError());
     ASSERT_EQ(res.GetError(), storage::Error::SERIALIZATION_ERROR);
-    EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 1U);
     acc2.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc2, storage::View::OLD), 1U);
-    EXPECT_EQ(CountVertices(&acc2, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::OLD), 1U);
+    EXPECT_EQ(CountVertices(acc2, storage::View::NEW), 1U);
   }
 
   // Finalize both accessors
@@ -506,8 +500,8 @@ TEST(StorageV2, VertexDeleteSerializationError) {
     auto acc = store.Access();
     auto vertex = acc.FindVertex(gid, storage::View::OLD);
     ASSERT_FALSE(vertex);
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     ASSERT_FALSE(acc.Commit().HasError());
   }
 }
@@ -525,17 +519,17 @@ TEST(StorageV2, VertexDeleteSpecialCases) {
     auto vertex = acc.CreateVertex();
     gid1 = vertex.Gid();
     ASSERT_FALSE(acc.FindVertex(gid1, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid1, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
     auto res = acc.DeleteVertex(&vertex);
     ASSERT_TRUE(res.HasValue());
     ASSERT_TRUE(res.GetValue());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.Abort();
   }
 
@@ -545,17 +539,17 @@ TEST(StorageV2, VertexDeleteSpecialCases) {
     auto vertex = acc.CreateVertex();
     gid2 = vertex.Gid();
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::OLD).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
     ASSERT_TRUE(acc.FindVertex(gid2, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 1U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 1U);
     auto res = acc.DeleteVertex(&vertex);
     ASSERT_TRUE(res.HasValue());
     ASSERT_TRUE(res.GetValue());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.AdvanceCommand();
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     ASSERT_FALSE(acc.Commit().HasError());
   }
 
@@ -566,8 +560,8 @@ TEST(StorageV2, VertexDeleteSpecialCases) {
     ASSERT_FALSE(acc.FindVertex(gid1, storage::View::NEW).has_value());
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::OLD).has_value());
     ASSERT_FALSE(acc.FindVertex(gid2, storage::View::NEW).has_value());
-    EXPECT_EQ(CountVertices(&acc, storage::View::OLD), 0U);
-    EXPECT_EQ(CountVertices(&acc, storage::View::NEW), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::OLD), 0U);
+    EXPECT_EQ(CountVertices(acc, storage::View::NEW), 0U);
     acc.Abort();
   }
 }
diff --git a/tests/unit/test_utils.hpp b/tests/unit/test_utils.hpp
index 36591d97e..671ca2aec 100644
--- a/tests/unit/test_utils.hpp
+++ b/tests/unit/test_utils.hpp
@@ -18,7 +18,7 @@ TResult ExpectNoError(const char *file, int line, TFunc func, TArgs &&...args) {
   static_assert(std::is_trivially_copyable_v<TFunc>);
   static_assert((std::is_trivially_copyable_v<std::remove_reference_t<TArgs>> && ...));
   TResult result{};
-  EXPECT_EQ(func(args..., &result), MGP_ERROR_NO_ERROR) << fmt::format("Source of error: {} at line {}", file, line);
+  EXPECT_EQ(func(args..., &result), MGP_ERROR_NO_ERROR) << fmt::format("Source of error: {}:{}", file, line);
   return result;
 }
 }  // namespace test_utils