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
This commit is contained in:
Matija Santl 2019-05-13 15:26:56 +02:00
parent 1e79313538
commit 37c68f0508
29 changed files with 942 additions and 1301 deletions

View File

@ -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
)

View File

@ -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;
};

View File

@ -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

View File

@ -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<storage::Property> &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<std::string> 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<storage::Property>{property},
std::vector<std::string>{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<storage::Property> &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<std::string> 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<storage::Property>{property},
std::vector<std::string>{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<storage::constraints::LabelProperty>
GraphDbAccessor::ListUniqueLabelPropertyConstraints() const {
return db_->storage().unique_label_property_constraints_.ListConstraints();
std::vector<storage::constraints::ConstraintEntry>
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<Vertex> &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> &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<Vertex> &accessor,
const Vertex *vertex) {
db_->storage().unique_label_property_constraints_.UpdateOnRemoveProperty(
property, accessor, transaction());
storage::Property property, const PropertyValue &previous_value,
const RecordAccessor<Vertex> &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));

View File

@ -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<storage::Property> &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<storage::Property> &properties);
/**
* Returns a list of currently active unique constraints.
*/
std::vector<storage::constraints::LabelProperty>
ListUniqueLabelPropertyConstraints() const;
std::vector<storage::constraints::ConstraintEntry> 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<Vertex> &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<Vertex> &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> &vertex_accessor,
const Vertex *vertex);
};

View File

@ -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!");
}
}

View File

@ -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;
};

View File

@ -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);
}
}
}

View File

@ -58,11 +58,13 @@ bool Encode(const fs::path &snapshot_file, database::GraphDb &db,
// Write unique constraints to snapshoot
{
std::vector<communication::bolt::Value> 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<int64_t>(rule.properties.size()));
for (auto &p : rule.properties) {
unique_constraints.emplace_back(dba.PropertyName(p));
}
}
encoder.WriteList(unique_constraints);
}

View File

@ -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<storage::Property> 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;
}
}

View File

@ -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());
}
}

View File

@ -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<std::vector<TypedValue>>();
@ -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<std::vector<TypedValue>>();
@ -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<std::vector<TypedValue>> results;
for (auto &e : db_accessor->ListUniqueConstraints()) {
std::vector<std::string> 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<TypedValue> 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<storage::Property> 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<std::vector<TypedValue>>();
} 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<std::vector<TypedValue>>();
} catch (const database::TransactionException &e) {
throw QueryRuntimeException(e.what());
}
};
break;
}
} break;
}
return callback;
#else
throw utils::NotYetImplemented("Constraints");

View File

@ -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

View File

@ -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_);

View File

@ -0,0 +1,258 @@
#include "storage/single_node/constraints/unique_constraints.hpp"
#include <algorithm>
#include "storage/single_node/vertex_accessor.hpp"
namespace storage::constraints {
namespace {
auto FindIn(storage::Label label,
const std::vector<storage::Property> &properties,
const std::list<impl::LabelPropertiesEntry> &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<storage::Property> &properties) const {
return FindIn(label, properties, constraints_) != constraints_.end();
}
std::vector<ConstraintEntry> UniqueConstraints::ListConstraints() const {
std::vector<ConstraintEntry> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (!utils::Contains(vertex.labels_, constraint.label)) continue;
std::vector<PropertyValue> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (constraint.label != label) continue;
std::vector<PropertyValue> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (constraint.label != label) continue;
std::vector<PropertyValue> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (!utils::Contains(vertex.labels_, constraint.label)) continue;
if (!utils::Contains(constraint.properties, property)) continue;
std::vector<PropertyValue> old_values;
std::vector<PropertyValue> 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<Vertex> &accessor, const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (!utils::Contains(vertex.labels_, constraint.label)) continue;
if (!utils::Contains(constraint.properties, property)) continue;
std::vector<PropertyValue> 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<Vertex> &accessor, const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (!utils::Contains(vertex.labels_, constraint.label)) continue;
std::vector<PropertyValue> 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<std::mutex> 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

View File

@ -13,13 +13,16 @@ namespace tx {
class Snapshot;
}; // namespace tx
class VertexAccessor;
class Vertex;
template <typename TRecord>
class RecordAccessor;
namespace storage::constraints {
namespace impl {
struct LabelPropertyPair {
LabelPropertyPair(gid::Gid gid, const std::vector<PropertyValue> &v,
const tx::Transaction &t)
const tx::Transaction &t)
: values(v), record(gid, t) {}
std::vector<PropertyValue> values;
@ -27,7 +30,8 @@ struct LabelPropertyPair {
};
struct LabelPropertiesEntry {
LabelPropertiesEntry(storage::Label l, const std::vector<storage::Property> &p)
LabelPropertiesEntry(storage::Label l,
const std::vector<storage::Property> &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<storage::Property> 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<storage::Property> &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<storage::Property> &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<storage::Property> &properties) const;
/// Returns list of unique constraints.
std::vector<LabelProperties> ListConstraints() const;
std::vector<ConstraintEntry> ListConstraints() const;
/// Updates unique constraint versions when adding new constraint rule.
///
/// @throws ConstraintViolationException
/// @throws SerializationError
void Update(const RecordAccessor<Vertex> &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<Vertex> &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<Vertex> &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<Vertex> &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<Vertex> &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<Vertex> &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:

View File

@ -1,155 +0,0 @@
#include "storage/single_node/constraints/unique_label_properties_constraint.hpp"
#include <algorithm>
#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<storage::Property> &properties,
const std::list<impl::LabelPropertiesEntry> &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<impl::LabelPropertyPair> &version_pairs,
const std::vector<PropertyValue> &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<storage::Property> &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<storage::Property> &properties) {
auto constraint = FindIn(label, properties, constraints_);
if (constraint != constraints_.end()) constraints_.erase(constraint);
}
bool UniqueLabelPropertiesConstraint::Exists(
storage::Label label,
const std::vector<storage::Property> &properties) const {
return FindIn(label, properties, constraints_) != constraints_.end();
}
std::vector<LabelProperties> UniqueLabelPropertiesConstraint::ListConstraints()
const {
std::vector<LabelProperties> 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<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (constraint.label != label) continue;
std::vector<PropertyValue> 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<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (constraint.label != label) continue;
std::vector<PropertyValue> 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<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (!utils::Contains(constraint.properties, property)) continue;
if (!utils::Contains(vertex.labels_, constraint.label)) continue;
std::vector<PropertyValue> 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<std::mutex> guard(lock_);
for (auto &constraint : constraints_) {
if (!utils::Contains(constraint.properties, property)) continue;
if (!utils::Contains(vertex.labels_, constraint.label)) continue;
std::vector<PropertyValue> 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

View File

@ -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<impl::LabelPropertyEntry> &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<LabelProperty> UniqueLabelPropertyConstraint::ListConstraints()
const {
std::vector<LabelProperty> 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<Vertex> &accessor, const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> 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<Vertex> &accessor, const tx::Transaction &t) {
auto &vertex = accessor.current();
std::lock_guard<std::mutex> 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<Vertex> &accessor,
const tx::Transaction &t) {
auto &vertex = accessor.current();
auto gid = accessor.gid();
std::lock_guard<std::mutex> 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<std::mutex> 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

View File

@ -1,138 +0,0 @@
/// @file
#pragma once
#include <list>
#include <mutex>
#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 <typename TRecord>
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<Pair> 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<LabelProperty> ListConstraints() const;
/// Updates unique constraint versions.
///
/// @throws IndexConstraintViolationException
/// @throws SerializationError
void Update(const RecordAccessor<Vertex> &accessor, const tx::Transaction &t);
/// Updates unique constraint versions.
///
/// @throws IndexConstraintViolationException
/// @throws SerializationError
void UpdateOnAddLabel(storage::Label label,
const RecordAccessor<Vertex> &accessor,
const tx::Transaction &t);
/// Updates unique constraint versions.
///
/// @throws SerializationError
void UpdateOnRemoveLabel(storage::Label label,
const RecordAccessor<Vertex> &accessor,
const tx::Transaction &t);
/// Updates unique constraint versions.
///
/// @throws IndexConstraintViolationException
/// @throws SerializationError
void UpdateOnAddProperty(storage::Property property,
const PropertyValue &value,
const RecordAccessor<Vertex> &accessor,
const tx::Transaction &t);
/// Updates unique constraint versions.
///
/// @throws SerializationError
void UpdateOnRemoveProperty(storage::Property property,
const RecordAccessor<Vertex> &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<impl::LabelPropertyEntry> constraints_;
};
} // namespace storage::constraints

View File

@ -25,8 +25,9 @@ void RecordAccessor<Vertex>::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<Vertex>::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);
}

View File

@ -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<std::string> properties_on_disk_;

View File

@ -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();
}
{

View File

@ -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

View File

@ -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();
}
{

View File

@ -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);
}

View File

@ -303,3 +303,51 @@ TEST_F(InterpreterTest, ShortestPath) {
EXPECT_TRUE(any_match);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_F(InterpreterTest, UniqueConstraintTest) {
ResultStreamFaker<query::TypedValue> 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();
}
}

View File

@ -0,0 +1,298 @@
#include <gtest/gtest.h>
#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();
}

View File

@ -1,376 +0,0 @@
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "database/single_node/graph_db.hpp"
#include "database/single_node/graph_db_accessor.hpp"
#include "storage/single_node/constraints/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();
}

View File

@ -1,227 +0,0 @@
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "database/single_node/graph_db.hpp"
#include "database/single_node/graph_db_accessor.hpp"
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();
}