Existence constraints for vertex for single node
Summary: Existence constraint ensures that all nodes with certain label have a certain property. `ExistenceRule` defines label -> properties rule and `ExistenceConstraints` manages all constraints. Reviewers: msantl, ipaljak, teon.banek, mferencevic Reviewed By: msantl, teon.banek, mferencevic Subscribers: mferencevic, pullbot Differential Revision: https://phabricator.memgraph.io/D1797
This commit is contained in:
parent
5af47c6df5
commit
a14c4f1864
@ -50,6 +50,7 @@ set(mg_single_node_sources
|
||||
storage/common/types/property_value_store.cpp
|
||||
storage/single_node/record_accessor.cpp
|
||||
storage/single_node/vertex_accessor.cpp
|
||||
storage/single_node/constraints/existence_constraints.cpp
|
||||
transactions/single_node/engine.cpp
|
||||
memgraph_init.cpp
|
||||
)
|
||||
|
@ -94,7 +94,8 @@ class GraphDb {
|
||||
/// Create a new accessor by starting a new transaction.
|
||||
std::unique_ptr<GraphDbAccessor> Access();
|
||||
std::unique_ptr<GraphDbAccessor> AccessBlocking(
|
||||
std::experimental::optional<tx::TransactionId> parent_tx);
|
||||
std::experimental::optional<tx::TransactionId> parent_tx =
|
||||
std::experimental::nullopt);
|
||||
/// Create an accessor for a running transaction.
|
||||
std::unique_ptr<GraphDbAccessor> Access(tx::TransactionId);
|
||||
|
||||
|
@ -184,7 +184,7 @@ void GraphDbAccessor::DeleteIndex(storage::Label label,
|
||||
|
||||
void GraphDbAccessor::UpdateLabelIndices(storage::Label label,
|
||||
const VertexAccessor &vertex_accessor,
|
||||
const Vertex *const vertex) {
|
||||
const Vertex *vertex) {
|
||||
DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted";
|
||||
auto *vlist_ptr = vertex_accessor.address();
|
||||
|
||||
@ -194,19 +194,70 @@ void GraphDbAccessor::UpdateLabelIndices(storage::Label label,
|
||||
"Node couldn't be updated due to index constraint violation!");
|
||||
}
|
||||
db_.storage().labels_index_.Update(label, vlist_ptr, vertex);
|
||||
|
||||
if (!db_.storage().existence_constraints_.CheckIfSatisfies(vertex)) {
|
||||
throw IndexConstraintViolationException(
|
||||
"Node couldn't be updated due to existence constraint violation!");
|
||||
}
|
||||
}
|
||||
|
||||
void GraphDbAccessor::UpdatePropertyIndex(
|
||||
storage::Property property, const RecordAccessor<Vertex> &vertex_accessor,
|
||||
const Vertex *const vertex) {
|
||||
const Vertex *vertex) {
|
||||
DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted";
|
||||
if (!db_.storage().label_property_index_.UpdateOnProperty(
|
||||
property, vertex_accessor.address(), vertex)) {
|
||||
throw IndexConstraintViolationException(
|
||||
"Node couldn't be updated due to index constraint violation!");
|
||||
"Node couldn't be updated due to existence constraint violation!");
|
||||
}
|
||||
}
|
||||
|
||||
void GraphDbAccessor::UpdateOnPropertyRemove(storage::Property property,
|
||||
const Vertex *vertex,
|
||||
const RecordAccessor<Vertex> &accessor) {
|
||||
if (!db_.storage().existence_constraints_.CheckIfSatisfies(vertex)) {
|
||||
throw IndexConstraintViolationException(
|
||||
"Node couldn't be updated due to existence constraint violation!");
|
||||
}
|
||||
}
|
||||
|
||||
void GraphDbAccessor::BuildExistenceConstraint(
|
||||
storage::Label label, const std::vector<storage::Property> &properties) {
|
||||
auto dba =
|
||||
db_.AccessBlocking(std::experimental::make_optional(transaction().id_));
|
||||
ExistenceRule rule{label, properties};
|
||||
|
||||
for (auto v : dba->Vertices(false)) {
|
||||
if (!CheckIfSatisfiesExistenceRule(v.GetOld(), rule)) {
|
||||
throw IndexConstraintViolationException(
|
||||
"Node couldn't be updated due to existence constraint violation!");
|
||||
}
|
||||
}
|
||||
|
||||
db_.storage().existence_constraints_.AddConstraint(rule);
|
||||
// TODO
|
||||
//wal().Emplace(database::StateDelta::BuildExistenceConstraint();
|
||||
dba->Commit();
|
||||
}
|
||||
|
||||
void GraphDbAccessor::DeleteExistenceConstraint(
|
||||
storage::Label label, const std::vector<storage::Property> &properties) {
|
||||
auto dba =
|
||||
db_.AccessBlocking(std::experimental::make_optional(transaction().id_));
|
||||
ExistenceRule rule{label, properties};
|
||||
db_.storage().existence_constraints_.RemoveConstraint(rule);
|
||||
// TODO
|
||||
//wal().Emplace(database::StateDelta::DeleteExistenceConstraint();
|
||||
dba->Commit();
|
||||
}
|
||||
|
||||
bool GraphDbAccessor::ExistenceConstraintExists(
|
||||
storage::Label label,
|
||||
const std::vector<storage::Property> &properties) const {
|
||||
ExistenceRule rule{label, properties};
|
||||
return db_.storage().existence_constraints_.Exists(rule);
|
||||
}
|
||||
|
||||
int64_t GraphDbAccessor::VerticesCount() const {
|
||||
DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted";
|
||||
return db_.storage().vertices_.access().size();
|
||||
|
@ -469,6 +469,26 @@ class GraphDbAccessor {
|
||||
return db_.storage().label_property_index_.Keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new existence constraint.
|
||||
*/
|
||||
void BuildExistenceConstraint(
|
||||
storage::Label label, const std::vector<storage::Property> &properties);
|
||||
|
||||
/**
|
||||
* Deletes existing existence constraint.
|
||||
*/
|
||||
void DeleteExistenceConstraint(
|
||||
storage::Label label, const std::vector<storage::Property> &properties);
|
||||
|
||||
/**
|
||||
* Checks whether constraint exists.
|
||||
*/
|
||||
bool ExistenceConstraintExists(
|
||||
storage::Label label,
|
||||
const std::vector<storage::Property> &properties) const;
|
||||
|
||||
|
||||
/**
|
||||
* Return approximate number of all vertices in the database.
|
||||
* Note that this is always an over-estimate and never an under-estimate.
|
||||
@ -639,6 +659,13 @@ class GraphDbAccessor {
|
||||
bool commited_{false};
|
||||
bool aborted_{false};
|
||||
|
||||
/**
|
||||
* Notifies storage about change.
|
||||
*/
|
||||
void UpdateOnPropertyRemove(storage::Property property,
|
||||
const Vertex *vertex,
|
||||
const RecordAccessor<Vertex> &accessor);
|
||||
|
||||
/**
|
||||
* Insert this vertex into corresponding any label + 'property' index.
|
||||
* @param property - vertex will be inserted into indexes which contain this
|
||||
|
@ -0,0 +1,53 @@
|
||||
#include "storage/single_node/constraints/existence_constraints.hpp"
|
||||
|
||||
namespace database {
|
||||
bool Contains(const PropertyValueStore &store,
|
||||
const std::vector<storage::Property> &properties) {
|
||||
for (auto &property : properties) {
|
||||
if (store.at(property).IsNull()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckIfSatisfiesExistenceRule(const Vertex *vertex,
|
||||
const database::ExistenceRule &rule) {
|
||||
if (!utils::Contains(vertex->labels_, rule.label)) return true;
|
||||
if (!Contains(vertex->properties_, rule.properties)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExistenceConstraints::AddConstraint(const ExistenceRule &rule) {
|
||||
auto found = std::find(constraints_.begin(), constraints_.end(), rule);
|
||||
if (found != constraints_.end()) return;
|
||||
|
||||
constraints_.push_back(rule);
|
||||
}
|
||||
|
||||
void ExistenceConstraints::RemoveConstraint(const ExistenceRule &rule) {
|
||||
auto found = std::find(constraints_.begin(), constraints_.end(), rule);
|
||||
if (found != constraints_.end()) {
|
||||
constraints_.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
bool ExistenceConstraints::Exists(const ExistenceRule &rule) const {
|
||||
auto found = std::find(constraints_.begin(), constraints_.end(), rule);
|
||||
return found != constraints_.end();
|
||||
}
|
||||
|
||||
bool ExistenceConstraints::CheckIfSatisfies(const Vertex *vertex) const {
|
||||
for (auto &constraint : constraints_) {
|
||||
if (!CheckIfSatisfiesExistenceRule(vertex, constraint)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::list<ExistenceRule> &ExistenceConstraints::ListConstraints() const {
|
||||
return constraints_;
|
||||
}
|
||||
} // namespace database
|
@ -0,0 +1,76 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/common/types/property_value.hpp"
|
||||
#include "storage/common/types/types.hpp"
|
||||
#include "storage/single_node/vertex.hpp"
|
||||
#include "transactions/type.hpp"
|
||||
|
||||
namespace database {
|
||||
|
||||
/// Existence rule defines label -> set of properties rule. This means that
|
||||
/// every vertex with that label has to have every property in the set of
|
||||
/// properties. This rule doesn't care about the PropertyValues for those
|
||||
/// properties.
|
||||
struct ExistenceRule {
|
||||
storage::Label label;
|
||||
std::vector<storage::Property> properties;
|
||||
|
||||
bool operator==(const ExistenceRule &rule) const {
|
||||
return label == rule.label && properties == rule.properties;
|
||||
}
|
||||
|
||||
bool operator!=(const ExistenceRule &rule) const { return !(*this == rule); }
|
||||
};
|
||||
|
||||
bool CheckIfSatisfiesExistenceRule(const Vertex *vertex,
|
||||
const database::ExistenceRule &rule);
|
||||
|
||||
/// ExistenceConstraints contains all active constrains. Existence constraints
|
||||
/// are restriction on the vertices and are defined by ExistenceRule.
|
||||
/// To create and delete constraint, the caller must ensure that there are no
|
||||
/// other transactions running in parallel.
|
||||
/// Additionally, for adding constraint caller must check existing vertices for
|
||||
/// constraint violations before adding that constraint. You may use
|
||||
/// CheckIfSatisfiesExistenceRule function for that.
|
||||
/// This is needed to ensure logical correctness of transactions.
|
||||
/// Once created, the client uses method CheckIfSatisfies to check that updated
|
||||
/// vertex doesn't violate any of the existing constraints. If it does, that
|
||||
/// update must be reverted.
|
||||
class ExistenceConstraints {
|
||||
public:
|
||||
ExistenceConstraints() = default;
|
||||
ExistenceConstraints(const ExistenceConstraints &) = delete;
|
||||
ExistenceConstraints(ExistenceConstraints &&) = delete;
|
||||
ExistenceConstraints &operator=(const ExistenceConstraints &) = delete;
|
||||
ExistenceConstraints &operator=(ExistenceConstraints &&) = delete;
|
||||
|
||||
/// Adds new constraint, if the constraint already exists this method does
|
||||
/// nothing. This method doesn't check if any of the existing vertices breaks
|
||||
/// this constraint. Caller must do that instead. Caller must also ensure
|
||||
/// that no other transaction is running in parallel.
|
||||
void AddConstraint(const ExistenceRule &rule);
|
||||
|
||||
/// Removes existing constraint, if the constraint doesn't exist this method
|
||||
/// does nothing. Caller must ensure that no other transaction is running in
|
||||
/// parallel.
|
||||
void RemoveConstraint(const ExistenceRule &rule);
|
||||
|
||||
/// Checks whether given constraint is visible.
|
||||
bool Exists(const ExistenceRule &rule) const;
|
||||
|
||||
/// Check if given vertex satisfies all visible constraints.
|
||||
// TODO I could check this faster if I knew exactly what changed
|
||||
bool CheckIfSatisfies(const Vertex *vertex) const;
|
||||
|
||||
/// Returns list of all constraints.
|
||||
const std::list<ExistenceRule> &ListConstraints() const;
|
||||
|
||||
private:
|
||||
std::list<ExistenceRule> constraints_;
|
||||
};
|
||||
}; // namespace database
|
@ -48,7 +48,8 @@ void RecordAccessor<Vertex>::PropsErase(storage::Property key) {
|
||||
StateDelta::PropsSetVertex(dba.transaction_id(), gid(), key,
|
||||
dba.PropertyName(key), PropertyValue::Null);
|
||||
update().properties_.set(key, PropertyValue::Null);
|
||||
db_accessor().wal().Emplace(delta);
|
||||
dba.UpdateOnPropertyRemove(key, GetNew(), *this);
|
||||
dba.wal().Emplace(delta);
|
||||
}
|
||||
|
||||
template <>
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "storage/common/types/types.hpp"
|
||||
#include "storage/common/kvstore/kvstore.hpp"
|
||||
#include "storage/single_node/edge.hpp"
|
||||
#include "storage/single_node/constraints/existence_constraints.hpp"
|
||||
#include "storage/single_node/indexes/key_index.hpp"
|
||||
#include "storage/single_node/indexes/label_property_index.hpp"
|
||||
#include "storage/single_node/vertex.hpp"
|
||||
@ -75,6 +76,9 @@ class Storage {
|
||||
KeyIndex<storage::Label, Vertex> labels_index_;
|
||||
LabelPropertyIndex label_property_index_;
|
||||
|
||||
// existence constraints
|
||||
ExistenceConstraints existence_constraints_;
|
||||
|
||||
std::vector<std::string> properties_on_disk_;
|
||||
|
||||
/// Gets the Vertex/Edge main storage map.
|
||||
|
@ -128,6 +128,9 @@ target_link_libraries(${test_prefix}edges_distributed mg-distributed kvstore_dum
|
||||
add_unit_test(edges_single_node.cpp)
|
||||
target_link_libraries(${test_prefix}edges_single_node mg-single-node kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(existence_constraints.cpp)
|
||||
target_link_libraries(${test_prefix}existence_constraints mg-single-node kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(gid.cpp)
|
||||
target_link_libraries(${test_prefix}gid mg-distributed kvstore_dummy_lib)
|
||||
|
||||
|
140
tests/unit/existence_constraints.cpp
Normal file
140
tests/unit/existence_constraints.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "database/single_node/graph_db.hpp"
|
||||
#include "database/single_node/graph_db_accessor.hpp"
|
||||
#include "storage/single_node/constraints/existence_constraints.hpp"
|
||||
|
||||
class ExistenceConstraintsTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {}
|
||||
database::ExistenceConstraints constraints_;
|
||||
database::GraphDb db_;
|
||||
};
|
||||
|
||||
TEST_F(ExistenceConstraintsTest, MultiBuildDrop) {
|
||||
auto d = db_.Access();
|
||||
auto label = d->Label("label");
|
||||
auto prop = d->Property("property");
|
||||
database::ExistenceRule rule{label, std::vector<storage::Property>{prop}};
|
||||
d->Commit();
|
||||
|
||||
{
|
||||
auto dba = db_.AccessBlocking();
|
||||
constraints_.AddConstraint(rule);
|
||||
EXPECT_TRUE(constraints_.Exists(rule));
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.AccessBlocking();
|
||||
constraints_.RemoveConstraint(rule);
|
||||
EXPECT_FALSE(constraints_.Exists(rule));
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExistenceConstraintsTest, InsertTest) {
|
||||
auto d = db_.Access();
|
||||
auto label = d->Label("label");
|
||||
auto prop = d->Property("property");
|
||||
database::ExistenceRule rule{label, std::vector<storage::Property>{prop}};
|
||||
d->Commit();
|
||||
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label);
|
||||
EXPECT_TRUE(constraints_.CheckIfSatisfies(v.GetNew()));
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
bool can_add_constraint = true;
|
||||
for (auto v : dba->Vertices(false)) {
|
||||
if (!database::CheckIfSatisfiesExistenceRule(v.GetOld(), rule)) {
|
||||
can_add_constraint = false;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_FALSE(can_add_constraint);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.AccessBlocking();
|
||||
constraints_.AddConstraint(rule);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v1 = dba->InsertVertex();
|
||||
v1.add_label(label);
|
||||
EXPECT_FALSE(
|
||||
constraints_.CheckIfSatisfies(v1.GetNew()));
|
||||
auto v2 = dba->InsertVertex();
|
||||
v2.PropsSet(prop, PropertyValue(false));
|
||||
v2.add_label(label);
|
||||
EXPECT_TRUE(constraints_.CheckIfSatisfies(v2.GetNew()));
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.AccessBlocking();
|
||||
constraints_.RemoveConstraint(rule);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label);
|
||||
EXPECT_TRUE(constraints_.CheckIfSatisfies(v.GetNew()));
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExistenceConstraintsTest, GraphDbAccessor) {
|
||||
auto d = db_.Access();
|
||||
auto label = d->Label("label");
|
||||
auto prop = d->Property("property");
|
||||
auto properties = std::vector<storage::Property>{prop};
|
||||
d->Commit();
|
||||
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
dba->BuildExistenceConstraint(label, properties);
|
||||
// Constraint is not visible because transaction creates blocking
|
||||
// transaction with different id;
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
EXPECT_TRUE(dba->ExistenceConstraintExists(label, properties));
|
||||
auto v1 = dba->InsertVertex();
|
||||
EXPECT_THROW(v1.add_label(label),
|
||||
database::IndexConstraintViolationException);
|
||||
auto v2 = dba->InsertVertex();
|
||||
v2.PropsSet(prop, PropertyValue(false));
|
||||
v2.add_label(label);
|
||||
EXPECT_THROW(v2.PropsErase(prop),
|
||||
database::IndexConstraintViolationException);
|
||||
v2.remove_label(label);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
dba->DeleteExistenceConstraint(label, properties);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
EXPECT_FALSE(dba->ExistenceConstraintExists(label, properties));
|
||||
auto v1 = dba->InsertVertex();
|
||||
v1.add_label(label);
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
Reference in New Issue
Block a user