Add schema.assert (#1485)

This commit is contained in:
Andi 2023-11-21 09:19:50 +01:00 committed by GitHub
parent d03fafcef6
commit 1d90b60f56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1373 additions and 39 deletions

View File

@ -236,6 +236,54 @@ inline mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp
// mgp_graph
inline bool create_label_index(mgp_graph *graph, const char *label) {
return MgInvoke<int>(mgp_create_label_index, graph, label);
}
inline bool drop_label_index(mgp_graph *graph, const char *label) {
return MgInvoke<int>(mgp_drop_label_index, graph, label);
}
inline mgp_list *list_all_label_indices(mgp_graph *graph, mgp_memory *memory) {
return MgInvoke<mgp_list *>(mgp_list_all_label_indices, graph, memory);
}
inline bool create_label_property_index(mgp_graph *graph, const char *label, const char *property) {
return MgInvoke<int>(mgp_create_label_property_index, graph, label, property);
}
inline bool drop_label_property_index(mgp_graph *graph, const char *label, const char *property) {
return MgInvoke<int>(mgp_drop_label_property_index, graph, label, property);
}
inline mgp_list *list_all_label_property_indices(mgp_graph *graph, mgp_memory *memory) {
return MgInvoke<mgp_list *>(mgp_list_all_label_property_indices, graph, memory);
}
inline bool create_existence_constraint(mgp_graph *graph, const char *label, const char *property) {
return MgInvoke<int>(mgp_create_existence_constraint, graph, label, property);
}
inline bool drop_existence_constraint(mgp_graph *graph, const char *label, const char *property) {
return MgInvoke<int>(mgp_drop_existence_constraint, graph, label, property);
}
inline mgp_list *list_all_existence_constraints(mgp_graph *graph, mgp_memory *memory) {
return MgInvoke<mgp_list *>(mgp_list_all_existence_constraints, graph, memory);
}
inline bool create_unique_constraint(mgp_graph *memgraph_graph, const char *label, mgp_value *properties) {
return MgInvoke<int>(mgp_create_unique_constraint, memgraph_graph, label, properties);
}
inline bool drop_unique_constraint(mgp_graph *memgraph_graph, const char *label, mgp_value *properties) {
return MgInvoke<int>(mgp_drop_unique_constraint, memgraph_graph, label, properties);
}
inline mgp_list *list_all_unique_constraints(mgp_graph *graph, mgp_memory *memory) {
return MgInvoke<mgp_list *>(mgp_list_all_unique_constraints, graph, memory);
}
inline bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
inline mgp_vertex *graph_create_vertex(mgp_graph *graph, mgp_memory *memory) {

View File

@ -876,6 +876,65 @@ enum mgp_error mgp_edge_iter_properties(struct mgp_edge *e, struct mgp_memory *m
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);
/// Creates label index for given label.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if label index already exists, result will be 0, otherwise 1.
enum mgp_error mgp_create_label_index(struct mgp_graph *graph, const char *label, int *result);
/// Drop label index.
enum mgp_error mgp_drop_label_index(struct mgp_graph *graph, const char *label, int *result);
/// List all label indices.
enum mgp_error mgp_list_all_label_indices(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_list **result);
/// Creates label-property index for given label and propery.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if label property index already exists, result will be 0, otherwise 1.
enum mgp_error mgp_create_label_property_index(struct mgp_graph *graph, const char *label, const char *property,
int *result);
/// Drops label-property index for given label and propery.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if dropping label property index failed, result will be 0, otherwise 1.
enum mgp_error mgp_drop_label_property_index(struct mgp_graph *graph, const char *label, const char *property,
int *result);
/// List all label+property indices.
enum mgp_error mgp_list_all_label_property_indices(struct mgp_graph *graph, struct mgp_memory *memory,
struct mgp_list **result);
/// Creates existence constraint for given label and property.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if creating existence constraint failed, result will be 0, otherwise 1.
enum mgp_error mgp_create_existence_constraint(struct mgp_graph *graph, const char *label, const char *property,
int *result);
/// Drops existence constraint for given label and property.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if dropping existence constraint failed, result will be 0, otherwise 1.
enum mgp_error mgp_drop_existence_constraint(struct mgp_graph *graph, const char *label, const char *property,
int *result);
/// List all existence constraints.
enum mgp_error mgp_list_all_existence_constraints(struct mgp_graph *graph, struct mgp_memory *memory,
struct mgp_list **result);
/// Creates unique constraint for given label and properties.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if creating unique constraint failed, result will be 0, otherwise 1.
enum mgp_error mgp_create_unique_constraint(struct mgp_graph *graph, const char *label, struct mgp_value *properties,
int *result);
/// Drops unique constraint for given label and properties.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if dropping unique constraint failed, result will be 0, otherwise 1.
enum mgp_error mgp_drop_unique_constraint(struct mgp_graph *graph, const char *label, struct mgp_value *properties,
int *result);
/// List all unique constraints
enum mgp_error mgp_list_all_unique_constraints(struct mgp_graph *graph, struct mgp_memory *memory,
struct mgp_list **result);
/// Result is non-zero if the graph can be modified.
/// If a graph is immutable, then vertices cannot be created or deleted, and all of the returned vertices will be
/// immutable also. The same applies for edges.

View File

@ -21,12 +21,10 @@
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <vector>
#include <functional>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "_mgp.hpp"
#include "mg_exceptions.hpp"
@ -1289,7 +1287,7 @@ class Value {
std::string_view ValueString() const;
std::string_view ValueString();
/// @pre Value type needs to be Type::List.
const List ValueList() const;
List ValueList() const;
List ValueList();
/// @pre Value type needs to be Type::Map.
const Map ValueMap() const;
@ -3651,7 +3649,7 @@ inline std::string_view Value::ValueString() {
return mgp::value_get_string(ptr_);
}
inline const List Value::ValueList() const {
inline List Value::ValueList() const {
if (Type() != Type::List) {
throw ValueException("Type of value is wrong: expected List.");
}
@ -4279,9 +4277,77 @@ inline void AddParamsReturnsToProc(mgp_proc *proc, std::vector<Parameter> &param
}
} // namespace detail
inline bool CreateLabelIndex(mgp_graph *memgaph_graph, const std::string_view label) {
return create_label_index(memgaph_graph, label.data());
}
inline bool DropLabelIndex(mgp_graph *memgaph_graph, const std::string_view label) {
return drop_label_index(memgaph_graph, label.data());
}
inline List ListAllLabelIndices(mgp_graph *memgraph_graph) {
auto *label_indices = mgp::MemHandlerCallback(list_all_label_indices, memgraph_graph);
if (label_indices == nullptr) {
throw ValueException("Couldn't list all label indices");
}
return List(label_indices);
}
inline bool CreateLabelPropertyIndex(mgp_graph *memgaph_graph, const std::string_view label,
const std::string_view property) {
return create_label_property_index(memgaph_graph, label.data(), property.data());
}
inline bool DropLabelPropertyIndex(mgp_graph *memgaph_graph, const std::string_view label,
const std::string_view property) {
return drop_label_property_index(memgaph_graph, label.data(), property.data());
}
inline List ListAllLabelPropertyIndices(mgp_graph *memgraph_graph) {
auto *label_property_indices = mgp::MemHandlerCallback(list_all_label_property_indices, memgraph_graph);
if (label_property_indices == nullptr) {
throw ValueException("Couldn't list all label+property indices");
}
return List(label_property_indices);
}
inline bool CreateExistenceConstraint(mgp_graph *memgraph_graph, const std::string_view label,
const std::string_view property) {
return create_existence_constraint(memgraph_graph, label.data(), property.data());
}
inline bool DropExistenceConstraint(mgp_graph *memgraph_graph, const std::string_view label,
const std::string_view property) {
return drop_existence_constraint(memgraph_graph, label.data(), property.data());
}
inline List ListAllExistenceConstraints(mgp_graph *memgraph_graph) {
auto *existence_constraints = mgp::MemHandlerCallback(list_all_existence_constraints, memgraph_graph);
if (existence_constraints == nullptr) {
throw ValueException("Couldn't list all existence_constraints");
}
return List(existence_constraints);
}
inline bool CreateUniqueConstraint(mgp_graph *memgraph_graph, const std::string_view label, mgp_value *properties) {
return create_unique_constraint(memgraph_graph, label.data(), properties);
}
inline bool DropUniqueConstraint(mgp_graph *memgraph_graph, const std::string_view label, mgp_value *properties) {
return drop_unique_constraint(memgraph_graph, label.data(), properties);
}
inline List ListAllUniqueConstraints(mgp_graph *memgraph_graph) {
auto *unique_constraints = mgp::MemHandlerCallback(list_all_unique_constraints, memgraph_graph);
if (unique_constraints == nullptr) {
throw ValueException("Couldn't list all unique_constraints");
}
return List(unique_constraints);
}
void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType proc_type,
std::vector<Parameter> parameters, std::vector<Return> returns, mgp_module *module,
mgp_memory *memory) {
mgp_memory * /*memory*/) {
auto *proc = (proc_type == ProcedureType::Read) ? mgp::module_add_read_procedure(module, name.data(), callback)
: mgp::module_add_write_procedure(module, name.data(), callback);
detail::AddParamsReturnsToProc(proc, parameters, returns);
@ -4289,7 +4355,7 @@ void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType pro
void AddBatchProcedure(mgp_proc_cb callback, mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
std::string_view name, ProcedureType proc_type, std::vector<Parameter> parameters,
std::vector<Return> returns, mgp_module *module, mgp_memory *memory) {
std::vector<Return> returns, mgp_module *module, mgp_memory * /*memory*/) {
auto *proc = (proc_type == ProcedureType::Read)
? mgp::module_add_batch_read_procedure(module, name.data(), callback, initializer, cleanup)
: mgp::module_add_batch_write_procedure(module, name.data(), callback, initializer, cleanup);

View File

@ -10,18 +10,33 @@
// licenses/APL.txt.
#include <mgp.hpp>
#include "utils/string.hpp"
#include <optional>
namespace Schema {
/*NodeTypeProperties and RelTypeProperties constants*/
constexpr std::string_view kStatusKept = "Kept";
constexpr std::string_view kStatusCreated = "Created";
constexpr std::string_view kStatusDropped = "Dropped";
constexpr std::string_view kReturnNodeType = "nodeType";
constexpr std::string_view kProcedureNodeType = "node_type_properties";
constexpr std::string_view kProcedureRelType = "rel_type_properties";
constexpr std::string_view kProcedureAssert = "assert";
constexpr std::string_view kReturnLabels = "nodeLabels";
constexpr std::string_view kReturnRelType = "relType";
constexpr std::string_view kReturnPropertyName = "propertyName";
constexpr std::string_view kReturnPropertyType = "propertyTypes";
constexpr std::string_view kReturnMandatory = "mandatory";
constexpr std::string_view kReturnLabel = "label";
constexpr std::string_view kReturnKey = "key";
constexpr std::string_view kReturnKeys = "keys";
constexpr std::string_view kReturnUnique = "unique";
constexpr std::string_view kReturnAction = "action";
constexpr std::string_view kParameterIndices = "indices";
constexpr std::string_view kParameterUniqueConstraints = "unique_constraints";
constexpr std::string_view kParameterExistenceConstraints = "existence_constraints";
constexpr std::string_view kParameterDropExisting = "drop_existing";
std::string TypeOf(const mgp::Type &type);
@ -35,6 +50,7 @@ void ProcessPropertiesRel(mgp::Record &record, const std::string_view &type, con
void NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
void RelTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
void Assert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
} // namespace Schema
/*we have << operator for type in Cpp API, but in it we return somewhat different strings than I would like in this
@ -92,21 +108,22 @@ void Schema::ProcessPropertiesRel(mgp::Record &record, const std::string_view &t
record.Insert(std::string(kReturnMandatory).c_str(), mandatory);
}
void Schema::NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result,
mgp_memory *memory) {
mgp::MemoryDispatcherGuard guard{memory};
;
const auto record_factory = mgp::RecordFactory(result);
try {
const mgp::Graph graph = mgp::Graph(memgraph_graph);
for (auto node : graph.Nodes()) {
std::string type = "";
std::string type;
mgp::List labels = mgp::List();
for (auto label : node.Labels()) {
labels.AppendExtend(mgp::Value(label));
type += ":`" + std::string(label) + "`";
}
if (node.Properties().size() == 0) {
if (node.Properties().empty()) {
auto record = record_factory.NewRecord();
ProcessPropertiesNode<std::string>(record, type, labels, "", "", false);
continue;
@ -126,16 +143,15 @@ void Schema::NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_r
}
}
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};
;
const auto record_factory = mgp::RecordFactory(result);
try {
const mgp::Graph graph = mgp::Graph(memgraph_graph);
for (auto rel : graph.Relationships()) {
std::string type = ":`" + std::string(rel.Type()) + "`";
if (rel.Properties().size() == 0) {
if (rel.Properties().empty()) {
auto record = record_factory.NewRecord();
ProcessPropertiesRel<std::string>(record, type, "", "", false);
continue;
@ -155,29 +171,436 @@ void Schema::RelTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_re
}
}
void InsertRecordForLabelIndex(const auto &record_factory, const std::string_view label,
const std::string_view status) {
auto record = record_factory.NewRecord();
record.Insert(std::string(Schema::kReturnLabel).c_str(), label);
record.Insert(std::string(Schema::kReturnKey).c_str(), "");
record.Insert(std::string(Schema::kReturnKeys).c_str(), mgp::List());
record.Insert(std::string(Schema::kReturnUnique).c_str(), false);
record.Insert(std::string(Schema::kReturnAction).c_str(), status);
}
void InsertRecordForUniqueConstraint(const auto &record_factory, const std::string_view label,
const mgp::List &properties, const std::string_view status) {
auto record = record_factory.NewRecord();
record.Insert(std::string(Schema::kReturnLabel).c_str(), label);
record.Insert(std::string(Schema::kReturnKey).c_str(), properties.ToString());
record.Insert(std::string(Schema::kReturnKeys).c_str(), properties);
record.Insert(std::string(Schema::kReturnUnique).c_str(), true);
record.Insert(std::string(Schema::kReturnAction).c_str(), status);
}
void InsertRecordForLabelPropertyIndexAndExistenceConstraint(const auto &record_factory, const std::string_view label,
const std::string_view property,
const std::string_view status) {
auto record = record_factory.NewRecord();
record.Insert(std::string(Schema::kReturnLabel).c_str(), label);
record.Insert(std::string(Schema::kReturnKey).c_str(), property);
record.Insert(std::string(Schema::kReturnKeys).c_str(), mgp::List({mgp::Value(property)}));
record.Insert(std::string(Schema::kReturnUnique).c_str(), false);
record.Insert(std::string(Schema::kReturnAction).c_str(), status);
}
void ProcessCreatingLabelIndex(const std::string_view label, const std::set<std::string_view> &existing_label_indices,
mgp_graph *memgraph_graph, const auto &record_factory) {
if (existing_label_indices.contains(label)) {
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusKept);
} else if (mgp::CreateLabelIndex(memgraph_graph, label)) {
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusCreated);
}
}
template <typename TFunc>
void ProcessCreatingLabelPropertyIndexAndExistenceConstraint(const std::string_view label,
const std::string_view property,
const std::set<std::string_view> &existing_collection,
const TFunc &func_creation, mgp_graph *memgraph_graph,
const auto &record_factory) {
const auto label_property_search_key = std::string(label) + ":" + std::string(property);
if (existing_collection.contains(label_property_search_key)) {
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusKept);
} else if (func_creation(memgraph_graph, label, property)) {
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusCreated);
}
}
/// We collect properties for which index was created.
using AssertedIndices = std::set<std::string, std::less<>>;
AssertedIndices CreateIndicesForLabel(const std::string_view label, const mgp::Value &properties_val,
mgp_graph *memgraph_graph, const auto &record_factory,
const std::set<std::string_view> &existing_label_indices,
const std::set<std::string_view> &existing_label_property_indices) {
AssertedIndices asserted_indices;
if (!properties_val.IsList()) {
return {};
}
if (const auto properties = properties_val.ValueList();
properties.Empty() && mgp::CreateLabelIndex(memgraph_graph, label)) {
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusCreated);
asserted_indices.emplace("");
} else {
std::for_each(properties.begin(), properties.end(),
[&label, &existing_label_indices, &existing_label_property_indices, &memgraph_graph, &record_factory,
&asserted_indices](const mgp::Value &property) {
if (!property.IsString()) {
return;
}
const auto property_str = property.ValueString();
if (property_str.empty()) {
ProcessCreatingLabelIndex(label, existing_label_indices, memgraph_graph, record_factory);
asserted_indices.emplace("");
} else {
ProcessCreatingLabelPropertyIndexAndExistenceConstraint(
label, property_str, existing_label_property_indices, mgp::CreateLabelPropertyIndex,
memgraph_graph, record_factory);
asserted_indices.emplace(property_str);
}
});
}
return asserted_indices;
}
void ProcessIndices(const mgp::Map &indices_map, mgp_graph *memgraph_graph, const auto &record_factory,
bool drop_existing) {
auto mgp_existing_label_indices = mgp::ListAllLabelIndices(memgraph_graph);
auto mgp_existing_label_property_indices = mgp::ListAllLabelPropertyIndices(memgraph_graph);
std::set<std::string_view> existing_label_indices;
std::transform(mgp_existing_label_indices.begin(), mgp_existing_label_indices.end(),
std::inserter(existing_label_indices, existing_label_indices.begin()),
[](const mgp::Value &index) { return index.ValueString(); });
std::set<std::string_view> existing_label_property_indices;
std::transform(mgp_existing_label_property_indices.begin(), mgp_existing_label_property_indices.end(),
std::inserter(existing_label_property_indices, existing_label_property_indices.begin()),
[](const mgp::Value &index) { return index.ValueString(); });
std::set<std::string> asserted_label_indices;
std::set<std::string> asserted_label_property_indices;
auto merge_label_property = [](const std::string &label, const std::string &property) {
return label + ":" + property;
};
for (const auto &index : indices_map) {
const std::string_view label = index.key;
const mgp::Value &properties_val = index.value;
AssertedIndices asserted_indices_new = CreateIndicesForLabel(
label, properties_val, memgraph_graph, record_factory, existing_label_indices, existing_label_property_indices);
if (!drop_existing) {
continue;
}
std::ranges::for_each(asserted_indices_new, [&asserted_label_indices, &asserted_label_property_indices, label,
&merge_label_property](const std::string &property) {
if (property.empty()) {
asserted_label_indices.emplace(label);
} else {
asserted_label_property_indices.emplace(merge_label_property(std::string(label), property));
}
});
}
if (!drop_existing) {
return;
}
std::set<std::string_view> label_indices_to_drop;
std::ranges::set_difference(existing_label_indices, asserted_label_indices,
std::inserter(label_indices_to_drop, label_indices_to_drop.begin()));
std::ranges::for_each(label_indices_to_drop, [memgraph_graph, &record_factory](const std::string_view label) {
if (mgp::DropLabelIndex(memgraph_graph, label)) {
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusDropped);
}
});
std::set<std::string_view> label_property_indices_to_drop;
std::ranges::set_difference(existing_label_property_indices, asserted_label_property_indices,
std::inserter(label_property_indices_to_drop, label_property_indices_to_drop.begin()));
auto decouple_label_property = [](const std::string_view label_property) {
const auto label_size = label_property.find(':');
const auto label = std::string(label_property.substr(0, label_size));
const auto property = std::string(label_property.substr(label_size + 1));
return std::make_pair(label, property);
};
std::ranges::for_each(label_property_indices_to_drop, [memgraph_graph, &record_factory, decouple_label_property](
const std::string_view label_property) {
const auto [label, property] = decouple_label_property(label_property);
if (mgp::DropLabelPropertyIndex(memgraph_graph, label, property)) {
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusDropped);
}
});
}
using ExistenceConstraintsStorage = std::set<std::string_view>;
ExistenceConstraintsStorage CreateExistenceConstraintsForLabel(
const std::string_view label, const mgp::Value &properties_val, mgp_graph *memgraph_graph,
const auto &record_factory, const std::set<std::string_view> &existing_existence_constraints) {
ExistenceConstraintsStorage asserted_existence_constraints;
if (!properties_val.IsList()) {
return asserted_existence_constraints;
}
auto validate_property = [](const mgp::Value &property) -> bool {
return property.IsString() && !property.ValueString().empty();
};
const auto &properties = properties_val.ValueList();
std::for_each(properties.begin(), properties.end(),
[&label, &existing_existence_constraints, &asserted_existence_constraints, &memgraph_graph,
&record_factory, &validate_property](const mgp::Value &property) {
if (!validate_property(property)) {
return;
}
const std::string_view property_str = property.ValueString();
asserted_existence_constraints.emplace(property_str);
ProcessCreatingLabelPropertyIndexAndExistenceConstraint(
label, property_str, existing_existence_constraints, mgp::CreateExistenceConstraint,
memgraph_graph, record_factory);
});
return asserted_existence_constraints;
}
void ProcessExistenceConstraints(const mgp::Map &existence_constraints_map, mgp_graph *memgraph_graph,
const auto &record_factory, bool drop_existing) {
auto mgp_existing_existence_constraints = mgp::ListAllExistenceConstraints(memgraph_graph);
std::set<std::string_view> existing_existence_constraints;
std::transform(mgp_existing_existence_constraints.begin(), mgp_existing_existence_constraints.end(),
std::inserter(existing_existence_constraints, existing_existence_constraints.begin()),
[](const mgp::Value &constraint) { return constraint.ValueString(); });
auto merge_label_property = [](const std::string_view label, const std::string_view property) {
auto str = std::string(label) + ":";
str += property;
return str;
};
ExistenceConstraintsStorage asserted_existence_constraints;
for (const auto &existing_constraint : existence_constraints_map) {
const std::string_view label = existing_constraint.key;
const mgp::Value &properties_val = existing_constraint.value;
auto asserted_existence_constraints_new = CreateExistenceConstraintsForLabel(
label, properties_val, memgraph_graph, record_factory, existing_existence_constraints);
if (!drop_existing) {
continue;
}
std::ranges::for_each(asserted_existence_constraints_new, [&asserted_existence_constraints, &merge_label_property,
label](const std::string_view property) {
asserted_existence_constraints.emplace(merge_label_property(label, property));
});
}
if (!drop_existing) {
return;
}
std::set<std::string_view> existence_constraints_to_drop;
std::ranges::set_difference(existing_existence_constraints, asserted_existence_constraints,
std::inserter(existence_constraints_to_drop, existence_constraints_to_drop.begin()));
auto decouple_label_property = [](const std::string_view label_property) {
const auto label_size = label_property.find(':');
const auto label = std::string(label_property.substr(0, label_size));
const auto property = std::string(label_property.substr(label_size + 1));
return std::make_pair(label, property);
};
std::ranges::for_each(existence_constraints_to_drop, [&](const std::string_view label_property) {
const auto [label, property] = decouple_label_property(label_property);
if (mgp::DropExistenceConstraint(memgraph_graph, label, property)) {
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusDropped);
}
});
}
using AssertedUniqueConstraintsStorage = std::set<std::set<std::string_view>>;
AssertedUniqueConstraintsStorage CreateUniqueConstraintsForLabel(
const std::string_view label, const mgp::Value &unique_props_nested,
const std::map<std::string_view, AssertedUniqueConstraintsStorage> &existing_unique_constraints,
mgp_graph *memgraph_graph, const auto &record_factory) {
AssertedUniqueConstraintsStorage asserted_unique_constraints;
if (!unique_props_nested.IsList()) {
return asserted_unique_constraints;
}
auto validate_unique_constraint_props = [](const mgp::Value &properties) -> bool {
if (!properties.IsList()) {
return false;
}
const auto &properties_list = properties.ValueList();
if (properties_list.Empty()) {
return false;
}
return std::all_of(properties_list.begin(), properties_list.end(), [](const mgp::Value &property) {
return property.IsString() && !property.ValueString().empty();
});
};
auto unique_constraint_exists =
[](const std::string_view label, const std::set<std::string_view> &properties,
const std::map<std::string_view, AssertedUniqueConstraintsStorage> &existing_unique_constraints) -> bool {
auto iter = existing_unique_constraints.find(label);
if (iter == existing_unique_constraints.end()) {
return false;
}
return iter->second.find(properties) != iter->second.end();
};
for (const auto unique_props_nested_list = unique_props_nested.ValueList();
const auto &properties : unique_props_nested_list) {
if (!validate_unique_constraint_props(properties)) {
continue;
}
const auto properties_list = properties.ValueList();
std::set<std::string_view> properties_coll;
std::transform(properties_list.begin(), properties_list.end(),
std::inserter(properties_coll, properties_coll.begin()),
[](const mgp::Value &property) { return property.ValueString(); });
if (unique_constraint_exists(label, properties_coll, existing_unique_constraints)) {
InsertRecordForUniqueConstraint(record_factory, label, properties_list, Schema::kStatusKept);
} else if (mgp::CreateUniqueConstraint(memgraph_graph, label, properties.ptr())) {
InsertRecordForUniqueConstraint(record_factory, label, properties_list, Schema::kStatusCreated);
}
asserted_unique_constraints.emplace(std::move(properties_coll));
}
return asserted_unique_constraints;
}
void ProcessUniqueConstraints(const mgp::Map &unique_constraints_map, mgp_graph *memgraph_graph,
const auto &record_factory, bool drop_existing) {
auto mgp_existing_unique_constraints = mgp::ListAllUniqueConstraints(memgraph_graph);
// label-unique_constraints pair
std::map<std::string_view, AssertedUniqueConstraintsStorage> existing_unique_constraints;
for (const auto &constraint : mgp_existing_unique_constraints) {
auto constraint_list = constraint.ValueList();
std::set<std::string_view> properties;
for (int i = 1; i < constraint_list.Size(); i++) {
properties.emplace(constraint_list[i].ValueString());
}
const std::string_view label = constraint_list[0].ValueString();
auto [it, inserted] = existing_unique_constraints.try_emplace(label, AssertedUniqueConstraintsStorage{properties});
if (!inserted) {
it->second.emplace(std::move(properties));
}
}
std::map<std::string_view, AssertedUniqueConstraintsStorage> asserted_unique_constraints;
for (const auto &[label, unique_props_nested] : unique_constraints_map) {
auto asserted_unique_constraints_new = CreateUniqueConstraintsForLabel(
label, unique_props_nested, existing_unique_constraints, memgraph_graph, record_factory);
if (drop_existing) {
asserted_unique_constraints.emplace(label, std::move(asserted_unique_constraints_new));
}
}
if (!drop_existing) {
return;
}
std::vector<std::pair<std::string_view, std::set<std::string_view>>> unique_constraints_to_drop;
// Check for each label for we found existing constraint in the DB whether it was asserted.
// If no unique constraint was found with label, we can drop all unique constraints for this label. (if branch)
// If some unique constraint was found with label, we can drop only those unique constraints that were not asserted.
// (else branch.)
std::ranges::for_each(existing_unique_constraints, [&asserted_unique_constraints, &unique_constraints_to_drop](
const auto &existing_label_unique_constraints) {
const auto &label = existing_label_unique_constraints.first;
const auto &existing_unique_constraints_for_label = existing_label_unique_constraints.second;
const auto &asserted_unique_constraints_for_label = asserted_unique_constraints.find(label);
if (asserted_unique_constraints_for_label == asserted_unique_constraints.end()) {
std::ranges::for_each(
std::make_move_iterator(existing_unique_constraints_for_label.begin()),
std::make_move_iterator(existing_unique_constraints_for_label.end()),
[&unique_constraints_to_drop, &label](std::set<std::string_view> existing_unique_constraint_for_label) {
unique_constraints_to_drop.emplace_back(label, std::move(existing_unique_constraint_for_label));
});
} else {
const auto &asserted_unique_constraints_for_label_coll = asserted_unique_constraints_for_label->second;
std::ranges::for_each(
std::make_move_iterator(existing_unique_constraints_for_label.begin()),
std::make_move_iterator(existing_unique_constraints_for_label.end()),
[&unique_constraints_to_drop, &label, &asserted_unique_constraints_for_label_coll](
std::set<std::string_view> existing_unique_constraint_for_label) {
if (!asserted_unique_constraints_for_label_coll.contains(existing_unique_constraint_for_label)) {
unique_constraints_to_drop.emplace_back(label, std::move(existing_unique_constraint_for_label));
}
});
}
});
std::ranges::for_each(
unique_constraints_to_drop, [memgraph_graph, &record_factory](const auto &label_unique_constraint) {
const auto &[label, unique_constraint] = label_unique_constraint;
auto unique_constraint_list = mgp::List();
std::ranges::for_each(unique_constraint, [&unique_constraint_list](const std::string_view &property) {
unique_constraint_list.AppendExtend(mgp::Value(property));
});
if (mgp::DropUniqueConstraint(memgraph_graph, label, mgp::Value(unique_constraint_list).ptr())) {
InsertRecordForUniqueConstraint(record_factory, label, unique_constraint_list, Schema::kStatusDropped);
}
});
}
void Schema::Assert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
mgp::MemoryDispatcherGuard guard{memory};
const auto record_factory = mgp::RecordFactory(result);
auto arguments = mgp::List(args);
auto indices_map = arguments[0].ValueMap();
auto unique_constraints_map = arguments[1].ValueMap();
auto existence_constraints_map = arguments[2].ValueMap();
auto drop_existing = arguments[3].ValueBool();
ProcessIndices(indices_map, memgraph_graph, record_factory, drop_existing);
ProcessExistenceConstraints(existence_constraints_map, memgraph_graph, record_factory, drop_existing);
ProcessUniqueConstraints(unique_constraints_map, memgraph_graph, record_factory, drop_existing);
}
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
try {
mgp::MemoryDispatcherGuard guard{memory};
;
AddProcedure(Schema::NodeTypeProperties, std::string(Schema::kProcedureNodeType).c_str(), mgp::ProcedureType::Read,
{},
{mgp::Return(std::string(Schema::kReturnNodeType).c_str(), mgp::Type::String),
mgp::Return(std::string(Schema::kReturnLabels).c_str(), {mgp::Type::List, mgp::Type::String}),
mgp::Return(std::string(Schema::kReturnPropertyName).c_str(), mgp::Type::String),
mgp::Return(std::string(Schema::kReturnPropertyType).c_str(), mgp::Type::Any),
mgp::Return(std::string(Schema::kReturnMandatory).c_str(), mgp::Type::Bool)},
AddProcedure(Schema::NodeTypeProperties, Schema::kProcedureNodeType, mgp::ProcedureType::Read, {},
{mgp::Return(Schema::kReturnNodeType, mgp::Type::String),
mgp::Return(Schema::kReturnLabels, {mgp::Type::List, mgp::Type::String}),
mgp::Return(Schema::kReturnPropertyName, mgp::Type::String),
mgp::Return(Schema::kReturnPropertyType, mgp::Type::Any),
mgp::Return(Schema::kReturnMandatory, mgp::Type::Bool)},
module, memory);
AddProcedure(Schema::RelTypeProperties, std::string(Schema::kProcedureRelType).c_str(), mgp::ProcedureType::Read,
{},
{mgp::Return(std::string(Schema::kReturnRelType).c_str(), mgp::Type::String),
mgp::Return(std::string(Schema::kReturnPropertyName).c_str(), mgp::Type::String),
mgp::Return(std::string(Schema::kReturnPropertyType).c_str(), mgp::Type::Any),
mgp::Return(std::string(Schema::kReturnMandatory).c_str(), mgp::Type::Bool)},
AddProcedure(Schema::RelTypeProperties, Schema::kProcedureRelType, mgp::ProcedureType::Read, {},
{mgp::Return(Schema::kReturnRelType, mgp::Type::String),
mgp::Return(Schema::kReturnPropertyName, mgp::Type::String),
mgp::Return(Schema::kReturnPropertyType, mgp::Type::Any),
mgp::Return(Schema::kReturnMandatory, mgp::Type::Bool)},
module, memory);
AddProcedure(
Schema::Assert, Schema::kProcedureAssert, mgp::ProcedureType::Read,
{
mgp::Parameter(Schema::kParameterIndices, {mgp::Type::Map, mgp::Type::Any}),
mgp::Parameter(Schema::kParameterUniqueConstraints, {mgp::Type::Map, mgp::Type::Any}),
mgp::Parameter(Schema::kParameterExistenceConstraints, {mgp::Type::Map, mgp::Type::Any},
mgp::Value(mgp::Map{})),
mgp::Parameter(Schema::kParameterDropExisting, mgp::Type::Bool, mgp::Value(true)),
},
{mgp::Return(Schema::kReturnLabel, mgp::Type::String), mgp::Return(Schema::kReturnKey, mgp::Type::String),
mgp::Return(Schema::kReturnKeys, {mgp::Type::List, mgp::Type::String}),
mgp::Return(Schema::kReturnUnique, mgp::Type::Bool), mgp::Return(Schema::kReturnAction, mgp::Type::String)},
module, memory);
} catch (const std::exception &e) {
std::cerr << "Error while initializing query module: " << e.what() << std::endl;
return 1;
}

View File

@ -139,6 +139,8 @@ std::optional<VertexAccessor> SubgraphDbAccessor::FindVertex(storage::Gid gid, s
query::Graph *SubgraphDbAccessor::getGraph() { return graph_; }
DbAccessor *SubgraphDbAccessor::GetAccessor() { return &db_accessor_; }
VertexAccessor SubgraphVertexAccessor::GetVertexAccessor() const { return impl_; }
storage::Result<EdgeVertexAccessorResult> SubgraphVertexAccessor::OutEdges(storage::View view) const {

View File

@ -694,6 +694,8 @@ class SubgraphDbAccessor final {
std::optional<VertexAccessor> FindVertex(storage::Gid gid, storage::View view);
Graph *getGraph();
DbAccessor *GetAccessor();
};
} // namespace memgraph::query

View File

@ -147,6 +147,8 @@ void memgraph::query::CurrentDB::CleanupDBTransaction(bool abort) {
namespace memgraph::query {
constexpr std::string_view kSchemaAssert = "SCHEMA.ASSERT";
template <typename>
constexpr auto kAlwaysFalse = false;
@ -3715,7 +3717,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
// TODO: ATM only a single database, will change when we have multiple database transactions
bool could_commit = utils::Downcast<CypherQuery>(parsed_query.query) != nullptr;
bool unique = utils::Downcast<IndexQuery>(parsed_query.query) != nullptr ||
utils::Downcast<ConstraintQuery>(parsed_query.query) != nullptr;
utils::Downcast<ConstraintQuery>(parsed_query.query) != nullptr ||
upper_case_query.find(kSchemaAssert) != std::string::npos;
SetupDatabaseTransaction(could_commit, unique);
}

View File

@ -2535,6 +2535,333 @@ mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_mem
result);
}
mgp_error mgp_create_label_index(mgp_graph *graph, const char *label, int *result) {
return WrapExceptions(
[graph, label]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
const auto index_res =
std::visit(memgraph::utils::Overloaded{
[label_id](memgraph::query::DbAccessor *impl) { return impl->CreateIndex(label_id); },
[label_id](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->CreateIndex(label_id);
}},
graph->impl);
return index_res.HasError() ? 0 : 1;
},
result);
}
mgp_error mgp_drop_label_index(mgp_graph *graph, const char *label, int *result) {
return WrapExceptions(
[graph, label]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
const auto index_res =
std::visit(memgraph::utils::Overloaded{
[label_id](memgraph::query::DbAccessor *impl) { return impl->DropIndex(label_id); },
[label_id](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->DropIndex(label_id);
}},
graph->impl);
return index_res.HasError() ? 0 : 1;
},
result);
}
mgp_error mgp_list_all_label_indices(mgp_graph *graph, mgp_memory *memory, mgp_list **result) {
return WrapExceptions([graph, memory, result]() {
const auto index_res = std::visit(
memgraph::utils::Overloaded{
[](memgraph::query::DbAccessor *impl) { return impl->ListAllIndices().label; },
[](memgraph::query::SubgraphDbAccessor *impl) { return impl->GetAccessor()->ListAllIndices().label; }},
graph->impl);
if (const auto err = mgp_list_make_empty(index_res.size(), memory, result); err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all label indices failed due to failure of creating list");
}
for (const auto &label : index_res) {
const auto label_id_str = std::visit([label](const auto *impl) { return impl->LabelToName(label); }, graph->impl);
mgp_value *label_value = nullptr;
if (const auto err_str = mgp_value_make_string(label_id_str.c_str(), memory, &label_value);
err_str != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all label indices failed due to failure of creating label value");
}
if (const auto err_list = mgp_list_append_extend(*result, label_value);
err_list != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all label indices failed due to failure of appending label value");
}
mgp_value_destroy(label_value);
}
});
}
mgp_error mgp_create_label_property_index(mgp_graph *graph, const char *label, const char *property, int *result) {
return WrapExceptions(
[graph, label, property]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
const auto property_id =
std::visit([property](auto *impl) { return impl->NameToProperty(property); }, graph->impl);
const auto index_res =
std::visit(memgraph::utils::Overloaded{[label_id, property_id](memgraph::query::DbAccessor *impl) {
return impl->CreateIndex(label_id, property_id);
},
[label_id, property_id](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->CreateIndex(label_id, property_id);
}},
graph->impl);
return index_res.HasError() ? 0 : 1;
},
result);
}
mgp_error mgp_drop_label_property_index(mgp_graph *graph, const char *label, const char *property, int *result) {
return WrapExceptions(
[graph, label, property]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
const auto property_id =
std::visit([property](auto *impl) { return impl->NameToProperty(property); }, graph->impl);
const auto index_res =
std::visit(memgraph::utils::Overloaded{[label_id, property_id](memgraph::query::DbAccessor *impl) {
return impl->DropIndex(label_id, property_id);
},
[label_id, property_id](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->DropIndex(label_id, property_id);
}},
graph->impl);
return index_res.HasError() ? 0 : 1;
},
result);
}
mgp_error create_and_append_label_property_to_mgp_list(mgp_graph *graph, mgp_memory *memory, mgp_list **result,
const auto &label_property_pair) {
return WrapExceptions([graph, memory, result, &label_property_pair]() {
const auto label_id_str = std::visit(
[label_id = label_property_pair.first](const auto *impl) { return impl->LabelToName(label_id); }, graph->impl);
const auto property_id_str = std::visit(
[property_id = label_property_pair.second](const auto *impl) { return impl->PropertyToName(property_id); },
graph->impl);
// This is hack to avoid dealing with pairs
mgp_value *label_property = nullptr;
auto final_str = label_id_str + ":";
final_str += property_id_str;
if (const auto err_str = mgp_value_make_string(final_str.c_str(), memory, &label_property);
err_str != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error(
"Creating a list of label+property pairs failed due to failure of creating label+property value");
}
if (const auto err_list = mgp_list_append_extend(*result, label_property);
err_list != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error(
"Creating a list of label-property pairs due to failure of appending label+property value");
}
mgp_value_destroy(label_property);
});
}
mgp_error mgp_list_all_label_property_indices(mgp_graph *graph, mgp_memory *memory, mgp_list **result) {
return WrapExceptions([graph, memory, result]() {
const auto index_res =
std::visit(memgraph::utils::Overloaded{
[](memgraph::query::DbAccessor *impl) { return impl->ListAllIndices().label_property; },
[](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->ListAllIndices().label_property;
}},
graph->impl);
if (const auto err = mgp_list_make_empty(index_res.size(), memory, result); err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all label+property indices failed due to failure of creating list");
}
for (const auto &label_property_pair : index_res) {
if (const auto err = create_and_append_label_property_to_mgp_list(graph, memory, result, label_property_pair);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error(
"Listing all label+property indices failed due to failure of appending label+property value");
}
}
});
}
mgp_error mgp_create_existence_constraint(mgp_graph *graph, const char *label, const char *property, int *result) {
return WrapExceptions(
[graph, label, property]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
const auto property_id =
std::visit([property](auto *impl) { return impl->NameToProperty(property); }, graph->impl);
const auto exist_res = std::visit(
memgraph::utils::Overloaded{[label_id, property_id](memgraph::query::DbAccessor *impl) {
return impl->CreateExistenceConstraint(label_id, property_id);
},
[label_id, property_id](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->CreateExistenceConstraint(label_id, property_id);
}},
graph->impl);
return exist_res.HasError() ? 0 : 1;
},
result);
}
mgp_error mgp_drop_existence_constraint(mgp_graph *graph, const char *label, const char *property, int *result) {
return WrapExceptions(
[graph, label, property]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
const auto property_id =
std::visit([property](auto *impl) { return impl->NameToProperty(property); }, graph->impl);
const auto exist_res = std::visit(
memgraph::utils::Overloaded{[label_id, property_id](memgraph::query::DbAccessor *impl) {
return impl->DropExistenceConstraint(label_id, property_id);
},
[label_id, property_id](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->DropExistenceConstraint(label_id, property_id);
}},
graph->impl);
return exist_res.HasError() ? 0 : 1;
},
result);
}
mgp_error mgp_list_all_existence_constraints(mgp_graph *graph, mgp_memory *memory, mgp_list **result) {
return WrapExceptions([graph, memory, result]() {
const auto constraint_res =
std::visit(memgraph::utils::Overloaded{
[](memgraph::query::DbAccessor *impl) { return impl->ListAllConstraints().existence; },
[](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->ListAllConstraints().existence;
}},
graph->impl);
if (const auto err = mgp_list_make_empty(constraint_res.size(), memory, result);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all existence constraints failed due to failure of creating a list");
}
for (const auto &label_property_pair : constraint_res) {
if (const auto err = create_and_append_label_property_to_mgp_list(graph, memory, result, label_property_pair);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error(
"Listing all existence constraints failed due to failure of appending label+property value");
}
}
});
}
mgp_error mgp_create_unique_constraint(mgp_graph *graph, const char *label, mgp_value *properties, int *result) {
return WrapExceptions(
[graph, label, properties]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
std::set<memgraph::storage::PropertyId> property_ids;
for (const auto &elem : properties->list_v->elems) {
property_ids.insert(std::visit(
[prop_str = elem.string_v](auto *impl) { return impl->NameToProperty(prop_str); }, graph->impl));
}
const auto unique_res = std::visit(
memgraph::utils::Overloaded{[label_id, property_ids](memgraph::query::DbAccessor *impl) {
return impl->CreateUniqueConstraint(label_id, property_ids);
},
[label_id, property_ids](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->CreateUniqueConstraint(label_id, property_ids);
}},
graph->impl);
return unique_res.HasError() ? 0 : 1;
},
result);
}
mgp_error mgp_drop_unique_constraint(mgp_graph *graph, const char *label, mgp_value *properties, int *result) {
return WrapExceptions(
[graph, label, properties]() {
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label); }, graph->impl);
std::set<memgraph::storage::PropertyId> property_ids;
for (const auto &elem : properties->list_v->elems) {
property_ids.insert(std::visit(
[prop_str = elem.string_v](auto *impl) { return impl->NameToProperty(prop_str); }, graph->impl));
}
const auto unique_res = std::visit(
memgraph::utils::Overloaded{[label_id, property_ids](memgraph::query::DbAccessor *impl) {
return impl->DropUniqueConstraint(label_id, property_ids);
},
[label_id, property_ids](memgraph::query::SubgraphDbAccessor *impl) {
return impl->GetAccessor()->DropUniqueConstraint(label_id, property_ids);
}},
graph->impl);
return unique_res == memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS ? 1 : 0;
},
result);
}
mgp_error mgp_list_all_unique_constraints(mgp_graph *graph, mgp_memory *memory, mgp_list **result) {
return WrapExceptions([graph, memory, result]() {
const auto constraints_res = std::visit(
memgraph::utils::Overloaded{
[](memgraph::query::DbAccessor *impl) { return impl->ListAllConstraints().unique; },
[](memgraph::query::SubgraphDbAccessor *impl) { return impl->GetAccessor()->ListAllConstraints().unique; }},
graph->impl);
if (const auto err = mgp_list_make_empty(constraints_res.size(), memory, result);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of creating a list");
}
for (const auto &label_properties_pair : constraints_res) {
const std::string label_id_str =
std::visit([label_id = label_properties_pair.first](const auto *impl) { return impl->LabelToName(label_id); },
graph->impl);
const std::vector<std::string> properties_str = std::visit(
[property_ids = label_properties_pair.second](const auto *impl) {
std::vector<std::string> property_ids_str;
property_ids_str.reserve(property_ids.size());
std::transform(property_ids.begin(), property_ids.end(), std::back_inserter(property_ids_str),
[impl](const auto &property_id) { return impl->PropertyToName(property_id); });
return property_ids_str;
},
graph->impl);
mgp_list *label_properties_mgp_list = nullptr;
if (const auto properties_mgp_list_err =
mgp_list_make_empty(properties_str.size() + 1, memory, &label_properties_mgp_list);
properties_mgp_list_err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of creating an inner list");
}
mgp_value *mgp_value_label = nullptr;
if (const auto err_label = mgp_value_make_string(label_id_str.c_str(), memory, &mgp_value_label);
err_label != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of creating a label value");
}
if (const auto err_label_into_list = mgp_list_append_extend(label_properties_mgp_list, mgp_value_label);
err_label_into_list != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of appending a label value");
}
mgp_value_destroy(mgp_value_label);
for (const std::string &property_str : properties_str) {
mgp_value *property_mgp_value = nullptr;
if (const auto err_str = mgp_value_make_string(property_str.c_str(), memory, &property_mgp_value);
err_str != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of creating a property value");
}
if (const auto err_list = mgp_list_append_extend(label_properties_mgp_list, property_mgp_value);
err_list != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of appending a property value");
}
mgp_value_destroy(property_mgp_value);
}
mgp_value value(label_properties_mgp_list, label_properties_mgp_list->GetMemoryResource());
if (const auto err_list = mgp_list_append_extend(*result, &value); err_list != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Listing all unique constraints failed due to failure of creating label+property value");
}
mgp_value_destroy(&value);
}
});
}
mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) {
*result = MgpGraphIsMutable(*graph) ? 1 : 0;
return mgp_error::MGP_ERROR_NO_ERROR;

View File

@ -1003,7 +1003,7 @@ void InMemoryStorage::InMemoryAccessor::FinalizeTransaction() {
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(LabelId label) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Creating label index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
if (!mem_label_index->CreateIndex(label, in_memory->vertices_.access(), std::nullopt)) {
@ -1017,7 +1017,7 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(
LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Creating label-property index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(in_memory->indices_.label_property_index_.get());
@ -1031,7 +1031,7 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::DropIndex(LabelId label) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Dropping label index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
if (!mem_label_index->DropIndex(label)) {
@ -1045,7 +1045,7 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::DropIndex(
LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Dropping label-property index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(in_memory->indices_.label_property_index_.get());
@ -1060,7 +1060,7 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA
utils::BasicResult<StorageExistenceConstraintDefinitionError, void>
InMemoryStorage::InMemoryAccessor::CreateExistenceConstraint(LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Creating existence requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *existence_constraints = in_memory->constraints_.existence_constraints_.get();
if (existence_constraints->ConstraintExists(label, property)) {
@ -1078,7 +1078,7 @@ InMemoryStorage::InMemoryAccessor::CreateExistenceConstraint(LabelId label, Prop
utils::BasicResult<StorageExistenceConstraintDroppingError, void>
InMemoryStorage::InMemoryAccessor::DropExistenceConstraint(LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Dropping existence constraint requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *existence_constraints = in_memory->constraints_.existence_constraints_.get();
if (!existence_constraints->DropConstraint(label, property)) {
@ -1090,7 +1090,7 @@ InMemoryStorage::InMemoryAccessor::DropExistenceConstraint(LabelId label, Proper
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
InMemoryStorage::InMemoryAccessor::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Creating unique constraint requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(in_memory->constraints_.unique_constraints_.get());
@ -1107,7 +1107,7 @@ InMemoryStorage::InMemoryAccessor::CreateUniqueConstraint(LabelId label, const s
UniqueConstraints::DeletionStatus InMemoryStorage::InMemoryAccessor::DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
MG_ASSERT(unique_guard_.owns_lock(), "Dropping unique constraint requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(in_memory->constraints_.unique_constraints_.get());

View File

@ -15,6 +15,410 @@ import pytest
from common import connect, execute_and_fetch_all
def test_assert_creates_label_index_empty_list():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: []}, {}) YIELD * RETURN *;",
)
)
assert results == [("Created", "", [], "Person", False)]
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label", "Person", None, 0)]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
def test_assert_creates_label_index_empty_string():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['']}, {}) YIELD * RETURN *;",
)
)
assert results == [("Created", "", [], "Person", False)]
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label", "Person", None, 0)]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
def test_assert_index_wrong_properties_type():
cursor = connect().cursor()
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ''}, {}) YIELD * RETURN *;",
)
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
def test_assert_property_is_not_a_string():
cursor = connect().cursor()
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['name', 1]}, {}) YIELD * RETURN *;",
)
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label+property", "Person", "name", 0)]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(name);")
def test_assert_creates_label_index_multiple_empty_strings():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['', '', '', '']}, {}) YIELD * RETURN *;",
)
)
assert results == [("Created", "", [], "Person", False)]
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label", "Person", None, 0)]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
def test_assert_creates_label_property_index():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['name']}, {}) YIELD * RETURN *;",
)
)
assert results == [("Created", "name", ["name"], "Person", False)]
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label+property", "Person", "name", 0)]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(name);")
def test_assert_creates_multiple_indices():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['', 'id', 'name'], Ball: ['', 'size', 'size', '']}, {}) YIELD * RETURN *;",
)
)
assert len(results) == 5
assert results[0] == ("Created", "", [], "Ball", False)
assert results[1] == ("Created", "size", ["size"], "Ball", False)
assert results[2] == ("Created", "", [], "Person", False)
assert results[3] == ("Created", "id", ["id"], "Person", False)
assert results[4] == ("Created", "name", ["name"], "Person", False)
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert len(show_index_results) == 5
assert show_index_results[0] == ("label", "Ball", None, 0)
assert show_index_results[1] == ("label", "Person", None, 0)
assert show_index_results[2] == ("label+property", "Ball", "size", 0)
assert show_index_results[3] == ("label+property", "Person", "id", 0)
assert show_index_results[4] == ("label+property", "Person", "name", 0)
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(name);")
execute_and_fetch_all(cursor, "DROP INDEX ON :Ball;")
execute_and_fetch_all(cursor, "DROP INDEX ON :Ball(size);")
def test_assert_creates_existence_constraints():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({}, {}, {Person: ['name', 'surname']}) YIELD * RETURN *;",
)
)
assert results == [
("Created", "name", ["name"], "Person", False),
("Created", "surname", ["surname"], "Person", False),
]
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("exists", "Person", "name"), ("exists", "Person", "surname")]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
def test_assert_dropping_indices():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person(name);")
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "CREATE INDEX ON :Ball(size);")
execute_and_fetch_all(cursor, "CREATE INDEX ON :Ball;")
results = list(execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}) YIELD * RETURN *;"))
assert len(results) == 4
assert results[0] == ("Dropped", "", [], "Ball", False)
assert results[1] == ("Dropped", "size", ["size"], "Ball", False)
assert results[2] == ("Dropped", "id", ["id"], "Person", False)
assert results[3] == ("Dropped", "name", ["name"], "Person", False)
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == []
def test_assert_existence_constraint_properties_not_list():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {Person: 'name'}) YIELD * RETURN *;")
assert list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;")) == []
def test_assert_existence_constraint_property_not_string():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {Person: ['name', 1]}) YIELD * RETURN *;")
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("exists", "Person", "name")]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
def test_assert_existence_constraint_property_empty_string():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {Person: ['']}) YIELD * RETURN *;")
assert list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;")) == []
def test_assert_creates_indices_and_existence_constraints():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['', 'id']}, {}, {Person: ['name', 'surname']}) YIELD * RETURN *;",
)
)
assert len(results) == 4
assert results[0] == ("Created", "", [], "Person", False)
assert results[1] == ("Created", "id", ["id"], "Person", False)
assert results[2] == ("Created", "name", ["name"], "Person", False)
assert results[3] == ("Created", "surname", ["surname"], "Person", False)
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label", "Person", None, 0), ("label+property", "Person", "id", 0)]
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("exists", "Person", "name"), ("exists", "Person", "surname")]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
def test_assert_drops_existence_constraints():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
results = list(execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {}) YIELD * RETURN *;"))
assert len(results) == 2
assert results[0] == ("Dropped", "name", ["name"], "Person", False)
assert results[1] == ("Dropped", "surname", ["surname"], "Person", False)
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == []
def test_assert_creates_unique_constraints():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({}, {Person: [['name', 'surname']]}) YIELD * RETURN *;",
)
)
assert results == [("Created", "[name, surname]", ["name", "surname"], "Person", True)]
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("unique", "Person", ["name", "surname"])]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
def test_assert_creates_multiple_unique_constraints():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({}, {Person: [['name', 'surname'], ['id']]}) YIELD * RETURN *;",
)
)
assert results == [
("Created", "[name, surname]", ["name", "surname"], "Person", True),
("Created", "[id]", ["id"], "Person", True),
]
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("unique", "Person", ["name", "surname"]), ("unique", "Person", ["id"])]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
def test_assert_creates_unique_constraints_skip_invalid():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({}, {Person: [['name', 'surname'], 'wrong_type']}) YIELD * RETURN *;",
)
)
assert results == [("Created", "[name, surname]", ["name", "surname"], "Person", True)]
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("unique", "Person", ["name", "surname"])]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
def test_assert_creates_unique_constraints_skip_invalid_map_type():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({}, {Person: [['name', 'surname']], Ball: 'wrong_type'}) YIELD * RETURN *;",
)
)
assert results == [("Created", "[name, surname]", ["name", "surname"], "Person", True)]
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [("unique", "Person", ["name", "surname"])]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
def test_assert_creates_constraints_and_indices():
cursor = connect().cursor()
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['', 'id']}, {Person: [['name', 'surname'], ['id']]}, {Person: ['name', 'surname']}) YIELD * RETURN *;",
)
)
assert len(results) == 6
assert results[0] == ("Created", "", [], "Person", False)
assert results[1] == ("Created", "id", ["id"], "Person", False)
assert results[2] == ("Created", "name", ["name"], "Person", False)
assert results[3] == ("Created", "surname", ["surname"], "Person", False)
assert results[4] == ("Created", "[name, surname]", ["name", "surname"], "Person", True)
assert results[5] == ("Created", "[id]", ["id"], "Person", True)
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label", "Person", None, 0), ("label+property", "Person", "id", 0)]
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [
("exists", "Person", "name"),
("exists", "Person", "surname"),
("unique", "Person", ["name", "surname"]),
("unique", "Person", ["id"]),
]
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
def test_assert_drops_unique_constraints():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
results = list(execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {}) YIELD * RETURN *;"))
assert len(results) == 2
assert results[0] == ("Dropped", "[id]", ["id"], "Person", True)
assert results[1] == ("Dropped", "[name, surname]", ["name", "surname"], "Person", True)
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == []
def test_assert_drops_indices_and_constraints():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person;")
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
results = list(execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {}) YIELD * RETURN *;"))
assert len(results) == 6
assert results[0] == ("Dropped", "", [], "Person", False)
assert results[1] == ("Dropped", "id", ["id"], "Person", False)
assert results[2] == ("Dropped", "name", ["name"], "Person", False)
assert results[3] == ("Dropped", "surname", ["surname"], "Person", False)
assert results[4] == ("Dropped", "[id]", ["id"], "Person", True)
assert results[5] == ("Dropped", "[name, surname]", ["name", "surname"], "Person", True)
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == []
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == []
def test_assert_does_not_drop_indices_and_constraints():
cursor = connect().cursor()
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person;")
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
results = list(execute_and_fetch_all(cursor, "CALL libschema.assert({}, {}, {}, false) YIELD * RETURN *;"))
assert len(results) == 0
show_index_results = list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;"))
assert show_index_results == [("label", "Person", None, 0), ("label+property", "Person", "id", 0)]
show_constraint_results = list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;"))
assert show_constraint_results == [
("exists", "Person", "name"),
("exists", "Person", "surname"),
("unique", "Person", ["name", "surname"]),
("unique", "Person", ["id"]),
]
execute_and_fetch_all(cursor, "DROP INDEX ON :Person;")
execute_and_fetch_all(cursor, "DROP INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "DROP CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
def test_assert_keeps_existing_indices_and_constraints():
cursor = connect().cursor()
assert list(execute_and_fetch_all(cursor, "SHOW INDEX INFO;")) == []
assert list(execute_and_fetch_all(cursor, "SHOW CONSTRAINT INFO;")) == []
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person;")
execute_and_fetch_all(cursor, "CREATE INDEX ON :Person(id);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.name, n.surname IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);")
execute_and_fetch_all(cursor, "CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.surname);")
results = list(
execute_and_fetch_all(
cursor,
"CALL libschema.assert({Person: ['id']}, {Person: [['name', 'surname']]}, {Person: ['name']}) YIELD * RETURN *;",
)
)
print(results)
assert len(results) == 6
assert results[0] == ("Kept", "id", ["id"], "Person", False) # label+property index on Person(id) should be kept
assert results[1] == ("Dropped", "", [], "Person", False) # label index on Person should be deleted
assert results[2] == (
"Kept",
"name",
["name"],
"Person",
False,
) # existence constraint on Person(name) should be kept
assert results[3] == (
"Dropped",
"surname",
["surname"],
"Person",
False,
) # existence constraint on surname should be deleted
assert results[4] == (
"Kept",
"[name, surname]",
["name", "surname"],
"Person",
True,
) # unique constraint on Person(name, surname) should be kept
assert results[5] == (
"Dropped",
"[id]",
["id"],
"Person",
True,
) # unique constraint on Person(id) should be deleted
def test_node_type_properties1():
cursor = connect().cursor()
execute_and_fetch_all(

View File

@ -27,7 +27,7 @@ workloads:
args: ["query_modules/mgps_test.py"]
<<: *in_memory_cluster
- name: "Convert query module test"
- name: "Schema test"
pre_set_workload: "tests/e2e/x.sh"
binary: "tests/e2e/pytest_runner.sh"
proc: "query_modules/"