From 37c68f05085ae7fe1258ea60cc0ed1976136ce1d Mon Sep 17 00:00:00 2001 From: Matija Santl Date: Mon, 13 May 2019 15:26:56 +0200 Subject: [PATCH] Add multiple properties unique constraint Summary: Unique constraint now support multiple properties Depends on D2043 Reviewers: ipaljak, mferencevic, vkasljevic Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2044 --- src/CMakeLists.txt | 3 +- .../distributed/graph_db_accessor.hpp | 4 +- src/database/single_node/exceptions.hpp | 11 +- .../single_node/graph_db_accessor.cpp | 148 ++++--- .../single_node/graph_db_accessor.hpp | 43 +- .../single_node_ha/graph_db_accessor.cpp | 12 +- .../single_node_ha/graph_db_accessor.hpp | 9 +- src/durability/single_node/recovery.cpp | 6 +- src/durability/single_node/snapshooter.cpp | 10 +- src/durability/single_node/state_delta.cpp | 8 +- src/query/common.hpp | 2 +- src/query/interpreter.cpp | 104 +++-- .../single_node/constraints/exceptions.hpp | 13 +- .../single_node/constraints/record.cpp | 18 +- .../constraints/unique_constraints.cpp | 258 ++++++++++++ ..._constraint.hpp => unique_constraints.hpp} | 105 +++-- .../unique_label_properties_constraint.cpp | 155 -------- .../unique_label_property_constraint.cpp | 183 --------- .../unique_label_property_constraint.hpp | 138 ------- src/storage/single_node/record_accessor.cpp | 6 +- src/storage/single_node/storage.hpp | 5 +- src/storage/single_node/storage_gc.hpp | 1 + tests/unit/CMakeLists.txt | 7 +- tests/unit/durability.cpp | 37 +- tests/unit/graph_db_accessor_index_api.cpp | 8 +- tests/unit/interpreter.cpp | 48 +++ tests/unit/unique_constraints.cpp | 298 ++++++++++++++ .../unique_label_properties_constraint.cpp | 376 ------------------ .../unit/unique_label_property_constraint.cpp | 227 ----------- 29 files changed, 942 insertions(+), 1301 deletions(-) create mode 100644 src/storage/single_node/constraints/unique_constraints.cpp rename src/storage/single_node/constraints/{unique_label_properties_constraint.hpp => unique_constraints.hpp} (51%) delete mode 100644 src/storage/single_node/constraints/unique_label_properties_constraint.cpp delete mode 100644 src/storage/single_node/constraints/unique_label_property_constraint.cpp delete mode 100644 src/storage/single_node/constraints/unique_label_property_constraint.hpp create mode 100644 tests/unit/unique_constraints.cpp delete mode 100644 tests/unit/unique_label_properties_constraint.cpp delete mode 100644 tests/unit/unique_label_property_constraint.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 655327bf4..f6044bfee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,8 +70,7 @@ set(mg_single_node_sources storage/single_node/record_accessor.cpp storage/single_node/vertex_accessor.cpp storage/single_node/constraints/record.cpp - storage/single_node/constraints/unique_label_properties_constraint.cpp - storage/single_node/constraints/unique_label_property_constraint.cpp + storage/single_node/constraints/unique_constraints.cpp transactions/single_node/engine.cpp memgraph_init.cpp ) diff --git a/src/database/distributed/graph_db_accessor.hpp b/src/database/distributed/graph_db_accessor.hpp index e0a94867b..5768286b5 100644 --- a/src/database/distributed/graph_db_accessor.hpp +++ b/src/database/distributed/graph_db_accessor.hpp @@ -29,7 +29,7 @@ class UpdatesRpcClients; namespace database { /** Thrown when inserting in an index with constraint. */ -class IndexConstraintViolationException : public utils::BasicException { +class ConstraintViolationException : public utils::BasicException { using utils::BasicException::BasicException; }; @@ -45,7 +45,7 @@ class IndexCreationOnWorkerException : public utils::BasicException { /// Thrown on concurrent index creation when the transaction engine fails to /// start a new transaction. -class IndexTransactionException : public utils::BasicException { +class TransactionException : public utils::BasicException { using utils::BasicException::BasicException; }; diff --git a/src/database/single_node/exceptions.hpp b/src/database/single_node/exceptions.hpp index 069d599fa..efdb1a559 100644 --- a/src/database/single_node/exceptions.hpp +++ b/src/database/single_node/exceptions.hpp @@ -10,9 +10,14 @@ class IndexExistsException : public utils::BasicException { using utils::BasicException::BasicException; }; -/// Thrown on concurrent index creation when the transaction engine fails to -/// start a new transaction. -class IndexTransactionException : public utils::BasicException { +/// Thrown when the transaction engine fails to start a new transaction. +class TransactionException : public utils::BasicException { using utils::BasicException::BasicException; }; + +/// Thrown when the data violates given constraints. +class ConstraintViolationException : public utils::BasicException { + using utils::BasicException::BasicException; +}; + } // namespace database diff --git a/src/database/single_node/graph_db_accessor.cpp b/src/database/single_node/graph_db_accessor.cpp index 9e03998c9..16e0115d9 100644 --- a/src/database/single_node/graph_db_accessor.cpp +++ b/src/database/single_node/graph_db_accessor.cpp @@ -155,12 +155,12 @@ void GraphDbAccessor::BuildIndex(storage::Label label, dba.PopulateIndex(key); dba.EnableIndex(key); dba.Commit(); - } catch (const IndexConstraintViolationException &) { + } catch (const ConstraintViolationException &) { db_->storage().label_property_index_.DeleteIndex(key); throw; } catch (const tx::TransactionEngineError &e) { db_->storage().label_property_index_.DeleteIndex(key); - throw IndexTransactionException(e.what()); + throw TransactionException(e.what()); } } @@ -180,7 +180,7 @@ void GraphDbAccessor::PopulateIndex(const LabelPropertyIndex::Key &key) { continue; if (!db_->storage().label_property_index_.UpdateOnLabelProperty( vertex.address(), vertex.current_)) { - throw IndexConstraintViolationException( + throw ConstraintViolationException( "Index couldn't be created due to constraint violation!"); } } @@ -201,79 +201,88 @@ void GraphDbAccessor::DeleteIndex(storage::Label label, dba.Commit(); } catch (const tx::TransactionEngineError &e) { - throw IndexTransactionException(e.what()); + throw TransactionException(e.what()); } } -void GraphDbAccessor::BuildUniqueConstraint(storage::Label label, - storage::Property property) { +void GraphDbAccessor::BuildUniqueConstraint( + storage::Label label, const std::vector &properties) { + DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted"; + + storage::constraints::ConstraintEntry entry{label, properties}; + if (!db_->storage().unique_constraints_.AddConstraint(entry)) { + // Already exists + return; + } + try { auto dba = db_->AccessBlocking(std::make_optional(transaction().id_)); - if (!db_->storage().unique_label_property_constraints_.AddConstraint( - label, property, dba.transaction())) { - // Already exists - return; - } + for (auto v : dba.Vertices(false)) { if (std::find(v.labels().begin(), v.labels().end(), label) != v.labels().end()) { - db_->storage().unique_label_property_constraints_.Update( - v, dba.transaction()); + db_->storage().unique_constraints_.Update(v, dba.transaction()); } } + std::vector property_names(properties.size()); + std::transform(properties.begin(), properties.end(), property_names.begin(), + [&dba](storage::Property property) { + return dba.PropertyName(property); + }); + dba.wal().Emplace(database::StateDelta::BuildUniqueConstraint( - dba.transaction().id_, label, dba.LabelName(label), - std::vector{property}, - std::vector{dba.PropertyName(property)})); + dba.transaction().id_, label, dba.LabelName(label), properties, + property_names)); + dba.Commit(); - } catch (const IndexConstraintViolationException &) { - db_->storage().unique_label_property_constraints_.RemoveConstraint(label, - property); - throw IndexConstraintViolationException( - "Constraint cannot be built due to existing unique constraint " - "violation!"); + } catch (const tx::TransactionEngineError &e) { - db_->storage().unique_label_property_constraints_.RemoveConstraint(label, - property); - throw IndexTransactionException(e.what()); + db_->storage().unique_constraints_.RemoveConstraint(entry); + throw TransactionException(e.what()); + } catch (const storage::constraints::ViolationException &e) { + db_->storage().unique_constraints_.RemoveConstraint(entry); + throw ConstraintViolationException(e.what()); + } catch (const storage::constraints::SerializationException &e) { + db_->storage().unique_constraints_.RemoveConstraint(entry); + throw mvcc::SerializationError(); } catch (...) { - db_->storage().unique_label_property_constraints_.RemoveConstraint(label, - property); + db_->storage().unique_constraints_.RemoveConstraint(entry); throw; } } -void GraphDbAccessor::DeleteUniqueConstraint(storage::Label label, - storage::Property property) { +void GraphDbAccessor::DeleteUniqueConstraint( + storage::Label label, const std::vector &properties) { + storage::constraints::ConstraintEntry entry{label, properties}; try { auto dba = db_->AccessBlocking(std::make_optional(transaction().id_)); - if (!db_->storage().unique_label_property_constraints_.RemoveConstraint( - label, property)) { - // Nothing to do + if (!db_->storage().unique_constraints_.RemoveConstraint(entry)) { + // Nothing was deleted + return; } + std::vector property_names(properties.size()); + std::transform(properties.begin(), properties.end(), property_names.begin(), + [&dba](storage::Property property) { + return dba.PropertyName(property); + }); + dba.wal().Emplace(database::StateDelta::DropUniqueConstraint( - dba.transaction().id_, label, dba.LabelName(label), - std::vector{property}, - std::vector{dba.PropertyName(property)})); + dba.transaction().id_, label, dba.LabelName(label), properties, + property_names)); + dba.Commit(); } catch (const tx::TransactionEngineError &e) { - throw IndexTransactionException(e.what()); + throw TransactionException(e.what()); } } -bool GraphDbAccessor::UniqueConstraintExists(storage::Label label, - storage::Property property) const { - return db_->storage().unique_label_property_constraints_.Exists(label, - property); -} - -std::vector -GraphDbAccessor::ListUniqueLabelPropertyConstraints() const { - return db_->storage().unique_label_property_constraints_.ListConstraints(); +std::vector +GraphDbAccessor::ListUniqueConstraints() const { + return db_->storage().unique_constraints_.ListConstraints(); } void GraphDbAccessor::UpdateOnAddLabel(storage::Label label, @@ -282,12 +291,18 @@ void GraphDbAccessor::UpdateOnAddLabel(storage::Label label, DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted"; auto *vlist_ptr = vertex_accessor.address(); - db_->storage().unique_label_property_constraints_.UpdateOnAddLabel( - label, vertex_accessor, transaction()); + try { + db_->storage().unique_constraints_.UpdateOnAddLabel(label, vertex_accessor, + transaction()); + } catch (const storage::constraints::SerializationException &e) { + throw mvcc::SerializationError(); + } catch (const storage::constraints::ViolationException &e) { + throw ConstraintViolationException(e.what()); + } if (!db_->storage().label_property_index_.UpdateOnLabel(label, vlist_ptr, vertex)) { - throw IndexConstraintViolationException( + throw ConstraintViolationException( "Node couldn't be updated due to index constraint violation!"); } @@ -296,30 +311,43 @@ void GraphDbAccessor::UpdateOnAddLabel(storage::Label label, void GraphDbAccessor::UpdateOnRemoveLabel( storage::Label label, const RecordAccessor &accessor) { - db_->storage().unique_label_property_constraints_.UpdateOnRemoveLabel( - label, accessor, transaction()); + db_->storage().unique_constraints_.UpdateOnRemoveLabel(label, accessor, + transaction()); } void GraphDbAccessor::UpdateOnAddProperty( - storage::Property property, const PropertyValue &value, + storage::Property property, const PropertyValue &previous_value, + const PropertyValue &new_value, const RecordAccessor &vertex_accessor, const Vertex *vertex) { DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted"; - db_->storage().unique_label_property_constraints_.UpdateOnAddProperty( - property, value, vertex_accessor, transaction()); + try { + db_->storage().unique_constraints_.UpdateOnAddProperty( + property, previous_value, new_value, vertex_accessor, transaction()); + } catch (const storage::constraints::SerializationException &e) { + throw mvcc::SerializationError(); + } catch (const storage::constraints::ViolationException &e) { + throw ConstraintViolationException(e.what()); + } if (!db_->storage().label_property_index_.UpdateOnProperty( property, vertex_accessor.address(), vertex)) { - throw IndexConstraintViolationException( + throw ConstraintViolationException( "Node couldn't be updated due to unique index violation!"); } } void GraphDbAccessor::UpdateOnRemoveProperty( - storage::Property property, const RecordAccessor &accessor, - const Vertex *vertex) { - db_->storage().unique_label_property_constraints_.UpdateOnRemoveProperty( - property, accessor, transaction()); + storage::Property property, const PropertyValue &previous_value, + const RecordAccessor &accessor, const Vertex *vertex) { + DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted"; + + try { + db_->storage().unique_constraints_.UpdateOnRemoveProperty( + property, previous_value, accessor, transaction()); + } catch (const storage::constraints::SerializationException &e) { + throw mvcc::SerializationError(); + } } int64_t GraphDbAccessor::VerticesCount() const { @@ -405,6 +433,10 @@ bool GraphDbAccessor::RemoveVertex(VertexAccessor &vertex_accessor, vertex_accessor.out_degree() + vertex_accessor.in_degree() > 0) return false; + // Notify unique constraints that vertex_accessor has been deleted + db_->storage().unique_constraints_.UpdateOnRemoveVertex(vertex_accessor, + transaction()); + auto *vlist_ptr = vertex_accessor.address(); wal().Emplace(database::StateDelta::RemoveVertex( transaction_->id_, vlist_ptr->gid_, check_empty)); diff --git a/src/database/single_node/graph_db_accessor.hpp b/src/database/single_node/graph_db_accessor.hpp index 4e84a4f1b..2f35b00f2 100644 --- a/src/database/single_node/graph_db_accessor.hpp +++ b/src/database/single_node/graph_db_accessor.hpp @@ -427,31 +427,30 @@ class GraphDbAccessor { void EnableIndex(const LabelPropertyIndex::Key &key); /** - * Creates new unique constraint that consists of label and property. + * Creates new unique constraint that consists of a label and multiple + * properties. * If the constraint already exists, this method does nothing. * - * @throws IndexConstraintViolationException if constraint couldn't be build + * @throws ConstraintViolationException if constraint couldn't be build * due to existing constraint violation. + * @throws TransactionEngineError if the engine doesn't accept transactions. + * @throws mvcc::SerializationError on serialization errors. */ - void BuildUniqueConstraint(storage::Label label, storage::Property property); + void BuildUniqueConstraint(storage::Label label, + const std::vector &properties); /** * Deletes existing unique constraint. * If the constraint doesn't exist, this method does nothing. */ - void DeleteUniqueConstraint(storage::Label label, storage::Property property); - - /** - * Checks if unique constraint exists. - */ - bool UniqueConstraintExists(storage::Label label, - storage::Property property) const; + void DeleteUniqueConstraint(storage::Label label, + const std::vector &properties); /** * Returns a list of currently active unique constraints. */ - std::vector - ListUniqueLabelPropertyConstraints() const; + std::vector ListUniqueConstraints() + const; /** * @brief - Returns true if the given label+property index already exists and @@ -616,7 +615,7 @@ class GraphDbAccessor { bool aborted_{false}; /** - * Notifies storage about a change. + * Notifies storage about label addition. * * @param label - label that was added * @param vertex_accessor - vertex_accessor that was updated @@ -627,7 +626,7 @@ class GraphDbAccessor { const Vertex *vertex); /** - * Notifies storage about a change. + * Notifies storage about label removal. * * @param label - label that was removed * @param vertex_accessor - vertex_accessor that was updated @@ -636,24 +635,30 @@ class GraphDbAccessor { const RecordAccessor &accessor); /** - * Notifies storage about a change. + * Notifies storage about a property removal. + * + * @param property - property that was removed + * @param previous_value - previous value of the property * @param vertex_accessor - vertex_accessor that was updated * @param vertex - vertex that was updated */ void UpdateOnRemoveProperty(storage::Property property, + const PropertyValue &previous_value, const RecordAccessor &accessor, - const Vertex* vertex); + const Vertex *vertex); /** - * Notifies storage about a change. + * Notifies storage about a property addition. * * @param property - property that was added - * @param value - corresponding value that was added + * @param previous_value - previous value of the property + * @param new_value - new value of the property * @param vertex_accessor - vertex accessor that was updated * @param vertex - vertex that was updated */ void UpdateOnAddProperty(storage::Property property, - const PropertyValue &value, + const PropertyValue &previous_value, + const PropertyValue &new_value, const RecordAccessor &vertex_accessor, const Vertex *vertex); }; diff --git a/src/database/single_node_ha/graph_db_accessor.cpp b/src/database/single_node_ha/graph_db_accessor.cpp index ec13ab415..d3b01041a 100644 --- a/src/database/single_node_ha/graph_db_accessor.cpp +++ b/src/database/single_node_ha/graph_db_accessor.cpp @@ -155,12 +155,12 @@ void GraphDbAccessor::BuildIndex(storage::Label label, dba.PopulateIndex(key); dba.EnableIndex(key); dba.Commit(); - } catch (const IndexConstraintViolationException &) { + } catch (const ConstraintViolationException &) { db_->storage().label_property_index_.DeleteIndex(key); throw; } catch (const tx::TransactionEngineError &e) { db_->storage().label_property_index_.DeleteIndex(key); - throw IndexTransactionException(e.what()); + throw TransactionException(e.what()); } } @@ -179,7 +179,7 @@ void GraphDbAccessor::PopulateIndex(const LabelPropertyIndex::Key &key) { continue; if (!db_->storage().label_property_index_.UpdateOnLabelProperty( vertex.address(), vertex.current_)) { - throw IndexConstraintViolationException( + throw ConstraintViolationException( "Index couldn't be created due to constraint violation!"); } } @@ -200,7 +200,7 @@ void GraphDbAccessor::DeleteIndex(storage::Label label, dba.Commit(); } catch (const tx::TransactionEngineError &e) { - throw IndexTransactionException(e.what()); + throw TransactionException(e.what()); } } @@ -212,7 +212,7 @@ void GraphDbAccessor::UpdateLabelIndices(storage::Label label, if (!db_->storage().label_property_index_.UpdateOnLabel(label, vlist_ptr, vertex)) { - throw IndexConstraintViolationException( + throw ConstraintViolationException( "Node couldn't be updated due to index constraint violation!"); } db_->storage().labels_index_.Update(label, vlist_ptr, vertex); @@ -224,7 +224,7 @@ void GraphDbAccessor::UpdatePropertyIndex( DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted"; if (!db_->storage().label_property_index_.UpdateOnProperty( property, vertex_accessor.address(), vertex)) { - throw IndexConstraintViolationException( + throw ConstraintViolationException( "Node couldn't be updated due to index constraint violation!"); } } diff --git a/src/database/single_node_ha/graph_db_accessor.hpp b/src/database/single_node_ha/graph_db_accessor.hpp index 87b8fa9be..b6caf3ed6 100644 --- a/src/database/single_node_ha/graph_db_accessor.hpp +++ b/src/database/single_node_ha/graph_db_accessor.hpp @@ -24,7 +24,7 @@ namespace database { /** Thrown when inserting in an index with constraint. */ -class IndexConstraintViolationException : public utils::BasicException { +class ConstraintViolationException : public utils::BasicException { using utils::BasicException::BasicException; }; @@ -33,14 +33,9 @@ class IndexExistsException : public utils::BasicException { using utils::BasicException::BasicException; }; -/** Thrown when creating an index which already exists. */ -class IndexCreationOnWorkerException : public utils::BasicException { - using utils::BasicException::BasicException; -}; - /// Thrown on concurrent index creation when the transaction engine fails to /// start a new transaction. -class IndexTransactionException : public utils::BasicException { +class TransactionException : public utils::BasicException { using utils::BasicException::BasicException; }; diff --git a/src/durability/single_node/recovery.cpp b/src/durability/single_node/recovery.cpp index 007f5ca9f..48971dff7 100644 --- a/src/durability/single_node/recovery.cpp +++ b/src/durability/single_node/recovery.cpp @@ -557,12 +557,10 @@ void RecoverUniqueConstraints( properties.push_back(dba.Property(prop)); } - DCHECK(properties.size() == 1) - << "Unique constraint with multiple properties is not supported"; if (constraint.create) { - dba.BuildUniqueConstraint(label, properties[0]); + dba.BuildUniqueConstraint(label, properties); } else { - dba.DeleteUniqueConstraint(label, properties[0]); + dba.DeleteUniqueConstraint(label, properties); } } } diff --git a/src/durability/single_node/snapshooter.cpp b/src/durability/single_node/snapshooter.cpp index 361ac2568..bbce464a7 100644 --- a/src/durability/single_node/snapshooter.cpp +++ b/src/durability/single_node/snapshooter.cpp @@ -58,11 +58,13 @@ bool Encode(const fs::path &snapshot_file, database::GraphDb &db, // Write unique constraints to snapshoot { std::vector unique_constraints; - for (const auto &rule : dba.ListUniqueLabelPropertyConstraints()) { + for (const auto &rule : dba.ListUniqueConstraints()) { unique_constraints.emplace_back(dba.LabelName(rule.label)); - // UniqueLabelPropertyConstraint has label and single property rule - unique_constraints.emplace_back(1); - unique_constraints.emplace_back(dba.PropertyName(rule.property)); + unique_constraints.emplace_back( + static_cast(rule.properties.size())); + for (auto &p : rule.properties) { + unique_constraints.emplace_back(dba.PropertyName(p)); + } } encoder.WriteList(unique_constraints); } diff --git a/src/durability/single_node/state_delta.cpp b/src/durability/single_node/state_delta.cpp index d840f586f..fb33a8fb5 100644 --- a/src/durability/single_node/state_delta.cpp +++ b/src/durability/single_node/state_delta.cpp @@ -421,9 +421,7 @@ void StateDelta::Apply(GraphDbAccessor &dba) const { properties.push_back(dba.Property(p)); } - DCHECK(properties.size() == 1) - << "Unique constraint with multiple properties is not supported"; - dba.BuildUniqueConstraint(dba.Label(label_name), properties[0]); + dba.BuildUniqueConstraint(dba.Label(label_name), properties); } break; case Type::DROP_UNIQUE_CONSTRAINT: { std::vector properties; @@ -432,9 +430,7 @@ void StateDelta::Apply(GraphDbAccessor &dba) const { properties.push_back(dba.Property(p)); } - DCHECK(properties.size() == 1) - << "Unique constraint with multiple properties is not supported"; - dba.DeleteUniqueConstraint(dba.Label(label_name), properties[0]); + dba.DeleteUniqueConstraint(dba.Label(label_name), properties); } break; } } diff --git a/src/query/common.hpp b/src/query/common.hpp index d1bbe7a00..245af5a1c 100644 --- a/src/query/common.hpp +++ b/src/query/common.hpp @@ -101,7 +101,7 @@ void PropsSetChecked(TRecordAccessor *record, const storage::Property &key, } catch (const RecordDeletedError &) { throw QueryRuntimeException( "Trying to set properties on a deleted graph element."); - } catch (const database::IndexConstraintViolationException &e) { + } catch (const database::ConstraintViolationException &e) { throw QueryRuntimeException(e.what()); } } diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index f79c45def..24f191909 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -556,14 +556,14 @@ Callback HandleIndexQuery(IndexQuery *index_query, db_accessor->BuildIndex(label, properties[0], action == IndexQuery::Action::CREATE_UNIQUE); invalidate_plan_cache(); - } catch (const database::IndexConstraintViolationException &e) { + } catch (const database::ConstraintViolationException &e) { throw QueryRuntimeException(e.what()); } catch (const database::IndexExistsException &e) { if (action == IndexQuery::Action::CREATE_UNIQUE) { throw QueryRuntimeException(e.what()); } // Otherwise ignore creating an existing index. - } catch (const database::IndexTransactionException &e) { + } catch (const database::TransactionException &e) { throw QueryRuntimeException(e.what()); } return std::vector>(); @@ -575,7 +575,7 @@ Callback HandleIndexQuery(IndexQuery *index_query, CHECK(properties.size() == 1); db_accessor->DeleteIndex(label, properties[0]); invalidate_plan_cache(); - } catch (const database::IndexTransactionException &e) { + } catch (const database::TransactionException &e) { throw QueryRuntimeException(e.what()); } return std::vector>(); @@ -630,7 +630,28 @@ Callback HandleInfoQuery(InfoQuery *info_query, }; break; case InfoQuery::InfoType::CONSTRAINT: - throw utils::NotYetImplemented("constraint info"); +#ifdef MG_SINGLE_NODE + callback.header = {"constraint type", "label", "properties"}; + callback.fn = [db_accessor] { + std::vector> results; + for (auto &e : db_accessor->ListUniqueConstraints()) { + std::vector property_names(e.properties.size()); + std::transform(e.properties.begin(), e.properties.end(), + property_names.begin(), [&db_accessor](const auto &p) { + return db_accessor->PropertyName(p); + }); + + std::vector constraint{"unique", + db_accessor->LabelName(e.label), + utils::Join(property_names, ",")}; + + results.emplace_back(constraint); + } + return results; + }; +#else + throw utils::NotYetImplemented("constraints info"); +#endif break; case InfoQuery::InfoType::RAFT: #if defined(MG_SINGLE_NODE_HA) @@ -655,31 +676,56 @@ Callback HandleInfoQuery(InfoQuery *info_query, Callback HandleConstraintQuery(ConstraintQuery *constraint_query, database::GraphDbAccessor *db_accessor) { #ifdef MG_SINGLE_NODE - Callback callback; - switch (constraint_query->action_type_) { - case ConstraintQuery::ActionType::CREATE: { - switch (constraint_query->constraint_.type) { - case Constraint::Type::NODE_KEY: - throw utils::NotYetImplemented("Node key constraints"); - case Constraint::Type::EXISTS: - throw utils::NotYetImplemented("Existence constraints"); - case Constraint::Type::UNIQUE: - throw utils::NotYetImplemented("Unique constraints"); - } - break; - } - case ConstraintQuery::ActionType::DROP: { - switch (constraint_query->constraint_.type) { - case Constraint::Type::NODE_KEY: - throw utils::NotYetImplemented("Node key constraints"); - case Constraint::Type::EXISTS: - throw utils::NotYetImplemented("Existence constraints"); - case Constraint::Type::UNIQUE: - throw utils::NotYetImplemented("Unique constraints"); - } - break; - } - } + std::vector properties; + auto label = db_accessor->Label(constraint_query->constraint_.label.name); + properties.reserve(constraint_query->constraint_.properties.size()); + for (const auto &prop : constraint_query->constraint_.properties) { + properties.push_back(db_accessor->Property(prop.name)); + } + + Callback callback; + switch (constraint_query->action_type_) { + case ConstraintQuery::ActionType::CREATE: { + switch (constraint_query->constraint_.type) { + case Constraint::Type::NODE_KEY: + throw utils::NotYetImplemented("Node key constraints"); + case Constraint::Type::EXISTS: + throw utils::NotYetImplemented("Existence constraints"); + case Constraint::Type::UNIQUE: + callback.fn = [label, properties, db_accessor] { + try { + db_accessor->BuildUniqueConstraint(label, properties); + return std::vector>(); + } catch (const database::ConstraintViolationException &e) { + throw QueryRuntimeException(e.what()); + } catch (const database::TransactionException &e) { + throw QueryRuntimeException(e.what()); + } catch (const mvcc::SerializationError &e) { + throw QueryRuntimeException(e.what()); + } + }; + break; + } + } break; + case ConstraintQuery::ActionType::DROP: { + switch (constraint_query->constraint_.type) { + case Constraint::Type::NODE_KEY: + throw utils::NotYetImplemented("Node key constraints"); + case Constraint::Type::EXISTS: + throw utils::NotYetImplemented("Existence constraints"); + case Constraint::Type::UNIQUE: + callback.fn = [label, properties, db_accessor] { + try { + db_accessor->DeleteUniqueConstraint(label, properties); + return std::vector>(); + } catch (const database::TransactionException &e) { + throw QueryRuntimeException(e.what()); + } + }; + break; + } + } break; + } return callback; #else throw utils::NotYetImplemented("Constraints"); diff --git a/src/storage/single_node/constraints/exceptions.hpp b/src/storage/single_node/constraints/exceptions.hpp index 2ca4e2101..4e216b23f 100644 --- a/src/storage/single_node/constraints/exceptions.hpp +++ b/src/storage/single_node/constraints/exceptions.hpp @@ -4,9 +4,16 @@ #include "utils/exceptions.hpp" -namespace database { -/** Thrown when inserting in an unique constraint. */ -class IndexConstraintViolationException : public utils::BasicException { +namespace storage::constraints { + +/// Thrown when a violation of a constraint occurs. +class ViolationException : public utils::BasicException { using utils::BasicException::BasicException; }; + +/// Thrown when multiple transactions alter the same constraint. +class SerializationException : 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 index 45bcf4e3e..443ea369c 100644 --- a/src/storage/single_node/constraints/record.cpp +++ b/src/storage/single_node/constraints/record.cpp @@ -13,23 +13,27 @@ 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 + // Throw SerializationException // - delted of inserted after this transaction - // Throw IndexConstraintViolationException + // Throw ViolationException // - 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(); + if (t.id_ < tx_id_cre || (tx_id_exp != 0 && t.id_ < tx_id_exp)) { + throw SerializationException( + "Node couldn't be updated due to unique constraint serialization " + "error!"); + } 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( + if ((has_entry && !is_aborted) || (!has_entry && is_aborted)) { + throw ViolationException( "Node couldn't be updated due to unique constraint violation!"); + } curr_gid = gid; tx_id_cre = t.id_; @@ -43,7 +47,7 @@ void Record::Remove(gid::Gid gid, const tx::Transaction &t) { // Nothing // - remove before or in this transaction and not aborted // - insert before and aborted - // Throw SerializationError + // Throw SerializationException // - delete or insert after this transaction t.TakeLock(lock_); diff --git a/src/storage/single_node/constraints/unique_constraints.cpp b/src/storage/single_node/constraints/unique_constraints.cpp new file mode 100644 index 000000000..1a193369f --- /dev/null +++ b/src/storage/single_node/constraints/unique_constraints.cpp @@ -0,0 +1,258 @@ +#include "storage/single_node/constraints/unique_constraints.hpp" + +#include + +#include "storage/single_node/vertex_accessor.hpp" + +namespace storage::constraints { + +namespace { +auto FindIn(storage::Label label, + const std::vector &properties, + const std::list &constraints) { + return std::find_if( + constraints.begin(), constraints.end(), [label, properties](auto &c) { + return c.label == label && + std::is_permutation(properties.begin(), properties.end(), + c.properties.begin(), c.properties.end()); + }); +} +} // anonymous namespace + +bool UniqueConstraints::AddConstraint(const ConstraintEntry &entry) { + auto constraint = FindIn(entry.label, entry.properties, constraints_); + if (constraint == constraints_.end()) { + constraints_.emplace_back(entry.label, entry.properties); + return true; + } + return false; +} + +bool UniqueConstraints::RemoveConstraint(const ConstraintEntry &entry) { + auto constraint = FindIn(entry.label, entry.properties, constraints_); + if (constraint != constraints_.end()) { + constraints_.erase(constraint); + return true; + } + return false; +} + +bool UniqueConstraints::Exists( + storage::Label label, + const std::vector &properties) const { + return FindIn(label, properties, constraints_) != constraints_.end(); +} + +std::vector UniqueConstraints::ListConstraints() const { + std::vector constraints(constraints_.size()); + std::transform(constraints_.begin(), constraints_.end(), constraints.begin(), + [](auto &c) { + return ConstraintEntry{c.label, c.properties}; + }); + return constraints; +} + +void UniqueConstraints::Update(const RecordAccessor &accessor, + const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (!utils::Contains(vertex.labels_, constraint.label)) continue; + std::vector values; + for (auto p : constraint.properties) { + auto value = vertex.properties_.at(p); + if (value.IsNull()) break; + values.emplace_back(value); + } + if (values.size() != constraint.properties.size()) continue; + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [values](const impl::LabelPropertyPair &p) { + return p.values == values; + }); + if (entry != constraint.version_pairs.end()) { + entry->record.Insert(accessor.gid(), t); + } else { + constraint.version_pairs.emplace_back(accessor.gid(), values, t); + } + } +} + +void UniqueConstraints::UpdateOnAddLabel(storage::Label label, + const RecordAccessor &accessor, + const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (constraint.label != label) continue; + std::vector values; + for (auto p : constraint.properties) { + auto value = vertex.properties_.at(p); + if (value.IsNull()) break; + values.emplace_back(value); + } + if (values.size() != constraint.properties.size()) continue; + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [values](const impl::LabelPropertyPair &p) { + return p.values == values; + }); + if (entry != constraint.version_pairs.end()) { + entry->record.Insert(accessor.gid(), t); + } else { + constraint.version_pairs.emplace_back(accessor.gid(), values, t); + } + } +} + +void UniqueConstraints::UpdateOnRemoveLabel( + storage::Label label, const RecordAccessor &accessor, + const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (constraint.label != label) continue; + std::vector values; + for (auto p : constraint.properties) { + auto value = vertex.properties_.at(p); + if (value.IsNull()) break; + values.emplace_back(value); + } + if (values.size() != constraint.properties.size()) continue; + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [values](const impl::LabelPropertyPair &p) { + return p.values == values; + }); + if (entry != constraint.version_pairs.end()) + entry->record.Remove(accessor.gid(), t); + } +} + +void UniqueConstraints::UpdateOnAddProperty( + storage::Property property, const PropertyValue &previous_value, + const PropertyValue &new_value, const RecordAccessor &accessor, + const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (!utils::Contains(vertex.labels_, constraint.label)) continue; + if (!utils::Contains(constraint.properties, property)) continue; + + std::vector old_values; + std::vector new_values; + for (auto p : constraint.properties) { + auto value = vertex.properties_.at(p); + + if (p == property) { + if (!previous_value.IsNull()) old_values.emplace_back(previous_value); + if (!new_value.IsNull()) new_values.emplace_back(new_value); + } else { + if (value.IsNull()) break; + old_values.emplace_back(value); + new_values.emplace_back(value); + } + } + + // First we need to remove the old entry if there was one. + if (old_values.size() == constraint.properties.size()) { + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [old_values](const impl::LabelPropertyPair &p) { + return p.values == old_values; + }); + if (entry != constraint.version_pairs.end()) + entry->record.Remove(accessor.gid(), t); + } + + if (new_values.size() != constraint.properties.size()) continue; + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [new_values](const impl::LabelPropertyPair &p) { + return p.values == new_values; + }); + if (entry != constraint.version_pairs.end()) { + entry->record.Insert(accessor.gid(), t); + } else { + constraint.version_pairs.emplace_back(accessor.gid(), new_values, t); + } + } +} + +void UniqueConstraints::UpdateOnRemoveProperty( + storage::Property property, const PropertyValue &previous_value, + const RecordAccessor &accessor, const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (!utils::Contains(vertex.labels_, constraint.label)) continue; + if (!utils::Contains(constraint.properties, property)) continue; + + std::vector values; + for (auto p : constraint.properties) { + auto value = vertex.properties_.at(p); + if (p == property) { + values.emplace_back(previous_value); + } else { + if (value.IsNull()) break; + values.emplace_back(value); + } + } + + if (values.size() != constraint.properties.size()) continue; + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [values](const impl::LabelPropertyPair &p) { + return p.values == values; + }); + if (entry != constraint.version_pairs.end()) { + entry->record.Remove(accessor.gid(), t); + } + } +} + +void UniqueConstraints::UpdateOnRemoveVertex( + const RecordAccessor &accessor, const tx::Transaction &t) { + auto &vertex = accessor.current(); + std::lock_guard guard(lock_); + for (auto &constraint : constraints_) { + if (!utils::Contains(vertex.labels_, constraint.label)) continue; + + std::vector values; + for (auto p : constraint.properties) { + auto value = vertex.properties_.at(p); + if (value.IsNull()) break; + values.emplace_back(value); + } + + if (values.size() != constraint.properties.size()) continue; + auto entry = std::find_if(constraint.version_pairs.begin(), + constraint.version_pairs.end(), + [values](const impl::LabelPropertyPair &p) { + return p.values == values; + }); + if (entry != constraint.version_pairs.end()) { + entry->record.Remove(accessor.gid(), t); + } + } +} + +void UniqueConstraints::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();) { + 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())) { + p = constraint.version_pairs.erase(p); + } else { + ++p; + } + } + } +} +} // namespace storage::constraints diff --git a/src/storage/single_node/constraints/unique_label_properties_constraint.hpp b/src/storage/single_node/constraints/unique_constraints.hpp similarity index 51% rename from src/storage/single_node/constraints/unique_label_properties_constraint.hpp rename to src/storage/single_node/constraints/unique_constraints.hpp index cfc2d5185..263896a6b 100644 --- a/src/storage/single_node/constraints/unique_label_properties_constraint.hpp +++ b/src/storage/single_node/constraints/unique_constraints.hpp @@ -13,13 +13,16 @@ namespace tx { class Snapshot; }; // namespace tx -class VertexAccessor; +class Vertex; + +template +class RecordAccessor; namespace storage::constraints { namespace impl { struct LabelPropertyPair { LabelPropertyPair(gid::Gid gid, const std::vector &v, - const tx::Transaction &t) + const tx::Transaction &t) : values(v), record(gid, t) {} std::vector values; @@ -27,7 +30,8 @@ struct LabelPropertyPair { }; struct LabelPropertiesEntry { - LabelPropertiesEntry(storage::Label l, const std::vector &p) + LabelPropertiesEntry(storage::Label l, + const std::vector &p) : label(l), properties(p) {} storage::Label label; @@ -36,66 +40,72 @@ struct LabelPropertiesEntry { }; } // namespace impl -struct LabelProperties { +struct ConstraintEntry { // This struct is used by ListConstraints method in order to avoid using // std::pair or something like that. storage::Label label; std::vector properties; }; -/// UniqueLabelPropertiesConstraint contains all unique constraints defined by -/// both label and set of properties. To create or delete unique constraint, caller -/// must ensure that there are no other transactions running in parallel. +/// UniqueConstraints contains all unique constraints defined by both label and +/// a set of properties. 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 UniqueLabelPropertiesConstraint { +/// AddConstraint to create unique constraint and then call Update for every +/// existing Vertex. If there is a unique constraint violation, the caller must +/// manually handle that by catching exceptions and calling RemoveConstraint +/// method. This is needed to ensure logical correctness of transactions. Once +/// created, client uses UpdateOn* methods to notify UniqueConstraint about +/// changes. In case of violation UpdateOn* methods throw +/// ConstraintViolationException exception. Methods can also throw +/// SerializationError. This class is thread safe. +class UniqueConstraints { public: - UniqueLabelPropertiesConstraint() = default; - UniqueLabelPropertiesConstraint(const UniqueLabelPropertiesConstraint &) = - delete; - UniqueLabelPropertiesConstraint(UniqueLabelPropertiesConstraint &&) = delete; - UniqueLabelPropertiesConstraint &operator=( - const UniqueLabelPropertiesConstraint &) = delete; - UniqueLabelPropertiesConstraint &operator=( - UniqueLabelPropertiesConstraint &&) = delete; + UniqueConstraints() = default; + UniqueConstraints(const UniqueConstraints &) = delete; + UniqueConstraints(UniqueConstraints &&) = delete; + UniqueConstraints &operator=(const UniqueConstraints &) = delete; + UniqueConstraints &operator=(UniqueConstraints &&) = delete; + + ~UniqueConstraints() = default; /// 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, - const std::vector &properties, - const tx::Transaction &t); + /// + /// @return true if the constraint doesn't exists and was added. + bool AddConstraint(const ConstraintEntry &entry); /// 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, - const std::vector &properties); + /// + /// @return true if the constraint existed and was removed. + bool RemoveConstraint(const ConstraintEntry &entry); /// Checks whether given unique constraint is visible. bool Exists(storage::Label label, const std::vector &properties) const; /// Returns list of unique constraints. - std::vector ListConstraints() const; + std::vector ListConstraints() const; + + /// Updates unique constraint versions when adding new constraint rule. + /// + /// @throws ConstraintViolationException + /// @throws SerializationError + void Update(const RecordAccessor &accessor, const tx::Transaction &t); /// Updates unique constraint versions when adding label. /// @param label - label that was added /// @param accessor - accessor that was updated /// @param t - current transaction /// - /// @throws IndexConstraintViolationException + /// @throws ConstraintViolationException /// @throws SerializationError - void UpdateOnAddLabel(storage::Label label, const VertexAccessor &accessor, + void UpdateOnAddLabel(storage::Label label, + const RecordAccessor &accessor, const tx::Transaction &t); /// Updates unique constraint versions when removing label. @@ -104,35 +114,48 @@ class UniqueLabelPropertiesConstraint { /// @param t - current transaction /// /// @throws SerializationError - void UpdateOnRemoveLabel(storage::Label label, const VertexAccessor &accessor, + void UpdateOnRemoveLabel(storage::Label label, + const RecordAccessor &accessor, const tx::Transaction &t); /// Updates unique constraint versions when adding property. /// @param property - property that was added - /// @param value - property value that was added + /// @param previous_value - previous value of the property + /// @param new_value - new value of the property /// @param accessor - accessor that was updated /// @param t - current transaction /// - /// @throws IndexConstraintViolationException + /// @throws ConstraintViolationException /// @throws SerializationError void UpdateOnAddProperty(storage::Property property, - const PropertyValue &value, - const VertexAccessor &accessor, + const PropertyValue &previous_value, + const PropertyValue &new_value, + const RecordAccessor &accessor, const tx::Transaction &t); /// Updates unique constraint versions when removing property. /// @param property - property that was removed - /// @param value - property value that was removed + /// @param previous_value - previous value of the property /// @param accessor - accessor that was updated /// @param t - current transaction /// /// @throws SerializationError void UpdateOnRemoveProperty(storage::Property property, - const PropertyValue &value, - const VertexAccessor &accessor, + const PropertyValue &previous_value, + const RecordAccessor &accessor, const tx::Transaction &t); + /// Updates unique constraint versions when removing a vertex. + /// @param accessor - accessor that was updated + /// @param t - current transaction + /// + /// @throws SerializationError + void UpdateOnRemoveVertex(const RecordAccessor &accessor, + const tx::Transaction &t); + /// Removes records that are no longer visible. + /// @param snapshot - the GC snapshot. + /// @param engine - current transaction engine. void Refresh(const tx::Snapshot &snapshot, const tx::Engine &engine); private: diff --git a/src/storage/single_node/constraints/unique_label_properties_constraint.cpp b/src/storage/single_node/constraints/unique_label_properties_constraint.cpp deleted file mode 100644 index 57aca3997..000000000 --- a/src/storage/single_node/constraints/unique_label_properties_constraint.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "storage/single_node/constraints/unique_label_properties_constraint.hpp" - -#include - -#include "storage/single_node/constraints/common.hpp" -#include "storage/single_node/vertex_accessor.hpp" - -namespace storage::constraints { -auto FindIn(storage::Label label, - const std::vector &properties, - const std::list &constraints) { - return std::find_if( - constraints.begin(), constraints.end(), [label, properties](auto &c) { - return c.label == label && - std::is_permutation(properties.begin(), properties.end(), - c.properties.begin(), c.properties.end()); - }); -} - -auto FindEntry(std::list &version_pairs, - const std::vector &values) { - return std::find_if(version_pairs.begin(), version_pairs.end(), - [values](auto &p) { return p.values == values; }); -} - -void UniqueLabelPropertiesConstraint::AddConstraint( - storage::Label label, const std::vector &properties, - const tx::Transaction &t) { - auto constraint = FindIn(label, properties, constraints_); - if (constraint == constraints_.end()) - constraints_.emplace_back(label, properties); -} - -void UniqueLabelPropertiesConstraint::RemoveConstraint( - storage::Label label, const std::vector &properties) { - auto constraint = FindIn(label, properties, constraints_); - if (constraint != constraints_.end()) constraints_.erase(constraint); -} - -bool UniqueLabelPropertiesConstraint::Exists( - storage::Label label, - const std::vector &properties) const { - return FindIn(label, properties, constraints_) != constraints_.end(); -} - -std::vector UniqueLabelPropertiesConstraint::ListConstraints() - const { - std::vector constraints; - constraints.reserve(constraints_.size()); - std::transform(constraints_.begin(), constraints_.end(), - std::back_inserter(constraints), [](auto &c) { - return LabelProperties{c.label, c.properties}; - }); - return constraints; -} - -void UniqueLabelPropertiesConstraint::UpdateOnAddLabel( - storage::Label label, const VertexAccessor &accessor, - const tx::Transaction &t) { - auto &vertex = accessor.current(); - std::lock_guard guard(lock_); - for (auto &constraint : constraints_) { - if (constraint.label != label) continue; - std::vector values; - for (auto p : constraint.properties) { - auto value = vertex.properties_.at(p); - if (value.IsNull()) break; - values.emplace_back(value); - } - if (values.size() != constraint.properties.size()) continue; - auto entry = FindEntry(constraint.version_pairs, values); - if (entry != constraint.version_pairs.end()) { - entry->record.Insert(accessor.gid(), t); - } else { - constraint.version_pairs.emplace_back(accessor.gid(), values, t); - } - } -} - -void UniqueLabelPropertiesConstraint::UpdateOnRemoveLabel( - storage::Label label, const VertexAccessor &accessor, - const tx::Transaction &t) { - auto &vertex = accessor.current(); - std::lock_guard guard(lock_); - for (auto &constraint : constraints_) { - if (constraint.label != label) continue; - std::vector values; - for (auto p : constraint.properties) { - auto value = vertex.properties_.at(p); - if (value.IsNull()) break; - values.emplace_back(value); - } - if (values.size() != constraint.properties.size()) continue; - auto entry = FindEntry(constraint.version_pairs, values); - if (entry != constraint.version_pairs.end()) - entry->record.Remove(accessor.gid(), t); - } -} - -void UniqueLabelPropertiesConstraint::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 (!utils::Contains(constraint.properties, property)) continue; - if (!utils::Contains(vertex.labels_, constraint.label)) continue; - std::vector values; - for (auto p : constraint.properties) { - auto value = vertex.properties_.at(p); - if (value.IsNull()) break; - values.emplace_back(value); - } - if (values.size() != constraint.properties.size()) continue; - auto entry = FindEntry(constraint.version_pairs, values); - if (entry != constraint.version_pairs.end()) { - entry->record.Insert(accessor.gid(), t); - } else { - constraint.version_pairs.emplace_back(accessor.gid(), values, t); - } - } -} - -void UniqueLabelPropertiesConstraint::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 (!utils::Contains(constraint.properties, property)) continue; - if (!utils::Contains(vertex.labels_, constraint.label)) continue; - - std::vector values; - for (auto p : constraint.properties) { - if (p == property) { - values.emplace_back(value); - } else { - auto v = vertex.properties_.at(p); - if (v.IsNull()) break; - values.emplace_back(v); - } - } - - if (values.size() != constraint.properties.size()) continue; - auto entry = FindEntry(constraint.version_pairs, values); - if (entry != constraint.version_pairs.end()) - entry->record.Remove(accessor.gid(), t); - } -} - -void UniqueLabelPropertiesConstraint::Refresh(const tx::Snapshot &snapshot, - const tx::Engine &engine) { - common::UniqueConstraintRefresh(snapshot, engine, constraints_, lock_); -} -} // namespace storage::constraints diff --git a/src/storage/single_node/constraints/unique_label_property_constraint.cpp b/src/storage/single_node/constraints/unique_label_property_constraint.cpp deleted file mode 100644 index ed55570d5..000000000 --- a/src/storage/single_node/constraints/unique_label_property_constraint.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "storage/single_node/constraints/unique_label_property_constraint.hpp" - -#include "storage/single_node/constraints/common.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; - }); -} - -bool 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); - return true; - } else { - return false; - } -} - -bool UniqueLabelPropertyConstraint::RemoveConstraint( - storage::Label label, storage::Property property) { - auto found = FindIn(label, property, constraints_); - if (found != constraints_.end()) { - constraints_.erase(found); - return true; - } else { - return false; - } -} - -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::Update( - const RecordAccessor &accessor, const tx::Transaction &t) { - auto &vertex = accessor.current(); - std::lock_guard guard(lock_); - for (auto &constraint : constraints_) { - bool contains_label = utils::Contains(vertex.labels_, constraint.label); - auto value = vertex.properties_.at(constraint.property); - if (contains_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::UpdateOnAddLabel( - storage::Label label, const RecordAccessor &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 RecordAccessor &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 RecordAccessor &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 RecordAccessor &accessor, - const tx::Transaction &t) { - auto &vertex = accessor.current(); - auto gid = accessor.gid(); - 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.record.curr_gid == gid && p.record.tx_id_exp == 0) { - p.record.Remove(gid, t); - break; - } - } - } - } -} - -void UniqueLabelPropertyConstraint::Refresh(const tx::Snapshot &snapshot, - const tx::Engine &engine) { -// <<<<<<< HEAD -// 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); -// } -// } -// } -// ======= - common::UniqueConstraintRefresh(snapshot, engine, constraints_, lock_); -// >>>>>>> Apply requested changes -} -} // 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 deleted file mode 100644 index e69f33f1a..000000000 --- a/src/storage/single_node/constraints/unique_label_property_constraint.hpp +++ /dev/null @@ -1,138 +0,0 @@ -/// @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 Vertex; - -template -class RecordAccessor; - -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. - bool 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. - bool 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 Update(const RecordAccessor &accessor, const tx::Transaction &t); - - /// Updates unique constraint versions. - /// - /// @throws IndexConstraintViolationException - /// @throws SerializationError - void UpdateOnAddLabel(storage::Label label, - const RecordAccessor &accessor, - const tx::Transaction &t); - - /// Updates unique constraint versions. - /// - /// @throws SerializationError - void UpdateOnRemoveLabel(storage::Label label, - const RecordAccessor &accessor, - const tx::Transaction &t); - - /// Updates unique constraint versions. - /// - /// @throws IndexConstraintViolationException - /// @throws SerializationError - void UpdateOnAddProperty(storage::Property property, - const PropertyValue &value, - const RecordAccessor &accessor, - const tx::Transaction &t); - - /// Updates unique constraint versions. - /// - /// @throws SerializationError - void UpdateOnRemoveProperty(storage::Property property, - const RecordAccessor &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.cpp b/src/storage/single_node/record_accessor.cpp index 920fca275..bb4bdd730 100644 --- a/src/storage/single_node/record_accessor.cpp +++ b/src/storage/single_node/record_accessor.cpp @@ -25,8 +25,9 @@ void RecordAccessor::PropsSet(storage::Property key, auto &dba = db_accessor(); auto delta = StateDelta::PropsSetVertex(dba.transaction_id(), gid(), key, dba.PropertyName(key), value); + auto previous_value = PropsAt(key); update().properties_.set(key, value); - dba.UpdateOnAddProperty(key, value, *this, &update()); + dba.UpdateOnAddProperty(key, previous_value, value, *this, &update()); db_accessor().wal().Emplace(delta); } @@ -47,8 +48,9 @@ void RecordAccessor::PropsErase(storage::Property key) { auto delta = StateDelta::PropsSetVertex(dba.transaction_id(), gid(), key, dba.PropertyName(key), PropertyValue::Null); + auto previous_value = PropsAt(key); update().properties_.set(key, PropertyValue::Null); - dba.UpdateOnRemoveProperty(key, *this, GetNew()); + dba.UpdateOnRemoveProperty(key, previous_value, *this, &update()); dba.wal().Emplace(delta); } diff --git a/src/storage/single_node/storage.hpp b/src/storage/single_node/storage.hpp index ed6f23d93..d3152fe57 100644 --- a/src/storage/single_node/storage.hpp +++ b/src/storage/single_node/storage.hpp @@ -6,7 +6,7 @@ #include "data_structures/concurrent/concurrent_map.hpp" #include "storage/common/kvstore/kvstore.hpp" #include "storage/common/types/types.hpp" -#include "storage/single_node/constraints/unique_label_property_constraint.hpp" +#include "storage/single_node/constraints/unique_constraints.hpp" #include "storage/single_node/edge.hpp" #include "storage/single_node/indexes/key_index.hpp" #include "storage/single_node/indexes/label_property_index.hpp" @@ -77,8 +77,7 @@ class Storage { LabelPropertyIndex label_property_index_; // unique constraints - storage::constraints::UniqueLabelPropertyConstraint - unique_label_property_constraints_; + storage::constraints::UniqueConstraints unique_constraints_; std::vector properties_on_disk_; diff --git a/src/storage/single_node/storage_gc.hpp b/src/storage/single_node/storage_gc.hpp index 8464887d5..fdb5466a1 100644 --- a/src/storage/single_node/storage_gc.hpp +++ b/src/storage/single_node/storage_gc.hpp @@ -101,6 +101,7 @@ class StorageGc { utils::Timer x; storage_.labels_index_.Refresh(snapshot_gc, tx_engine_); storage_.label_property_index_.Refresh(snapshot_gc, tx_engine_); + storage_.unique_constraints_.Refresh(snapshot_gc, tx_engine_); VLOG(21) << "Garbage collector index phase time: " << x.Elapsed().count(); } { diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index f9ed50181..5f8fa7fa6 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -280,11 +280,8 @@ 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) - -add_unit_test(unique_label_properties_constraint.cpp) -target_link_libraries(${test_prefix}unique_label_properties_constraint mg-single-node kvstore_dummy_lib) +add_unit_test(unique_constraints.cpp) +target_link_libraries(${test_prefix}unique_constraints mg-single-node kvstore_dummy_lib) # Test mg-communication diff --git a/tests/unit/durability.cpp b/tests/unit/durability.cpp index 2e3a97d49..6d44c8ba3 100644 --- a/tests/unit/durability.cpp +++ b/tests/unit/durability.cpp @@ -168,17 +168,22 @@ class DbGenerator { bool CompareUniqueConstraints(const database::GraphDbAccessor &dba1, const database::GraphDbAccessor &dba2) { - auto c1 = dba1.ListUniqueLabelPropertyConstraints(); - auto c2 = dba2.ListUniqueLabelPropertyConstraints(); + auto c1 = dba1.ListUniqueConstraints(); + auto c2 = dba2.ListUniqueConstraints(); return c1.size() == c2.size() && - std::is_permutation(c1.begin(), c1.end(), c2.begin(), - [&dba1, &dba2](auto &r1, auto &r2) { - return dba1.LabelName(r1.label) == - dba2.LabelName(r2.label) && - dba1.PropertyName(r1.property) == - dba2.PropertyName(r2.property); - }); + std::is_permutation( + c1.begin(), c1.end(), c2.begin(), + [&dba1, &dba2](auto &r1, auto &r2) { + return dba1.LabelName(r1.label) == dba2.LabelName(r2.label) && + std::is_permutation( + r1.properties.begin(), r1.properties.end(), + r2.properties.begin(), r2.properties.end(), + [&dba1, &dba2](auto &p1, auto &p2) { + return dba1.PropertyName(p1) == + dba2.PropertyName(p2); + }); + }); } /** Checks if the given databases have the same contents (indices, @@ -1017,14 +1022,14 @@ TEST_F(Durability, UniqueConstraintRecoveryWal) { gen.InsertVertex(); auto l1 = dba.Label("l1"); auto p1 = dba.Property("p1"); - dba.BuildUniqueConstraint(l1, p1); + dba.BuildUniqueConstraint(l1, {p1}); gen.InsertEdge(); auto l2 = dba.Label("l2"); auto p2 = dba.Property("p2"); - dba.BuildUniqueConstraint(l2, p2); + dba.BuildUniqueConstraint(l2, {p2}); gen.InsertVertex(); gen.InsertEdge(); - dba.DeleteUniqueConstraint(l1, p1); + dba.DeleteUniqueConstraint(l1, {p1}); dba.Commit(); } { @@ -1048,14 +1053,14 @@ TEST_F(Durability, UniqueConstraintRecoverySnapshotAndWal) { gen.InsertVertex(); auto l1 = dba.Label("l1"); auto p1 = dba.Property("p1"); - dba.BuildUniqueConstraint(l1, p1); + dba.BuildUniqueConstraint(l1, {p1}); gen.InsertEdge(); auto l2 = dba.Label("l2"); auto p2 = dba.Property("p2"); - dba.BuildUniqueConstraint(l2, p2); + dba.BuildUniqueConstraint(l2, {p2}); gen.InsertVertex(); gen.InsertEdge(); - dba.DeleteUniqueConstraint(l1, p1); + dba.DeleteUniqueConstraint(l1, {p1}); dba.Commit(); } // create snapshot with build unique constraint @@ -1068,7 +1073,7 @@ TEST_F(Durability, UniqueConstraintRecoverySnapshotAndWal) { auto l3 = dba.Label("l3"); auto p3 = dba.Property("p3"); - dba.BuildUniqueConstraint(l3, p3); + dba.BuildUniqueConstraint(l3, {p3}); dba.Commit(); } { diff --git a/tests/unit/graph_db_accessor_index_api.cpp b/tests/unit/graph_db_accessor_index_api.cpp index 7ef90d05e..a65fccbce 100644 --- a/tests/unit/graph_db_accessor_index_api.cpp +++ b/tests/unit/graph_db_accessor_index_api.cpp @@ -161,7 +161,7 @@ TEST(GraphDbAccessorIndexApi, LabelPropertyBuildIndexConcurrent) { dba.BuildIndex(dba.Label("l" + std::to_string(index)), dba.Property("p" + std::to_string(index)), false); // If it throws, make sure the exception is right. - } catch (const database::IndexTransactionException &e) { + } catch (const database::TransactionException &e) { // Nothing to see here, move along. } catch (...) { failed.store(true); @@ -455,7 +455,7 @@ TEST_F(GraphDbAccessorIndex, UniqueConstraintViolationOnInsert) { dba.BuildIndex(label, property, true); Commit(); AddVertex(0); - EXPECT_THROW(AddVertex(0), database::IndexConstraintViolationException); + EXPECT_THROW(AddVertex(0), database::ConstraintViolationException); } TEST_F(GraphDbAccessorIndex, UniqueConstraintViolationOnBuild) { @@ -463,7 +463,7 @@ TEST_F(GraphDbAccessorIndex, UniqueConstraintViolationOnBuild) { AddVertex(0); Commit(); EXPECT_THROW(dba.BuildIndex(label, property, true), - database::IndexConstraintViolationException); + database::ConstraintViolationException); } TEST_F(GraphDbAccessorIndex, UniqueConstraintUpdateProperty) { @@ -474,5 +474,5 @@ TEST_F(GraphDbAccessorIndex, UniqueConstraintUpdateProperty) { vertex_accessor.PropsSet(property, 10); EXPECT_THROW(vertex_accessor.PropsSet(property, 0), - database::IndexConstraintViolationException); + database::ConstraintViolationException); } diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index ede541dc8..985555965 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -303,3 +303,51 @@ TEST_F(InterpreterTest, ShortestPath) { EXPECT_TRUE(any_match); } } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(InterpreterTest, UniqueConstraintTest) { + ResultStreamFaker stream; + { + auto dba = db_.Access(); + interpreter_("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;", dba, + {}, true) + .PullAll(stream); + dba.Commit(); + } + + { + auto dba = db_.Access(); + interpreter_("CREATE (:A{a:1, b:1})", dba, {}, true).PullAll(stream); + dba.Commit(); + } + + { + auto dba = db_.Access(); + interpreter_("CREATE (:A{a:2, b:2})", dba, {}, true).PullAll(stream); + dba.Commit(); + } + + { + auto dba = db_.Access(); + ASSERT_THROW( + interpreter_("CREATE (:A{a:1, b:1})", dba, {}, true).PullAll(stream), + query::QueryRuntimeException); + dba.Commit(); + } + + { + auto dba = db_.Access(); + interpreter_("MATCH (n:A{a:2, b:2}) SET n.a=1", dba, {}, true) + .PullAll(stream); + interpreter_("CREATE (:A{a:2, b:2})", dba, {}, true).PullAll(stream); + dba.Commit(); + } + + { + auto dba = db_.Access(); + interpreter_("MATCH (n:A{a:2, b:2}) DETACH DELETE n", dba, {}, true) + .PullAll(stream); + interpreter_("CREATE (n:A{a:2, b:2})", dba, {}, true).PullAll(stream); + dba.Commit(); + } +} diff --git a/tests/unit/unique_constraints.cpp b/tests/unit/unique_constraints.cpp new file mode 100644 index 000000000..3992f5eaa --- /dev/null +++ b/tests/unit/unique_constraints.cpp @@ -0,0 +1,298 @@ +#include + +#include "database/single_node/graph_db.hpp" +#include "database/single_node/graph_db_accessor.hpp" +#include "storage/single_node/constraints/unique_constraints.hpp" + +using storage::constraints::ConstraintEntry; +using storage::constraints::UniqueConstraints; + +class UniqueConstraintsTest : public ::testing::Test { + protected: + void SetUp() override { + auto dba = db_.Access(); + label_ = dba.Label("label"); + property1_ = dba.Property("property1"); + property2_ = dba.Property("property2"); + property3_ = dba.Property("property3"); + dba.BuildUniqueConstraint(label_, {property1_, property2_, property3_}); + dba.Commit(); + } + + database::GraphDb db_; + storage::Label label_; + storage::Property property1_; + storage::Property property2_; + storage::Property property3_; + PropertyValue value1_{"value1"}; + PropertyValue value2_{"value2"}; + PropertyValue value3_{"value3"}; + UniqueConstraints constraints_; +}; + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, BuildDrop) { + auto constraint = + ConstraintEntry{label_, {property1_, property2_, property3_}}; + constraints_.AddConstraint(constraint); + + EXPECT_TRUE( + constraints_.Exists(label_, {property2_, property1_, property3_})); + EXPECT_TRUE( + constraints_.Exists(label_, {property1_, property2_, property3_})); + EXPECT_FALSE(constraints_.Exists(label_, {property2_, property3_})); + + constraints_.RemoveConstraint(constraint); + + EXPECT_FALSE( + constraints_.Exists(label_, {property2_, property1_, property3_})); + EXPECT_FALSE( + constraints_.Exists(label_, {property1_, property2_, property3_})); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, BuildWithViolation) { + auto dba = db_.Access(); + dba.DeleteUniqueConstraint(label_, {property1_, property2_, property3_}); + dba.Commit(); + + auto dba1 = db_.Access(); + auto v1 = dba1.InsertVertex(); + v1.add_label(label_); + v1.PropsSet(property1_, value1_); + v1.PropsSet(property2_, value2_); + v1.PropsSet(property3_, value3_); + + auto v2 = dba1.InsertVertex(); + v2.add_label(label_); + v2.PropsSet(property1_, value1_); + v2.PropsSet(property3_, value3_); + + auto v3 = dba1.InsertVertex(); + v3.add_label(label_); + v3.PropsSet(property3_, value3_); + v3.PropsSet(property1_, value1_); + v3.PropsSet(property2_, value2_); + dba1.Commit(); + + auto dba2 = db_.Access(); + EXPECT_THROW( + dba2.BuildUniqueConstraint(label_, {property1_, property2_, property3_}), + database::ConstraintViolationException); + dba2.Commit(); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertInsert) { + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.add_label(label_); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.PropsSet(property3_, value3_); + dba.Commit(); + } + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.add_label(label_); + v.PropsSet(property3_, value3_); + v.PropsSet(property2_, value2_); + EXPECT_THROW(v.PropsSet(property1_, value1_), + database::ConstraintViolationException); + dba.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertInsertDiffValues) { + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.add_label(label_); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.PropsSet(property3_, value3_); + dba.Commit(); + } + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + PropertyValue other3("Some other value 3"); + v.PropsSet(property3_, other3); + v.add_label(label_); + PropertyValue other2("Some other value 2"); + v.PropsSet(property2_, other2); + PropertyValue other1("Some other value 1"); + v.PropsSet(property1_, other1); + dba.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertAbortInsert) { + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property1_, value1_); + v.add_label(label_); + v.PropsSet(property2_, value2_); + v.PropsSet(property3_, value3_); + dba.Abort(); + } + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.PropsSet(property3_, value3_); + v.add_label(label_); + dba.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertRemoveAbortInsert) { + gid::Gid gid = 0; + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.PropsSet(property3_, value3_); + v.add_label(label_); + dba.Commit(); + } + { + auto dba = db_.Access(); + auto v = dba.FindVertex(gid, false); + v.PropsErase(property2_); + dba.Abort(); + } + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property1_, value1_); + v.add_label(label_); + v.PropsSet(property2_, value2_); + EXPECT_THROW(v.PropsSet(property3_, value3_), + database::ConstraintViolationException); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertInsertSameTransaction) { + { + auto dba = db_.Access(); + auto v1 = dba.InsertVertex(); + auto v2 = dba.InsertVertex(); + v1.add_label(label_); + v2.add_label(label_); + + v1.PropsSet(property1_, value1_); + v1.PropsSet(property2_, value2_); + + v2.PropsSet(property2_, value2_); + v2.PropsSet(property3_, value3_); + v2.PropsSet(property1_, value1_); + + EXPECT_THROW(v1.PropsSet(property3_, value3_), + database::ConstraintViolationException); + dba.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertInsertReversed) { + auto dba1 = db_.Access(); + auto dba2 = db_.Access(); + auto v1 = dba1.InsertVertex(); + auto v2 = dba2.InsertVertex(); + v1.add_label(label_); + v2.add_label(label_); + + v1.PropsSet(property1_, value1_); + v1.PropsSet(property2_, value2_); + + v2.PropsSet(property2_, value2_); + v2.PropsSet(property3_, value3_); + v2.PropsSet(property1_, value1_); + + EXPECT_THROW(v1.PropsSet(property3_, value3_), mvcc::SerializationError); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertRemoveInsert) { + gid::Gid gid = 0; + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.PropsSet(property3_, value3_); + v.add_label(label_); + dba.Commit(); + } + { + auto dba = db_.Access(); + auto v = dba.FindVertex(gid, false); + v.PropsErase(property2_); + dba.Commit(); + } + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property1_, value1_); + v.add_label(label_); + v.PropsSet(property2_, value2_); + v.PropsSet(property3_, value3_); + dba.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertRemoveInsertSameTransaction) { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.PropsSet(property3_, value3_); + v.add_label(label_); + v.PropsErase(property2_); + v.PropsSet(property2_, value2_); + dba.Commit(); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(UniqueConstraintsTest, InsertDropInsert) { + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property1_, value1_); + v.add_label(label_); + v.PropsSet(property2_, value2_); + v.PropsSet(property3_, value3_); + dba.Commit(); + } + { + auto dba = db_.Access(); + dba.DeleteUniqueConstraint(label_, {property2_, property3_, property1_}); + dba.Commit(); + } + { + auto dba = db_.Access(); + auto v = dba.InsertVertex(); + v.PropsSet(property2_, value2_); + v.PropsSet(property1_, value1_); + v.add_label(label_); + v.PropsSet(property3_, value3_); + dba.Commit(); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + google::InitGoogleLogging(argv[0]); + return RUN_ALL_TESTS(); +} diff --git a/tests/unit/unique_label_properties_constraint.cpp b/tests/unit/unique_label_properties_constraint.cpp deleted file mode 100644 index 447738ee2..000000000 --- a/tests/unit/unique_label_properties_constraint.cpp +++ /dev/null @@ -1,376 +0,0 @@ - -#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_properties_constraint.hpp" - -class UniqueLabelPropertiesTest : public ::testing::Test { - public: - void SetUp() override { - auto dba = db_.AccessBlocking(); - label_ = dba.Label("label"); - property1_ = dba.Property("property1"); - property2_ = dba.Property("property2"); - property3_ = dba.Property("property3"); - constraint_.AddConstraint(label_, {property1_, property2_, property3_}, - dba.transaction()); - dba.Commit(); - } - - database::GraphDb db_; - storage::Label label_; - storage::Property property1_; - storage::Property property2_; - storage::Property property3_; - PropertyValue value1_{"value1"}; - PropertyValue value2_{"value2"}; - PropertyValue value3_{"value3"}; - storage::constraints::UniqueLabelPropertiesConstraint constraint_; -}; - -TEST_F(UniqueLabelPropertiesTest, BuildDrop) { - { - auto dba = db_.Access(); - EXPECT_TRUE( - constraint_.Exists(label_, {property2_, property1_, property3_})); - EXPECT_TRUE( - constraint_.Exists(label_, {property1_, property2_, property3_})); - EXPECT_FALSE( - constraint_.Exists(label_, {property1_, property2_})); - dba.Commit(); - } - { - auto dba = db_.AccessBlocking(); - constraint_.RemoveConstraint(label_, {property2_, property1_, property3_}); - dba.Commit(); - } - { - auto dba = db_.Access(); - EXPECT_FALSE( - constraint_.Exists(label_, {property2_, property1_, property3_})); - EXPECT_FALSE( - constraint_.Exists(label_, {property1_, property2_, property3_})); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertiesTest, BuildWithViolation) { - auto dba1 = db_.Access(); - auto v1 = dba1.InsertVertex(); - v1.add_label(label_); - v1.PropsSet(property1_, value1_); - v1.PropsSet(property2_, value2_); - v1.PropsSet(property3_, value3_); - - auto v2 = dba1.InsertVertex(); - v2.add_label(label_); - v2.PropsSet(property1_, value1_); - v2.PropsSet(property3_, value3_); - - auto v3 = dba1.InsertVertex(); - v3.add_label(label_); - v3.PropsSet(property3_, value3_); - v3.PropsSet(property1_, value1_); - v3.PropsSet(property2_, value2_); - dba1.Commit(); - - auto dba2 = db_.Access(); - auto v4 = dba2.FindVertex(v1.gid(), false); - auto v5 = dba2.FindVertex(v2.gid(), false); - auto v6 = dba2.FindVertex(v3.gid(), false); - constraint_.UpdateOnAddLabel(label_, v4, dba2.transaction()); - constraint_.UpdateOnAddLabel(label_, v5, dba2.transaction()); - EXPECT_THROW(constraint_.UpdateOnAddLabel(label_, v6, dba2.transaction()), - database::IndexConstraintViolationException); -} - -TEST_F(UniqueLabelPropertiesTest, InsertInsert) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, 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(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - EXPECT_THROW(constraint_.UpdateOnAddProperty(property1_, value1_, v, - dba.transaction()), - database::IndexConstraintViolationException); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertiesTest, InsertInsertDiffValues) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - PropertyValue other3("Some other value 3"); - v.PropsSet(property3_, other3); - constraint_.UpdateOnAddProperty(property3_, other3, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - PropertyValue other2("Some other value 2"); - v.PropsSet(property2_, other2); - constraint_.UpdateOnAddProperty(property2_, other2, v, dba.transaction()); - PropertyValue other1("Some other value 1"); - v.PropsSet(property1_, other1); - constraint_.UpdateOnAddProperty(property1_, other1, v, dba.transaction()); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertiesTest, InsertAbortInsert) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - dba.Abort(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertiesTest, InsertRemoveAbortInsert) { - gid::Gid gid = 0; - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.FindVertex(gid, false); - v.PropsErase(property2_); - constraint_.UpdateOnRemoveProperty(property2_, value2_, v, - dba.transaction()); - dba.Abort(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - EXPECT_THROW(constraint_.UpdateOnAddProperty(property3_, value3_, v, - dba.transaction()), - database::IndexConstraintViolationException); - } -} - -TEST_F(UniqueLabelPropertiesTest, InsertInsertSameTransaction) { - { - auto dba = db_.Access(); - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - v2.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v2, dba.transaction()); - v1.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v1, dba.transaction()); - v1.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v1, - dba.transaction()); - v1.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v1, - dba.transaction()); - v2.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v2, - dba.transaction()); - v2.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v2, - dba.transaction()); - v2.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v2, - dba.transaction()); - - v1.PropsSet(property3_, value3_); - EXPECT_THROW(constraint_.UpdateOnAddProperty(property3_, value3_, v1, - dba.transaction()), - database::IndexConstraintViolationException); - } -} - -TEST_F(UniqueLabelPropertiesTest, InsertInsertReversed) { - auto dba1 = db_.Access(); - auto dba2 = db_.Access(); - auto v1 = dba1.InsertVertex(); - auto v2 = dba2.InsertVertex(); - v2.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v2, dba2.transaction()); - v1.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v1, dba1.transaction()); - v1.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v1, - dba1.transaction()); - v1.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v1, - dba1.transaction()); - v2.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v2, - dba2.transaction()); - v2.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v2, - dba2.transaction()); - v2.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v2, - dba2.transaction()); - - v1.PropsSet(property3_, value3_); - EXPECT_THROW(constraint_.UpdateOnAddProperty(property3_, value3_, v1, - dba1.transaction()), - mvcc::SerializationError); -} - -TEST_F(UniqueLabelPropertiesTest, InsertRemoveInsert) { - gid::Gid gid = 0; - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.FindVertex(gid, false); - v.PropsErase(property2_); - constraint_.UpdateOnRemoveProperty(property2_, value2_, v, - dba.transaction()); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - } -} - -TEST_F(UniqueLabelPropertiesTest, InsertRemoveInsertSameTransaction) { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsErase(property2_); - constraint_.UpdateOnRemoveProperty(property2_, value2_, v, - dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); -} - -TEST_F(UniqueLabelPropertiesTest, InsertDropInsert) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - } - { - auto dba = db_.AccessBlocking(); - constraint_.RemoveConstraint(label_, {property2_, property3_, property1_}); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.PropsSet(property2_, value2_); - constraint_.UpdateOnAddProperty(property2_, value2_, v, dba.transaction()); - v.PropsSet(property1_, value1_); - constraint_.UpdateOnAddProperty(property1_, value1_, v, dba.transaction()); - v.add_label(label_); - constraint_.UpdateOnAddLabel(label_, v, dba.transaction()); - v.PropsSet(property3_, value3_); - constraint_.UpdateOnAddProperty(property3_, value3_, v, dba.transaction()); - dba.Commit(); - } -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - google::InitGoogleLogging(argv[0]); - return RUN_ALL_TESTS(); -} diff --git a/tests/unit/unique_label_property_constraint.cpp b/tests/unit/unique_label_property_constraint.cpp deleted file mode 100644 index ad4e6e816..000000000 --- a/tests/unit/unique_label_property_constraint.cpp +++ /dev/null @@ -1,227 +0,0 @@ -#include -#include -#include - -#include "database/single_node/graph_db.hpp" -#include "database/single_node/graph_db_accessor.hpp" - -class UniqueLabelPropertyTest : public ::testing::Test { - public: - void SetUp() override { - auto dba = db_.Access(); - label_ = dba.Label("label"); - property_ = dba.Property("property"); - dba.BuildUniqueConstraint(label_, property_); - dba.Commit(); - } - - database::GraphDb db_; - storage::Label label_; - storage::Property property_; - PropertyValue value_{"value"}; -}; - -TEST_F(UniqueLabelPropertyTest, BuildDrop) { - { - auto dba = db_.Access(); - EXPECT_TRUE(dba.UniqueConstraintExists(label_, property_)); - dba.Commit(); - } - { - auto dba = db_.Access(); - dba.DeleteUniqueConstraint(label_, property_); - dba.Commit(); - } - { - auto dba = db_.Access(); - EXPECT_FALSE(dba.UniqueConstraintExists(label_, property_)); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertyTest, BuildWithViolation) { - auto dba1 = db_.Access(); - auto l1 = dba1.Label("l1"); - auto p1 = dba1.Property("p1"); - - auto v1 = dba1.InsertVertex(); - v1.add_label(l1); - v1.PropsSet(p1, value_); - - auto v2 = dba1.InsertVertex(); - v2.add_label(l1); - v2.PropsSet(p1, value_); - dba1.Commit(); - - auto dba2 = db_.Access(); - EXPECT_THROW(dba2.BuildUniqueConstraint(l1, p1), - database::IndexConstraintViolationException); -} - -TEST_F(UniqueLabelPropertyTest, InsertInsert) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - EXPECT_THROW(v.PropsSet(property_, value_), - database::IndexConstraintViolationException); - } -} - -TEST_F(UniqueLabelPropertyTest, InsertInsertDiffValues) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - PropertyValue other_value{"Some other value"}; - v.add_label(label_); - v.PropsSet(property_, other_value); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertyTest, InsertAbortInsert) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - dba.Abort(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - dba.Commit(); - } -} - -TEST_F(UniqueLabelPropertyTest, InsertRemoveAbortInsert) { - gid::Gid gid = 0; - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - gid = v.gid(); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.FindVertex(gid, false); - v.PropsErase(property_); - dba.Abort(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - EXPECT_THROW(v.PropsSet(property_, value_), - database::IndexConstraintViolationException); - } -} - -TEST_F(UniqueLabelPropertyTest, InsertInsertSameTransaction) { - { - auto dba = db_.Access(); - auto v1 = dba.InsertVertex(); - v1.add_label(label_); - v1.PropsSet(property_, value_); - - auto v2 = dba.InsertVertex(); - v2.add_label(label_); - EXPECT_THROW(v2.PropsSet(property_, value_), - database::IndexConstraintViolationException); - } -} - -TEST_F(UniqueLabelPropertyTest, InsertInsertReversed) { - auto dba1 = db_.Access(); - auto dba2 = db_.Access(); - - auto v2 = dba2.InsertVertex(); - v2.add_label(label_); - v2.PropsSet(property_, value_); - dba2.Commit(); - - auto v1 = dba1.InsertVertex(); - v1.add_label(label_); - EXPECT_THROW(v1.PropsSet(property_, value_), - mvcc::SerializationError); -} - -TEST_F(UniqueLabelPropertyTest, InsertRemoveInsert) { - gid::Gid gid = 0; - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - gid = v.gid(); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.FindVertex(gid, false); - v.PropsErase(property_); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - } -} - -TEST_F(UniqueLabelPropertyTest, InsertRemoveInsertSameTransaction) { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - v.PropsErase(property_); - v.PropsSet(property_, value_); - dba.Commit(); -} - -TEST_F(UniqueLabelPropertyTest, InsertDropInsert) { - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - dba.Commit(); - } - { - auto dba = db_.Access(); - dba.DeleteUniqueConstraint(label_, property_); - dba.Commit(); - } - { - auto dba = db_.Access(); - auto v = dba.InsertVertex(); - v.add_label(label_); - v.PropsSet(property_, value_); - dba.Commit(); - } -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - google::InitGoogleLogging(argv[0]); - return RUN_ALL_TESTS(); -}