Skip to content

Commit cb4d4db

Browse files
authored
Fix schema query module (memgraph#1510)
1 parent 39ee248 commit cb4d4db

2 files changed

Lines changed: 199 additions & 22 deletions

File tree

query_modules/schema.cpp

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,31 +108,83 @@ void Schema::ProcessPropertiesRel(mgp::Record &record, const std::string_view &t
108108
record.Insert(std::string(kReturnMandatory).c_str(), mandatory);
109109
}
110110

111+
struct Property {
112+
std::string name;
113+
mgp::Value value;
114+
115+
Property(const std::string &name, mgp::Value &&value) : name(name), value(std::move(value)) {}
116+
};
117+
118+
struct LabelsHash {
119+
std::size_t operator()(const std::set<std::string> &set) const {
120+
std::size_t seed = set.size();
121+
for (const auto &i : set) {
122+
seed ^= std::hash<std::string>{}(i) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
123+
}
124+
return seed;
125+
}
126+
};
127+
128+
struct LabelsComparator {
129+
bool operator()(const std::set<std::string> &lhs, const std::set<std::string> &rhs) const { return lhs == rhs; }
130+
};
131+
132+
struct PropertyComparator {
133+
bool operator()(const Property &lhs, const Property &rhs) const { return lhs.name < rhs.name; }
134+
};
135+
136+
struct PropertyInfo {
137+
std::set<Property, PropertyComparator> properties;
138+
bool mandatory;
139+
};
140+
111141
void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result,
112142
mgp_memory *memory) {
113143
mgp::MemoryDispatcherGuard guard{memory};
114144
const auto record_factory = mgp::RecordFactory(result);
115145
try {
116-
const mgp::Graph graph = mgp::Graph(memgraph_graph);
117-
for (auto node : graph.Nodes()) {
118-
std::string type;
119-
mgp::List labels = mgp::List();
146+
std::unordered_map<std::set<std::string>, PropertyInfo, LabelsHash, LabelsComparator> node_types_properties;
147+
148+
for (auto node : mgp::Graph(memgraph_graph).Nodes()) {
149+
std::set<std::string> labels_set = {};
120150
for (auto label : node.Labels()) {
121-
labels.AppendExtend(mgp::Value(label));
122-
type += ":`" + std::string(label) + "`";
151+
labels_set.emplace(label);
152+
}
153+
154+
if (node_types_properties.find(labels_set) == node_types_properties.end()) {
155+
node_types_properties[labels_set] = PropertyInfo{std::set<Property, PropertyComparator>(), true};
123156
}
124157

125158
if (node.Properties().empty()) {
126-
auto record = record_factory.NewRecord();
127-
ProcessPropertiesNode<std::string>(record, type, labels, "", "", false);
159+
node_types_properties[labels_set].mandatory = false; // if there is node with no property, it is not mandatory
128160
continue;
129161
}
130162

163+
auto &property_info = node_types_properties.at(labels_set);
131164
for (auto &[key, prop] : node.Properties()) {
132-
auto property_type = mgp::List();
165+
property_info.properties.emplace(key, std::move(prop));
166+
if (property_info.mandatory) {
167+
property_info.mandatory =
168+
property_info.properties.size() == 1; // if there is only one property, it is mandatory
169+
}
170+
}
171+
}
172+
173+
for (auto &[labels, property_info] : node_types_properties) {
174+
std::string label_type;
175+
mgp::List labels_list = mgp::List();
176+
for (auto const &label : labels) {
177+
label_type += ":`" + std::string(label) + "`";
178+
labels_list.AppendExtend(mgp::Value(label));
179+
}
180+
for (auto const &prop : property_info.properties) {
181+
auto record = record_factory.NewRecord();
182+
ProcessPropertiesNode(record, label_type, labels_list, prop.name, TypeOf(prop.value.Type()),
183+
property_info.mandatory);
184+
}
185+
if (property_info.properties.empty()) {
133186
auto record = record_factory.NewRecord();
134-
property_type.AppendExtend(mgp::Value(TypeOf(prop.Type())));
135-
ProcessPropertiesNode<mgp::List>(record, type, labels, key, property_type, true);
187+
ProcessPropertiesNode<std::string>(record, label_type, labels_list, "", "", false);
136188
}
137189
}
138190

@@ -144,23 +196,41 @@ void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph,
144196

145197
void Schema::RelTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
146198
mgp::MemoryDispatcherGuard guard{memory};
199+
200+
std::unordered_map<std::string, PropertyInfo> rel_types_properties;
147201
const auto record_factory = mgp::RecordFactory(result);
148202
try {
149203
const mgp::Graph graph = mgp::Graph(memgraph_graph);
150-
151204
for (auto rel : graph.Relationships()) {
152-
std::string type = ":`" + std::string(rel.Type()) + "`";
205+
std::string rel_type = std::string(rel.Type());
206+
if (rel_types_properties.find(rel_type) == rel_types_properties.end()) {
207+
rel_types_properties[rel_type] = PropertyInfo{std::set<Property, PropertyComparator>(), true};
208+
}
209+
153210
if (rel.Properties().empty()) {
154-
auto record = record_factory.NewRecord();
155-
ProcessPropertiesRel<std::string>(record, type, "", "", false);
211+
rel_types_properties[rel_type].mandatory = false; // if there is rel with no property, it is not mandatory
156212
continue;
157213
}
158214

215+
auto &property_info = rel_types_properties.at(rel_type);
159216
for (auto &[key, prop] : rel.Properties()) {
160-
auto property_type = mgp::List();
217+
property_info.properties.emplace(key, std::move(prop));
218+
if (property_info.mandatory) {
219+
property_info.mandatory =
220+
property_info.properties.size() == 1; // if there is only one property, it is mandatory
221+
}
222+
}
223+
}
224+
225+
for (auto &[type, property_info] : rel_types_properties) {
226+
std::string type_str = ":`" + std::string(type) + "`";
227+
for (auto const &prop : property_info.properties) {
228+
auto record = record_factory.NewRecord();
229+
ProcessPropertiesRel(record, type_str, prop.name, TypeOf(prop.value.Type()), property_info.mandatory);
230+
}
231+
if (property_info.properties.empty()) {
161232
auto record = record_factory.NewRecord();
162-
property_type.AppendExtend(mgp::Value(TypeOf(prop.Type())));
163-
ProcessPropertiesRel<mgp::List>(record, type, key, property_type, true);
233+
ProcessPropertiesRel<std::string>(record, type_str, "", "", false);
164234
}
165235
}
166236

tests/e2e/query_modules/schema_test.py

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,31 +431,105 @@ def test_node_type_properties1():
431431
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
432432
)[0]
433433
)
434-
assert (result) == [":`Activity`", ["Activity"], "location", ["String"], True]
434+
assert (result) == [":`Activity`", ["Activity"], "location", "String", False]
435435

436436
result = list(
437437
execute_and_fetch_all(
438438
cursor,
439439
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
440440
)[1]
441441
)
442-
assert (result) == [":`Activity`", ["Activity"], "name", ["String"], True]
442+
assert (result) == [":`Activity`", ["Activity"], "name", "String", False]
443443

444444
result = list(
445445
execute_and_fetch_all(
446446
cursor,
447447
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
448448
)[2]
449449
)
450-
assert (result) == [":`Dog`", ["Dog"], "name", ["String"], True]
450+
assert (result) == [":`Dog`", ["Dog"], "name", "String", False]
451451

452452
result = list(
453453
execute_and_fetch_all(
454454
cursor,
455455
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
456456
)[3]
457457
)
458-
assert (result) == [":`Dog`", ["Dog"], "owner", ["String"], True]
458+
assert (result) == [":`Dog`", ["Dog"], "owner", "String", False]
459+
460+
461+
def test_node_type_properties2():
462+
cursor = connect().cursor()
463+
execute_and_fetch_all(
464+
cursor,
465+
"""
466+
CREATE (d:MyNode)
467+
CREATE (n:MyNode)
468+
""",
469+
)
470+
result = execute_and_fetch_all(
471+
cursor,
472+
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
473+
)
474+
assert (list(result[0])) == [":`MyNode`", ["MyNode"], "", "", False]
475+
assert (result.__len__()) == 1
476+
477+
478+
def test_node_type_properties3():
479+
cursor = connect().cursor()
480+
execute_and_fetch_all(
481+
cursor,
482+
"""
483+
CREATE (d:Dog {name: 'Rex', owner: 'Carl'})
484+
CREATE (n:Dog)
485+
""",
486+
)
487+
result = execute_and_fetch_all(
488+
cursor,
489+
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
490+
)
491+
492+
assert (list(result[0])) == [":`Dog`", ["Dog"], "name", "String", False]
493+
assert (list(result[1])) == [":`Dog`", ["Dog"], "owner", "String", False]
494+
assert (result.__len__()) == 2
495+
496+
497+
def test_node_type_properties4():
498+
cursor = connect().cursor()
499+
execute_and_fetch_all(
500+
cursor,
501+
"""
502+
CREATE (n:Label1:Label2 {property1: 'value1', property2: 'value2'})
503+
CREATE (m:Label2:Label1 {property3: 'value3'})
504+
""",
505+
)
506+
result = list(
507+
execute_and_fetch_all(
508+
cursor,
509+
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
510+
)
511+
)
512+
assert (list(result[0])) == [":`Label1`:`Label2`", ["Label1", "Label2"], "property1", "String", False]
513+
assert (list(result[1])) == [":`Label1`:`Label2`", ["Label1", "Label2"], "property2", "String", False]
514+
assert (list(result[2])) == [":`Label1`:`Label2`", ["Label1", "Label2"], "property3", "String", False]
515+
assert (result.__len__()) == 3
516+
517+
518+
def test_node_type_properties5():
519+
cursor = connect().cursor()
520+
execute_and_fetch_all(
521+
cursor,
522+
"""
523+
CREATE (d:Dog {name: 'Rex'})
524+
""",
525+
)
526+
result = execute_and_fetch_all(
527+
cursor,
528+
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
529+
)
530+
531+
assert (list(result[0])) == [":`Dog`", ["Dog"], "name", "String", True]
532+
assert (result.__len__()) == 1
459533

460534

461535
def test_rel_type_properties1():
@@ -473,5 +547,38 @@ def test_rel_type_properties1():
473547
assert (result) == [":`LOVES`", "", "", False]
474548

475549

550+
def test_rel_type_properties2():
551+
cursor = connect().cursor()
552+
execute_and_fetch_all(
553+
cursor,
554+
"""
555+
CREATE (d:Dog {name: 'Rex', owner: 'Carl'})-[l:LOVES]->(a:Activity {name: 'Running', location: 'Zadar'})
556+
CREATE (n:Dog {name: 'Simba', owner: 'Lucy'})-[j:LOVES {duration: 30}]->(b:Activity {name: 'Running', location: 'Zadar'})
557+
""",
558+
)
559+
result = execute_and_fetch_all(
560+
cursor,
561+
f"CALL libschema.rel_type_properties() YIELD relType,propertyName, propertyTypes , mandatory RETURN relType, propertyName, propertyTypes , mandatory;",
562+
)
563+
assert (list(result[0])) == [":`LOVES`", "duration", "Int", False]
564+
assert (result.__len__()) == 1
565+
566+
567+
def test_rel_type_properties3():
568+
cursor = connect().cursor()
569+
execute_and_fetch_all(
570+
cursor,
571+
"""
572+
CREATE (n:Dog {name: 'Simba', owner: 'Lucy'})-[j:LOVES {duration: 30}]->(b:Activity {name: 'Running', location: 'Zadar'})
573+
""",
574+
)
575+
result = execute_and_fetch_all(
576+
cursor,
577+
f"CALL libschema.rel_type_properties() YIELD relType,propertyName, propertyTypes , mandatory RETURN relType, propertyName, propertyTypes , mandatory;",
578+
)
579+
assert (list(result[0])) == [":`LOVES`", "duration", "Int", True]
580+
assert (result.__len__()) == 1
581+
582+
476583
if __name__ == "__main__":
477584
sys.exit(pytest.main([__file__, "-rA"]))

0 commit comments

Comments
 (0)