Support property set for unique constraints
Summary: Before this change, unique constraints supported only pairs of label and a single property. With this change, unique constraints can be created for label and set of properties. Better tests for unique constraints in general are also included in this diff. Reviewers: mferencevic, teon.banek Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2653
This commit is contained in:
parent
4669e0ae7d
commit
95c9755e13
@ -930,8 +930,10 @@ PreparedQuery PrepareConstraintQuery(
|
||||
auto violation = res.GetError();
|
||||
auto label_name =
|
||||
interpreter_context->db->LabelToName(violation.label);
|
||||
CHECK(violation.properties.size() == 1U);
|
||||
auto property_name =
|
||||
interpreter_context->db->PropertyToName(violation.property);
|
||||
interpreter_context->db->PropertyToName(
|
||||
*violation.properties.begin());
|
||||
throw QueryRuntimeException(
|
||||
"Unable to create a constraint :{}({}), because an existing "
|
||||
"node violates it.",
|
||||
@ -1111,8 +1113,10 @@ void Interpreter::Commit() {
|
||||
const auto &constraint_violation = maybe_constraint_violation.GetError();
|
||||
auto label_name =
|
||||
execution_db_accessor_->LabelToName(constraint_violation.label);
|
||||
CHECK(constraint_violation.properties.size() == 1U);
|
||||
auto property_name =
|
||||
execution_db_accessor_->PropertyToName(constraint_violation.property);
|
||||
execution_db_accessor_->PropertyToName(
|
||||
*constraint_violation.properties.begin());
|
||||
execution_db_accessor_ = std::nullopt;
|
||||
db_accessor_ = std::nullopt;
|
||||
throw QueryException(
|
||||
|
@ -1,32 +1,62 @@
|
||||
#include "storage/v2/constraints.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
|
||||
namespace storage {
|
||||
namespace {
|
||||
|
||||
/// Helper function that determines position of the given `property` in the
|
||||
/// sorted `property_array` using binary search. In the case that `property`
|
||||
/// cannot be found, `std::nullopt` is returned.
|
||||
std::optional<size_t> FindPropertyPosition(
|
||||
const PropertyIdArray &property_array, PropertyId property) {
|
||||
auto it =
|
||||
std::lower_bound(property_array.values,
|
||||
property_array.values + property_array.size, property);
|
||||
if (it == property_array.values + property_array.size || *it != property) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return it - property_array.values;
|
||||
}
|
||||
|
||||
/// Helper function for validating unique constraints on commit. Returns true if
|
||||
/// the last commited version of the given vertex contains the given label and
|
||||
/// property value. This function should be called when commit lock is active.
|
||||
bool LastCommitedVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
PropertyId key, const PropertyValue &value,
|
||||
const Transaction &transaction,
|
||||
uint64_t commit_timestamp) {
|
||||
/// the last committed version of the given vertex contains the given label and
|
||||
/// set of property values. This function should be called when commit lock is
|
||||
/// active.
|
||||
bool LastCommittedVersionHasLabelProperty(
|
||||
const Vertex &vertex, LabelId label, const std::set<PropertyId> &properties,
|
||||
const PropertyValueArray &value_array, const Transaction &transaction,
|
||||
uint64_t commit_timestamp) {
|
||||
CHECK(properties.size() == value_array.size) << "Invalid database state!";
|
||||
|
||||
PropertyIdArray property_array(properties.size());
|
||||
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
|
||||
memset(current_value_equal_to_value, 0, sizeof(current_value_equal_to_value));
|
||||
|
||||
// Note that a guard lock isn't necessary to access vertex's data.
|
||||
// Any transaction that tries to write to that vertex will result in
|
||||
// serialization error.
|
||||
bool deleted = vertex.deleted;
|
||||
bool has_label = utils::Contains(vertex.labels, label);
|
||||
bool current_value_equal_to_value = value.IsNull();
|
||||
auto it = vertex.properties.find(key);
|
||||
if (it != vertex.properties.end()) {
|
||||
current_value_equal_to_value = it->second == value;
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &property : properties) {
|
||||
auto it = vertex.properties.find(property);
|
||||
current_value_equal_to_value[i] = value_array.values[i]->IsNull();
|
||||
if (it != vertex.properties.end()) {
|
||||
current_value_equal_to_value[i] = it->second == *value_array.values[i];
|
||||
}
|
||||
property_array.values[i] = property;
|
||||
i++;
|
||||
}
|
||||
|
||||
for (Delta *delta = vertex.delta; delta != nullptr;
|
||||
delta = delta->next.load(std::memory_order_acquire)) {
|
||||
delta = delta->next.load(std::memory_order_acquire)) {
|
||||
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||
if (ts < commit_timestamp || ts == transaction.transaction_id) {
|
||||
break;
|
||||
@ -34,8 +64,10 @@ bool LastCommitedVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
|
||||
switch (delta->action) {
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
if (delta->property.key == key) {
|
||||
current_value_equal_to_value = (delta->property.value == value);
|
||||
auto pos = FindPropertyPosition(property_array, delta->property.key);
|
||||
if (pos) {
|
||||
current_value_equal_to_value[*pos] =
|
||||
delta->property.value == *value_array.values[*pos];
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -63,40 +95,68 @@ bool LastCommitedVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
break;
|
||||
}
|
||||
}
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !deleted && has_label && current_value_equal_to_value;
|
||||
for (size_t i = 0; i < properties.size(); ++i) {
|
||||
if (!current_value_equal_to_value[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !deleted && has_label;
|
||||
}
|
||||
|
||||
/// Helper function for unique constraint garbage collection. Returns true if
|
||||
/// there's a reachable version of the vertex that has the given label and
|
||||
/// property value.
|
||||
/// property values.
|
||||
bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
PropertyId key, const PropertyValue &value,
|
||||
const std::set<PropertyId> &properties,
|
||||
const std::vector<PropertyValue> &values,
|
||||
uint64_t timestamp) {
|
||||
CHECK(properties.size() == values.size()) << "Invalid database state!";
|
||||
|
||||
PropertyIdArray property_array(properties.size());
|
||||
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
|
||||
memset(current_value_equal_to_value, 0, sizeof(current_value_equal_to_value));
|
||||
|
||||
bool has_label;
|
||||
bool deleted;
|
||||
bool current_value_equal_to_value = value.IsNull();
|
||||
Delta *delta;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
auto it = vertex.properties.find(key);
|
||||
if (it != vertex.properties.end()) {
|
||||
current_value_equal_to_value = it->second == value;
|
||||
}
|
||||
deleted = vertex.deleted;
|
||||
delta = vertex.delta;
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &property : properties) {
|
||||
auto it = vertex.properties.find(property);
|
||||
current_value_equal_to_value[i] = values[i].IsNull();
|
||||
if (it != vertex.properties.end()) {
|
||||
current_value_equal_to_value[i] = it->second == values[i];
|
||||
}
|
||||
property_array.values[i] = property;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!deleted && has_label && current_value_equal_to_value) {
|
||||
return true;
|
||||
{
|
||||
bool all_values_match = true;
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
if (!current_value_equal_to_value[i]) {
|
||||
all_values_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!deleted && has_label && all_values_match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (delta != nullptr) {
|
||||
@ -117,11 +177,14 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
has_label = false;
|
||||
}
|
||||
break;
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
if (delta->property.key == key) {
|
||||
current_value_equal_to_value = delta->property.value == value;
|
||||
case Delta::Action::SET_PROPERTY: {
|
||||
auto pos = FindPropertyPosition(property_array, delta->property.key);
|
||||
if (pos) {
|
||||
current_value_equal_to_value[*pos] =
|
||||
delta->property.value == values[*pos];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
CHECK(deleted) << "Invalid database state!";
|
||||
deleted = false;
|
||||
@ -138,7 +201,15 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
if (!deleted && has_label && current_value_equal_to_value) {
|
||||
|
||||
bool all_values_match = true;
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
if (!current_value_equal_to_value[i]) {
|
||||
all_values_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!deleted && has_label && all_values_match) {
|
||||
return true;
|
||||
}
|
||||
delta = delta->next.load(std::memory_order_acquire);
|
||||
@ -146,19 +217,94 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator<(const PropertyValueArray &lhs,
|
||||
const std::vector<PropertyValue> &rhs) {
|
||||
size_t n = std::min(lhs.size, rhs.size());
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (*lhs.values[i] < rhs[i]) {
|
||||
return true;
|
||||
}
|
||||
if (rhs[i] < *lhs.values[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return lhs.size < rhs.size();
|
||||
}
|
||||
|
||||
bool operator<(const std::vector<PropertyValue> &lhs,
|
||||
const PropertyValueArray &rhs) {
|
||||
size_t n = std::min(lhs.size(), rhs.size);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (lhs[i] < *rhs.values[i]) {
|
||||
return true;
|
||||
}
|
||||
if (*rhs.values[i] < lhs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return lhs.size() < rhs.size;
|
||||
}
|
||||
|
||||
bool operator==(const std::vector<PropertyValue> &lhs,
|
||||
const PropertyValueArray &rhs) {
|
||||
if (lhs.size() != rhs.size) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < rhs.size; ++i) {
|
||||
if (lhs[i] == *rhs.values[i]) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Helper function that, given the set of `properties`, extracts corresponding
|
||||
/// property values from the `vertex`. `PropertyValueArray` is returned instead
|
||||
/// of `std::vector` to avoid `std::bad_alloc` exception.
|
||||
std::optional<PropertyValueArray> ExtractPropertyValues(
|
||||
const Vertex &vertex, const std::set<PropertyId> &properties) {
|
||||
PropertyValueArray value_array(properties.size());
|
||||
size_t i = 0;
|
||||
for (const auto &prop : properties) {
|
||||
auto it = vertex.properties.find(prop);
|
||||
if (it == vertex.properties.end() || it->second.IsNull()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value_array.values[i++] = &it->second;
|
||||
}
|
||||
return value_array;
|
||||
}
|
||||
|
||||
/// Helper function that converts optional property value array to optional
|
||||
/// `std::vector` of property values.
|
||||
/// @throw std::bad_alloc
|
||||
std::optional<std::vector<PropertyValue>> OptionalPropertyValueArrayToVector(
|
||||
const std::optional<PropertyValueArray> &value_array) {
|
||||
if (!value_array) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<PropertyValue> values;
|
||||
values.reserve(value_array->size);
|
||||
for (size_t i = 0; i < value_array->size; ++i) {
|
||||
values.push_back(*value_array->values[i]);
|
||||
}
|
||||
return std::move(values);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool operator==(const ConstraintViolation &lhs,
|
||||
const ConstraintViolation &rhs) {
|
||||
return lhs.type == rhs.type && lhs.label == rhs.label &&
|
||||
lhs.property == rhs.property;
|
||||
lhs.properties == rhs.properties;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator<(const Entry &rhs) {
|
||||
if (value < rhs.value) {
|
||||
if (values < rhs.values) {
|
||||
return true;
|
||||
}
|
||||
if (rhs.value < value) {
|
||||
if (rhs.values < values) {
|
||||
return false;
|
||||
}
|
||||
return std::make_tuple(vertex, timestamp) <
|
||||
@ -166,42 +312,59 @@ bool UniqueConstraints::Entry::operator<(const Entry &rhs) {
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator==(const Entry &rhs) {
|
||||
return value == rhs.value && vertex == rhs.vertex &&
|
||||
return values == rhs.values && vertex == rhs.vertex &&
|
||||
timestamp == rhs.timestamp;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator<(const PropertyValue &rhs) {
|
||||
return value < rhs;
|
||||
bool UniqueConstraints::Entry::operator<(
|
||||
const std::vector<PropertyValue> &rhs) {
|
||||
return values < rhs;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator==(const PropertyValue &rhs) {
|
||||
return value == rhs;
|
||||
bool UniqueConstraints::Entry::operator==(
|
||||
const std::vector<PropertyValue> &rhs) {
|
||||
return values == rhs;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator<(const PropertyValueArray &rhs) {
|
||||
return values < rhs;
|
||||
}
|
||||
|
||||
bool UniqueConstraints::Entry::operator==(const PropertyValueArray &rhs) {
|
||||
return values == rhs;
|
||||
}
|
||||
|
||||
void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex,
|
||||
const Transaction &tx) {
|
||||
for (auto &[label_prop, storage] : constraints_) {
|
||||
if (!utils::Contains(vertex->labels, label_prop.first)) {
|
||||
for (auto &[label_props, storage] : constraints_) {
|
||||
if (!utils::Contains(vertex->labels, label_props.first)) {
|
||||
continue;
|
||||
}
|
||||
auto it = vertex->properties.find(label_prop.second);
|
||||
if (it != vertex->properties.end() && !it->second.IsNull()) {
|
||||
auto values = OptionalPropertyValueArrayToVector(
|
||||
ExtractPropertyValues(*vertex, label_props.second));
|
||||
if (values) {
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{it->second, vertex, tx.start_timestamp});
|
||||
acc.insert(Entry{std::move(*values), vertex, tx.start_timestamp});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils::BasicResult<ConstraintViolation, bool> UniqueConstraints::CreateConstraint(
|
||||
LabelId label, PropertyId property,
|
||||
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus>
|
||||
UniqueConstraints::CreateConstraint(
|
||||
LabelId label, const std::set<PropertyId> &properties,
|
||||
utils::SkipList<Vertex>::Accessor vertices) {
|
||||
if (properties.empty() ||
|
||||
properties.size() > kUniqueConstraintsMaxProperties) {
|
||||
return CreationStatus::INVALID_PROPERTIES_SIZE;
|
||||
}
|
||||
|
||||
auto [constraint, emplaced] = constraints_.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(label, property),
|
||||
std::forward_as_tuple());
|
||||
std::piecewise_construct, std::forward_as_tuple(label, properties),
|
||||
std::forward_as_tuple());
|
||||
|
||||
if (!emplaced) {
|
||||
// Constraint already exists.
|
||||
return false;
|
||||
return CreationStatus::ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
bool violation_found = false;
|
||||
@ -213,21 +376,21 @@ utils::BasicResult<ConstraintViolation, bool> UniqueConstraints::CreateConstrain
|
||||
if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
|
||||
continue;
|
||||
}
|
||||
auto property_it = vertex.properties.find(property);
|
||||
if (property_it == vertex.properties.end()) {
|
||||
auto values = OptionalPropertyValueArrayToVector(
|
||||
ExtractPropertyValues(vertex, properties));
|
||||
if (!values) {
|
||||
continue;
|
||||
}
|
||||
const PropertyValue &value = property_it->second;
|
||||
|
||||
// Check whether there already is a vertex with the same value for the given
|
||||
// label and property.
|
||||
auto it = acc.find_equal_or_greater(value);
|
||||
if (it != acc.end() && it->value == value) {
|
||||
// Check whether there already is a vertex with the same values for the
|
||||
// given label and property.
|
||||
auto it = acc.find_equal_or_greater(*values);
|
||||
if (it != acc.end() && it->values == *values) {
|
||||
violation_found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
acc.insert(Entry{value, &vertex, 0});
|
||||
acc.insert(Entry{std::move(*values), &vertex, 0});
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,64 +399,63 @@ utils::BasicResult<ConstraintViolation, bool> UniqueConstraints::CreateConstrain
|
||||
// be removed.
|
||||
constraints_.erase(constraint);
|
||||
return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label,
|
||||
property};
|
||||
properties};
|
||||
}
|
||||
return true;
|
||||
return CreationStatus::SUCCESS;
|
||||
}
|
||||
|
||||
std::optional<ConstraintViolation> UniqueConstraints::Validate(
|
||||
const Vertex &vertex, const Transaction &tx,
|
||||
uint64_t commit_timestamp) const {
|
||||
if (vertex.deleted) {
|
||||
if (vertex.deleted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (const auto &[label_prop, storage] : constraints_) {
|
||||
const auto &label = label_prop.first;
|
||||
const auto &property = label_prop.second;
|
||||
auto property_it = vertex.properties.find(property);
|
||||
if (!utils::Contains(vertex.labels, label) ||
|
||||
property_it == vertex.properties.end()) {
|
||||
// We are only interested in constraints relevant to the current vertex,
|
||||
// i.e. having (label, property) included in the given vertex.
|
||||
for (const auto &[label_props, storage] : constraints_) {
|
||||
const auto &label = label_props.first;
|
||||
const auto &properties = label_props.second;
|
||||
if (!utils::Contains(vertex.labels, label)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto &value = property_it->second;
|
||||
auto value_array = ExtractPropertyValues(vertex, properties);
|
||||
if (!value_array) {
|
||||
continue;
|
||||
}
|
||||
auto acc = storage.access();
|
||||
auto it = acc.find_equal_or_greater(value);
|
||||
auto it = acc.find_equal_or_greater(*value_array);
|
||||
for (; it != acc.end(); ++it) {
|
||||
if (value < it->value) {
|
||||
if (*value_array < it->values) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The `vertex` that is going to be commited violates a unique constraint
|
||||
// The `vertex` that is going to be committed violates a unique constraint
|
||||
// if it's different than a vertex indexed in the list of constraints and
|
||||
// has the same label and property value as the last commited version of
|
||||
// has the same label and property value as the last committed version of
|
||||
// the vertex from the list.
|
||||
if (&vertex != it->vertex &&
|
||||
LastCommitedVersionHasLabelProperty(*it->vertex, label, property,
|
||||
value, tx, commit_timestamp)) {
|
||||
if (&vertex != it->vertex && LastCommittedVersionHasLabelProperty(
|
||||
*it->vertex, label, properties,
|
||||
*value_array, tx, commit_timestamp)) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label,
|
||||
property};
|
||||
properties};
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> UniqueConstraints::ListConstraints()
|
||||
const {
|
||||
std::vector<std::pair<LabelId, PropertyId>> ret;
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>>
|
||||
UniqueConstraints::ListConstraints() const {
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> ret;
|
||||
ret.reserve(constraints_.size());
|
||||
for (const auto &item : constraints_) {
|
||||
ret.push_back(item.first);
|
||||
for (const auto &[label_props, _] : constraints_) {
|
||||
ret.push_back(label_props);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void UniqueConstraints::RemoveObsoleteEntries(
|
||||
uint64_t oldest_active_start_timestamp) {
|
||||
for (auto &[label_prop, storage] : constraints_) {
|
||||
for (auto &[label_props, storage] : constraints_) {
|
||||
auto acc = storage.access();
|
||||
for (auto it = acc.begin(); it != acc.end();) {
|
||||
auto next_it = it;
|
||||
@ -305,9 +467,9 @@ void UniqueConstraints::RemoveObsoleteEntries(
|
||||
}
|
||||
|
||||
if ((next_it != acc.end() && it->vertex == next_it->vertex &&
|
||||
it->value == next_it->value) ||
|
||||
!AnyVersionHasLabelProperty(*it->vertex, label_prop.first,
|
||||
label_prop.second, it->value,
|
||||
it->values == next_it->values) ||
|
||||
!AnyVersionHasLabelProperty(*it->vertex, label_props.first,
|
||||
label_props.second, it->values,
|
||||
oldest_active_start_timestamp)) {
|
||||
acc.remove(*it);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v2/id_types.hpp"
|
||||
@ -11,6 +12,25 @@
|
||||
|
||||
namespace storage {
|
||||
|
||||
// NOLINTNEXTLINE(misc-definitions-in-headers)
|
||||
const size_t kUniqueConstraintsMaxProperties = 32;
|
||||
|
||||
/// Utility class to store data in a fixed size array. The array is used
|
||||
/// instead of `std::vector` to avoid `std::bad_alloc` exception where not
|
||||
/// necessary.
|
||||
template <class T>
|
||||
struct FixedCapacityArray {
|
||||
size_t size;
|
||||
T values[kUniqueConstraintsMaxProperties];
|
||||
|
||||
explicit FixedCapacityArray(size_t array_size) : size(array_size) {
|
||||
CHECK(size <= kUniqueConstraintsMaxProperties) << "Invalid array size!";
|
||||
}
|
||||
};
|
||||
|
||||
using PropertyValueArray = FixedCapacityArray<const PropertyValue *>;
|
||||
using PropertyIdArray = FixedCapacityArray<PropertyId>;
|
||||
|
||||
struct ConstraintViolation {
|
||||
enum class Type {
|
||||
EXISTENCE,
|
||||
@ -19,51 +39,65 @@ struct ConstraintViolation {
|
||||
|
||||
Type type;
|
||||
LabelId label;
|
||||
PropertyId property;
|
||||
|
||||
// While multiple properties are supported by unique constraints, the
|
||||
// `properties` set will always have exactly one element in the case of
|
||||
// existence constraint violation.
|
||||
std::set<PropertyId> properties;
|
||||
};
|
||||
|
||||
bool operator==(const ConstraintViolation &lhs,
|
||||
const ConstraintViolation &rhs);
|
||||
bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs);
|
||||
|
||||
// TODO(tsabolcec): Support property sets. Unique constraints could be defined
|
||||
// for pairs (label, property set). However, current implementation supports
|
||||
// only pairs of label and a single property.
|
||||
class UniqueConstraints {
|
||||
private:
|
||||
struct Entry {
|
||||
PropertyValue value;
|
||||
std::vector<PropertyValue> values;
|
||||
const Vertex *vertex;
|
||||
uint64_t timestamp;
|
||||
|
||||
bool operator<(const Entry &rhs);
|
||||
bool operator==(const Entry &rhs);
|
||||
|
||||
bool operator<(const PropertyValue &rhs);
|
||||
bool operator==(const PropertyValue &rhs);
|
||||
bool operator<(const std::vector<PropertyValue> &rhs);
|
||||
bool operator==(const std::vector<PropertyValue> &rhs);
|
||||
|
||||
bool operator<(const PropertyValueArray &rhs);
|
||||
bool operator==(const PropertyValueArray &rhs);
|
||||
};
|
||||
|
||||
public:
|
||||
/// Status for creation of unique constraints.
|
||||
/// Note that this does not cover the case when the constraint is violated.
|
||||
enum class CreationStatus {
|
||||
SUCCESS,
|
||||
ALREADY_EXISTS,
|
||||
INVALID_PROPERTIES_SIZE,
|
||||
};
|
||||
|
||||
/// Indexes the given vertex for relevant labels and properties.
|
||||
/// This method should be called before committing and validating vertices
|
||||
/// against unique constraints.
|
||||
/// @throw std::bad_alloc
|
||||
void UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx);
|
||||
|
||||
/// Creates unique constraint on the given `label` and `property`.
|
||||
/// Creates unique constraint on the given `label` and a list of `properties`.
|
||||
/// Returns constraint violation if there are multiple vertices with the same
|
||||
/// label and property values. Returns false if constraint already existed and
|
||||
/// true on success.
|
||||
/// label and property values. Returns `CreationStatus::ALREADY_EXISTS` if
|
||||
/// constraint already existed, `CreationStatus::INVALID_PROPERTY_SIZE` if
|
||||
/// the given list of properties is empty or the list of properties exceeds
|
||||
/// the maximum allowed number of properties, and `CreationStatus::SUCCESS` on
|
||||
/// success.
|
||||
/// @throw std::bad_alloc
|
||||
utils::BasicResult<ConstraintViolation, bool> CreateConstraint(
|
||||
LabelId label, PropertyId property,
|
||||
utils::BasicResult<ConstraintViolation, CreationStatus> CreateConstraint(
|
||||
LabelId label, const std::set<PropertyId> &properties,
|
||||
utils::SkipList<Vertex>::Accessor vertices);
|
||||
|
||||
bool DropConstraint(LabelId label, PropertyId property) {
|
||||
return constraints_.erase({label, property}) > 0;
|
||||
bool DropConstraint(LabelId label, const std::set<PropertyId> &properties) {
|
||||
return constraints_.erase({label, properties}) > 0;
|
||||
}
|
||||
|
||||
bool ConstraintExists(LabelId label, PropertyId property) const {
|
||||
return constraints_.find({label, property}) != constraints_.end();
|
||||
bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) {
|
||||
return constraints_.find({label, properties}) != constraints_.end();
|
||||
}
|
||||
|
||||
/// Validates the given vertex against unique constraints before committing.
|
||||
@ -73,14 +107,16 @@ class UniqueConstraints {
|
||||
const Transaction &tx,
|
||||
uint64_t commit_timestamp) const;
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> ListConstraints() const;
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const;
|
||||
|
||||
/// GC method that removes outdated entries from constraints' storages.
|
||||
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
|
||||
|
||||
void Clear() { constraints_.clear(); }
|
||||
|
||||
private:
|
||||
std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> constraints_;
|
||||
std::map<std::pair<LabelId, std::set<PropertyId>>, utils::SkipList<Entry>>
|
||||
constraints_;
|
||||
};
|
||||
|
||||
struct Constraints {
|
||||
@ -89,16 +125,15 @@ struct Constraints {
|
||||
};
|
||||
|
||||
/// Adds a unique constraint to `constraints`. Returns true if the constraint
|
||||
/// was successfuly added, false if it already exists and an
|
||||
/// `ExistenceConstraintViolation` if there is an existing vertex violating the
|
||||
/// was successfully added, false if it already exists and a
|
||||
/// `ConstraintViolation` if there is an existing vertex violating the
|
||||
/// constraint.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
inline utils::BasicResult<ConstraintViolation, bool>
|
||||
CreateExistenceConstraint(Constraints *constraints, LabelId label,
|
||||
PropertyId property,
|
||||
utils::SkipList<Vertex>::Accessor vertices) {
|
||||
inline utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
|
||||
Constraints *constraints, LabelId label, PropertyId property,
|
||||
utils::SkipList<Vertex>::Accessor vertices) {
|
||||
if (utils::Contains(constraints->existence_constraints,
|
||||
std::make_pair(label, property))) {
|
||||
return false;
|
||||
@ -107,7 +142,7 @@ CreateExistenceConstraint(Constraints *constraints, LabelId label,
|
||||
if (!vertex.deleted && utils::Contains(vertex.labels, label) &&
|
||||
vertex.properties.find(property) == vertex.properties.end()) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label,
|
||||
property};
|
||||
std::set<PropertyId>{property}};
|
||||
}
|
||||
}
|
||||
constraints->existence_constraints.emplace_back(label, property);
|
||||
@ -129,8 +164,8 @@ inline bool DropExistenceConstraint(Constraints *constraints, LabelId label,
|
||||
}
|
||||
|
||||
/// Verifies that the given vertex satisfies all existence constraints. Returns
|
||||
/// nullopt if all checks pass, and `ExistenceConstraintViolation` describing
|
||||
/// the violated constraint otherwise.
|
||||
/// `std::nullopt` if all checks pass, and `ConstraintViolation` describing the
|
||||
/// violated constraint otherwise.
|
||||
[[nodiscard]] inline std::optional<ConstraintViolation>
|
||||
ValidateExistenceConstraints(const Vertex &vertex,
|
||||
const Constraints &constraints) {
|
||||
@ -138,7 +173,7 @@ ValidateExistenceConstraints(const Vertex &vertex,
|
||||
if (!vertex.deleted && utils::Contains(vertex.labels, label) &&
|
||||
vertex.properties.find(property) == vertex.properties.end()) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label,
|
||||
property};
|
||||
std::set<PropertyId>{property}};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
|
@ -17,7 +17,8 @@ auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it,
|
||||
Transaction *tx, View view, Indices *indices,
|
||||
Constraints *constraints, Config::Items config) {
|
||||
while (it != end) {
|
||||
*vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view);
|
||||
*vertex =
|
||||
VertexAccessor::Create(&*it, tx, indices, constraints, config, view);
|
||||
if (!*vertex) {
|
||||
++it;
|
||||
continue;
|
||||
@ -638,8 +639,7 @@ EdgeTypeId Storage::Accessor::NameToEdgeType(const std::string &name) {
|
||||
|
||||
void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; }
|
||||
|
||||
utils::BasicResult<ConstraintViolation, void>
|
||||
Storage::Accessor::Commit() {
|
||||
utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit() {
|
||||
CHECK(is_transaction_active_) << "The transaction is already terminated!";
|
||||
CHECK(!transaction_.must_abort) << "The transaction can't be committed!";
|
||||
|
||||
@ -700,8 +700,9 @@ Storage::Accessor::Commit() {
|
||||
|
||||
// No need to take any locks here because we modified this vertex and no
|
||||
// one else can touch it until we commit.
|
||||
unique_constraint_violation = storage_->constraints_.unique_constraints
|
||||
.Validate(*prev.vertex, transaction_, commit_timestamp);
|
||||
unique_constraint_violation =
|
||||
storage_->constraints_.unique_constraints.Validate(
|
||||
*prev.vertex, transaction_, commit_timestamp);
|
||||
if (unique_constraint_violation) {
|
||||
break;
|
||||
}
|
||||
@ -712,8 +713,8 @@ Storage::Accessor::Commit() {
|
||||
// that committed transactions are sorted by the commit timestamp in the
|
||||
// WAL files. We supply the new commit timestamp to the function so that
|
||||
// it knows what will be the final commit timestamp. The WAL must be
|
||||
// written before actually committing the transaction (before setting the
|
||||
// commit timestamp) so that no other transaction can see the
|
||||
// written before actually committing the transaction (before setting
|
||||
// the commit timestamp) so that no other transaction can see the
|
||||
// modifications before they are written to disk.
|
||||
storage_->durability_.AppendToWal(transaction_, commit_timestamp);
|
||||
|
||||
@ -727,9 +728,9 @@ Storage::Accessor::Commit() {
|
||||
CHECK(transaction_.commit_timestamp != nullptr)
|
||||
<< "Invalid database state!";
|
||||
transaction_.commit_timestamp->store(commit_timestamp,
|
||||
std::memory_order_release);
|
||||
// Release engine lock because we don't have to hold it anymore and
|
||||
// emplace back could take a long time.
|
||||
std::memory_order_release);
|
||||
// Release engine lock because we don't have to hold it anymore
|
||||
// and emplace back could take a long time.
|
||||
engine_guard.unlock();
|
||||
committed_transactions.emplace_back(std::move(transaction_));
|
||||
});
|
||||
@ -1056,20 +1057,20 @@ bool Storage::DropExistenceConstraint(LabelId label, PropertyId property) {
|
||||
return true;
|
||||
}
|
||||
|
||||
utils::BasicResult<ConstraintViolation, bool>
|
||||
Storage::CreateUniqueConstraint(LabelId label, PropertyId property) {
|
||||
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus>
|
||||
Storage::CreateUniqueConstraint(LabelId label,
|
||||
const std::set<PropertyId> &properties) {
|
||||
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
|
||||
auto ret = constraints_.unique_constraints.CreateConstraint(
|
||||
label, property, vertices_.access());
|
||||
if (ret.HasError() || !ret.GetValue()) return ret;
|
||||
// TODO(tsabolcec): Append action to the WAL.
|
||||
return true;
|
||||
return constraints_.unique_constraints.CreateConstraint(label, properties,
|
||||
vertices_.access());
|
||||
}
|
||||
|
||||
bool Storage::DropUniqueConstraint(LabelId label, PropertyId property) {
|
||||
bool Storage::DropUniqueConstraint(LabelId label,
|
||||
const std::set<PropertyId> &properties) {
|
||||
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
|
||||
// TODO(tsabolcec): Append action to the WAL.
|
||||
return constraints_.unique_constraints.DropConstraint(label, property);
|
||||
return constraints_.unique_constraints.DropConstraint(label, properties);
|
||||
}
|
||||
|
||||
ConstraintsInfo Storage::ListAllConstraints() const {
|
||||
|
@ -146,7 +146,7 @@ struct IndicesInfo {
|
||||
/// storage.
|
||||
struct ConstraintsInfo {
|
||||
std::vector<std::pair<LabelId, PropertyId>> existence;
|
||||
std::vector<std::pair<LabelId, PropertyId>> unique;
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
||||
};
|
||||
|
||||
/// Structure used to return information about the storage.
|
||||
@ -344,24 +344,29 @@ class Storage final {
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
utils::BasicResult<ConstraintViolation, bool>
|
||||
CreateExistenceConstraint(LabelId label, PropertyId property);
|
||||
utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
|
||||
LabelId label, PropertyId property);
|
||||
|
||||
/// Removes an existence constraint. Returns true if the constraint was
|
||||
/// removed, and false if it doesn't exist.
|
||||
bool DropExistenceConstraint(LabelId label, PropertyId property);
|
||||
|
||||
/// Creates a unique constraint. Returns true if the constraint was
|
||||
/// successfully added, false if it already exists and a `ConstraintViolation`
|
||||
/// if there are at least two vertices violating the constraint.
|
||||
/// Creates a unique constraint. In the case of two vertices violating the
|
||||
/// constraint, it returns `ConstraintViolation`. Otherwise returns a
|
||||
/// `UniqueConstraints::CreationStatus` enum with the following possibilities:
|
||||
/// * `SUCCESS` if the constraint was successfully created,
|
||||
/// * `ALREADY_EXISTS` if the constraint already existed, or
|
||||
/// * `INVALID_PROPERTIES_SIZE` if the property set is empty or exceeds
|
||||
/// the limit of maximum number of properties.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
utils::BasicResult<ConstraintViolation, bool>
|
||||
CreateUniqueConstraint(LabelId label, PropertyId property);
|
||||
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus>
|
||||
CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties);
|
||||
|
||||
/// Removes a unique constraint. Returns true if the constraint was removed,
|
||||
/// and false if it doesn't exist.
|
||||
bool DropUniqueConstraint(LabelId label, PropertyId property);
|
||||
bool DropUniqueConstraint(LabelId label,
|
||||
const std::set<PropertyId> &properties);
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const;
|
||||
|
||||
|
@ -11,12 +11,6 @@ using testing::UnorderedElementsAre;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define ASSERT_NO_ERROR(result) ASSERT_FALSE((result).HasError())
|
||||
|
||||
bool operator==(const ConstraintViolation &lhs,
|
||||
const ConstraintViolation &rhs) {
|
||||
return lhs.type == rhs.type && lhs.label == rhs.label &&
|
||||
lhs.property == rhs.property;
|
||||
}
|
||||
|
||||
class ConstraintsTest : public testing::Test {
|
||||
protected:
|
||||
ConstraintsTest()
|
||||
@ -82,7 +76,7 @@ TEST_F(ConstraintsTest, ExistenceConstraintsCreateFailure1) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
@ -110,7 +104,7 @@ TEST_F(ConstraintsTest, ExistenceConstraintsCreateFailure2) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
@ -141,7 +135,7 @@ TEST_F(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
|
||||
{
|
||||
@ -162,7 +156,7 @@ TEST_F(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
|
||||
{
|
||||
@ -191,37 +185,48 @@ TEST_F(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsCreateAndDrop) {
|
||||
EXPECT_EQ(storage.ListAllConstraints().unique.size(), 0);
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
EXPECT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
EXPECT_TRUE(res.HasValue());
|
||||
EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
EXPECT_THAT(storage.ListAllConstraints().unique,
|
||||
UnorderedElementsAre(std::make_pair(label1, prop1)));
|
||||
UnorderedElementsAre(
|
||||
std::make_pair(label1, std::set<PropertyId>{prop1})));
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
EXPECT_TRUE(res.HasValue() && !res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
EXPECT_TRUE(res.HasValue());
|
||||
EXPECT_EQ(res.GetValue(),
|
||||
UniqueConstraints::CreationStatus::ALREADY_EXISTS);
|
||||
}
|
||||
EXPECT_THAT(storage.ListAllConstraints().unique,
|
||||
UnorderedElementsAre(std::make_pair(label1, prop1)));
|
||||
UnorderedElementsAre(
|
||||
std::make_pair(label1, std::set<PropertyId>{prop1})));
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label2, prop1);
|
||||
EXPECT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label2, {prop1});
|
||||
EXPECT_TRUE(res.HasValue() &&
|
||||
res.GetValue() == UniqueConstraints::CreationStatus::SUCCESS);
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
EXPECT_THAT(storage.ListAllConstraints().unique,
|
||||
UnorderedElementsAre(std::make_pair(label1, prop1),
|
||||
std::make_pair(label2, prop1)));
|
||||
EXPECT_TRUE(storage.DropUniqueConstraint(label1, prop1));
|
||||
EXPECT_FALSE(storage.DropUniqueConstraint(label1, prop1));
|
||||
UnorderedElementsAre(
|
||||
std::make_pair(label1, std::set<PropertyId>{prop1}),
|
||||
std::make_pair(label2, std::set<PropertyId>{prop1})));
|
||||
EXPECT_TRUE(storage.DropUniqueConstraint(label1, {prop1}));
|
||||
EXPECT_FALSE(storage.DropUniqueConstraint(label1, {prop1}));
|
||||
EXPECT_THAT(storage.ListAllConstraints().unique,
|
||||
UnorderedElementsAre(std::make_pair(label2, prop1)));
|
||||
EXPECT_TRUE(storage.DropUniqueConstraint(label2, prop1));
|
||||
EXPECT_FALSE(storage.DropUniqueConstraint(label2, prop2));
|
||||
UnorderedElementsAre(
|
||||
std::make_pair(label2, std::set<PropertyId>{prop1})));
|
||||
EXPECT_TRUE(storage.DropUniqueConstraint(label2, {prop1}));
|
||||
EXPECT_FALSE(storage.DropUniqueConstraint(label2, {prop2}));
|
||||
EXPECT_EQ(storage.ListAllConstraints().unique.size(), 0);
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label2, prop1);
|
||||
EXPECT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label2, {prop1});
|
||||
EXPECT_TRUE(res.HasValue());
|
||||
EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
EXPECT_THAT(storage.ListAllConstraints().unique,
|
||||
UnorderedElementsAre(std::make_pair(label2, prop1)));
|
||||
UnorderedElementsAre(
|
||||
std::make_pair(label2, std::set<PropertyId>{prop1})));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
@ -237,11 +242,11 @@ TEST_F(ConstraintsTest, UniqueConstraintsCreateFailure1) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
|
||||
{
|
||||
@ -253,8 +258,9 @@ TEST_F(ConstraintsTest, UniqueConstraintsCreateFailure1) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
EXPECT_TRUE(!res.HasError() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,11 +277,11 @@ TEST_F(ConstraintsTest, UniqueConstraintsCreateFailure2) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
|
||||
{
|
||||
@ -289,18 +295,14 @@ TEST_F(ConstraintsTest, UniqueConstraintsCreateFailure2) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
EXPECT_TRUE(!res.HasError() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsNoViolation1) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
}
|
||||
|
||||
Gid gid1;
|
||||
Gid gid2;
|
||||
{
|
||||
@ -309,10 +311,27 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation1) {
|
||||
auto vertex2 = acc.CreateVertex();
|
||||
gid1 = vertex1.Gid();
|
||||
gid2 = vertex2.Gid();
|
||||
|
||||
ASSERT_NO_ERROR(vertex1.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex2.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex1 = acc.FindVertex(gid1, View::OLD);
|
||||
auto vertex2 = acc.FindVertex(gid2, View::OLD);
|
||||
|
||||
ASSERT_NO_ERROR(vertex1->SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(vertex2->AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex2->SetProperty(prop2, PropertyValue(3)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
@ -329,8 +348,9 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation1) {
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsNoViolation2) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
@ -358,8 +378,9 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation2) {
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsNoViolation3) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
@ -393,8 +414,9 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation3) {
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsNoViolation4) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
@ -428,8 +450,9 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation4) {
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit1) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
@ -444,15 +467,16 @@ TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit1) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit2) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
@ -486,15 +510,16 @@ TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit2) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit3) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() & res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
@ -520,6 +545,10 @@ TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit3) {
|
||||
auto vertex3 = acc2.FindVertex(gid1, View::OLD); // vertex3 == vertex1
|
||||
auto vertex4 = acc3.FindVertex(gid2, View::OLD); // vertex4 == vertex2
|
||||
|
||||
// Setting `prop2` shouldn't affect the remaining code.
|
||||
ASSERT_NO_ERROR(vertex3->SetProperty(prop2, PropertyValue(3)));
|
||||
ASSERT_NO_ERROR(vertex4->SetProperty(prop2, PropertyValue(3)));
|
||||
|
||||
ASSERT_NO_ERROR(vertex3->SetProperty(prop1, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(vertex4->SetProperty(prop1, PropertyValue(1)));
|
||||
|
||||
@ -527,20 +556,21 @@ TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit3) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
res = acc3.Commit();
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set<PropertyId>{prop1}}));
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsLabelAlteration) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, prop1);
|
||||
ASSERT_TRUE(res.HasValue() && res.GetValue());
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
Gid gid1;
|
||||
@ -574,7 +604,22 @@ TEST_F(ConstraintsTest, UniqueConstraintsLabelAlteration) {
|
||||
ASSERT_NO_ERROR(vertex1->AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->RemoveLabel(label1));
|
||||
|
||||
// Reapplying labels shouldn't affect the remaining code.
|
||||
ASSERT_NO_ERROR(vertex1->RemoveLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1->AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->RemoveLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1->RemoveLabel(label2));
|
||||
|
||||
// Commit the second transaction.
|
||||
ASSERT_NO_ERROR(acc2.Commit());
|
||||
|
||||
// Reapplying labels after first commit shouldn't affect the remaining code.
|
||||
ASSERT_NO_ERROR(vertex1->RemoveLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1->AddLabel(label1));
|
||||
|
||||
|
||||
// Commit the first transaction.
|
||||
ASSERT_NO_ERROR(acc1.Commit());
|
||||
}
|
||||
|
||||
@ -589,7 +634,7 @@ TEST_F(ConstraintsTest, UniqueConstraintsLabelAlteration) {
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set{prop1}}));
|
||||
}
|
||||
|
||||
{
|
||||
@ -613,12 +658,330 @@ TEST_F(ConstraintsTest, UniqueConstraintsLabelAlteration) {
|
||||
ASSERT_NO_ERROR(vertex1->AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->AddLabel(label1));
|
||||
|
||||
// Reapply everything.
|
||||
ASSERT_NO_ERROR(vertex1->RemoveLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->RemoveLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1->AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2->AddLabel(label1));
|
||||
|
||||
ASSERT_NO_ERROR(acc2.Commit());
|
||||
|
||||
auto res = acc1.Commit();
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
prop1}));
|
||||
std::set{prop1}}));
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsPropertySetSize) {
|
||||
{
|
||||
// This should fail since unique constraint cannot be created for an empty
|
||||
// property set.
|
||||
auto res = storage.CreateUniqueConstraint(label1, {});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(),
|
||||
UniqueConstraints::CreationStatus::INVALID_PROPERTIES_SIZE);
|
||||
}
|
||||
|
||||
// Create a set of 33 properties.
|
||||
std::set<PropertyId> properties;
|
||||
for (int i = 1; i <= 33; ++i) {
|
||||
properties.insert(storage.NameToProperty("prop" + std::to_string(i)));
|
||||
}
|
||||
|
||||
{
|
||||
// This should fail since list of properties exceeds the maximum number of
|
||||
// properties, which is 32.
|
||||
auto res = storage.CreateUniqueConstraint(label1, properties);
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(),
|
||||
UniqueConstraints::CreationStatus::INVALID_PROPERTIES_SIZE);
|
||||
}
|
||||
|
||||
// Remove one property from the set.
|
||||
properties.erase(properties.begin());
|
||||
|
||||
{
|
||||
// Creating a constraint for 32 properties should succeed.
|
||||
auto res = storage.CreateUniqueConstraint(label1, properties);
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
EXPECT_THAT(storage.ListAllConstraints().unique,
|
||||
UnorderedElementsAre(std::make_pair(label1, properties)));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsMultipleProperties) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
// An attempt to create an existing unique constraint.
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop2, prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(),
|
||||
UniqueConstraints::CreationStatus::ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
Gid gid1;
|
||||
Gid gid2;
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex1 = acc.CreateVertex();
|
||||
auto vertex2 = acc.CreateVertex();
|
||||
gid1 = vertex1.Gid();
|
||||
gid2 = vertex2.Gid();
|
||||
|
||||
ASSERT_NO_ERROR(vertex1.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex1.SetProperty(prop2, PropertyValue(2)));
|
||||
|
||||
ASSERT_NO_ERROR(vertex2.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex2.SetProperty(prop2, PropertyValue(3)));
|
||||
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
// Try to change property of the second vertex so it becomes the same as the
|
||||
// first vertex. It should fail.
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex2 = acc.FindVertex(gid2, View::OLD);
|
||||
ASSERT_NO_ERROR(vertex2->SetProperty(prop2, PropertyValue(2)));
|
||||
auto res = acc.Commit();
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
std::set<PropertyId>{prop1, prop2}}));
|
||||
}
|
||||
|
||||
// Then change the second property of both vertex to null. Property values of
|
||||
// both vertices should now be equal. However, this operation should succeed
|
||||
// since null value is treated as non-existing property.
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex1 = acc.FindVertex(gid1, View::OLD);
|
||||
auto vertex2 = acc.FindVertex(gid2, View::OLD);
|
||||
ASSERT_NO_ERROR(vertex1->SetProperty(prop2, PropertyValue()));
|
||||
ASSERT_NO_ERROR(vertex2->SetProperty(prop2, PropertyValue()));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsInsertAbortInsert) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
acc.Abort();
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
Gid gid;
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
gid = vertex.Gid();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.FindVertex(gid, View::OLD);
|
||||
ASSERT_NO_ERROR(acc.DeleteVertex(&*vertex));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
Gid gid;
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
gid = vertex.Gid();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.FindVertex(gid, View::OLD);
|
||||
ASSERT_NO_ERROR(acc.DeleteVertex(&*vertex));
|
||||
acc.Abort();
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(2)));
|
||||
|
||||
auto res = acc.Commit();
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
std::set{prop1, prop2}}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
Gid gid1;
|
||||
Gid gid2;
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex1 = acc.CreateVertex();
|
||||
auto vertex2 = acc.CreateVertex();
|
||||
gid1 = vertex1.Gid();
|
||||
gid2 = vertex2.Gid();
|
||||
|
||||
ASSERT_NO_ERROR(vertex1.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex2.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(2)));
|
||||
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto acc1 = storage.Access();
|
||||
auto acc2 = storage.Access();
|
||||
auto vertex1 = acc1.FindVertex(gid1, View::OLD);
|
||||
auto vertex2 = acc2.FindVertex(gid2, View::OLD);
|
||||
|
||||
ASSERT_NO_ERROR(acc2.DeleteVertex(&*vertex2));
|
||||
ASSERT_NO_ERROR(vertex1->SetProperty(prop1, PropertyValue(2)));
|
||||
|
||||
auto res = acc1.Commit();
|
||||
ASSERT_TRUE(res.HasError());
|
||||
EXPECT_EQ(res.GetError(),
|
||||
(ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1,
|
||||
std::set{prop1}}));
|
||||
|
||||
ASSERT_NO_ERROR(acc2.Commit());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsInsertDropInsert) {
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(storage.DropUniqueConstraint(label1, {prop2, prop1}));
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConstraintsTest, UniqueConstraintsComparePropertyValues) {
|
||||
// Purpose of this test is to make sure that extracted property values
|
||||
// are correctly compared.
|
||||
|
||||
{
|
||||
auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2});
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
|
||||
{
|
||||
auto acc = storage.Access();
|
||||
auto vertex = acc.CreateVertex();
|
||||
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(0)));
|
||||
ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(3)));
|
||||
ASSERT_NO_ERROR(acc.Commit());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user