diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e426cadf0..dc4315e74 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,8 @@ set(mg_single_node_sources storage/single_node/record_accessor.cpp storage/single_node/vertex_accessor.cpp storage/single_node/constraints/existence_constraints.cpp + storage/single_node/constraints/record.cpp + storage/single_node/constraints/unique_label_property_constraint.cpp transactions/single_node/engine.cpp memgraph_init.cpp ) diff --git a/src/database/single_node/graph_db_accessor.hpp b/src/database/single_node/graph_db_accessor.hpp index e2f35e36f..614c78580 100644 --- a/src/database/single_node/graph_db_accessor.hpp +++ b/src/database/single_node/graph_db_accessor.hpp @@ -13,6 +13,7 @@ #include "database/single_node/graph_db.hpp" #include "storage/common/types/types.hpp" +#include "storage/single_node/constraints/exceptions.hpp" #include "storage/single_node/edge_accessor.hpp" #include "storage/single_node/vertex_accessor.hpp" #include "transactions/transaction.hpp" @@ -22,11 +23,6 @@ namespace database { -/** Thrown when inserting in an index with constraint. */ -class IndexConstraintViolationException : public utils::BasicException { - using utils::BasicException::BasicException; -}; - /** Thrown when creating an index which already exists. */ class IndexExistsException : public utils::BasicException { using utils::BasicException::BasicException; diff --git a/src/storage/single_node/constraints/exceptions.hpp b/src/storage/single_node/constraints/exceptions.hpp new file mode 100644 index 000000000..2ca4e2101 --- /dev/null +++ b/src/storage/single_node/constraints/exceptions.hpp @@ -0,0 +1,12 @@ +/// @file + +#pragma once + +#include "utils/exceptions.hpp" + +namespace database { +/** Thrown when inserting in an unique constraint. */ +class IndexConstraintViolationException : public utils::BasicException { + using utils::BasicException::BasicException; +}; +} // namespace database diff --git a/src/storage/single_node/constraints/record.cpp b/src/storage/single_node/constraints/record.cpp new file mode 100644 index 000000000..45bcf4e3e --- /dev/null +++ b/src/storage/single_node/constraints/record.cpp @@ -0,0 +1,62 @@ +#include "storage/single_node/constraints/record.hpp" + +#include "storage/single_node/constraints/exceptions.hpp" +#include "storage/single_node/mvcc/version_list.hpp" +#include "transactions/single_node/engine.hpp" +#include "transactions/transaction.hpp" + +namespace storage::constraints::impl { +Record::Record(gid::Gid gid, const tx::Transaction &t) + : curr_gid(gid), tx_id_cre(t.id_) {} + +void Record::Insert(gid::Gid gid, const tx::Transaction &t) { + // Insert + // - delete before or in this transaction and not aborted + // - insert before and aborted + // Throw SerializationError + // - delted of inserted after this transaction + // Throw IndexConstraintViolationException + // - insert before or in this transaction and not aborted + // - delete before and aborted + + t.TakeLock(lock_); + if (t.id_ < tx_id_cre || (tx_id_exp != 0 && t.id_ < tx_id_exp)) + throw mvcc::SerializationError(); + + bool has_entry = tx_id_exp == 0; + bool is_aborted = has_entry ? t.engine_.Info(tx_id_cre).is_aborted() + : t.engine_.Info(tx_id_exp).is_aborted(); + + if ((has_entry && !is_aborted) || (!has_entry && is_aborted)) + throw database::IndexConstraintViolationException( + "Node couldn't be updated due to unique constraint violation!"); + + curr_gid = gid; + tx_id_cre = t.id_; + tx_id_exp = 0; +} + +void Record::Remove(gid::Gid gid, const tx::Transaction &t) { + // Remove + // - insert before or in this transaction and not aborted + // - remove before and aborted + // Nothing + // - remove before or in this transaction and not aborted + // - insert before and aborted + // Throw SerializationError + // - delete or insert after this transaction + + t.TakeLock(lock_); + DCHECK(gid == curr_gid); + if (t.id_ < tx_id_cre || (tx_id_exp != 0 && t.id_ < tx_id_exp)) + throw mvcc::SerializationError(); + + bool has_entry = tx_id_exp == 0; + bool is_aborted = has_entry ? t.engine_.Info(tx_id_cre).is_aborted() + : t.engine_.Info(tx_id_exp).is_aborted(); + + if ((!has_entry && !is_aborted) || (has_entry && is_aborted)) return; + + tx_id_exp = t.id_; +} +} // namespace storage::constraints::impl diff --git a/src/storage/single_node/constraints/record.hpp b/src/storage/single_node/constraints/record.hpp new file mode 100644 index 000000000..97271ee1c --- /dev/null +++ b/src/storage/single_node/constraints/record.hpp @@ -0,0 +1,25 @@ +/// @file + +#pragma once + +#include "storage/common/locking/record_lock.hpp" +#include "storage/single_node/gid.hpp" +#include "transactions/type.hpp" + +namespace tx { +class Transaction; +} // namespace tx + +namespace storage::constraints::impl { +/// Contains records of creation and deletion of entry in a constraint. +struct Record { + Record(gid::Gid gid, const tx::Transaction &t); + void Insert(gid::Gid gid, const tx::Transaction &t); + void Remove(gid::Gid gid, const tx::Transaction &t); + + gid::Gid curr_gid; + tx::TransactionId tx_id_cre; + tx::TransactionId tx_id_exp{0}; + RecordLock lock_; +}; +} // namespace storage::constraints::impl diff --git a/src/storage/single_node/constraints/unique_label_property_constraint.cpp b/src/storage/single_node/constraints/unique_label_property_constraint.cpp new file mode 100644 index 000000000..877c84fe4 --- /dev/null +++ b/src/storage/single_node/constraints/unique_label_property_constraint.cpp @@ -0,0 +1,143 @@ +#include "storage/single_node/constraints/unique_label_property_constraint.hpp" + +#include "storage/single_node/vertex_accessor.hpp" +#include "utils/algorithm.hpp" + +namespace storage::constraints { +auto FindIn(storage::Label label, storage::Property property, + const std::list &constraints) { + return std::find_if(constraints.begin(), constraints.end(), + [label, property](auto &c) { + return c.label == label && c.property == property; + }); +} + +void UniqueLabelPropertyConstraint::AddConstraint(storage::Label label, + storage::Property property, + const tx::Transaction &t) { + auto found = FindIn(label, property, constraints_); + if (found == constraints_.end()) constraints_.emplace_back(label, property); +} + +void UniqueLabelPropertyConstraint::RemoveConstraint( + storage::Label label, storage::Property property) { + auto found = FindIn(label, property, constraints_); + if (found != constraints_.end()) constraints_.erase(found); +} + +bool UniqueLabelPropertyConstraint::Exists(storage::Label label, + storage::Property property) const { + return FindIn(label, property, constraints_) != constraints_.end(); +} + +std::vector UniqueLabelPropertyConstraint::ListConstraints() + const { + std::vector constraints; + constraints.reserve(constraints_.size()); + std::transform(constraints_.begin(), constraints_.end(), + std::back_inserter(constraints), [](auto &c) { + return LabelProperty{c.label, c.property}; + }); + return constraints; +} + +void UniqueLabelPropertyConstraint::UpdateOnAddLabel( + storage::Label label, const VertexAccessor &accessor, + const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + auto value = vertex.properties_.at(constraint.property); + if (constraint.label == label && !value.IsNull()) { + bool found = false; + for (auto &p : constraint.version_pairs) { + if (p.value == value) { + p.record.Insert(accessor.gid(), t); + found = true; + break; + } + } + + if (!found) { + constraint.version_pairs.emplace_back(accessor.gid(), value, t); + } + } + } +} +void UniqueLabelPropertyConstraint::UpdateOnRemoveLabel( + storage::Label label, const VertexAccessor &accessor, + const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + auto value = vertex.properties_.at(constraint.property); + if (constraint.label == label && !value.IsNull()) { + for (auto &p : constraint.version_pairs) { + if (p.value == value) { + p.record.Remove(accessor.gid(), t); + break; + } + } + } + } +} + +void UniqueLabelPropertyConstraint::UpdateOnAddProperty( + storage::Property property, const PropertyValue &value, + const VertexAccessor &accessor, const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (constraint.property == property && + utils::Contains(vertex.labels_, constraint.label)) { + bool found = false; + for (auto &p : constraint.version_pairs) { + if (p.value == value) { + p.record.Insert(accessor.gid(), t); + found = true; + break; + } + } + + if (!found) { + constraint.version_pairs.emplace_back(accessor.gid(), value, t); + } + } + } +} + +void UniqueLabelPropertyConstraint::UpdateOnRemoveProperty( + storage::Property property, const PropertyValue &value, + const VertexAccessor &accessor, const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (constraint.property == property && + utils::Contains(vertex.labels_, constraint.label)) { + for (auto &p : constraint.version_pairs) { + if (p.value == value) { + p.record.Remove(accessor.gid(), t); + break; + } + } + } + } +} + +void UniqueLabelPropertyConstraint::Refresh(const tx::Snapshot &snapshot, + const tx::Engine &engine) { + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + for (auto p = constraint.version_pairs.begin(); + p != constraint.version_pairs.end(); ++p) { + auto exp_id = p->record.tx_id_exp; + auto cre_id = p->record.tx_id_cre; + if ((exp_id != 0 && exp_id < snapshot.back() && + engine.Info(exp_id).is_committed() && !snapshot.contains(exp_id)) || + (cre_id < snapshot.back() && engine.Info(cre_id).is_aborted())) { + constraint.version_pairs.erase(p); + } + } + } +} +} // namespace storage::constraints diff --git a/src/storage/single_node/constraints/unique_label_property_constraint.hpp b/src/storage/single_node/constraints/unique_label_property_constraint.hpp new file mode 100644 index 000000000..7091fa9d6 --- /dev/null +++ b/src/storage/single_node/constraints/unique_label_property_constraint.hpp @@ -0,0 +1,128 @@ +/// @file + +#pragma once + +#include +#include + +#include "storage/common/types/property_value.hpp" +#include "storage/common/types/types.hpp" +#include "storage/single_node/constraints/record.hpp" + +namespace tx { +class Snapshot; +}; + +class VertexAccessor; + +namespace storage::constraints { +namespace impl { +struct Pair { + Pair(gid::Gid gid, const PropertyValue &v, const tx::Transaction &t) + : value(v), record(gid, t) {} + + PropertyValue value; + Record record; +}; + +/// Contains data for every unique constraint rule. It consists of label, +/// property and all property values for that property. For each property value +/// there is a record that contains ids of transactions that created and +/// deleted record with that value. +struct LabelPropertyEntry { + explicit LabelPropertyEntry(storage::Label l, storage::Property p) + : label(l), property(p) {} + + storage::Label label; + storage::Property property; + std::list version_pairs; +}; +} // namespace impl + +struct LabelProperty { + // This struct is used by ListConstraints method in order to avoid using + // std::pair or something like that. + storage::Label label; + storage::Property property; +}; + +/// UniqueLabelPropertyConstraint contains all unique constraints defined by +/// both label and property. To create or delete unique constraint, caller +/// must ensure that there are no other transactions running in parallel. +/// Additionally, for adding unique constraint caller must first call +/// AddConstraint to create unique constraint and then call UpdateOnAddLabel or +/// UpdateOnAddProperty for every existing Vertex. If there is a unique +/// constraint violation then caller must manually handle that by catching +/// exception and calling RemoveConstraint method. This is needed to ensure +/// logical correctness of transactions. Once created, client uses UpdateOn +/// methods to notify UniqueLabelPropertyConstraint about changes. In case of +/// violation UpdateOn methods throw IndexConstraintViolationException +/// exception. Methods can also throw SerializationError. This class is thread +/// safe. +class UniqueLabelPropertyConstraint { + public: + UniqueLabelPropertyConstraint() = default; + UniqueLabelPropertyConstraint(const UniqueLabelPropertyConstraint &) = delete; + UniqueLabelPropertyConstraint(UniqueLabelPropertyConstraint &&) = delete; + UniqueLabelPropertyConstraint &operator=( + const UniqueLabelPropertyConstraint &) = delete; + UniqueLabelPropertyConstraint &operator=(UniqueLabelPropertyConstraint &&) = + delete; + + /// Add new unique constraint, if 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(storage::Label label, storage::Property property, + const tx::Transaction &t); + + /// Removes existing unique constraint, if the constraint doesn't exist this + /// method does nothing. Caller must ensure that no other transaction is + /// running in parallel. + void RemoveConstraint(storage::Label label, storage::Property property); + + /// Checks whether given unique constraint is visible. + bool Exists(storage::Label label, storage::Property property) const; + + /// Returns list of unique constraints. + std::vector ListConstraints() const; + + /// Updates unique constraint versions. + /// + /// @throws IndexConstraintViolationException + /// @throws SerializationError + void UpdateOnAddLabel(storage::Label label, const VertexAccessor &accessor, + const tx::Transaction &t); + + /// Updates unique constraint versions. + /// + /// @throws SerializationError + void UpdateOnRemoveLabel(storage::Label label, const VertexAccessor &accessor, + const tx::Transaction &t); + + /// Updates unique constraint versions. + /// + /// @throws IndexConstraintViolationException + /// @throws SerializationError + void UpdateOnAddProperty(storage::Property property, + const PropertyValue &value, + const VertexAccessor &accessor, + const tx::Transaction &t); + + /// Updates unique constraint versions. + /// + /// @throws SerializationError + void UpdateOnRemoveProperty(storage::Property property, + const PropertyValue &value, + const VertexAccessor &accessor, + const tx::Transaction &t); + + /// Removes records that are no longer visible. + void Refresh(const tx::Snapshot &snapshot, const tx::Engine &engine); + + private: + std::mutex lock_; + + std::list constraints_; +}; +} // namespace storage::constraints diff --git a/src/storage/single_node/record_accessor.hpp b/src/storage/single_node/record_accessor.hpp index 277391f75..1f025a16a 100644 --- a/src/storage/single_node/record_accessor.hpp +++ b/src/storage/single_node/record_accessor.hpp @@ -149,6 +149,10 @@ class RecordAccessor { */ int64_t CypherId() const; + /** Returns the current version (either new_ or old_) set on this + * RecordAccessor. */ + const TRecord ¤t() const; + protected: /** * Pointer to the version (either old_ or new_) that READ operations @@ -160,10 +164,6 @@ class RecordAccessor { */ mutable TRecord *current_{nullptr}; - /** Returns the current version (either new_ or old_) set on this - * RecordAccessor. */ - const TRecord ¤t() const; - private: // The database accessor for which this record accessor is created // Provides means of getting to the transaction and database functions. diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index e29c715d6..541da0806 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -285,6 +285,9 @@ target_link_libraries(${test_prefix}transaction_engine_single_node_ha mg-single- add_unit_test(typed_value.cpp) target_link_libraries(${test_prefix}typed_value mg-single-node kvstore_dummy_lib) +add_unit_test(unique_label_property_constraint.cpp) +target_link_libraries(${test_prefix}unique_label_property_constraint mg-single-node kvstore_dummy_lib) + # Test mg-communication add_unit_test(bolt_chunked_decoder_buffer.cpp) diff --git a/tests/unit/unique_label_property_constraint.cpp b/tests/unit/unique_label_property_constraint.cpp new file mode 100644 index 000000000..bdc6301ac --- /dev/null +++ b/tests/unit/unique_label_property_constraint.cpp @@ -0,0 +1,280 @@ +#include +#include +#include + +#include "database/single_node/graph_db.hpp" +#include "database/single_node/graph_db_accessor.hpp" +#include "storage/single_node/constraints/unique_label_property_constraint.hpp" + +class UniqueLabelPropertyTest : public ::testing::Test { + public: + void SetUp() override { + auto dba = db_.AccessBlocking(); + label_ = dba->Label("label"); + property_ = dba->Property("property"); + constraint_.AddConstraint(label_, property_, dba->transaction()); + dba->Commit(); + } + + database::GraphDb db_; + storage::Label label_; + storage::Property property_; + PropertyValue value_{"value"}; + storage::constraints::UniqueLabelPropertyConstraint constraint_; +}; + +TEST_F(UniqueLabelPropertyTest, BuildDrop) { + { + auto dba = db_.Access(); + EXPECT_TRUE(constraint_.Exists(label_, property_)); + dba->Commit(); + } + { + auto dba = db_.Access(); + constraint_.RemoveConstraint(label_, property_); + dba->Commit(); + } + { + auto dba = db_.Access(); + EXPECT_FALSE(constraint_.Exists(label_, property_)); + dba->Commit(); + } +} + +TEST_F(UniqueLabelPropertyTest, BuildWithViolation) { + auto dba1 = db_.Access(); + auto v1 = dba1->InsertVertex(); + v1.add_label(label_); + v1.PropsSet(property_, value_); + + auto v2 = dba1->InsertVertex(); + v2.add_label(label_); + v2.PropsSet(property_, value_); + dba1->Commit(); + + auto dba2 = db_.Access(); + auto v3 = dba2->FindVertex(v1.gid(), false); + auto v4 = dba2->FindVertex(v2.gid(), false); + constraint_.UpdateOnAddLabel(label_, v3, dba2->transaction()); + EXPECT_THROW(constraint_.UpdateOnAddLabel(label_, v4, dba2->transaction()), + database::IndexConstraintViolationException); + EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v4, + dba2->transaction()), + database::IndexConstraintViolationException); +} + +TEST_F(UniqueLabelPropertyTest, InsertInsert) { + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Commit(); + } + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v, + dba->transaction()), + database::IndexConstraintViolationException); + EXPECT_THROW(constraint_.UpdateOnAddLabel(label_, v, dba->transaction()), + database::IndexConstraintViolationException); + dba->Commit(); + } +} + +TEST_F(UniqueLabelPropertyTest, InsertInsertDiffValues) { + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Commit(); + } + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + PropertyValue other_value{"Some other value"}; + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, other_value); + constraint_.UpdateOnAddProperty(property_, other_value, v, + dba->transaction()); + dba->Commit(); + } +} + +TEST_F(UniqueLabelPropertyTest, InsertAbortInsert) { + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Abort(); + } + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Commit(); + } +} + +TEST_F(UniqueLabelPropertyTest, InsertRemoveAbortInsert) { + gid::Gid gid = 0; + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + gid = v.gid(); + dba->Commit(); + } + { + auto dba = db_.Access(); + auto v = dba->FindVertex(gid, false); + v.PropsErase(property_); + constraint_.UpdateOnRemoveProperty(property_, value_, v, + dba->transaction()); + dba->Abort(); + } + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v, + dba->transaction()), + database::IndexConstraintViolationException); + } +} + +TEST_F(UniqueLabelPropertyTest, InsertInsertSameTransaction) { + { + auto dba = db_.Access(); + auto v1 = dba->InsertVertex(); + v1.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v1, dba->transaction()); + v1.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v1, dba->transaction()); + + auto v2 = dba->InsertVertex(); + v2.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v2, dba->transaction()); + v2.PropsSet(property_, value_); + EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v2, + dba->transaction()), + database::IndexConstraintViolationException); + } +} + +TEST_F(UniqueLabelPropertyTest, InsertInsertReversed) { + auto dba1 = db_.Access(); + auto dba2 = db_.Access(); + + auto v2 = dba2->InsertVertex(); + v2.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v2, dba2->transaction()); + v2.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v2, dba2->transaction()); + dba2->Commit(); + + auto v1 = dba1->InsertVertex(); + v1.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v1, dba1->transaction()); + v1.PropsSet(property_, value_); + EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v1, + dba1->transaction()), + mvcc::SerializationError); +} + +TEST_F(UniqueLabelPropertyTest, InsertRemoveInsert) { + gid::Gid gid = 0; + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + gid = v.gid(); + dba->Commit(); + } + { + auto dba = db_.Access(); + auto v = dba->FindVertex(gid, false); + v.PropsErase(property_); + constraint_.UpdateOnRemoveProperty(property_, value_, v, + dba->transaction()); + dba->Commit(); + } + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + } +} + +TEST_F(UniqueLabelPropertyTest, InsertRemoveInsertSameTransaction) { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + v.PropsErase(property_); + constraint_.UpdateOnRemoveProperty(property_, value_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Commit(); +} + +TEST_F(UniqueLabelPropertyTest, InsertDropInsert) { + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Commit(); + } + { + auto dba = db_.AccessBlocking(); + constraint_.RemoveConstraint(label_, property_); + dba->Commit(); + } + { + auto dba = db_.Access(); + auto v = dba->InsertVertex(); + v.add_label(label_); + constraint_.UpdateOnAddLabel(label_, v, dba->transaction()); + v.PropsSet(property_, value_); + constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction()); + dba->Commit(); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + google::InitGoogleLogging(argv[0]); + return RUN_ALL_TESTS(); +}