Add unique constraints capability of serving as indices

This commit is contained in:
Josip Mrden
2023-10-11 19:14:08 +02:00
parent 06868c8be7
commit aed6a2de24
18 changed files with 472 additions and 15 deletions

View File

@@ -623,6 +623,14 @@ class DbAccessor final {
const std::set<storage::PropertyId> &properties) {
return accessor_->DropUniqueConstraint(label, properties);
}
bool UniqueConstraintExists(storage::LabelId label, storage::PropertyId prop) const {
return accessor_->UniqueConstraintExists(label, prop);
}
bool IndexedScanExists(storage::LabelId label, storage::PropertyId prop) const {
return LabelPropertyIndexExists(label, prop) || UniqueConstraintExists(label, prop);
}
};
class SubgraphDbAccessor final {

View File

@@ -43,7 +43,8 @@ class PostProcessor final {
template <class TPlanningContext>
std::unique_ptr<LogicalOperator> Rewrite(std::unique_ptr<LogicalOperator> plan, TPlanningContext *context) {
return RewriteWithIndexLookup(std::move(plan), context->symbol_table, context->ast_storage, context->db);
return RewriteWithIndexLookup(std::move(plan), context->symbol_table, context->ast_storage, context->db,
parameters_);
}
template <class TVertexCounts>

View File

@@ -25,6 +25,7 @@
#include <gflags/gflags.h>
#include "query/parameters.hpp"
#include "query/plan/operator.hpp"
#include "query/plan/preprocess.hpp"
@@ -41,8 +42,8 @@ Expression *RemoveAndExpressions(Expression *expr, const std::unordered_set<Expr
template <class TDbAccessor>
class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
public:
IndexLookupRewriter(SymbolTable *symbol_table, AstStorage *ast_storage, TDbAccessor *db)
: symbol_table_(symbol_table), ast_storage_(ast_storage), db_(db) {}
IndexLookupRewriter(SymbolTable *symbol_table, AstStorage *ast_storage, TDbAccessor *db, const Parameters &parameters)
: symbol_table_(symbol_table), ast_storage_(ast_storage), db_(db), parameters_(parameters) {}
using HierarchicalLogicalOperatorVisitor::PostVisit;
using HierarchicalLogicalOperatorVisitor::PreVisit;
@@ -481,6 +482,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
SymbolTable *symbol_table_;
AstStorage *ast_storage_;
TDbAccessor *db_;
const Parameters &parameters_;
// Collected filters, pending for examination if they can be used for advanced
// lookup operations (by index, node ID, ...).
Filters filters_;
@@ -509,7 +511,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
}
void RewriteBranch(std::shared_ptr<LogicalOperator> *branch) {
IndexLookupRewriter<TDbAccessor> rewriter(symbol_table_, ast_storage_, db_);
IndexLookupRewriter<TDbAccessor> rewriter(symbol_table_, ast_storage_, db_, parameters_);
(*branch)->Accept(rewriter);
if (rewriter.new_root_) {
*branch = rewriter.new_root_;
@@ -585,7 +587,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
continue;
}
const auto &property = filter.property_filter->property_;
if (!db_->LabelPropertyIndexExists(GetLabel(label), GetProperty(property))) {
if (!db_->LabelPropertyIndexExists(GetLabel(label), GetProperty(property)) &&
!db_->UniqueConstraintExists(GetLabel(label), GetProperty(property))) {
continue;
}
auto is_better_type = [&found](PropertyFilter::Type type) {
@@ -597,7 +600,10 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
return type_sort_ix < found_sort_ix;
};
int64_t vertex_count = db_->VerticesCount(GetLabel(label), GetProperty(property));
std::optional<storage::PropertyValue> maybe_property_value = ConstPropertyValue(filter.property_filter->value_);
int64_t vertex_count = maybe_property_value.has_value()
? db_->VerticesCount(GetLabel(label), GetProperty(property), *maybe_property_value)
: db_->VerticesCount(GetLabel(label), GetProperty(property));
std::optional<storage::LabelPropertyIndexStats> new_stats =
db_->GetIndexStats(GetLabel(label), GetProperty(property));
@@ -721,6 +727,17 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
filter_exprs_for_removal_.insert(removed_expressions.begin(), removed_expressions.end());
return std::make_unique<ScanAllByLabel>(input, node_symbol, GetLabel(label), view);
}
// If the expression is a constant property value, it is returned. Otherwise,
// return nullopt.
std::optional<storage::PropertyValue> ConstPropertyValue(const Expression *expression) {
if (auto *literal = utils::Downcast<const PrimitiveLiteral>(expression)) {
return literal->value_;
} else if (auto *param_lookup = utils::Downcast<const ParameterLookup>(expression)) {
return parameters_.AtTokenPosition(param_lookup->token_position_);
}
return std::nullopt;
}
};
} // namespace impl
@@ -728,8 +745,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
template <class TDbAccessor>
std::unique_ptr<LogicalOperator> RewriteWithIndexLookup(std::unique_ptr<LogicalOperator> root_op,
SymbolTable *symbol_table, AstStorage *ast_storage,
TDbAccessor *db) {
impl::IndexLookupRewriter<TDbAccessor> rewriter(symbol_table, ast_storage, db);
TDbAccessor *db, const Parameters &parameters) {
impl::IndexLookupRewriter<TDbAccessor> rewriter(symbol_table, ast_storage, db, parameters);
root_op->Accept(rewriter);
if (rewriter.new_root_) {
// This shouldn't happen in real use case, because IndexLookupRewriter

View File

@@ -87,6 +87,10 @@ class VertexCountCache {
return db_->GetIndexStats(label, property);
}
bool UniqueConstraintExists(storage::LabelId label, storage::PropertyId prop) const {
return db_->UniqueConstraintExists(label, prop);
}
private:
typedef std::pair<storage::LabelId, storage::PropertyId> LabelPropertyKey;

View File

@@ -16,6 +16,7 @@
#include "storage/v2/constraints/constraint_violation.hpp"
#include "storage/v2/transaction.hpp"
#include "storage/v2/vertex.hpp"
#include "storage/v2/vertex_accessor.hpp"
#include "utils/result.hpp"
namespace memgraph::storage {
@@ -58,6 +59,12 @@ class UniqueConstraints {
virtual std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const = 0;
virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const = 0;
virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const = 0;
virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const = 0;
virtual void Clear() = 0;
protected:

View File

@@ -2103,6 +2103,8 @@ UniqueConstraints::DeletionStatus DiskStorage::DiskAccessor::DropUniqueConstrain
return UniqueConstraints::DeletionStatus::SUCCESS;
}
bool DiskStorage::DiskAccessor::UniqueConstraintExists(LabelId label, PropertyId property) const { return false; }
Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
/// We acquire the transaction engine lock here because we access (and
/// modify) the transaction engine variables (`transaction_id` and

View File

@@ -228,6 +228,8 @@ class DiskStorage final : public Storage {
UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label,
const std::set<PropertyId> &properties) override;
bool UniqueConstraintExists(LabelId label, PropertyId property) const override;
private:
/// Flushes vertices and edges to the disk with the commit timestamp.
/// At the time of calling, the commit_timestamp_ must already exist.

View File

@@ -311,6 +311,17 @@ std::vector<std::pair<LabelId, std::set<PropertyId>>> DiskUniqueConstraints::Lis
return {constraints_.begin(), constraints_.end()};
}
uint64_t DiskUniqueConstraints::ApproximateVertexCount(LabelId label, PropertyId property) const { return 10; };
uint64_t DiskUniqueConstraints::ApproximateVertexCount(LabelId label, PropertyId property,
const PropertyValue &value) const {
return 10;
};
uint64_t DiskUniqueConstraints::ApproximateVertexCount(LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const {
return 10;
};
void DiskUniqueConstraints::Clear() {
constraints_.clear();

View File

@@ -54,6 +54,11 @@ class DiskUniqueConstraints : public UniqueConstraints {
std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const override;
uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const override;
uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override;
uint64_t ApproximateVertexCount(LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const override;
void Clear() override;
RocksDBStorage *GetRocksDBStorage() const;

View File

@@ -1106,12 +1106,28 @@ UniqueConstraints::DeletionStatus InMemoryStorage::InMemoryAccessor::DropUniqueC
return UniqueConstraints::DeletionStatus::SUCCESS;
}
bool InMemoryStorage::InMemoryAccessor::UniqueConstraintExists(LabelId label, PropertyId property) const {
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(in_memory->constraints_.unique_constraints_.get());
return mem_unique_constraints->ConstraintExists(label, {property});
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, View view) {
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get());
return VerticesIterable(mem_label_index->Vertices(label, view, storage_, &transaction_));
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, PropertyId property, View view) {
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) {
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(mem_storage->constraints_.unique_constraints_.get());
return VerticesIterable(
mem_unique_constraints->Vertices(label, property, std::nullopt, std::nullopt, view, storage_, &transaction_));
}
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get());
return VerticesIterable(
@@ -1120,6 +1136,15 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, Prop
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, PropertyId property,
const PropertyValue &value, View view) {
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) {
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(mem_storage->constraints_.unique_constraints_.get());
return VerticesIterable(mem_unique_constraints->Vertices(label, property, utils::MakeBoundInclusive(value),
utils::MakeBoundInclusive(value), view, storage_,
&transaction_));
}
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get());
return VerticesIterable(mem_label_property_index->Vertices(label, property, utils::MakeBoundInclusive(value),
@@ -1130,6 +1155,14 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, Prop
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(
LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) {
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) {
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(mem_storage->constraints_.unique_constraints_.get());
return VerticesIterable(
mem_unique_constraints->Vertices(label, property, lower_bound, upper_bound, view, storage_, &transaction_));
}
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get());
return VerticesIterable(

View File

@@ -115,16 +115,24 @@ class InMemoryStorage final : public Storage {
/// Return approximate number of vertices with the given label and property.
/// Note that this is always an over-estimate and never an under-estimate.
uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const override {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->ApproximateVertexCount(label,
property);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) {
return mem_storage->constraints_.unique_constraints_->ApproximateVertexCount(label, property);
}
return mem_storage->indices_.label_property_index_->ApproximateVertexCount(label, property);
}
/// Return approximate number of vertices with the given label and the given
/// value for the given property. Note that this is always an over-estimate
/// and never an under-estimate.
uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->ApproximateVertexCount(
label, property, value);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) {
return mem_storage->constraints_.unique_constraints_->ApproximateVertexCount(label, property, value);
}
return mem_storage->indices_.label_property_index_->ApproximateVertexCount(label, property, value);
}
/// Return approximate number of vertices with the given label and value for
@@ -133,8 +141,12 @@ class InMemoryStorage final : public Storage {
uint64_t ApproximateVertexCount(LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const override {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->ApproximateVertexCount(
label, property, lower, upper);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) {
return mem_storage->constraints_.unique_constraints_->ApproximateVertexCount(label, property, lower, upper);
}
return mem_storage->indices_.label_property_index_->ApproximateVertexCount(label, property, lower, upper);
}
template <typename TResult, typename TIndex, typename TIndexKey>
@@ -191,6 +203,8 @@ class InMemoryStorage final : public Storage {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->IndexExists(label, property);
}
bool UniqueConstraintExists(LabelId label, PropertyId property) const override;
IndicesInfo ListAllIndices() const override;
ConstraintsInfo ListAllConstraints() const override;

View File

@@ -10,6 +10,9 @@
// licenses/APL.txt.
#include "storage/v2/inmemory/unique_constraints.hpp"
#include "storage/v2/indices/indices_utils.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/bound.hpp"
namespace memgraph::storage {
@@ -407,6 +410,30 @@ std::vector<std::pair<LabelId, std::set<PropertyId>>> InMemoryUniqueConstraints:
return ret;
}
uint64_t InMemoryUniqueConstraints::ApproximateVertexCount(LabelId label, PropertyId property) const {
auto it = constraints_.find({label, {property}});
MG_ASSERT(it != constraints_.end(), "Unique constraints for label {} and property {} doesn't exist", label.AsUint(),
property.AsUint());
return 1;
};
uint64_t InMemoryUniqueConstraints::ApproximateVertexCount(LabelId label, PropertyId property,
const PropertyValue &value) const {
auto it = constraints_.find({label, {property}});
MG_ASSERT(it != constraints_.end(), "Unique constraints for label {} and property {} doesn't exist", label.AsUint(),
property.AsUint());
return 1;
};
uint64_t InMemoryUniqueConstraints::ApproximateVertexCount(
LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const {
auto it = constraints_.find({label, {property}});
MG_ASSERT(it != constraints_.end(), "Unique constraints for label {} and property {} doesn't exist", label.AsUint(),
property.AsUint());
return 10;
};
void InMemoryUniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
for (auto &[label_props, storage] : constraints_) {
auto acc = storage.access();
@@ -434,4 +461,200 @@ void InMemoryUniqueConstraints::Clear() {
constraints_by_label_.clear();
}
InMemoryUniqueConstraints::Iterable::Iterator::Iterator(Iterable *self,
utils::SkipList<Entry>::Iterator constraint_iterator)
: self_(self),
constraint_iterator_(constraint_iterator),
current_vertex_accessor_(nullptr, self_->storage_, nullptr),
current_vertex_(nullptr) {
AdvanceUntilValid();
}
InMemoryUniqueConstraints::Iterable::Iterator &InMemoryUniqueConstraints::Iterable::Iterator::operator++() {
++constraint_iterator_;
AdvanceUntilValid();
return *this;
}
void InMemoryUniqueConstraints::Iterable::Iterator::AdvanceUntilValid() {
for (; constraint_iterator_ != self_->constraint_accessor_.end(); ++constraint_iterator_) {
if (constraint_iterator_->vertex == current_vertex_) {
continue;
}
auto iterator_value = constraint_iterator_->values[0];
if (self_->lower_bound_) {
if (iterator_value < self_->lower_bound_->value()) {
continue;
}
if (!self_->lower_bound_->IsInclusive() && iterator_value == self_->lower_bound_->value()) {
continue;
}
}
if (self_->upper_bound_) {
if (self_->upper_bound_->value() < iterator_value) {
constraint_iterator_ = self_->constraint_accessor_.end();
break;
}
if (!self_->upper_bound_->IsInclusive() && iterator_value == self_->upper_bound_->value()) {
constraint_iterator_ = self_->constraint_accessor_.end();
break;
}
}
if (CurrentVersionHasLabelProperty(*constraint_iterator_->vertex, self_->label_, self_->property_, iterator_value,
self_->transaction_, self_->view_)) {
current_vertex_ = const_cast<Vertex *>(constraint_iterator_->vertex);
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->storage_, self_->transaction_);
break;
}
}
}
// These constants represent the smallest possible value of each type that is
// contained in a `PropertyValue`. Note that numbers (integers and doubles) are
// treated as the same "type" in `PropertyValue`.
const PropertyValue kSmallestBool = PropertyValue(false);
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
static_assert(-std::numeric_limits<double>::infinity() < std::numeric_limits<int64_t>::min());
const PropertyValue kSmallestNumber = PropertyValue(-std::numeric_limits<double>::infinity());
const PropertyValue kSmallestString = PropertyValue("");
const PropertyValue kSmallestList = PropertyValue(std::vector<PropertyValue>());
const PropertyValue kSmallestMap = PropertyValue(std::map<std::string, PropertyValue>());
const PropertyValue kSmallestTemporalData =
PropertyValue(TemporalData{static_cast<TemporalType>(0), std::numeric_limits<int64_t>::min()});
InMemoryUniqueConstraints::Iterable::Iterable(utils::SkipList<Entry>::Accessor constraint_accessor, LabelId label,
PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
Storage *storage, Transaction *transaction)
: constraint_accessor_(std::move(constraint_accessor)),
label_(label),
property_(property),
lower_bound_(lower_bound),
upper_bound_(upper_bound),
view_(view),
storage_(storage),
transaction_(transaction) {
// We have to fix the bounds that the user provided to us. If the user
// provided only one bound we should make sure that only values of that type
// are returned by the iterator. We ensure this by supplying either an
// inclusive lower bound of the same type, or an exclusive upper bound of the
// following type. If neither bound is set we yield all items in the index.
// First we statically verify that our assumptions about the `PropertyValue`
// type ordering holds.
static_assert(PropertyValue::Type::Bool < PropertyValue::Type::Int);
static_assert(PropertyValue::Type::Int < PropertyValue::Type::Double);
static_assert(PropertyValue::Type::Double < PropertyValue::Type::String);
static_assert(PropertyValue::Type::String < PropertyValue::Type::List);
static_assert(PropertyValue::Type::List < PropertyValue::Type::Map);
// Remove any bounds that are set to `Null` because that isn't a valid value.
if (lower_bound_ && lower_bound_->value().IsNull()) {
lower_bound_ = std::nullopt;
}
if (upper_bound_ && upper_bound_->value().IsNull()) {
upper_bound_ = std::nullopt;
}
// Check whether the bounds are of comparable types if both are supplied.
if (lower_bound_ && upper_bound_ &&
!PropertyValue::AreComparableTypes(lower_bound_->value().type(), upper_bound_->value().type())) {
bounds_valid_ = false;
return;
}
// Set missing bounds.
if (lower_bound_ && !upper_bound_) {
// Here we need to supply an upper bound. The upper bound is set to an
// exclusive lower bound of the following type.
switch (lower_bound_->value().type()) {
case PropertyValue::Type::Null:
// This shouldn't happen because of the nullopt-ing above.
LOG_FATAL("Invalid database state!");
break;
case PropertyValue::Type::Bool:
upper_bound_ = utils::MakeBoundExclusive(kSmallestNumber);
break;
case PropertyValue::Type::Int:
case PropertyValue::Type::Double:
// Both integers and doubles are treated as the same type in
// `PropertyValue` and they are interleaved when sorted.
upper_bound_ = utils::MakeBoundExclusive(kSmallestString);
break;
case PropertyValue::Type::String:
upper_bound_ = utils::MakeBoundExclusive(kSmallestList);
break;
case PropertyValue::Type::List:
upper_bound_ = utils::MakeBoundExclusive(kSmallestMap);
break;
case PropertyValue::Type::Map:
upper_bound_ = utils::MakeBoundExclusive(kSmallestTemporalData);
break;
case PropertyValue::Type::TemporalData:
// This is the last type in the order so we leave the upper bound empty.
break;
}
}
if (upper_bound_ && !lower_bound_) {
// Here we need to supply a lower bound. The lower bound is set to an
// inclusive lower bound of the current type.
switch (upper_bound_->value().type()) {
case PropertyValue::Type::Null:
// This shouldn't happen because of the nullopt-ing above.
LOG_FATAL("Invalid database state!");
break;
case PropertyValue::Type::Bool:
lower_bound_ = utils::MakeBoundInclusive(kSmallestBool);
break;
case PropertyValue::Type::Int:
case PropertyValue::Type::Double:
// Both integers and doubles are treated as the same type in
// `PropertyValue` and they are interleaved when sorted.
lower_bound_ = utils::MakeBoundInclusive(kSmallestNumber);
break;
case PropertyValue::Type::String:
lower_bound_ = utils::MakeBoundInclusive(kSmallestString);
break;
case PropertyValue::Type::List:
lower_bound_ = utils::MakeBoundInclusive(kSmallestList);
break;
case PropertyValue::Type::Map:
lower_bound_ = utils::MakeBoundInclusive(kSmallestMap);
break;
case PropertyValue::Type::TemporalData:
lower_bound_ = utils::MakeBoundInclusive(kSmallestTemporalData);
break;
}
}
}
InMemoryUniqueConstraints::Iterable::Iterator InMemoryUniqueConstraints::Iterable::begin() {
// If the bounds are set and don't have comparable types we don't yield any
// items from the index.
if (!bounds_valid_) return {this, constraint_accessor_.end()};
auto constraint_iterator = constraint_accessor_.begin();
if (lower_bound_) {
constraint_iterator = constraint_accessor_.find_equal_or_greater(std::vector<PropertyValue>{lower_bound_->value()});
}
return {this, constraint_iterator};
}
InMemoryUniqueConstraints::Iterable::Iterator InMemoryUniqueConstraints::Iterable::end() {
return {this, constraint_accessor_.end()};
}
InMemoryUniqueConstraints::Iterable InMemoryUniqueConstraints::Vertices(
LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Storage *storage,
Transaction *transaction) {
auto it = constraints_.find({label, {property}});
MG_ASSERT(it != constraints_.end(), "Constraint for label {} and property {} doesn't exist", label.AsUint(),
property.AsUint());
return {it->second.access(), label, property, lower_bound, upper_bound, view, storage, transaction};
}
} // namespace memgraph::storage

View File

@@ -89,11 +89,63 @@ class InMemoryUniqueConstraints : public UniqueConstraints {
std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const override;
class Iterable {
public:
Iterable(utils::SkipList<Entry>::Accessor constraint_accessor, LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Storage *storage,
Transaction *transaction);
class Iterator {
public:
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator constraint_iterator);
VertexAccessor const &operator*() const { return current_vertex_accessor_; }
bool operator==(const Iterator &other) const { return constraint_iterator_ == other.constraint_iterator_; }
bool operator!=(const Iterator &other) const { return constraint_iterator_ != other.constraint_iterator_; }
Iterator &operator++();
private:
void AdvanceUntilValid();
Iterable *self_;
utils::SkipList<Entry>::Iterator constraint_iterator_;
VertexAccessor current_vertex_accessor_;
Vertex *current_vertex_;
};
Iterator begin();
Iterator end();
private:
utils::SkipList<Entry>::Accessor constraint_accessor_;
LabelId label_;
PropertyId property_;
std::optional<utils::Bound<PropertyValue>> lower_bound_;
std::optional<utils::Bound<PropertyValue>> upper_bound_;
bool bounds_valid_{true};
View view_;
Storage *storage_;
Transaction *transaction_;
};
uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const override;
uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override;
uint64_t ApproximateVertexCount(LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const override;
/// GC method that removes outdated entries from constraints' storages.
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
void Clear() override;
Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Storage *storage,
Transaction *transaction);
private:
std::map<std::pair<LabelId, std::set<PropertyId>>, utils::SkipList<Entry>> constraints_;
std::map<LabelId, std::map<std::set<PropertyId>, utils::SkipList<Entry> *>> constraints_by_label_;

View File

@@ -228,6 +228,8 @@ class Storage {
virtual UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label,
const std::set<PropertyId> &properties) = 0;
virtual bool UniqueConstraintExists(LabelId label, PropertyId property) const = 0;
protected:
Storage *storage_;
std::shared_lock<utils::ResourceLock> storage_guard_;

View File

@@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "storage/v2/vertices_iterable.hpp"
#include "storage/v2/inmemory/unique_constraints.hpp"
namespace memgraph::storage {
@@ -26,6 +27,11 @@ VerticesIterable::VerticesIterable(InMemoryLabelPropertyIndex::Iterable vertices
new (&in_memory_vertices_by_label_property_) InMemoryLabelPropertyIndex::Iterable(std::move(vertices));
}
VerticesIterable::VerticesIterable(InMemoryUniqueConstraints::Iterable vertices)
: type_(Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY) {
new (&in_memory_vertices_by_unique_constraint_) InMemoryUniqueConstraints::Iterable(std::move(vertices));
}
VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(other.type_) {
switch (other.type_) {
case Type::ALL:
@@ -38,6 +44,10 @@ VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(ot
new (&in_memory_vertices_by_label_property_)
InMemoryLabelPropertyIndex::Iterable(std::move(other.in_memory_vertices_by_label_property_));
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
new (&in_memory_vertices_by_unique_constraint_)
InMemoryUniqueConstraints::Iterable(std::move(other.in_memory_vertices_by_unique_constraint_));
break;
}
}
@@ -52,6 +62,9 @@ VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
in_memory_vertices_by_label_property_.InMemoryLabelPropertyIndex::Iterable::~Iterable();
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
in_memory_vertices_by_unique_constraint_.InMemoryUniqueConstraints::Iterable::~Iterable();
break;
}
type_ = other.type_;
switch (other.type_) {
@@ -65,6 +78,10 @@ VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept
new (&in_memory_vertices_by_label_property_)
InMemoryLabelPropertyIndex::Iterable(std::move(other.in_memory_vertices_by_label_property_));
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
new (&in_memory_vertices_by_unique_constraint_)
InMemoryUniqueConstraints::Iterable(std::move(other.in_memory_vertices_by_unique_constraint_));
break;
}
return *this;
}
@@ -80,6 +97,9 @@ VerticesIterable::~VerticesIterable() {
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
in_memory_vertices_by_label_property_.InMemoryLabelPropertyIndex::Iterable::~Iterable();
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
in_memory_vertices_by_unique_constraint_.InMemoryUniqueConstraints::Iterable::~Iterable();
break;
}
}
@@ -91,6 +111,8 @@ VerticesIterable::Iterator VerticesIterable::begin() {
return Iterator(in_memory_vertices_by_label_.begin());
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
return Iterator(in_memory_vertices_by_label_property_.begin());
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
return Iterator(in_memory_vertices_by_unique_constraint_.begin());
}
}
@@ -102,6 +124,8 @@ VerticesIterable::Iterator VerticesIterable::end() {
return Iterator(in_memory_vertices_by_label_.end());
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
return Iterator(in_memory_vertices_by_label_property_.end());
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
return Iterator(in_memory_vertices_by_unique_constraint_.end());
}
}
@@ -121,6 +145,12 @@ VerticesIterable::Iterator::Iterator(InMemoryLabelPropertyIndex::Iterable::Itera
new (&in_memory_by_label_property_it_) InMemoryLabelPropertyIndex::Iterable::Iterator(std::move(it));
}
VerticesIterable::Iterator::Iterator(InMemoryUniqueConstraints::Iterable::Iterator it)
: type_(Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY) {
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
new (&in_memory_by_unique_constraint_it_) InMemoryUniqueConstraints::Iterable::Iterator(std::move(it));
}
VerticesIterable::Iterator::Iterator(const VerticesIterable::Iterator &other) : type_(other.type_) {
switch (other.type_) {
case Type::ALL:
@@ -133,6 +163,10 @@ VerticesIterable::Iterator::Iterator(const VerticesIterable::Iterator &other) :
new (&in_memory_by_label_property_it_)
InMemoryLabelPropertyIndex::Iterable::Iterator(other.in_memory_by_label_property_it_);
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
new (&in_memory_by_unique_constraint_it_)
InMemoryUniqueConstraints::Iterable::Iterator(other.in_memory_by_unique_constraint_it_);
break;
}
}
@@ -151,6 +185,10 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const Vertices
new (&in_memory_by_label_property_it_)
InMemoryLabelPropertyIndex::Iterable::Iterator(other.in_memory_by_label_property_it_);
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
new (&in_memory_by_unique_constraint_it_)
InMemoryUniqueConstraints::Iterable::Iterator(other.in_memory_by_unique_constraint_it_);
break;
}
return *this;
}
@@ -170,6 +208,11 @@ VerticesIterable::Iterator::Iterator(VerticesIterable::Iterator &&other) noexcep
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
InMemoryLabelPropertyIndex::Iterable::Iterator(std::move(other.in_memory_by_label_property_it_));
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
new (&in_memory_by_unique_constraint_it_)
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
InMemoryUniqueConstraints::Iterable::Iterator(std::move(other.in_memory_by_unique_constraint_it_));
break;
}
}
@@ -190,6 +233,11 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(VerticesIterab
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
InMemoryLabelPropertyIndex::Iterable::Iterator(std::move(other.in_memory_by_label_property_it_));
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
new (&in_memory_by_unique_constraint_it_)
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
InMemoryUniqueConstraints::Iterable::Iterator(std::move(other.in_memory_by_unique_constraint_it_));
break;
}
return *this;
}
@@ -207,6 +255,9 @@ void VerticesIterable::Iterator::Destroy() noexcept {
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
in_memory_by_label_property_it_.InMemoryLabelPropertyIndex::Iterable::Iterator::~Iterator();
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
in_memory_by_unique_constraint_it_.InMemoryUniqueConstraints::Iterable::Iterator::~Iterator();
break;
}
}
@@ -218,6 +269,8 @@ VertexAccessor const &VerticesIterable::Iterator::operator*() const {
return *in_memory_by_label_it_;
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
return *in_memory_by_label_property_it_;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
return *in_memory_by_unique_constraint_it_;
}
}
@@ -232,6 +285,9 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() {
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
++in_memory_by_label_property_it_;
break;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
++in_memory_by_unique_constraint_it_;
break;
}
return *this;
}
@@ -244,6 +300,8 @@ bool VerticesIterable::Iterator::operator==(const Iterator &other) const {
return in_memory_by_label_it_ == other.in_memory_by_label_it_;
case Type::BY_LABEL_PROPERTY_IN_MEMORY:
return in_memory_by_label_property_it_ == other.in_memory_by_label_property_it_;
case Type::BY_UNIQUE_CONSTRAINT_IN_MEMORY:
return in_memory_by_unique_constraint_it_ == other.in_memory_by_unique_constraint_it_;
}
}

View File

@@ -14,23 +14,26 @@
#include "storage/v2/all_vertices_iterable.hpp"
#include "storage/v2/inmemory/label_index.hpp"
#include "storage/v2/inmemory/label_property_index.hpp"
#include "storage/v2/inmemory/unique_constraints.hpp"
namespace memgraph::storage {
class VerticesIterable final {
enum class Type { ALL, BY_LABEL_IN_MEMORY, BY_LABEL_PROPERTY_IN_MEMORY };
enum class Type { ALL, BY_LABEL_IN_MEMORY, BY_LABEL_PROPERTY_IN_MEMORY, BY_UNIQUE_CONSTRAINT_IN_MEMORY };
Type type_;
union {
AllVerticesIterable all_vertices_;
InMemoryLabelIndex::Iterable in_memory_vertices_by_label_;
InMemoryLabelPropertyIndex::Iterable in_memory_vertices_by_label_property_;
InMemoryUniqueConstraints::Iterable in_memory_vertices_by_unique_constraint_;
};
public:
explicit VerticesIterable(AllVerticesIterable);
explicit VerticesIterable(InMemoryLabelIndex::Iterable);
explicit VerticesIterable(InMemoryLabelPropertyIndex::Iterable);
explicit VerticesIterable(InMemoryUniqueConstraints::Iterable);
VerticesIterable(const VerticesIterable &) = delete;
VerticesIterable &operator=(const VerticesIterable &) = delete;
@@ -46,6 +49,7 @@ class VerticesIterable final {
AllVerticesIterable::Iterator all_it_;
InMemoryLabelIndex::Iterable::Iterator in_memory_by_label_it_;
InMemoryLabelPropertyIndex::Iterable::Iterator in_memory_by_label_property_it_;
InMemoryUniqueConstraints::Iterable::Iterator in_memory_by_unique_constraint_it_;
};
void Destroy() noexcept;
@@ -54,6 +58,7 @@ class VerticesIterable final {
explicit Iterator(AllVerticesIterable::Iterator);
explicit Iterator(InMemoryLabelIndex::Iterable::Iterator);
explicit Iterator(InMemoryLabelPropertyIndex::Iterable::Iterator);
explicit Iterator(InMemoryUniqueConstraints::Iterable::Iterator);
Iterator(const Iterator &);
Iterator &operator=(const Iterator &);

View File

@@ -213,6 +213,10 @@ class InteractiveDbAccessor {
return label_property_index_.at(key);
}
bool UniqueConstraintExists(memgraph::storage::LabelId label_id, memgraph::storage::PropertyId property_id) {
return true;
}
std::optional<memgraph::storage::LabelIndexStats> GetIndexStats(const memgraph::storage::LabelId label) const {
return dba_->GetIndexStats(label);
}

View File

@@ -487,10 +487,19 @@ class FakeDbAccessor {
return 0;
}
int64_t VerticesCount(memgraph::storage::LabelId label, memgraph::storage::PropertyId property,
const memgraph::storage::PropertyValue &value) const {
return 0;
}
bool LabelIndexExists(memgraph::storage::LabelId label) const {
return label_index_.find(label) != label_index_.end();
}
bool UniqueConstraintExists(memgraph::storage::LabelId label, memgraph::storage::PropertyId property) const {
return false;
}
bool LabelPropertyIndexExists(memgraph::storage::LabelId label, memgraph::storage::PropertyId property) const {
for (auto &index : label_property_index_) {
if (std::get<0>(index) == label && std::get<1>(index) == property) {