316 lines
11 KiB
C++
316 lines
11 KiB
C++
// Copyright 2023 Memgraph Ltd.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
// License, and you may not use this file except in compliance with the Business Source License.
|
|
//
|
|
// As of the Change Date specified in that file, in accordance with
|
|
// the Business Source License, use of this software will be governed
|
|
// by the Apache License, Version 2.0, included in the file
|
|
// licenses/APL.txt.
|
|
|
|
#pragma once
|
|
|
|
#include <optional>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "storage/v2/config.hpp"
|
|
#include "storage/v2/property_value.hpp"
|
|
#include "storage/v2/transaction.hpp"
|
|
#include "storage/v2/vertex_accessor.hpp"
|
|
#include "utils/bound.hpp"
|
|
#include "utils/logging.hpp"
|
|
#include "utils/skip_list.hpp"
|
|
|
|
namespace memgraph::storage {
|
|
|
|
struct Indices;
|
|
struct Constraints;
|
|
|
|
using ParalellizedIndexCreationInfo =
|
|
std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>;
|
|
|
|
struct LabelIndexStats {
|
|
int64_t count;
|
|
double avg_degree;
|
|
};
|
|
|
|
class LabelIndex {
|
|
private:
|
|
struct Entry {
|
|
Vertex *vertex;
|
|
uint64_t timestamp;
|
|
|
|
bool operator<(const Entry &rhs) {
|
|
return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp);
|
|
}
|
|
bool operator==(const Entry &rhs) { return vertex == rhs.vertex && timestamp == rhs.timestamp; }
|
|
};
|
|
|
|
struct LabelStorage {
|
|
LabelId label;
|
|
utils::SkipList<Entry> vertices;
|
|
|
|
bool operator<(const LabelStorage &rhs) { return label < rhs.label; }
|
|
bool operator<(LabelId rhs) { return label < rhs; }
|
|
bool operator==(const LabelStorage &rhs) { return label == rhs.label; }
|
|
bool operator==(LabelId rhs) { return label == rhs; }
|
|
};
|
|
|
|
public:
|
|
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config)
|
|
: indices_(indices), constraints_(constraints), config_(config) {}
|
|
|
|
/// @throw std::bad_alloc
|
|
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
|
|
|
/// @throw std::bad_alloc
|
|
bool CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices,
|
|
const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info = std::nullopt);
|
|
|
|
/// Returns false if there was no index to drop
|
|
bool DropIndex(LabelId label) { return index_.erase(label) > 0; }
|
|
|
|
bool IndexExists(LabelId label) const { return index_.find(label) != index_.end(); }
|
|
|
|
std::vector<LabelId> ListIndices() const;
|
|
|
|
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
|
|
|
|
class Iterable {
|
|
public:
|
|
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction,
|
|
Indices *indices, Constraints *constraints, Config::Items config);
|
|
|
|
class Iterator {
|
|
public:
|
|
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
|
|
|
|
VertexAccessor operator*() const { return current_vertex_accessor_; }
|
|
|
|
bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; }
|
|
bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; }
|
|
|
|
Iterator &operator++();
|
|
|
|
private:
|
|
void AdvanceUntilValid();
|
|
|
|
Iterable *self_;
|
|
utils::SkipList<Entry>::Iterator index_iterator_;
|
|
VertexAccessor current_vertex_accessor_;
|
|
Vertex *current_vertex_;
|
|
};
|
|
|
|
Iterator begin() { return Iterator(this, index_accessor_.begin()); }
|
|
Iterator end() { return Iterator(this, index_accessor_.end()); }
|
|
|
|
private:
|
|
utils::SkipList<Entry>::Accessor index_accessor_;
|
|
LabelId label_;
|
|
View view_;
|
|
Transaction *transaction_;
|
|
Indices *indices_;
|
|
Constraints *constraints_;
|
|
Config::Items config_;
|
|
};
|
|
|
|
/// Returns an self with vertices visible from the given transaction.
|
|
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
|
|
auto it = index_.find(label);
|
|
MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint());
|
|
return Iterable(it->second.access(), label, view, transaction, indices_, constraints_, config_);
|
|
}
|
|
|
|
int64_t ApproximateVertexCount(LabelId label) {
|
|
auto it = index_.find(label);
|
|
MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint());
|
|
return it->second.size();
|
|
}
|
|
|
|
void SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats);
|
|
|
|
std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const;
|
|
|
|
std::vector<LabelId> ClearIndexStats();
|
|
|
|
void Clear() { index_.clear(); }
|
|
|
|
void RunGC();
|
|
|
|
private:
|
|
std::map<LabelId, utils::SkipList<Entry>> index_;
|
|
std::map<LabelId, storage::LabelIndexStats> stats_;
|
|
Indices *indices_;
|
|
Constraints *constraints_;
|
|
Config::Items config_;
|
|
};
|
|
|
|
struct LabelPropertyIndexStats {
|
|
int64_t count, distinct_values_count;
|
|
double statistic, avg_group_size, avg_degree;
|
|
};
|
|
|
|
class LabelPropertyIndex {
|
|
private:
|
|
struct Entry {
|
|
PropertyValue value;
|
|
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);
|
|
};
|
|
|
|
public:
|
|
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config)
|
|
: indices_(indices), constraints_(constraints), config_(config) {}
|
|
|
|
/// @throw std::bad_alloc
|
|
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
|
|
|
/// @throw std::bad_alloc
|
|
void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, const Transaction &tx);
|
|
|
|
/// @throw std::bad_alloc
|
|
bool CreateIndex(LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices,
|
|
const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info = std::nullopt);
|
|
|
|
bool DropIndex(LabelId label, PropertyId property) { return index_.erase({label, property}) > 0; }
|
|
|
|
bool IndexExists(LabelId label, PropertyId property) const { return index_.find({label, property}) != index_.end(); }
|
|
|
|
std::vector<std::pair<LabelId, PropertyId>> ListIndices() const;
|
|
|
|
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
|
|
|
|
class Iterable {
|
|
public:
|
|
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property,
|
|
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
|
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
|
|
Indices *indices, Constraints *constraints, Config::Items config);
|
|
|
|
class Iterator {
|
|
public:
|
|
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
|
|
|
|
VertexAccessor operator*() const { return current_vertex_accessor_; }
|
|
|
|
bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; }
|
|
bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; }
|
|
|
|
Iterator &operator++();
|
|
|
|
private:
|
|
void AdvanceUntilValid();
|
|
|
|
Iterable *self_;
|
|
utils::SkipList<Entry>::Iterator index_iterator_;
|
|
VertexAccessor current_vertex_accessor_;
|
|
Vertex *current_vertex_;
|
|
};
|
|
|
|
Iterator begin();
|
|
Iterator end();
|
|
|
|
private:
|
|
utils::SkipList<Entry>::Accessor index_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_;
|
|
Transaction *transaction_;
|
|
Indices *indices_;
|
|
Constraints *constraints_;
|
|
Config::Items config_;
|
|
};
|
|
|
|
Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
|
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
|
Transaction *transaction) {
|
|
auto it = index_.find({label, property});
|
|
MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(),
|
|
property.AsUint());
|
|
return Iterable(it->second.access(), label, property, lower_bound, upper_bound, view, transaction, indices_,
|
|
constraints_, config_);
|
|
}
|
|
|
|
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
|
auto it = index_.find({label, property});
|
|
MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(),
|
|
property.AsUint());
|
|
return it->second.size();
|
|
}
|
|
|
|
/// Supplying a specific value into the count estimation function will return
|
|
/// an estimated count of nodes which have their property's value set to
|
|
/// `value`. If the `value` specified is `Null`, then an average number of
|
|
/// equal elements is returned.
|
|
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const;
|
|
|
|
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
|
const std::optional<utils::Bound<PropertyValue>> &lower,
|
|
const std::optional<utils::Bound<PropertyValue>> &upper) const;
|
|
|
|
std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats();
|
|
|
|
std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStatsForLabel(const storage::LabelId &label);
|
|
|
|
void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property,
|
|
const storage::LabelPropertyIndexStats &stats);
|
|
|
|
std::optional<storage::LabelPropertyIndexStats> GetIndexStats(const storage::LabelId &label,
|
|
const storage::PropertyId &property) const;
|
|
|
|
void Clear() { index_.clear(); }
|
|
|
|
void RunGC();
|
|
|
|
private:
|
|
std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_;
|
|
std::map<std::pair<LabelId, PropertyId>, storage::LabelPropertyIndexStats> stats_;
|
|
Indices *indices_;
|
|
Constraints *constraints_;
|
|
Config::Items config_;
|
|
};
|
|
|
|
struct Indices {
|
|
Indices(Constraints *constraints, Config::Items config)
|
|
: label_index(this, constraints, config), label_property_index(this, constraints, config) {}
|
|
|
|
// Disable copy and move because members hold pointer to `this`.
|
|
Indices(const Indices &) = delete;
|
|
Indices(Indices &&) = delete;
|
|
Indices &operator=(const Indices &) = delete;
|
|
Indices &operator=(Indices &&) = delete;
|
|
~Indices() = default;
|
|
|
|
LabelIndex label_index;
|
|
LabelPropertyIndex label_property_index;
|
|
};
|
|
|
|
/// This function should be called from garbage collection to clean-up the
|
|
/// index.
|
|
void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp);
|
|
|
|
// Indices are updated whenever an update occurs, instead of only on commit or
|
|
// advance command. This is necessary because we want indices to support `NEW`
|
|
// view for use in Merge.
|
|
|
|
/// This function should be called whenever a label is added to a vertex.
|
|
/// @throw std::bad_alloc
|
|
void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex, const Transaction &tx);
|
|
|
|
/// This function should be called whenever a property is modified on a vertex.
|
|
/// @throw std::bad_alloc
|
|
void UpdateOnSetProperty(Indices *indices, PropertyId property, const PropertyValue &value, Vertex *vertex,
|
|
const Transaction &tx);
|
|
} // namespace memgraph::storage
|