UniqueLabelPropertyConstraint implementation
Summary: UniqueLabelPropertyConstraint defines label + property restriction on vertices. Label + Property + PropertyValue for that property must be unique at any given moment. Reviewers: msantl, ipaljak Reviewed By: msantl Subscribers: mferencevic, pullbot Differential Revision: https://phabricator.memgraph.io/D1884
This commit is contained in:
parent
11f3cb8838
commit
8a5842ef63
@ -52,6 +52,8 @@ set(mg_single_node_sources
|
||||
storage/single_node/record_accessor.cpp
|
||||
storage/single_node/vertex_accessor.cpp
|
||||
storage/single_node/constraints/existence_constraints.cpp
|
||||
storage/single_node/constraints/record.cpp
|
||||
storage/single_node/constraints/unique_label_property_constraint.cpp
|
||||
transactions/single_node/engine.cpp
|
||||
memgraph_init.cpp
|
||||
)
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "database/single_node/graph_db.hpp"
|
||||
#include "storage/common/types/types.hpp"
|
||||
#include "storage/single_node/constraints/exceptions.hpp"
|
||||
#include "storage/single_node/edge_accessor.hpp"
|
||||
#include "storage/single_node/vertex_accessor.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
@ -22,11 +23,6 @@
|
||||
|
||||
namespace database {
|
||||
|
||||
/** Thrown when inserting in an index with constraint. */
|
||||
class IndexConstraintViolationException : public utils::BasicException {
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
|
||||
/** Thrown when creating an index which already exists. */
|
||||
class IndexExistsException : public utils::BasicException {
|
||||
using utils::BasicException::BasicException;
|
||||
|
12
src/storage/single_node/constraints/exceptions.hpp
Normal file
12
src/storage/single_node/constraints/exceptions.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
/// @file
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace database {
|
||||
/** Thrown when inserting in an unique constraint. */
|
||||
class IndexConstraintViolationException : public utils::BasicException {
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
} // namespace database
|
62
src/storage/single_node/constraints/record.cpp
Normal file
62
src/storage/single_node/constraints/record.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include "storage/single_node/constraints/record.hpp"
|
||||
|
||||
#include "storage/single_node/constraints/exceptions.hpp"
|
||||
#include "storage/single_node/mvcc/version_list.hpp"
|
||||
#include "transactions/single_node/engine.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
|
||||
namespace storage::constraints::impl {
|
||||
Record::Record(gid::Gid gid, const tx::Transaction &t)
|
||||
: curr_gid(gid), tx_id_cre(t.id_) {}
|
||||
|
||||
void Record::Insert(gid::Gid gid, const tx::Transaction &t) {
|
||||
// Insert
|
||||
// - delete before or in this transaction and not aborted
|
||||
// - insert before and aborted
|
||||
// Throw SerializationError
|
||||
// - delted of inserted after this transaction
|
||||
// Throw IndexConstraintViolationException
|
||||
// - insert before or in this transaction and not aborted
|
||||
// - delete before and aborted
|
||||
|
||||
t.TakeLock(lock_);
|
||||
if (t.id_ < tx_id_cre || (tx_id_exp != 0 && t.id_ < tx_id_exp))
|
||||
throw mvcc::SerializationError();
|
||||
|
||||
bool has_entry = tx_id_exp == 0;
|
||||
bool is_aborted = has_entry ? t.engine_.Info(tx_id_cre).is_aborted()
|
||||
: t.engine_.Info(tx_id_exp).is_aborted();
|
||||
|
||||
if ((has_entry && !is_aborted) || (!has_entry && is_aborted))
|
||||
throw database::IndexConstraintViolationException(
|
||||
"Node couldn't be updated due to unique constraint violation!");
|
||||
|
||||
curr_gid = gid;
|
||||
tx_id_cre = t.id_;
|
||||
tx_id_exp = 0;
|
||||
}
|
||||
|
||||
void Record::Remove(gid::Gid gid, const tx::Transaction &t) {
|
||||
// Remove
|
||||
// - insert before or in this transaction and not aborted
|
||||
// - remove before and aborted
|
||||
// Nothing
|
||||
// - remove before or in this transaction and not aborted
|
||||
// - insert before and aborted
|
||||
// Throw SerializationError
|
||||
// - delete or insert after this transaction
|
||||
|
||||
t.TakeLock(lock_);
|
||||
DCHECK(gid == curr_gid);
|
||||
if (t.id_ < tx_id_cre || (tx_id_exp != 0 && t.id_ < tx_id_exp))
|
||||
throw mvcc::SerializationError();
|
||||
|
||||
bool has_entry = tx_id_exp == 0;
|
||||
bool is_aborted = has_entry ? t.engine_.Info(tx_id_cre).is_aborted()
|
||||
: t.engine_.Info(tx_id_exp).is_aborted();
|
||||
|
||||
if ((!has_entry && !is_aborted) || (has_entry && is_aborted)) return;
|
||||
|
||||
tx_id_exp = t.id_;
|
||||
}
|
||||
} // namespace storage::constraints::impl
|
25
src/storage/single_node/constraints/record.hpp
Normal file
25
src/storage/single_node/constraints/record.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/// @file
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/common/locking/record_lock.hpp"
|
||||
#include "storage/single_node/gid.hpp"
|
||||
#include "transactions/type.hpp"
|
||||
|
||||
namespace tx {
|
||||
class Transaction;
|
||||
} // namespace tx
|
||||
|
||||
namespace storage::constraints::impl {
|
||||
/// Contains records of creation and deletion of entry in a constraint.
|
||||
struct Record {
|
||||
Record(gid::Gid gid, const tx::Transaction &t);
|
||||
void Insert(gid::Gid gid, const tx::Transaction &t);
|
||||
void Remove(gid::Gid gid, const tx::Transaction &t);
|
||||
|
||||
gid::Gid curr_gid;
|
||||
tx::TransactionId tx_id_cre;
|
||||
tx::TransactionId tx_id_exp{0};
|
||||
RecordLock lock_;
|
||||
};
|
||||
} // namespace storage::constraints::impl
|
@ -0,0 +1,143 @@
|
||||
#include "storage/single_node/constraints/unique_label_property_constraint.hpp"
|
||||
|
||||
#include "storage/single_node/vertex_accessor.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
|
||||
namespace storage::constraints {
|
||||
auto FindIn(storage::Label label, storage::Property property,
|
||||
const std::list<impl::LabelPropertyEntry> &constraints) {
|
||||
return std::find_if(constraints.begin(), constraints.end(),
|
||||
[label, property](auto &c) {
|
||||
return c.label == label && c.property == property;
|
||||
});
|
||||
}
|
||||
|
||||
void UniqueLabelPropertyConstraint::AddConstraint(storage::Label label,
|
||||
storage::Property property,
|
||||
const tx::Transaction &t) {
|
||||
auto found = FindIn(label, property, constraints_);
|
||||
if (found == constraints_.end()) constraints_.emplace_back(label, property);
|
||||
}
|
||||
|
||||
void UniqueLabelPropertyConstraint::RemoveConstraint(
|
||||
storage::Label label, storage::Property property) {
|
||||
auto found = FindIn(label, property, constraints_);
|
||||
if (found != constraints_.end()) constraints_.erase(found);
|
||||
}
|
||||
|
||||
bool UniqueLabelPropertyConstraint::Exists(storage::Label label,
|
||||
storage::Property property) const {
|
||||
return FindIn(label, property, constraints_) != constraints_.end();
|
||||
}
|
||||
|
||||
std::vector<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::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_) {
|
||||
auto value = vertex.properties_.at(constraint.property);
|
||||
if (constraint.label == label && !value.IsNull()) {
|
||||
bool found = false;
|
||||
for (auto &p : constraint.version_pairs) {
|
||||
if (p.value == value) {
|
||||
p.record.Insert(accessor.gid(), t);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
constraint.version_pairs.emplace_back(accessor.gid(), value, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void UniqueLabelPropertyConstraint::UpdateOnRemoveLabel(
|
||||
storage::Label label, const VertexAccessor &accessor,
|
||||
const tx::Transaction &t) {
|
||||
auto &vertex = accessor.current();
|
||||
std::lock_guard<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 VertexAccessor &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 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 (constraint.property == property &&
|
||||
utils::Contains(vertex.labels_, constraint.label)) {
|
||||
for (auto &p : constraint.version_pairs) {
|
||||
if (p.value == value) {
|
||||
p.record.Remove(accessor.gid(), t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UniqueLabelPropertyConstraint::Refresh(const tx::Snapshot &snapshot,
|
||||
const tx::Engine &engine) {
|
||||
std::lock_guard<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace storage::constraints
|
@ -0,0 +1,128 @@
|
||||
/// @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 VertexAccessor;
|
||||
|
||||
namespace storage::constraints {
|
||||
namespace impl {
|
||||
struct Pair {
|
||||
Pair(gid::Gid gid, const PropertyValue &v, const tx::Transaction &t)
|
||||
: value(v), record(gid, t) {}
|
||||
|
||||
PropertyValue value;
|
||||
Record record;
|
||||
};
|
||||
|
||||
/// Contains data for every unique constraint rule. It consists of label,
|
||||
/// property and all property values for that property. For each property value
|
||||
/// there is a record that contains ids of transactions that created and
|
||||
/// deleted record with that value.
|
||||
struct LabelPropertyEntry {
|
||||
explicit LabelPropertyEntry(storage::Label l, storage::Property p)
|
||||
: label(l), property(p) {}
|
||||
|
||||
storage::Label label;
|
||||
storage::Property property;
|
||||
std::list<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.
|
||||
void AddConstraint(storage::Label label, storage::Property property,
|
||||
const tx::Transaction &t);
|
||||
|
||||
/// Removes existing unique constraint, if the constraint doesn't exist this
|
||||
/// method does nothing. Caller must ensure that no other transaction is
|
||||
/// running in parallel.
|
||||
void RemoveConstraint(storage::Label label, storage::Property property);
|
||||
|
||||
/// Checks whether given unique constraint is visible.
|
||||
bool Exists(storage::Label label, storage::Property property) const;
|
||||
|
||||
/// Returns list of unique constraints.
|
||||
std::vector<LabelProperty> ListConstraints() const;
|
||||
|
||||
/// Updates unique constraint versions.
|
||||
///
|
||||
/// @throws IndexConstraintViolationException
|
||||
/// @throws SerializationError
|
||||
void UpdateOnAddLabel(storage::Label label, const VertexAccessor &accessor,
|
||||
const tx::Transaction &t);
|
||||
|
||||
/// Updates unique constraint versions.
|
||||
///
|
||||
/// @throws SerializationError
|
||||
void UpdateOnRemoveLabel(storage::Label label, const VertexAccessor &accessor,
|
||||
const tx::Transaction &t);
|
||||
|
||||
/// Updates unique constraint versions.
|
||||
///
|
||||
/// @throws IndexConstraintViolationException
|
||||
/// @throws SerializationError
|
||||
void UpdateOnAddProperty(storage::Property property,
|
||||
const PropertyValue &value,
|
||||
const VertexAccessor &accessor,
|
||||
const tx::Transaction &t);
|
||||
|
||||
/// Updates unique constraint versions.
|
||||
///
|
||||
/// @throws SerializationError
|
||||
void UpdateOnRemoveProperty(storage::Property property,
|
||||
const PropertyValue &value,
|
||||
const VertexAccessor &accessor,
|
||||
const tx::Transaction &t);
|
||||
|
||||
/// Removes records that are no longer visible.
|
||||
void Refresh(const tx::Snapshot &snapshot, const tx::Engine &engine);
|
||||
|
||||
private:
|
||||
std::mutex lock_;
|
||||
|
||||
std::list<impl::LabelPropertyEntry> constraints_;
|
||||
};
|
||||
} // namespace storage::constraints
|
@ -149,6 +149,10 @@ class RecordAccessor {
|
||||
*/
|
||||
int64_t CypherId() const;
|
||||
|
||||
/** Returns the current version (either new_ or old_) set on this
|
||||
* RecordAccessor. */
|
||||
const TRecord ¤t() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Pointer to the version (either old_ or new_) that READ operations
|
||||
@ -160,10 +164,6 @@ class RecordAccessor {
|
||||
*/
|
||||
mutable TRecord *current_{nullptr};
|
||||
|
||||
/** Returns the current version (either new_ or old_) set on this
|
||||
* RecordAccessor. */
|
||||
const TRecord ¤t() const;
|
||||
|
||||
private:
|
||||
// The database accessor for which this record accessor is created
|
||||
// Provides means of getting to the transaction and database functions.
|
||||
|
@ -285,6 +285,9 @@ target_link_libraries(${test_prefix}transaction_engine_single_node_ha mg-single-
|
||||
add_unit_test(typed_value.cpp)
|
||||
target_link_libraries(${test_prefix}typed_value mg-single-node kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(unique_label_property_constraint.cpp)
|
||||
target_link_libraries(${test_prefix}unique_label_property_constraint mg-single-node kvstore_dummy_lib)
|
||||
|
||||
# Test mg-communication
|
||||
|
||||
add_unit_test(bolt_chunked_decoder_buffer.cpp)
|
||||
|
280
tests/unit/unique_label_property_constraint.cpp
Normal file
280
tests/unit/unique_label_property_constraint.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
#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_property_constraint.hpp"
|
||||
|
||||
class UniqueLabelPropertyTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
auto dba = db_.AccessBlocking();
|
||||
label_ = dba->Label("label");
|
||||
property_ = dba->Property("property");
|
||||
constraint_.AddConstraint(label_, property_, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
|
||||
database::GraphDb db_;
|
||||
storage::Label label_;
|
||||
storage::Property property_;
|
||||
PropertyValue value_{"value"};
|
||||
storage::constraints::UniqueLabelPropertyConstraint constraint_;
|
||||
};
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, BuildDrop) {
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
EXPECT_TRUE(constraint_.Exists(label_, property_));
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
constraint_.RemoveConstraint(label_, property_);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
EXPECT_FALSE(constraint_.Exists(label_, property_));
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, BuildWithViolation) {
|
||||
auto dba1 = db_.Access();
|
||||
auto v1 = dba1->InsertVertex();
|
||||
v1.add_label(label_);
|
||||
v1.PropsSet(property_, value_);
|
||||
|
||||
auto v2 = dba1->InsertVertex();
|
||||
v2.add_label(label_);
|
||||
v2.PropsSet(property_, value_);
|
||||
dba1->Commit();
|
||||
|
||||
auto dba2 = db_.Access();
|
||||
auto v3 = dba2->FindVertex(v1.gid(), false);
|
||||
auto v4 = dba2->FindVertex(v2.gid(), false);
|
||||
constraint_.UpdateOnAddLabel(label_, v3, dba2->transaction());
|
||||
EXPECT_THROW(constraint_.UpdateOnAddLabel(label_, v4, dba2->transaction()),
|
||||
database::IndexConstraintViolationException);
|
||||
EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v4,
|
||||
dba2->transaction()),
|
||||
database::IndexConstraintViolationException);
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertInsert) {
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v,
|
||||
dba->transaction()),
|
||||
database::IndexConstraintViolationException);
|
||||
EXPECT_THROW(constraint_.UpdateOnAddLabel(label_, v, dba->transaction()),
|
||||
database::IndexConstraintViolationException);
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertInsertDiffValues) {
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
PropertyValue other_value{"Some other value"};
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, other_value);
|
||||
constraint_.UpdateOnAddProperty(property_, other_value, v,
|
||||
dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertAbortInsert) {
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Abort();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertRemoveAbortInsert) {
|
||||
gid::Gid gid = 0;
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
gid = v.gid();
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->FindVertex(gid, false);
|
||||
v.PropsErase(property_);
|
||||
constraint_.UpdateOnRemoveProperty(property_, value_, v,
|
||||
dba->transaction());
|
||||
dba->Abort();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v,
|
||||
dba->transaction()),
|
||||
database::IndexConstraintViolationException);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertInsertSameTransaction) {
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v1 = dba->InsertVertex();
|
||||
v1.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v1, dba->transaction());
|
||||
v1.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v1, dba->transaction());
|
||||
|
||||
auto v2 = dba->InsertVertex();
|
||||
v2.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v2, dba->transaction());
|
||||
v2.PropsSet(property_, value_);
|
||||
EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v2,
|
||||
dba->transaction()),
|
||||
database::IndexConstraintViolationException);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertInsertReversed) {
|
||||
auto dba1 = db_.Access();
|
||||
auto dba2 = db_.Access();
|
||||
|
||||
auto v2 = dba2->InsertVertex();
|
||||
v2.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v2, dba2->transaction());
|
||||
v2.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v2, dba2->transaction());
|
||||
dba2->Commit();
|
||||
|
||||
auto v1 = dba1->InsertVertex();
|
||||
v1.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v1, dba1->transaction());
|
||||
v1.PropsSet(property_, value_);
|
||||
EXPECT_THROW(constraint_.UpdateOnAddProperty(property_, value_, v1,
|
||||
dba1->transaction()),
|
||||
mvcc::SerializationError);
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertRemoveInsert) {
|
||||
gid::Gid gid = 0;
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
gid = v.gid();
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->FindVertex(gid, false);
|
||||
v.PropsErase(property_);
|
||||
constraint_.UpdateOnRemoveProperty(property_, value_, v,
|
||||
dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertRemoveInsertSameTransaction) {
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
v.PropsErase(property_);
|
||||
constraint_.UpdateOnRemoveProperty(property_, value_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
|
||||
TEST_F(UniqueLabelPropertyTest, InsertDropInsert) {
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.AccessBlocking();
|
||||
constraint_.RemoveConstraint(label_, property_);
|
||||
dba->Commit();
|
||||
}
|
||||
{
|
||||
auto dba = db_.Access();
|
||||
auto v = dba->InsertVertex();
|
||||
v.add_label(label_);
|
||||
constraint_.UpdateOnAddLabel(label_, v, dba->transaction());
|
||||
v.PropsSet(property_, value_);
|
||||
constraint_.UpdateOnAddProperty(property_, value_, v, dba->transaction());
|
||||
dba->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
Reference in New Issue
Block a user