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:
parent
1e79313538
commit
37c68f0508
@ -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
|
||||
)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
try {
|
||||
auto dba = db_->AccessBlocking(std::make_optional(transaction().id_));
|
||||
if (!db_->storage().unique_label_property_constraints_.AddConstraint(
|
||||
label, property, dba.transaction())) {
|
||||
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_));
|
||||
|
||||
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));
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +676,13 @@ Callback HandleInfoQuery(InfoQuery *info_query,
|
||||
Callback HandleConstraintQuery(ConstraintQuery *constraint_query,
|
||||
database::GraphDbAccessor *db_accessor) {
|
||||
#ifdef MG_SINGLE_NODE
|
||||
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: {
|
||||
@ -664,10 +692,21 @@ Callback HandleConstraintQuery(ConstraintQuery *constraint_query,
|
||||
case Constraint::Type::EXISTS:
|
||||
throw utils::NotYetImplemented("Existence constraints");
|
||||
case Constraint::Type::UNIQUE:
|
||||
throw utils::NotYetImplemented("Unique constraints");
|
||||
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:
|
||||
@ -675,10 +714,17 @@ Callback HandleConstraintQuery(ConstraintQuery *constraint_query,
|
||||
case Constraint::Type::EXISTS:
|
||||
throw utils::NotYetImplemented("Existence constraints");
|
||||
case Constraint::Type::UNIQUE:
|
||||
throw utils::NotYetImplemented("Unique constraints");
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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_);
|
||||
|
258
src/storage/single_node/constraints/unique_constraints.cpp
Normal file
258
src/storage/single_node/constraints/unique_constraints.cpp
Normal 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
|
@ -13,7 +13,10 @@ namespace tx {
|
||||
class Snapshot;
|
||||
}; // namespace tx
|
||||
|
||||
class VertexAccessor;
|
||||
class Vertex;
|
||||
|
||||
template <typename TRecord>
|
||||
class RecordAccessor;
|
||||
|
||||
namespace storage::constraints {
|
||||
namespace impl {
|
||||
@ -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:
|
@ -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
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
@ -168,16 +168,21 @@ 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(),
|
||||
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);
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
298
tests/unit/unique_constraints.cpp
Normal file
298
tests/unit/unique_constraints.cpp
Normal 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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user