diff --git a/include/query/backend/cpp_old/clause_action.hpp b/include/query/backend/cpp_old/clause_action.hpp index 4829460e2..5c6225acb 100644 --- a/include/query/backend/cpp_old/clause_action.hpp +++ b/include/query/backend/cpp_old/clause_action.hpp @@ -18,5 +18,9 @@ enum class ClauseAction : uint32_t ReturnPack, ReturnProjection, ReturnCount, - ReturnLabels + ReturnLabels, + + UpdateEntityLabels, + UpdateEntityLabels_Identifier, + UpdateEntityLabels_Labels }; diff --git a/include/query/backend/cpp_old/code.hpp b/include/query/backend/cpp_old/code.hpp index e7419147f..bae141747 100644 --- a/include/query/backend/cpp_old/code.hpp +++ b/include/query/backend/cpp_old/code.hpp @@ -86,15 +86,75 @@ const std::string find_and_write_vertices_by_label = " stream.write_meta(\"rw\");\n"; const std::string find_and_write_vertices_by_label_and_properties = - "{0}\n" - " auto properties = query_properties(indices, args);\n" - " auto &label = t.label_find_or_create(\"{1}\");\n" - " stream.write_field(\"{2}\");\n" - " label.index().for_range(t).properties_filter(t, properties).for_all(\n" + "{{\n" + " DbAccessor _t(db);\n" + " {0}\n" + " auto properties = query_properties(indices, args);\n" + " auto &label = _t.label_find_or_create(\"{1}\");\n" + " stream.write_field(\"{2}\");\n" + " label.index().for_range(_t).properties_filter(_t, properties).for_all(\n" + " [&](auto vertex_accessor) -> void {{\n" + " "+ write_vertex_accessor + + " }});\n" + " stream.write_meta(\"rw\");\n" + " _t.commit();" + "}}\n"; + +// -- LABELS +const std::string set_vertex_element = + "{{\n" + " DbAccessor _t(db);" // TODO: HACK (set labels should somehow persist the state) + " {0}\n" + " auto properties = query_properties(indices, args);\n" + " auto &label = _t.label_find_or_create(\"{1}\");\n" + " label.index().for_range(_t).properties_filter(_t, properties).for_all(\n" " [&](auto vertex_accessor) -> void {{\n" - " "+ write_vertex_accessor + - " }});\n" - " stream.write_meta(\"rw\");\n"; + " auto {2} = _t.vertex_property_key(\"{2}\", args[{3}].key.flags());\n" + " vertex_accessor.set({2}, std::move(args[{3}]));\n" + " }}\n" + " );\n" + " _t.commit();\n" + "}}"; + +const std::string set_labels_start = + " {{\n" + " DbAccessor _t(db);" // TODO: HACK (set labels should somehow persist the state) + " {0}\n" + " auto properties = query_properties(indices, args);\n" + " auto &label = _t.label_find_or_create(\"{1}\");\n" + " label.index().for_range(_t).properties_filter(_t, properties).for_all(\n" + " [&](auto vertex_accessor) -> void {{\n"; +const std::string set_label = + " auto &{0} = _t.label_find_or_create(\"{0}\");\n" + " vertex_accessor.add_label({0});\n"; +const std::string set_labels_end = + " }}\n" + " );\n" + " _t.commit();" + " }}"; + +const std::string return_labels = + "{{\n" + " DbAccessor _t(db);" // TODO: HACK (set labels should somehow persist the state) + " {0}\n" + " auto properties = query_properties(indices, args);\n" + " auto &label = _t.label_find_or_create(\"{1}\");\n" + " stream.write_field(\"labels({2})\");\n" + " label.index().for_range(_t).properties_filter(_t, properties).for_all(\n" + " [&](auto vertex_accessor) -> void {{\n" + " auto &labels = vertex_accessor.labels();\n" + " stream.write_record();\n" + " stream.write_list_header(1);\n" // TODO: figure out why + " stream.write_list_header(labels.size());\n" + " for (auto &label : labels) {{\n" + " stream.write(label.get().str());\n" + " }}\n" + " stream.chunk();\n" + " }}\n" + " );\n" + " stream.write_meta(\"rw\");\n" + " _t.commit();\n" + "}}"; const std::string write_all_edges = "stream.write_field(\"{0}\");\n" diff --git a/include/query/backend/cpp_old/handlers/return.hpp b/include/query/backend/cpp_old/handlers/return.hpp index 0867be83e..845388a03 100644 --- a/include/query/backend/cpp_old/handlers/return.hpp +++ b/include/query/backend/cpp_old/handlers/return.hpp @@ -66,8 +66,6 @@ auto return_query_action = } } - - if (cypher_data.source(entity) == EntitySource::TypeIndex) { if (cypher_data.type(entity) == EntityType::Relationship) @@ -116,7 +114,17 @@ auto return_query_action = } if (kv.second == ClauseAction::ReturnLabels) { - // TODO: similar to above + if (cypher_data.source(name) == EntitySource::LabelIndex) + { + auto tags = cypher_data.tags(name); + if (tags.size() == 1) + { + auto label = tags.at(0); + code += code_line(code::return_labels, + cypher_data.print_indices(name), label, + name); + } + } } } diff --git a/include/query/backend/cpp_old/handlers/set.hpp b/include/query/backend/cpp_old/handlers/set.hpp index 324411a3b..970eb0917 100644 --- a/include/query/backend/cpp_old/handlers/set.hpp +++ b/include/query/backend/cpp_old/handlers/set.hpp @@ -7,21 +7,66 @@ auto set_query_action = [](CypherStateData &cypher_data, std::string code = ""; - for (auto const &kv : action_data.actions) { + for (auto const &kv : action_data.actions) + { auto name = kv.first; if (kv.second == ClauseAction::UpdateNode && cypher_data.status(name) == EntityStatus::Matched && - cypher_data.type(name) == EntityType::Node) { + cypher_data.source(name) == EntitySource::InternalId && + cypher_data.type(name) == EntityType::Node) + { code += update_properties(cypher_data, action_data, name); } + if (kv.second == ClauseAction::UpdateNode && + cypher_data.status(name) == EntityStatus::Matched && + cypher_data.source(name) == EntitySource::LabelIndex && + cypher_data.type(name) == EntityType::Node) + { + auto entity_data = action_data.get_entity_property(name); + for (auto &property : entity_data.properties) + { + auto index = action_data.parameter_index.at( + ParameterIndexKey(name, property)); + auto tmp_name = name::unique(); + auto label = cypher_data.tags(name).at(0); + // TODO: move this code inside the loop (in generated code) + code += code_line(code::set_vertex_element, + cypher_data.print_indices(name), label, + property, index); + } + } + if (kv.second == ClauseAction::UpdateRelationship && cypher_data.status(name) == EntityStatus::Matched && - cypher_data.type(name) == EntityType::Relationship) { + cypher_data.type(name) == EntityType::Relationship) + { code += update_properties(cypher_data, action_data, name); } } + for (auto const &set_entity_labels : action_data.label_set_elements) + { + auto &entity = set_entity_labels.entity; + + if (cypher_data.status(entity) == EntityStatus::Matched && + cypher_data.source(entity) == EntitySource::LabelIndex) + { + auto label = cypher_data.tags(entity).at(0); + if (cypher_data.has_properties(entity)) + { + code += code_line(code::set_labels_start, + cypher_data.print_indices(entity), label); + auto labels = set_entity_labels.labels; + for (auto const &set_label : labels) + { + code += code_line(code::set_label, set_label); + } + code += code_line(code::set_labels_end); + } + } + } + return code; }; diff --git a/include/query/backend/cpp_old/query_action_data.hpp b/include/query/backend/cpp_old/query_action_data.hpp index 1ef09db1c..faa7db64a 100644 --- a/include/query/backend/cpp_old/query_action_data.hpp +++ b/include/query/backend/cpp_old/query_action_data.hpp @@ -111,6 +111,22 @@ struct ReturnElement bool is_projection() const { return has_entity() && has_property(); } }; +struct LabelSetElement +{ + std::string entity; + std::vector<std::string> labels; + + LabelSetElement() = default; + LabelSetElement(const LabelSetElement&) = default; + LabelSetElement(LabelSetElement&&) = default; + + void clear() + { + entity.clear(); + labels.clear(); + } +}; + struct QueryActionData { std::map<ParameterIndexKey, uint64_t> parameter_index; @@ -118,6 +134,7 @@ struct QueryActionData std::map<std::string, EntityData> entity_data; std::map<std::string, RelationshipData> relationship_data; std::vector<ReturnElement> return_elements; + std::vector<LabelSetElement> label_set_elements; bool is_detach; CypherStateMachine csm; diff --git a/include/query/frontend/cypher/traverser.hpp b/include/query/frontend/cypher/traverser.hpp index d64660e47..8ca89f083 100644 --- a/include/query/frontend/cypher/traverser.hpp +++ b/include/query/frontend/cypher/traverser.hpp @@ -30,8 +30,8 @@ struct SetElementState void clear() { set_entity = ""; - set_prop = ""; - set_index = -1; + set_prop = ""; + set_index = -1; } }; @@ -52,7 +52,7 @@ struct PropertyState void clear() { - property_name = ""; + property_name = ""; property_index = -1; } }; @@ -79,6 +79,7 @@ private: RelationshipData::Direction direction; SetElementState set_element_state; + LabelSetElement labels_set_element; PropertyState property_state; void clear_state() @@ -95,7 +96,9 @@ private: void finish_query_execution() { generator.add_action(QueryAction::TransactionCommit); + code += generator.generate(); + generator.clear(); } @@ -182,7 +185,7 @@ public: code += generator.generate(); generator.add_action(QueryAction::Create); - state = CypherState::Create; + state = CypherState::Create; query_action = QueryAction::Create; Traverser::visit(ast_create); @@ -194,10 +197,12 @@ public: generator.add_action(QueryAction::Set); - state = CypherState::Set; + state = CypherState::Set; query_action = QueryAction::Set; Traverser::visit(ast_set); + + code += generator.generate(); } void visit(ast::Return &ast_return) override @@ -206,7 +211,7 @@ public: generator.add_action(QueryAction::Return); - state = CypherState::Return; + state = CypherState::Return; query_action = QueryAction::Return; Traverser::visit(ast_return); @@ -225,11 +230,14 @@ public: void visit(ast::Pattern &ast_pattern) override { // TODO: Is that traversal order OK for all cases? Probably NOT. - if (ast_pattern.has_next()) { + if (ast_pattern.has_next()) + { visit(*ast_pattern.next); visit(*ast_pattern.node); visit(*ast_pattern.relationship); - } else { + } + else + { Traverser::visit(ast_pattern); } } @@ -242,17 +250,22 @@ public: if (!ast_node.has_identifier()) return; auto name = ast_node.idn->name; - entity = name; + entity = name; visited_nodes.push_back(name); - if (state == CypherState::Match) { + if (state == CypherState::Match) + { action_data.actions[name] = ClauseAction::MatchNode; } - if (state == CypherState::Create) { - if (cypher_data.status(name) == EntityStatus::Matched) { + if (state == CypherState::Create) + { + if (cypher_data.status(name) == EntityStatus::Matched) + { action_data.actions[name] = ClauseAction::MatchNode; - } else { + } + else + { action_data.actions[name] = ClauseAction::CreateNode; } } @@ -260,7 +273,8 @@ public: Traverser::visit(ast_node); if (cypher_data.status(name) != EntityStatus::Matched && - state == CypherState::Create) { + state == CypherState::Create) + { cypher_data.node_created(name); } } @@ -286,9 +300,9 @@ public: // -- RELATIONSHIP -- void create_relationship(const std::string &name) { - auto &data = generator.action_data(); + auto &data = generator.action_data(); data.actions[name] = ClauseAction::CreateRelationship; - auto nodes = visited_nodes.last_two(); + auto nodes = visited_nodes.last_two(); data.relationship_data.emplace(name, RelationshipData(nodes, direction)); } @@ -301,7 +315,7 @@ public: if (!ast_relationship.has_name()) return; entity = ast_relationship.name(); - using ast_direction = ast::Relationship::Direction; + using ast_direction = ast::Relationship::Direction; using generator_direction = RelationshipData::Direction; if (ast_relationship.direction == ast_direction::Left) @@ -313,15 +327,19 @@ public: // TODO: add suport for Direction::Both // TODO: simplify somehow - if (state == CypherState::Create) { - if (!cypher_data.exist(entity)) { + if (state == CypherState::Create) + { + if (!cypher_data.exist(entity)) + { clause_action = ClauseAction::CreateRelationship; create_relationship(entity); } } - if (state == CypherState::Match) { - if (!cypher_data.exist(entity)) { + if (state == CypherState::Match) + { + if (!cypher_data.exist(entity)) + { action_data.actions[entity] = ClauseAction::MatchRelationship; } } @@ -331,20 +349,25 @@ public: void visit(ast::RelationshipSpecs &ast_relationship_specs) override { - if (state == CypherState::Match) { - if (ast_relationship_specs.has_identifier()) { - auto name = ast_relationship_specs.name(); + if (state == CypherState::Match) + { + if (ast_relationship_specs.has_identifier()) + { + auto name = ast_relationship_specs.name(); auto &cypher_data = generator.cypher_data(); - if (!cypher_data.exist(name)) { - clause_action = ClauseAction::MatchRelationship; - auto &data = generator.action_data(); + if (!cypher_data.exist(name)) + { + clause_action = ClauseAction::MatchRelationship; + auto &data = generator.action_data(); data.actions[name] = ClauseAction::MatchRelationship; } } } - if (state == CypherState::Create) { - if (ast_relationship_specs.has_identifier()) { + if (state == CypherState::Create) + { + if (ast_relationship_specs.has_identifier()) + { entity = ast_relationship_specs.name(); } } @@ -356,7 +379,8 @@ public: { auto &action_data = generator.action_data(); - if (ast_relationship_type_list.has_value()) { + if (ast_relationship_type_list.has_value()) + { auto type = ast_relationship_type_list.value->name; action_data.add_entity_tag(entity, type); action_data.csm.search_cost(entity, @@ -369,6 +393,13 @@ public: void visit(ast::LabelList &ast_label_list) override { + if (state == CypherState::Set) + { + clause_action = ClauseAction::UpdateEntityLabels_Labels; + Traverser::visit(ast_label_list); + return; + } + auto &action_data = generator.action_data(); auto &cypher_data = generator.cypher_data(); @@ -398,12 +429,13 @@ public: Traverser::visit(ast_property); // TODO: too ugly refactor somehow (clear_state part is awful) - if (entity.empty() || !property_state.is_defined()) { + if (entity.empty() || !property_state.is_defined()) + { clear_state(); return; } - auto prop = property_state.property_name; + auto prop = property_state.property_name; auto index = property_state.property_index; // update action data @@ -423,20 +455,33 @@ public: { property_state.property_name = ast_identifier.name; - if (state == CypherState::Delete) { + if (state == CypherState::Delete) + { auto &action_data = generator.action_data(); - auto name = ast_identifier.name; + auto name = ast_identifier.name; auto &cypher_data = generator.cypher_data(); if (cypher_data.type(name) == EntityType::Node) action_data.actions[name] = ClauseAction::DeleteNode; if (cypher_data.type(name) == EntityType::Relationship) action_data.actions[name] = ClauseAction::DeleteRelationship; } + + if (state == CypherState::Set && + clause_action == ClauseAction::UpdateEntityLabels_Identifier) + { + labels_set_element.entity = ast_identifier.name; + } + + if (state == CypherState::Set && + clause_action == ClauseAction::UpdateEntityLabels_Labels) + { + labels_set_element.labels.emplace_back(ast_identifier.name); + } } void visit(ast::Long &ast_long) override { - set_element_state.set_index = ast_long.value; + set_element_state.set_index = ast_long.value; property_state.property_index = ast_long.value; } @@ -454,17 +499,18 @@ public: { Traverser::visit(ast_set_element); - if (!set_element_state.is_defined()) { + if (!set_element_state.is_defined()) + { clear_state(); return; } auto entity = set_element_state.set_entity; - auto prop = set_element_state.set_prop; - auto index = set_element_state.set_index; + auto prop = set_element_state.set_prop; + auto index = set_element_state.set_index; auto &cypher_data = generator.cypher_data(); - auto entity_type = cypher_data.type(entity); + auto entity_type = cypher_data.type(entity); if (entity_type == EntityType::None) throw CypherSemanticError("Entity (" + entity + ") doesn't exist"); @@ -489,12 +535,16 @@ public: auto &action_data = generator.action_data(); - if (state == CypherState::Return) { + if (state == CypherState::Return) + { auto &return_elements = action_data.return_elements; - auto &entity = ast_accessor.entity_name(); - if (!ast_accessor.has_prop()) { + auto &entity = ast_accessor.entity_name(); + if (!ast_accessor.has_prop()) + { return_elements.emplace_back(ReturnElement(entity)); - } else { + } + else + { auto &property = ast_accessor.entity_prop(); return_elements.emplace_back(ReturnElement(entity, property)); } @@ -502,9 +552,10 @@ public: if (!ast_accessor.has_prop()) return; - if (state == CypherState::Set) { + if (state == CypherState::Set) + { set_element_state.set_entity = ast_accessor.entity_name(); - set_element_state.set_prop = ast_accessor.entity_prop(); + set_element_state.set_prop = ast_accessor.entity_prop(); } } @@ -513,6 +564,21 @@ public: Traverser::visit(ast_set_value); } + void visit(ast::LabelSetElement &ast_label_set_element) override + { + clause_action = ClauseAction::UpdateEntityLabels_Identifier; + + labels_set_element.clear(); + + Traverser::visit(ast_label_set_element); + + auto &action_data = generator.action_data(); + + action_data.label_set_elements.emplace_back(std::move(labels_set_element)); + + clause_action = ClauseAction::Undefined; + } + void visit(ast::Delete &ast_delete) override { code += generator.generate(); @@ -536,4 +602,10 @@ public: action_data.actions[ast_count.argument] = ClauseAction::ReturnCount; // } } + + void visit(ast::LabelsFunction &ast_label) override + { + auto &action_data = generator.action_data(); + action_data.actions[ast_label.argument] = ClauseAction::ReturnLabels; + } }; diff --git a/include/query/language/cypher/ast/ast_visitor.hpp b/include/query/language/cypher/ast/ast_visitor.hpp index 2cb8fb2d3..aa130d966 100644 --- a/include/query/language/cypher/ast/ast_visitor.hpp +++ b/include/query/language/cypher/ast/ast_visitor.hpp @@ -70,6 +70,7 @@ struct ReadWriteQuery; struct SetKey; struct SetValue; struct SetElement; +struct LabelSetElement; struct SetList; struct WithList; @@ -87,8 +88,8 @@ struct AstVisitor PatternList, Match, ReadQuery, Start, Where, WriteQuery, Create, Return, Distinct, Delete, DeleteQuery, UpdateQuery, Set, SetKey, ReadWriteQuery, IdentifierList, WithList, WithClause, WithQuery, Long, - CountFunction, LabelsFunction, - InternalIdExpr, SetValue, SetElement, SetList> + CountFunction, LabelsFunction, InternalIdExpr, SetValue, SetElement, + LabelSetElement, SetList> { }; } diff --git a/include/query/language/cypher/ast/set.hpp b/include/query/language/cypher/ast/set.hpp index c7e50887a..8ba49ad37 100644 --- a/include/query/language/cypher/ast/set.hpp +++ b/include/query/language/cypher/ast/set.hpp @@ -35,7 +35,19 @@ struct SetValue : public AstNode<SetValue> bool has_value() const { return value != nullptr; } }; -struct SetElement : public AstNode<SetElement> +struct SetElementBase : public AstVisitable +{ +}; + +template <class Derived> +struct SetElementDerivedBase : public Crtp<Derived>, public SetElementBase +{ + using uptr = std::unique_ptr<Derived>; + + virtual void accept(AstVisitor &visitor) { visitor.visit(this->derived()); } +}; + +struct SetElement : public SetElementDerivedBase<SetElement> { SetElement(Accessor* accessor, SetValue* set_value) : accessor(accessor), set_value(set_value) {} @@ -47,7 +59,19 @@ struct SetElement : public AstNode<SetElement> bool has_value() const { return set_value != nullptr; } }; -struct SetList : public List<SetElement, SetList> +struct LabelSetElement : public SetElementDerivedBase<LabelSetElement> +{ + LabelSetElement(Identifier* identifier, LabelList* label_list) + : identifier(identifier), label_list(label_list) {} + + Identifier* identifier; + LabelList* label_list; + + bool has_identifier() const { return identifier != nullptr; } + bool has_label_list() const { return label_list != nullptr; } +}; + +struct SetList : public List<SetElementBase, SetList> { using List::List; }; diff --git a/include/query/language/cypher/cypher.y b/include/query/language/cypher/cypher.y index fa3e81085..8dfaee29a 100644 --- a/include/query/language/cypher/cypher.y +++ b/include/query/language/cypher/cypher.y @@ -544,12 +544,16 @@ set_list(L) ::= set_element(E). { L = ast->create<ast::SetList>(E, nullptr); } -%type set_element {ast::SetElement*} +%type set_element {ast::SetElementBase*} set_element(E) ::= accessor(A) EQ set_value(V). { E = ast->create<ast::SetElement>(A, V); } +set_element(E) ::= idn(I) label_idn(L). { + E = ast->create<ast::LabelSetElement>(I, L); +} + %type accessor {ast::Accessor*} accessor(A) ::= idn(E) DOT idn(P). { diff --git a/include/query/language/cypher/debug/tree_print.hpp b/include/query/language/cypher/debug/tree_print.hpp index 89f29b7e2..6d555f940 100644 --- a/include/query/language/cypher/debug/tree_print.hpp +++ b/include/query/language/cypher/debug/tree_print.hpp @@ -395,6 +395,12 @@ public: Traverser::visit(set_element); } + void visit(ast::LabelSetElement &label_set_element) override + { + auto entry = printer.advance("Label Set Element"); + Traverser::visit(label_set_element); + } + void visit(ast::SetList &set_list) override { auto entry = printer.advance("Set List"); diff --git a/include/query/language/cypher/visitor/traverser.hpp b/include/query/language/cypher/visitor/traverser.hpp index be1d3cc92..d73ffe026 100644 --- a/include/query/language/cypher/visitor/traverser.hpp +++ b/include/query/language/cypher/visitor/traverser.hpp @@ -244,6 +244,12 @@ public: accept(set_element.set_value); } + void visit(ast::LabelSetElement& label_set_element) override + { + accept(label_set_element.identifier); + accept(label_set_element.label_list); + } + void visit(ast::SetList& set_list) override { accept(set_list.value); diff --git a/tests/integration/scenario.py b/tests/integration/scenario.py index 1c0d7a0e1..91d011739 100644 --- a/tests/integration/scenario.py +++ b/tests/integration/scenario.py @@ -4,18 +4,21 @@ import logging # TODO: auto import -from scenario import no_000001 +# TODO: better logging (scenario visibility) +# TODO: result storage +from scenario import no_000001, no_000002 from neo4j.v1 import GraphDatabase, basic_auth, types, Node -scenarios = [no_000001.scenario_1] +# scenarios = [no_000001.scenario_1, no_000002.scenario_2] +scenarios = [no_000002.scenario_2] # logging init log = logging.getLogger(__name__) # initialize driver and create session session = GraphDatabase.driver("bolt://localhost", - auth=basic_auth("neo4j", "1234"), - encrypted=0).session() + auth=basic_auth("neo4j", "1234"), + encrypted=0).session() def check(condition, scenario_no, message): @@ -55,6 +58,7 @@ if __name__ == "__main__": # in case that the result is single if count == 1: + # extract properties from record record = records[0] record_name, = record diff --git a/tests/integration/scenario/no_000001.py b/tests/integration/scenario/no_000001.py index 634cca994..0555264ab 100644 --- a/tests/integration/scenario/no_000001.py +++ b/tests/integration/scenario/no_000001.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- __author__ = "Marko Budiselic" +__date__ = "2016_11_02" class Scenario1: ''' diff --git a/tests/integration/scenario/no_000002.py b/tests/integration/scenario/no_000002.py new file mode 100644 index 000000000..8befeed63 --- /dev/null +++ b/tests/integration/scenario/no_000002.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "Marko Budiselic" +__date__ = "2016_11_03" + +class Scenario2: + ''' + Create node, edit labels and return all labels for the node. + ''' + + def __init__(self): + ''' + Constructor + ''' + self.no = 2 + self.desctiption = "Create node, edit labels and return all labels for the node." + self.init_graph = [] + self.queries = [ + ("MATCH (n) DETACH DELETE n", (0, None)), + ("CREATE (n:Garment {garment_id: 1234, garment_category_id: 1}) RETURN n", (1, [{"garment_id": 1234, "garment_category_id": 1}])), + ("MATCH (g:Garment {garment_id: 1234}) SET g:FF RETURN labels(g)", (1, [["Garment", "FF"]])), + ("MATCH(g:Garment {garment_id: 1234}) SET g.reveals = 50 RETURN g", (1, [{"garment_id": 1234, "garment_category_id": 1, "reveals": 50}])), + ("MATCH (n) RETURN n", (1, [{"garment_id": 1234, "garment_category_id": 1, "reveals": 50}])), + ] + self.side_effects = None + +scenario_2 = Scenario2()