Fix schema query module (#1510)
This commit is contained in:
parent
39ee248d34
commit
cb4d4db813
@ -108,31 +108,83 @@ void Schema::ProcessPropertiesRel(mgp::Record &record, const std::string_view &t
|
|||||||
record.Insert(std::string(kReturnMandatory).c_str(), mandatory);
|
record.Insert(std::string(kReturnMandatory).c_str(), mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Property {
|
||||||
|
std::string name;
|
||||||
|
mgp::Value value;
|
||||||
|
|
||||||
|
Property(const std::string &name, mgp::Value &&value) : name(name), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LabelsHash {
|
||||||
|
std::size_t operator()(const std::set<std::string> &set) const {
|
||||||
|
std::size_t seed = set.size();
|
||||||
|
for (const auto &i : set) {
|
||||||
|
seed ^= std::hash<std::string>{}(i) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LabelsComparator {
|
||||||
|
bool operator()(const std::set<std::string> &lhs, const std::set<std::string> &rhs) const { return lhs == rhs; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyComparator {
|
||||||
|
bool operator()(const Property &lhs, const Property &rhs) const { return lhs.name < rhs.name; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyInfo {
|
||||||
|
std::set<Property, PropertyComparator> properties;
|
||||||
|
bool mandatory;
|
||||||
|
};
|
||||||
|
|
||||||
void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result,
|
void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result,
|
||||||
mgp_memory *memory) {
|
mgp_memory *memory) {
|
||||||
mgp::MemoryDispatcherGuard guard{memory};
|
mgp::MemoryDispatcherGuard guard{memory};
|
||||||
const auto record_factory = mgp::RecordFactory(result);
|
const auto record_factory = mgp::RecordFactory(result);
|
||||||
try {
|
try {
|
||||||
const mgp::Graph graph = mgp::Graph(memgraph_graph);
|
std::unordered_map<std::set<std::string>, PropertyInfo, LabelsHash, LabelsComparator> node_types_properties;
|
||||||
for (auto node : graph.Nodes()) {
|
|
||||||
std::string type;
|
for (auto node : mgp::Graph(memgraph_graph).Nodes()) {
|
||||||
mgp::List labels = mgp::List();
|
std::set<std::string> labels_set = {};
|
||||||
for (auto label : node.Labels()) {
|
for (auto label : node.Labels()) {
|
||||||
labels.AppendExtend(mgp::Value(label));
|
labels_set.emplace(label);
|
||||||
type += ":`" + std::string(label) + "`";
|
}
|
||||||
|
|
||||||
|
if (node_types_properties.find(labels_set) == node_types_properties.end()) {
|
||||||
|
node_types_properties[labels_set] = PropertyInfo{std::set<Property, PropertyComparator>(), true};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.Properties().empty()) {
|
if (node.Properties().empty()) {
|
||||||
auto record = record_factory.NewRecord();
|
node_types_properties[labels_set].mandatory = false; // if there is node with no property, it is not mandatory
|
||||||
ProcessPropertiesNode<std::string>(record, type, labels, "", "", false);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &property_info = node_types_properties.at(labels_set);
|
||||||
for (auto &[key, prop] : node.Properties()) {
|
for (auto &[key, prop] : node.Properties()) {
|
||||||
auto property_type = mgp::List();
|
property_info.properties.emplace(key, std::move(prop));
|
||||||
|
if (property_info.mandatory) {
|
||||||
|
property_info.mandatory =
|
||||||
|
property_info.properties.size() == 1; // if there is only one property, it is mandatory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[labels, property_info] : node_types_properties) {
|
||||||
|
std::string label_type;
|
||||||
|
mgp::List labels_list = mgp::List();
|
||||||
|
for (auto const &label : labels) {
|
||||||
|
label_type += ":`" + std::string(label) + "`";
|
||||||
|
labels_list.AppendExtend(mgp::Value(label));
|
||||||
|
}
|
||||||
|
for (auto const &prop : property_info.properties) {
|
||||||
auto record = record_factory.NewRecord();
|
auto record = record_factory.NewRecord();
|
||||||
property_type.AppendExtend(mgp::Value(TypeOf(prop.Type())));
|
ProcessPropertiesNode(record, label_type, labels_list, prop.name, TypeOf(prop.value.Type()),
|
||||||
ProcessPropertiesNode<mgp::List>(record, type, labels, key, property_type, true);
|
property_info.mandatory);
|
||||||
|
}
|
||||||
|
if (property_info.properties.empty()) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
ProcessPropertiesNode<std::string>(record, label_type, labels_list, "", "", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,23 +196,41 @@ void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph,
|
|||||||
|
|
||||||
void Schema::RelTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
void Schema::RelTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||||
mgp::MemoryDispatcherGuard guard{memory};
|
mgp::MemoryDispatcherGuard guard{memory};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, PropertyInfo> rel_types_properties;
|
||||||
const auto record_factory = mgp::RecordFactory(result);
|
const auto record_factory = mgp::RecordFactory(result);
|
||||||
try {
|
try {
|
||||||
const mgp::Graph graph = mgp::Graph(memgraph_graph);
|
const mgp::Graph graph = mgp::Graph(memgraph_graph);
|
||||||
|
|
||||||
for (auto rel : graph.Relationships()) {
|
for (auto rel : graph.Relationships()) {
|
||||||
std::string type = ":`" + std::string(rel.Type()) + "`";
|
std::string rel_type = std::string(rel.Type());
|
||||||
|
if (rel_types_properties.find(rel_type) == rel_types_properties.end()) {
|
||||||
|
rel_types_properties[rel_type] = PropertyInfo{std::set<Property, PropertyComparator>(), true};
|
||||||
|
}
|
||||||
|
|
||||||
if (rel.Properties().empty()) {
|
if (rel.Properties().empty()) {
|
||||||
auto record = record_factory.NewRecord();
|
rel_types_properties[rel_type].mandatory = false; // if there is rel with no property, it is not mandatory
|
||||||
ProcessPropertiesRel<std::string>(record, type, "", "", false);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &property_info = rel_types_properties.at(rel_type);
|
||||||
for (auto &[key, prop] : rel.Properties()) {
|
for (auto &[key, prop] : rel.Properties()) {
|
||||||
auto property_type = mgp::List();
|
property_info.properties.emplace(key, std::move(prop));
|
||||||
|
if (property_info.mandatory) {
|
||||||
|
property_info.mandatory =
|
||||||
|
property_info.properties.size() == 1; // if there is only one property, it is mandatory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[type, property_info] : rel_types_properties) {
|
||||||
|
std::string type_str = ":`" + std::string(type) + "`";
|
||||||
|
for (auto const &prop : property_info.properties) {
|
||||||
auto record = record_factory.NewRecord();
|
auto record = record_factory.NewRecord();
|
||||||
property_type.AppendExtend(mgp::Value(TypeOf(prop.Type())));
|
ProcessPropertiesRel(record, type_str, prop.name, TypeOf(prop.value.Type()), property_info.mandatory);
|
||||||
ProcessPropertiesRel<mgp::List>(record, type, key, property_type, true);
|
}
|
||||||
|
if (property_info.properties.empty()) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
ProcessPropertiesRel<std::string>(record, type_str, "", "", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,7 +431,7 @@ def test_node_type_properties1():
|
|||||||
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
)[0]
|
)[0]
|
||||||
)
|
)
|
||||||
assert (result) == [":`Activity`", ["Activity"], "location", ["String"], True]
|
assert (result) == [":`Activity`", ["Activity"], "location", "String", False]
|
||||||
|
|
||||||
result = list(
|
result = list(
|
||||||
execute_and_fetch_all(
|
execute_and_fetch_all(
|
||||||
@ -439,7 +439,7 @@ def test_node_type_properties1():
|
|||||||
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
)[1]
|
)[1]
|
||||||
)
|
)
|
||||||
assert (result) == [":`Activity`", ["Activity"], "name", ["String"], True]
|
assert (result) == [":`Activity`", ["Activity"], "name", "String", False]
|
||||||
|
|
||||||
result = list(
|
result = list(
|
||||||
execute_and_fetch_all(
|
execute_and_fetch_all(
|
||||||
@ -447,7 +447,7 @@ def test_node_type_properties1():
|
|||||||
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
)[2]
|
)[2]
|
||||||
)
|
)
|
||||||
assert (result) == [":`Dog`", ["Dog"], "name", ["String"], True]
|
assert (result) == [":`Dog`", ["Dog"], "name", "String", False]
|
||||||
|
|
||||||
result = list(
|
result = list(
|
||||||
execute_and_fetch_all(
|
execute_and_fetch_all(
|
||||||
@ -455,7 +455,81 @@ def test_node_type_properties1():
|
|||||||
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
)[3]
|
)[3]
|
||||||
)
|
)
|
||||||
assert (result) == [":`Dog`", ["Dog"], "owner", ["String"], True]
|
assert (result) == [":`Dog`", ["Dog"], "owner", "String", False]
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_type_properties2():
|
||||||
|
cursor = connect().cursor()
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"""
|
||||||
|
CREATE (d:MyNode)
|
||||||
|
CREATE (n:MyNode)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
|
)
|
||||||
|
assert (list(result[0])) == [":`MyNode`", ["MyNode"], "", "", False]
|
||||||
|
assert (result.__len__()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_type_properties3():
|
||||||
|
cursor = connect().cursor()
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"""
|
||||||
|
CREATE (d:Dog {name: 'Rex', owner: 'Carl'})
|
||||||
|
CREATE (n:Dog)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (list(result[0])) == [":`Dog`", ["Dog"], "name", "String", False]
|
||||||
|
assert (list(result[1])) == [":`Dog`", ["Dog"], "owner", "String", False]
|
||||||
|
assert (result.__len__()) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_type_properties4():
|
||||||
|
cursor = connect().cursor()
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"""
|
||||||
|
CREATE (n:Label1:Label2 {property1: 'value1', property2: 'value2'})
|
||||||
|
CREATE (m:Label2:Label1 {property3: 'value3'})
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = list(
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert (list(result[0])) == [":`Label1`:`Label2`", ["Label1", "Label2"], "property1", "String", False]
|
||||||
|
assert (list(result[1])) == [":`Label1`:`Label2`", ["Label1", "Label2"], "property2", "String", False]
|
||||||
|
assert (list(result[2])) == [":`Label1`:`Label2`", ["Label1", "Label2"], "property3", "String", False]
|
||||||
|
assert (result.__len__()) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_type_properties5():
|
||||||
|
cursor = connect().cursor()
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"""
|
||||||
|
CREATE (d:Dog {name: 'Rex'})
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
f"CALL libschema.node_type_properties() YIELD nodeType, nodeLabels, propertyName, propertyTypes , mandatory RETURN nodeType, nodeLabels, propertyName, propertyTypes , mandatory ORDER BY propertyName, nodeLabels[0];",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (list(result[0])) == [":`Dog`", ["Dog"], "name", "String", True]
|
||||||
|
assert (result.__len__()) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_rel_type_properties1():
|
def test_rel_type_properties1():
|
||||||
@ -473,5 +547,38 @@ def test_rel_type_properties1():
|
|||||||
assert (result) == [":`LOVES`", "", "", False]
|
assert (result) == [":`LOVES`", "", "", False]
|
||||||
|
|
||||||
|
|
||||||
|
def test_rel_type_properties2():
|
||||||
|
cursor = connect().cursor()
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"""
|
||||||
|
CREATE (d:Dog {name: 'Rex', owner: 'Carl'})-[l:LOVES]->(a:Activity {name: 'Running', location: 'Zadar'})
|
||||||
|
CREATE (n:Dog {name: 'Simba', owner: 'Lucy'})-[j:LOVES {duration: 30}]->(b:Activity {name: 'Running', location: 'Zadar'})
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
f"CALL libschema.rel_type_properties() YIELD relType,propertyName, propertyTypes , mandatory RETURN relType, propertyName, propertyTypes , mandatory;",
|
||||||
|
)
|
||||||
|
assert (list(result[0])) == [":`LOVES`", "duration", "Int", False]
|
||||||
|
assert (result.__len__()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_rel_type_properties3():
|
||||||
|
cursor = connect().cursor()
|
||||||
|
execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
"""
|
||||||
|
CREATE (n:Dog {name: 'Simba', owner: 'Lucy'})-[j:LOVES {duration: 30}]->(b:Activity {name: 'Running', location: 'Zadar'})
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = execute_and_fetch_all(
|
||||||
|
cursor,
|
||||||
|
f"CALL libschema.rel_type_properties() YIELD relType,propertyName, propertyTypes , mandatory RETURN relType, propertyName, propertyTypes , mandatory;",
|
||||||
|
)
|
||||||
|
assert (list(result[0])) == [":`LOVES`", "duration", "Int", True]
|
||||||
|
assert (result.__len__()) == 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(pytest.main([__file__, "-rA"]))
|
sys.exit(pytest.main([__file__, "-rA"]))
|
||||||
|
Loading…
Reference in New Issue
Block a user