diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index 1798e36e5..f4306aa3b 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -623,6 +623,14 @@ class DbAccessor final { const std::set &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 { diff --git a/src/query/plan/planner.hpp b/src/query/plan/planner.hpp index 443680c37..cefaf3358 100644 --- a/src/query/plan/planner.hpp +++ b/src/query/plan/planner.hpp @@ -43,7 +43,8 @@ class PostProcessor final { template std::unique_ptr Rewrite(std::unique_ptr 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 diff --git a/src/query/plan/rewrite/index_lookup.hpp b/src/query/plan/rewrite/index_lookup.hpp index e10d14b82..06d3236e2 100644 --- a/src/query/plan/rewrite/index_lookup.hpp +++ b/src/query/plan/rewrite/index_lookup.hpp @@ -25,6 +25,7 @@ #include +#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 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 ¶meters) + : 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 ¶meters_; // 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 *branch) { - IndexLookupRewriter rewriter(symbol_table_, ast_storage_, db_); + IndexLookupRewriter 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 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 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(input, node_symbol, GetLabel(label), view); } + + // If the expression is a constant property value, it is returned. Otherwise, + // return nullopt. + std::optional ConstPropertyValue(const Expression *expression) { + if (auto *literal = utils::Downcast(expression)) { + return literal->value_; + } else if (auto *param_lookup = utils::Downcast(expression)) { + return parameters_.AtTokenPosition(param_lookup->token_position_); + } + return std::nullopt; + } }; } // namespace impl @@ -728,8 +745,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { template std::unique_ptr RewriteWithIndexLookup(std::unique_ptr root_op, SymbolTable *symbol_table, AstStorage *ast_storage, - TDbAccessor *db) { - impl::IndexLookupRewriter rewriter(symbol_table, ast_storage, db); + TDbAccessor *db, const Parameters ¶meters) { + impl::IndexLookupRewriter 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 diff --git a/src/query/plan/vertex_count_cache.hpp b/src/query/plan/vertex_count_cache.hpp index ff19ee95a..3a295349f 100644 --- a/src/query/plan/vertex_count_cache.hpp +++ b/src/query/plan/vertex_count_cache.hpp @@ -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 LabelPropertyKey; diff --git a/src/storage/v2/constraints/unique_constraints.hpp b/src/storage/v2/constraints/unique_constraints.hpp index b9ec04bfc..1d0971b41 100644 --- a/src/storage/v2/constraints/unique_constraints.hpp +++ b/src/storage/v2/constraints/unique_constraints.hpp @@ -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>> 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> &lower, + const std::optional> &upper) const = 0; + virtual void Clear() = 0; protected: diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp index 77832f0f6..c151d3ea9 100644 --- a/src/storage/v2/disk/storage.cpp +++ b/src/storage/v2/disk/storage.cpp @@ -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 diff --git a/src/storage/v2/disk/storage.hpp b/src/storage/v2/disk/storage.hpp index dc89a9641..415d1a93b 100644 --- a/src/storage/v2/disk/storage.hpp +++ b/src/storage/v2/disk/storage.hpp @@ -228,6 +228,8 @@ class DiskStorage final : public Storage { UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set &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. diff --git a/src/storage/v2/disk/unique_constraints.cpp b/src/storage/v2/disk/unique_constraints.cpp index 9519e4357..ddebd126c 100644 --- a/src/storage/v2/disk/unique_constraints.cpp +++ b/src/storage/v2/disk/unique_constraints.cpp @@ -311,6 +311,17 @@ std::vector>> 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> &lower, + const std::optional> &upper) const { + return 10; +}; + void DiskUniqueConstraints::Clear() { constraints_.clear(); diff --git a/src/storage/v2/disk/unique_constraints.hpp b/src/storage/v2/disk/unique_constraints.hpp index 0cc5a9586..dec45e4ac 100644 --- a/src/storage/v2/disk/unique_constraints.hpp +++ b/src/storage/v2/disk/unique_constraints.hpp @@ -54,6 +54,11 @@ class DiskUniqueConstraints : public UniqueConstraints { std::vector>> 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> &lower, + const std::optional> &upper) const override; void Clear() override; RocksDBStorage *GetRocksDBStorage() const; diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp index 29803b360..1f7bb7ea5 100644 --- a/src/storage/v2/inmemory/storage.cpp +++ b/src/storage/v2/inmemory/storage.cpp @@ -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(storage_); + auto *mem_unique_constraints = + static_cast(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(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(storage_); + + if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) { + auto *mem_unique_constraints = + static_cast(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(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(storage_); + if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) { + auto *mem_unique_constraints = + static_cast(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(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> &lower_bound, const std::optional> &upper_bound, View view) { + auto *mem_storage = static_cast(storage_); + if (mem_storage->constraints_.unique_constraints_->ConstraintExists(label, {property})) { + auto *mem_unique_constraints = + static_cast(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(storage_->indices_.label_property_index_.get()); return VerticesIterable( diff --git a/src/storage/v2/inmemory/storage.hpp b/src/storage/v2/inmemory/storage.hpp index 1ac4c9645..ee81a1e96 100644 --- a/src/storage/v2/inmemory/storage.hpp +++ b/src/storage/v2/inmemory/storage.hpp @@ -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(storage_)->indices_.label_property_index_->ApproximateVertexCount(label, - property); + auto *mem_storage = static_cast(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(storage_)->indices_.label_property_index_->ApproximateVertexCount( - label, property, value); + auto *mem_storage = static_cast(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> &lower, const std::optional> &upper) const override { - return static_cast(storage_)->indices_.label_property_index_->ApproximateVertexCount( - label, property, lower, upper); + auto *mem_storage = static_cast(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 @@ -191,6 +203,8 @@ class InMemoryStorage final : public Storage { return static_cast(storage_)->indices_.label_property_index_->IndexExists(label, property); } + bool UniqueConstraintExists(LabelId label, PropertyId property) const override; + IndicesInfo ListAllIndices() const override; ConstraintsInfo ListAllConstraints() const override; diff --git a/src/storage/v2/inmemory/unique_constraints.cpp b/src/storage/v2/inmemory/unique_constraints.cpp index 78929bc38..9a6dbb132 100644 --- a/src/storage/v2/inmemory/unique_constraints.cpp +++ b/src/storage/v2/inmemory/unique_constraints.cpp @@ -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>> 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> &lower, + const std::optional> &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::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(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::infinity() < std::numeric_limits::min()); +const PropertyValue kSmallestNumber = PropertyValue(-std::numeric_limits::infinity()); +const PropertyValue kSmallestString = PropertyValue(""); +const PropertyValue kSmallestList = PropertyValue(std::vector()); +const PropertyValue kSmallestMap = PropertyValue(std::map()); +const PropertyValue kSmallestTemporalData = + PropertyValue(TemporalData{static_cast(0), std::numeric_limits::min()}); + +InMemoryUniqueConstraints::Iterable::Iterable(utils::SkipList::Accessor constraint_accessor, LabelId label, + PropertyId property, + const std::optional> &lower_bound, + const std::optional> &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{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> &lower_bound, + const std::optional> &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 diff --git a/src/storage/v2/inmemory/unique_constraints.hpp b/src/storage/v2/inmemory/unique_constraints.hpp index 64565056b..166c26d58 100644 --- a/src/storage/v2/inmemory/unique_constraints.hpp +++ b/src/storage/v2/inmemory/unique_constraints.hpp @@ -89,11 +89,63 @@ class InMemoryUniqueConstraints : public UniqueConstraints { std::vector>> ListConstraints() const override; + class Iterable { + public: + Iterable(utils::SkipList::Accessor constraint_accessor, LabelId label, PropertyId property, + const std::optional> &lower_bound, + const std::optional> &upper_bound, View view, Storage *storage, + Transaction *transaction); + + class Iterator { + public: + Iterator(Iterable *self, utils::SkipList::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::Iterator constraint_iterator_; + VertexAccessor current_vertex_accessor_; + Vertex *current_vertex_; + }; + + Iterator begin(); + Iterator end(); + + private: + utils::SkipList::Accessor constraint_accessor_; + LabelId label_; + PropertyId property_; + std::optional> lower_bound_; + std::optional> 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> &lower, + const std::optional> &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> &lower_bound, + const std::optional> &upper_bound, View view, Storage *storage, + Transaction *transaction); + private: std::map>, utils::SkipList> constraints_; std::map, utils::SkipList *>> constraints_by_label_; diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 1a9d7567a..cfcd1aae9 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -228,6 +228,8 @@ class Storage { virtual UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set &properties) = 0; + virtual bool UniqueConstraintExists(LabelId label, PropertyId property) const = 0; + protected: Storage *storage_; std::shared_lock storage_guard_; diff --git a/src/storage/v2/vertices_iterable.cpp b/src/storage/v2/vertices_iterable.cpp index f6ff46da6..ab8fe4b0e 100644 --- a/src/storage/v2/vertices_iterable.cpp +++ b/src/storage/v2/vertices_iterable.cpp @@ -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_; } } diff --git a/src/storage/v2/vertices_iterable.hpp b/src/storage/v2/vertices_iterable.hpp index e057e8a38..31acb34ba 100644 --- a/src/storage/v2/vertices_iterable.hpp +++ b/src/storage/v2/vertices_iterable.hpp @@ -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 &); diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp index d5da0ba2b..6c0ae75d4 100644 --- a/tests/manual/interactive_planning.cpp +++ b/tests/manual/interactive_planning.cpp @@ -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 GetIndexStats(const memgraph::storage::LabelId label) const { return dba_->GetIndexStats(label); } diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index 0a8b4d3ab..a53931260 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -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) {