[StorageV2] Implement label and label-property indices
Reviewers: mferencevic, teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2225
This commit is contained in:
parent
6a2dc1eb9d
commit
35587cddbd
@ -1,5 +1,6 @@
|
|||||||
set(storage_v2_src_files
|
set(storage_v2_src_files
|
||||||
edge_accessor.cpp
|
edge_accessor.cpp
|
||||||
|
indices.cpp
|
||||||
vertex_accessor.cpp
|
vertex_accessor.cpp
|
||||||
storage.cpp)
|
storage.cpp)
|
||||||
|
|
||||||
|
@ -8,11 +8,11 @@
|
|||||||
namespace storage {
|
namespace storage {
|
||||||
|
|
||||||
VertexAccessor EdgeAccessor::FromVertex() const {
|
VertexAccessor EdgeAccessor::FromVertex() const {
|
||||||
return VertexAccessor{from_vertex_, transaction_};
|
return VertexAccessor{from_vertex_, transaction_, indices_};
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexAccessor EdgeAccessor::ToVertex() const {
|
VertexAccessor EdgeAccessor::ToVertex() const {
|
||||||
return VertexAccessor{to_vertex_, transaction_};
|
return VertexAccessor{to_vertex_, transaction_, indices_};
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<bool> EdgeAccessor::SetProperty(PropertyId property,
|
Result<bool> EdgeAccessor::SetProperty(PropertyId property,
|
||||||
|
@ -12,7 +12,7 @@ namespace storage {
|
|||||||
|
|
||||||
struct Vertex;
|
struct Vertex;
|
||||||
class VertexAccessor;
|
class VertexAccessor;
|
||||||
class Storage;
|
struct Indices;
|
||||||
|
|
||||||
class EdgeAccessor final {
|
class EdgeAccessor final {
|
||||||
private:
|
private:
|
||||||
@ -20,12 +20,13 @@ class EdgeAccessor final {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
EdgeAccessor(Edge *edge, EdgeTypeId edge_type, Vertex *from_vertex,
|
EdgeAccessor(Edge *edge, EdgeTypeId edge_type, Vertex *from_vertex,
|
||||||
Vertex *to_vertex, Transaction *transaction)
|
Vertex *to_vertex, Transaction *transaction, Indices *indices)
|
||||||
: edge_(edge),
|
: edge_(edge),
|
||||||
edge_type_(edge_type),
|
edge_type_(edge_type),
|
||||||
from_vertex_(from_vertex),
|
from_vertex_(from_vertex),
|
||||||
to_vertex_(to_vertex),
|
to_vertex_(to_vertex),
|
||||||
transaction_(transaction) {}
|
transaction_(transaction),
|
||||||
|
indices_(indices) {}
|
||||||
|
|
||||||
VertexAccessor FromVertex() const;
|
VertexAccessor FromVertex() const;
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ class EdgeAccessor final {
|
|||||||
Vertex *from_vertex_;
|
Vertex *from_vertex_;
|
||||||
Vertex *to_vertex_;
|
Vertex *to_vertex_;
|
||||||
Transaction *transaction_;
|
Transaction *transaction_;
|
||||||
|
Indices *indices_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace storage
|
} // namespace storage
|
||||||
|
595
src/storage/v2/indices.cpp
Normal file
595
src/storage/v2/indices.cpp
Normal file
@ -0,0 +1,595 @@
|
|||||||
|
#include "indices.hpp"
|
||||||
|
|
||||||
|
#include "storage/v2/mvcc.hpp"
|
||||||
|
|
||||||
|
namespace storage {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/// We treat doubles and integers the same for label property indices, so
|
||||||
|
/// special comparison operators are needed.
|
||||||
|
bool PropertyValueLess(const PropertyValue &lhs, const PropertyValue &rhs) {
|
||||||
|
if (lhs.type() == rhs.type()) {
|
||||||
|
return lhs < rhs;
|
||||||
|
}
|
||||||
|
if (lhs.IsInt() && rhs.IsDouble()) {
|
||||||
|
return lhs.ValueInt() < rhs.ValueDouble();
|
||||||
|
}
|
||||||
|
if (lhs.IsDouble() && rhs.IsInt()) {
|
||||||
|
return lhs.ValueDouble() < rhs.ValueInt();
|
||||||
|
}
|
||||||
|
return lhs.type() < rhs.type();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PropertyValueEqual(const PropertyValue &lhs, const PropertyValue &rhs) {
|
||||||
|
if (lhs.type() == rhs.type()) {
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
if (lhs.IsInt() && rhs.IsDouble()) {
|
||||||
|
return lhs.ValueInt() == rhs.ValueDouble();
|
||||||
|
}
|
||||||
|
if (lhs.IsDouble() && rhs.IsInt()) {
|
||||||
|
return lhs.ValueDouble() == rhs.ValueInt();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses deltas visible from transaction with start timestamp greater than
|
||||||
|
/// the provided timestamp, and calls the provided callback function for each
|
||||||
|
/// delta. If the callback ever returns true, traversal is stopped and the
|
||||||
|
/// function returns true. Otherwise, the function returns false.
|
||||||
|
template <typename TCallback>
|
||||||
|
bool AnyVersionSatisfiesPredicate(uint64_t timestamp, Delta *delta,
|
||||||
|
const TCallback &predicate) {
|
||||||
|
while (delta != nullptr) {
|
||||||
|
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||||
|
// This is a committed change that we see so we shouldn't undo it.
|
||||||
|
if (ts < timestamp) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (predicate(*delta)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Move to the next delta.
|
||||||
|
delta = delta->next.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for label index garbage collection. Returns true if there's
|
||||||
|
/// a reachable version of the vertex that has the given label.
|
||||||
|
bool AnyVersionHasLabel(Vertex *vertex, LabelId label, uint64_t timestamp) {
|
||||||
|
bool has_label;
|
||||||
|
bool deleted;
|
||||||
|
Delta *delta;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||||
|
has_label = utils::Contains(vertex->labels, label);
|
||||||
|
deleted = vertex->deleted;
|
||||||
|
delta = vertex->delta;
|
||||||
|
}
|
||||||
|
if (!deleted && has_label) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return AnyVersionSatisfiesPredicate(
|
||||||
|
timestamp, delta, [&has_label, &deleted, label](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_LABEL:
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(!has_label) << "Invalid database state!";
|
||||||
|
has_label = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Delta::Action::REMOVE_LABEL:
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(has_label) << "Invalid database state!";
|
||||||
|
has_label = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
CHECK(deleted) << "Invalid database state!";
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
CHECK(!deleted) << "Invalid database state!";
|
||||||
|
deleted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::SET_PROPERTY:
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for label-property index garbage collection. Returns true if
|
||||||
|
/// there's a reachable version of the vertex that has the given label and
|
||||||
|
/// property value.
|
||||||
|
bool AnyVersionHasLabelProperty(Vertex *vertex, LabelId label, PropertyId key,
|
||||||
|
const PropertyValue &value,
|
||||||
|
uint64_t timestamp) {
|
||||||
|
bool has_label;
|
||||||
|
PropertyValue current_value;
|
||||||
|
bool deleted;
|
||||||
|
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 = it->second;
|
||||||
|
}
|
||||||
|
deleted = vertex->deleted;
|
||||||
|
delta = vertex->delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deleted && has_label && PropertyValueEqual(current_value, value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnyVersionSatisfiesPredicate(
|
||||||
|
timestamp, delta,
|
||||||
|
[&has_label, ¤t_value, &deleted, label, key,
|
||||||
|
value](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_LABEL:
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(!has_label) << "Invalid database state!";
|
||||||
|
has_label = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Delta::Action::REMOVE_LABEL:
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(has_label) << "Invalid database state!";
|
||||||
|
has_label = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Delta::Action::SET_PROPERTY:
|
||||||
|
if (delta.property.key == key) {
|
||||||
|
current_value = delta.property.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
CHECK(deleted) << "Invalid database state!";
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
CHECK(!deleted) << "Invalid database state!";
|
||||||
|
deleted = true;
|
||||||
|
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 &&
|
||||||
|
PropertyValueEqual(current_value, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for iterating through label index. Returns true if this
|
||||||
|
// transaction can see the given vertex, and the visible version has the given
|
||||||
|
// label.
|
||||||
|
bool CurrentVersionHasLabel(Vertex *vertex, LabelId label,
|
||||||
|
Transaction *transaction, View view) {
|
||||||
|
bool deleted;
|
||||||
|
bool has_label;
|
||||||
|
Delta *delta;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||||
|
deleted = vertex->deleted;
|
||||||
|
has_label = utils::Contains(vertex->labels, label);
|
||||||
|
delta = vertex->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction, delta, view,
|
||||||
|
[&deleted, &has_label, label](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::REMOVE_LABEL: {
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(has_label) << "Invalid database state!";
|
||||||
|
has_label = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::ADD_LABEL: {
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(!has_label) << "Invalid database state!";
|
||||||
|
has_label = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
CHECK(!deleted) << "Invalid database state!";
|
||||||
|
deleted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
CHECK(deleted) << "Invalid database state!";
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::SET_PROPERTY:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for iterating through label-property index. Returns true if
|
||||||
|
// this transaction can see the given vertex, and the visible version has the
|
||||||
|
// given label and property.
|
||||||
|
bool CurrentVersionHasLabelProperty(Vertex *vertex, LabelId label,
|
||||||
|
PropertyId key, const PropertyValue &value,
|
||||||
|
Transaction *transaction, View view) {
|
||||||
|
bool deleted;
|
||||||
|
bool has_label;
|
||||||
|
PropertyValue current_value;
|
||||||
|
Delta *delta;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||||
|
deleted = vertex->deleted;
|
||||||
|
has_label = utils::Contains(vertex->labels, label);
|
||||||
|
auto it = vertex->properties.find(key);
|
||||||
|
if (it != vertex->properties.end()) {
|
||||||
|
current_value = it->second;
|
||||||
|
}
|
||||||
|
delta = vertex->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(
|
||||||
|
transaction, delta, view,
|
||||||
|
[&deleted, &has_label, ¤t_value, key, label](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::SET_PROPERTY: {
|
||||||
|
if (delta.property.key == key) {
|
||||||
|
current_value = delta.property.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
CHECK(!deleted) << "Invalid database state!";
|
||||||
|
deleted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
CHECK(deleted) << "Invalid database state!";
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::ADD_LABEL:
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(!has_label) << "Invalid database state!";
|
||||||
|
has_label = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Delta::Action::REMOVE_LABEL:
|
||||||
|
if (delta.label == label) {
|
||||||
|
CHECK(has_label) << "Invalid database state!";
|
||||||
|
has_label = false;
|
||||||
|
}
|
||||||
|
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 && PropertyValueEqual(current_value, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void LabelIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex,
|
||||||
|
const Transaction &tx) {
|
||||||
|
GetOrCreateStorage(label)->access().insert(Entry{vertex, tx.start_timestamp});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
||||||
|
auto index_acc = index_.access();
|
||||||
|
for (auto &label_storage : index_acc) {
|
||||||
|
auto vertices_acc = label_storage.vertices.access();
|
||||||
|
for (auto it = vertices_acc.begin(); it != vertices_acc.end();) {
|
||||||
|
auto next_it = it;
|
||||||
|
++next_it;
|
||||||
|
|
||||||
|
if (it->timestamp >= oldest_active_start_timestamp) {
|
||||||
|
it = next_it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((next_it != vertices_acc.end() && it->vertex == next_it->vertex) ||
|
||||||
|
!AnyVersionHasLabel(it->vertex, label_storage.label,
|
||||||
|
oldest_active_start_timestamp)) {
|
||||||
|
vertices_acc.remove(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
it = next_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::SkipList<LabelIndex::Entry> *LabelIndex::GetOrCreateStorage(
|
||||||
|
LabelId label) {
|
||||||
|
auto acc = index_.access();
|
||||||
|
auto it = acc.find(label);
|
||||||
|
if (it == acc.end()) {
|
||||||
|
LabelStorage label_storage{.label = label,
|
||||||
|
.vertices = utils::SkipList<Entry>()};
|
||||||
|
it = acc.insert(std::move(label_storage)).first;
|
||||||
|
}
|
||||||
|
return &it->vertices;
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelIndex::Iterable::Iterator::Iterator(
|
||||||
|
Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||||
|
: self_(self),
|
||||||
|
index_iterator_(index_iterator),
|
||||||
|
current_vertex_accessor_(nullptr, nullptr, nullptr),
|
||||||
|
current_vertex_(nullptr) {
|
||||||
|
AdvanceUntilValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelIndex::Iterable::Iterator &LabelIndex::Iterable::Iterator::operator++() {
|
||||||
|
++index_iterator_;
|
||||||
|
AdvanceUntilValid();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||||
|
for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) {
|
||||||
|
if (index_iterator_->vertex == current_vertex_) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (CurrentVersionHasLabel(index_iterator_->vertex, self_->label_,
|
||||||
|
self_->transaction_, self_->view_)) {
|
||||||
|
current_vertex_ = index_iterator_->vertex;
|
||||||
|
current_vertex_accessor_ =
|
||||||
|
VertexAccessor{current_vertex_, self_->transaction_, self_->indices_};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor,
|
||||||
|
LabelId label, View view,
|
||||||
|
Transaction *transaction, Indices *indices)
|
||||||
|
: index_accessor_(index_accessor),
|
||||||
|
label_(label),
|
||||||
|
view_(view),
|
||||||
|
transaction_(transaction),
|
||||||
|
indices_(indices) {}
|
||||||
|
|
||||||
|
bool LabelPropertyIndex::Entry::operator<(const Entry &rhs) {
|
||||||
|
if (PropertyValueLess(value, rhs.value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (PropertyValueLess(rhs.value, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::make_tuple(vertex, timestamp) <
|
||||||
|
std::make_tuple(rhs.vertex, rhs.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LabelPropertyIndex::Entry::operator==(const Entry &rhs) {
|
||||||
|
return PropertyValueEqual(value, rhs.value) && vertex == rhs.vertex &&
|
||||||
|
timestamp == rhs.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LabelPropertyIndex::Entry::operator<(const PropertyValue &rhs) {
|
||||||
|
return PropertyValueLess(value, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LabelPropertyIndex::Entry::operator==(const PropertyValue &rhs) {
|
||||||
|
return PropertyValueEqual(value, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelPropertyIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex,
|
||||||
|
const Transaction &tx) {
|
||||||
|
for (auto &[label_prop, storage] : index_) {
|
||||||
|
if (label_prop.first != label) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PropertyValue value;
|
||||||
|
{
|
||||||
|
utils::SpinLock guard(vertex->lock);
|
||||||
|
auto it = vertex->properties.find(label_prop.second);
|
||||||
|
if (it != vertex->properties.end()) {
|
||||||
|
value = it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!value.IsNull()) {
|
||||||
|
storage.access().insert(Entry{value, vertex, tx.start_timestamp});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelPropertyIndex::UpdateOnSetProperty(PropertyId property,
|
||||||
|
const PropertyValue &value,
|
||||||
|
Vertex *vertex,
|
||||||
|
const Transaction &tx) {
|
||||||
|
if (value.IsNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto &[label_prop, storage] : index_) {
|
||||||
|
if (label_prop.second != property) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool has_label;
|
||||||
|
{
|
||||||
|
utils::SpinLock guard(vertex->lock);
|
||||||
|
has_label = utils::Contains(vertex->labels, label_prop.first);
|
||||||
|
}
|
||||||
|
if (has_label) {
|
||||||
|
storage.access().insert(Entry{value, vertex, tx.start_timestamp});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LabelPropertyIndex::CreateIndex(
|
||||||
|
LabelId label, PropertyId property,
|
||||||
|
utils::SkipList<Vertex>::Accessor vertices) {
|
||||||
|
auto [it, emplaced] = index_.emplace(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(label, property),
|
||||||
|
std::forward_as_tuple());
|
||||||
|
if (!emplaced) {
|
||||||
|
// Index already exists.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto acc = it->second.access();
|
||||||
|
for (Vertex &vertex : vertices) {
|
||||||
|
if (!utils::Contains(vertex.labels, label)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto it = vertex.properties.find(property);
|
||||||
|
if (it == vertex.properties.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
acc.insert(Entry{it->second, &vertex, 0});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelPropertyIndex::RemoveObsoleteEntries(
|
||||||
|
uint64_t oldest_active_start_timestamp) {
|
||||||
|
for (auto &[label_property, index] : index_) {
|
||||||
|
auto index_acc = index.access();
|
||||||
|
for (auto it = index_acc.begin(); it != index_acc.end();) {
|
||||||
|
auto next_it = it;
|
||||||
|
++next_it;
|
||||||
|
|
||||||
|
if (it->timestamp >= oldest_active_start_timestamp) {
|
||||||
|
it = next_it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((next_it != index_acc.end() && it->vertex == next_it->vertex &&
|
||||||
|
PropertyValueEqual(it->value, next_it->value)) ||
|
||||||
|
!AnyVersionHasLabelProperty(it->vertex, label_property.first,
|
||||||
|
label_property.second, it->value,
|
||||||
|
oldest_active_start_timestamp)) {
|
||||||
|
index_acc.remove(*it);
|
||||||
|
}
|
||||||
|
it = next_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable::Iterator::Iterator(
|
||||||
|
Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||||
|
: self_(self),
|
||||||
|
index_iterator_(index_iterator),
|
||||||
|
current_vertex_accessor_(nullptr, nullptr, nullptr),
|
||||||
|
current_vertex_(nullptr) {
|
||||||
|
AdvanceUntilValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable::Iterator &LabelPropertyIndex::Iterable::Iterator::
|
||||||
|
operator++() {
|
||||||
|
++index_iterator_;
|
||||||
|
AdvanceUntilValid();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||||
|
for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) {
|
||||||
|
if (index_iterator_->vertex == current_vertex_) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self_->lower_bound_) {
|
||||||
|
if (PropertyValueLess(index_iterator_->value,
|
||||||
|
self_->lower_bound_->value())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!self_->lower_bound_->IsInclusive() &&
|
||||||
|
PropertyValueEqual(index_iterator_->value,
|
||||||
|
self_->lower_bound_->value())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self_->upper_bound_) {
|
||||||
|
if (PropertyValueLess(self_->upper_bound_->value(),
|
||||||
|
index_iterator_->value)) {
|
||||||
|
index_iterator_ = self_->index_accessor_.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!self_->upper_bound_->IsInclusive() &&
|
||||||
|
PropertyValueEqual(index_iterator_->value,
|
||||||
|
self_->upper_bound_->value())) {
|
||||||
|
index_iterator_ = self_->index_accessor_.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentVersionHasLabelProperty(index_iterator_->vertex, self_->label_,
|
||||||
|
self_->property_, index_iterator_->value,
|
||||||
|
self_->transaction_, self_->view_)) {
|
||||||
|
current_vertex_ = index_iterator_->vertex;
|
||||||
|
current_vertex_accessor_ =
|
||||||
|
VertexAccessor(current_vertex_, self_->transaction_, self_->indices_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable::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)
|
||||||
|
: index_accessor_(index_accessor),
|
||||||
|
label_(label),
|
||||||
|
property_(property),
|
||||||
|
lower_bound_(lower_bound),
|
||||||
|
upper_bound_(upper_bound),
|
||||||
|
view_(view),
|
||||||
|
transaction_(transaction),
|
||||||
|
indices_(indices) {}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable::Iterator LabelPropertyIndex::Iterable::begin() {
|
||||||
|
auto index_iterator = index_accessor_.begin();
|
||||||
|
if (lower_bound_) {
|
||||||
|
index_iterator =
|
||||||
|
index_accessor_.find_equal_or_greater(lower_bound_->value());
|
||||||
|
}
|
||||||
|
return Iterator(this, index_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable::Iterator LabelPropertyIndex::Iterable::end() {
|
||||||
|
return Iterator(this, index_accessor_.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveObsoleteEntries(Indices *indices,
|
||||||
|
uint64_t oldest_active_start_timestamp) {
|
||||||
|
indices->label_index.RemoveObsoleteEntries(oldest_active_start_timestamp);
|
||||||
|
indices->label_property_index.RemoveObsoleteEntries(
|
||||||
|
oldest_active_start_timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex,
|
||||||
|
const Transaction &tx) {
|
||||||
|
indices->label_index.UpdateOnAddLabel(label, vertex, tx);
|
||||||
|
indices->label_property_index.UpdateOnAddLabel(label, vertex, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateOnSetProperty(Indices *indices, PropertyId property,
|
||||||
|
const PropertyValue &value, Vertex *vertex,
|
||||||
|
const Transaction &tx) {
|
||||||
|
indices->label_property_index.UpdateOnSetProperty(property, value, vertex,
|
||||||
|
tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace storage
|
233
src/storage/v2/indices.hpp
Normal file
233
src/storage/v2/indices.hpp
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "storage/v2/property_value.hpp"
|
||||||
|
#include "storage/v2/transaction.hpp"
|
||||||
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
|
#include "utils/bound.hpp"
|
||||||
|
#include "utils/skip_list.hpp"
|
||||||
|
|
||||||
|
namespace storage {
|
||||||
|
|
||||||
|
struct Indices;
|
||||||
|
|
||||||
|
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:
|
||||||
|
explicit LabelIndex(Indices *indices) : indices_(indices) {}
|
||||||
|
|
||||||
|
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns an self with vertices visible from the given transaction.
|
||||||
|
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
|
||||||
|
return Iterable(GetOrCreateStorage(label)->access(), label, view,
|
||||||
|
transaction, indices_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
utils::SkipList<LabelStorage> index_;
|
||||||
|
Indices *indices_;
|
||||||
|
|
||||||
|
utils::SkipList<Entry> *GetOrCreateStorage(LabelId label);
|
||||||
|
};
|
||||||
|
|
||||||
|
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:
|
||||||
|
explicit LabelPropertyIndex(Indices *indices) : indices_(indices) {}
|
||||||
|
|
||||||
|
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
||||||
|
|
||||||
|
void UpdateOnSetProperty(PropertyId property, const PropertyValue &value,
|
||||||
|
Vertex *vertex, const Transaction &tx);
|
||||||
|
|
||||||
|
bool CreateIndex(LabelId label, PropertyId property,
|
||||||
|
utils::SkipList<Vertex>::Accessor vertices);
|
||||||
|
|
||||||
|
bool DropIndex(LabelId label, PropertyId property) {
|
||||||
|
return index_.erase({label, property}) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IndexExists(LabelId label, PropertyId property) {
|
||||||
|
return index_.find({label, property}) != index_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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_;
|
||||||
|
View view_;
|
||||||
|
Transaction *transaction_;
|
||||||
|
Indices *indices_;
|
||||||
|
};
|
||||||
|
|
||||||
|
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});
|
||||||
|
CHECK(it != index_.end())
|
||||||
|
<< "Index for label " << label.AsUint() << " and property "
|
||||||
|
<< property.AsUint() << " doesn't exist";
|
||||||
|
return Iterable(it->second.access(), label, property, lower_bound,
|
||||||
|
upper_bound, view, transaction, indices_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Indices *indices_;
|
||||||
|
std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Indices {
|
||||||
|
Indices() : label_index(this), label_property_index(this) {}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex,
|
||||||
|
const Transaction &tx);
|
||||||
|
|
||||||
|
/// This function should be called whenever a property is modified on a vertex.
|
||||||
|
void UpdateOnSetProperty(Indices *indices, PropertyId property,
|
||||||
|
const PropertyValue &value, Vertex *vertex,
|
||||||
|
const Transaction &tx);
|
||||||
|
|
||||||
|
} // namespace storage
|
@ -8,13 +8,11 @@
|
|||||||
|
|
||||||
namespace storage {
|
namespace storage {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it,
|
auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it,
|
||||||
utils::SkipList<Vertex>::Iterator end,
|
utils::SkipList<Vertex>::Iterator end,
|
||||||
Transaction *tx, View view) {
|
Transaction *tx, View view, Indices *indices) {
|
||||||
while (it != end) {
|
while (it != end) {
|
||||||
auto maybe_vertex = VertexAccessor::Create(&*it, tx, view);
|
auto maybe_vertex = VertexAccessor::Create(&*it, tx, indices, view);
|
||||||
if (!maybe_vertex) {
|
if (!maybe_vertex) {
|
||||||
++it;
|
++it;
|
||||||
continue;
|
continue;
|
||||||
@ -24,22 +22,25 @@ auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it,
|
|||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
VerticesIterable::Iterator::Iterator(VerticesIterable *self,
|
VerticesIterable::Iterator::Iterator(VerticesIterable *self,
|
||||||
utils::SkipList<Vertex>::Iterator it)
|
utils::SkipList<Vertex>::Iterator it)
|
||||||
: self_(self),
|
: self_(self),
|
||||||
it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(),
|
it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(),
|
||||||
self->transaction_, self->view_)) {}
|
self->transaction_, self->view_,
|
||||||
|
self->indices_)) {}
|
||||||
|
|
||||||
VertexAccessor VerticesIterable::Iterator::operator*() const {
|
VertexAccessor VerticesIterable::Iterator::operator*() const {
|
||||||
return *VertexAccessor::Create(&*it_, self_->transaction_, self_->view_);
|
// TODO: current vertex accessor could be cached to avoid reconstructing every
|
||||||
|
// time
|
||||||
|
return *VertexAccessor::Create(&*it_, self_->transaction_, self_->indices_,
|
||||||
|
self_->view_);
|
||||||
}
|
}
|
||||||
|
|
||||||
VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() {
|
VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() {
|
||||||
++it_;
|
++it_;
|
||||||
it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(),
|
it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(),
|
||||||
self_->transaction_, self_->view_);
|
self_->transaction_, self_->view_,
|
||||||
|
self_->indices_);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ VertexAccessor Storage::Accessor::CreateVertex() {
|
|||||||
CHECK(inserted) << "The vertex must be inserted here!";
|
CHECK(inserted) << "The vertex must be inserted here!";
|
||||||
CHECK(it != acc.end()) << "Invalid Vertex accessor!";
|
CHECK(it != acc.end()) << "Invalid Vertex accessor!";
|
||||||
delta->prev.Set(&*it);
|
delta->prev.Set(&*it);
|
||||||
return VertexAccessor{&*it, &transaction_};
|
return VertexAccessor{&*it, &transaction_, &storage_->indices_};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid,
|
std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid,
|
||||||
@ -96,7 +97,7 @@ std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid,
|
|||||||
auto acc = storage_->vertices_.access();
|
auto acc = storage_->vertices_.access();
|
||||||
auto it = acc.find(gid);
|
auto it = acc.find(gid);
|
||||||
if (it == acc.end()) return std::nullopt;
|
if (it == acc.end()) return std::nullopt;
|
||||||
return VertexAccessor::Create(&*it, &transaction_, view);
|
return VertexAccessor::Create(&*it, &transaction_, &storage_->indices_, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<bool> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) {
|
Result<bool> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) {
|
||||||
@ -144,7 +145,8 @@ Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
|||||||
|
|
||||||
for (const auto &item : in_edges) {
|
for (const auto &item : in_edges) {
|
||||||
auto [edge_type, from_vertex, edge] = item;
|
auto [edge_type, from_vertex, edge] = item;
|
||||||
EdgeAccessor e{edge, edge_type, from_vertex, vertex_ptr, &transaction_};
|
EdgeAccessor e{edge, edge_type, from_vertex,
|
||||||
|
vertex_ptr, &transaction_, &storage_->indices_};
|
||||||
auto ret = DeleteEdge(&e);
|
auto ret = DeleteEdge(&e);
|
||||||
if (ret.HasError()) {
|
if (ret.HasError()) {
|
||||||
CHECK(ret.GetError() == Error::SERIALIZATION_ERROR)
|
CHECK(ret.GetError() == Error::SERIALIZATION_ERROR)
|
||||||
@ -154,7 +156,8 @@ Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
|||||||
}
|
}
|
||||||
for (const auto &item : out_edges) {
|
for (const auto &item : out_edges) {
|
||||||
auto [edge_type, to_vertex, edge] = item;
|
auto [edge_type, to_vertex, edge] = item;
|
||||||
EdgeAccessor e{edge, edge_type, vertex_ptr, to_vertex, &transaction_};
|
EdgeAccessor e{edge, edge_type, vertex_ptr,
|
||||||
|
to_vertex, &transaction_, &storage_->indices_};
|
||||||
auto ret = DeleteEdge(&e);
|
auto ret = DeleteEdge(&e);
|
||||||
if (ret.HasError()) {
|
if (ret.HasError()) {
|
||||||
CHECK(ret.GetError() == Error::SERIALIZATION_ERROR)
|
CHECK(ret.GetError() == Error::SERIALIZATION_ERROR)
|
||||||
@ -235,8 +238,9 @@ Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from,
|
|||||||
edge_type, from_vertex, edge);
|
edge_type, from_vertex, edge);
|
||||||
to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge);
|
to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge);
|
||||||
|
|
||||||
return Result<EdgeAccessor>{
|
return Result<EdgeAccessor>{EdgeAccessor{edge, edge_type, from_vertex,
|
||||||
EdgeAccessor{edge, edge_type, from_vertex, to_vertex, &transaction_}};
|
to_vertex, &transaction_,
|
||||||
|
&storage_->indices_}};
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||||
@ -587,6 +591,32 @@ Storage::Accessor Storage::Access() {
|
|||||||
return Accessor{this, transaction_id, start_timestamp};
|
return Accessor{this, transaction_id, start_timestamp};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabelIndex::Iterable Storage::Accessor::Vertices(LabelId label, View view) {
|
||||||
|
return storage_->indices_.label_index.Vertices(label, view, &transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable Storage::Accessor::Vertices(LabelId label,
|
||||||
|
PropertyId property,
|
||||||
|
View view) {
|
||||||
|
return storage_->indices_.label_property_index.Vertices(
|
||||||
|
label, property, std::nullopt, std::nullopt, view, &transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable Storage::Accessor::Vertices(
|
||||||
|
LabelId label, PropertyId property, const PropertyValue &value, View view) {
|
||||||
|
return storage_->indices_.label_property_index.Vertices(
|
||||||
|
label, property, utils::MakeBoundInclusive(value),
|
||||||
|
utils::MakeBoundInclusive(value), view, &transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable Storage::Accessor::Vertices(
|
||||||
|
LabelId label, PropertyId property,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) {
|
||||||
|
return storage_->indices_.label_property_index.Vertices(
|
||||||
|
label, property, lower_bound, upper_bound, view, &transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
void Storage::CollectGarbage() {
|
void Storage::CollectGarbage() {
|
||||||
// Garbage collection must be performed in two phases. In the first phase,
|
// Garbage collection must be performed in two phases. In the first phase,
|
||||||
// deltas that won't be applied by any transaction anymore are unlinked from
|
// deltas that won't be applied by any transaction anymore are unlinked from
|
||||||
@ -606,9 +636,9 @@ void Storage::CollectGarbage() {
|
|||||||
// garbage_undo_buffers lock.
|
// garbage_undo_buffers lock.
|
||||||
std::list<std::pair<uint64_t, std::list<Delta>>> unlinked_undo_buffers;
|
std::list<std::pair<uint64_t, std::list<Delta>>> unlinked_undo_buffers;
|
||||||
|
|
||||||
// We will free only vertices deleted up until now in this GC cycle.
|
// We will only free vertices deleted up until now in this GC cycle, and we
|
||||||
// Otherwise, GC cycle might be prolonged by aborted transactions adding new
|
// will do it after cleaning-up the indices. That way we are sure that all
|
||||||
// edges and vertices to the lists over and over again.
|
// vertices that appear in an index also exist in main storage.
|
||||||
std::list<Gid> current_deleted_edges;
|
std::list<Gid> current_deleted_edges;
|
||||||
std::list<Gid> current_deleted_vertices;
|
std::list<Gid> current_deleted_vertices;
|
||||||
deleted_vertices_->swap(current_deleted_vertices);
|
deleted_vertices_->swap(current_deleted_vertices);
|
||||||
@ -697,10 +727,15 @@ void Storage::CollectGarbage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After unlinking deltas from vertices, we refresh the indices. That way
|
||||||
|
// we're sure that none of the vertices from `current_deleted_vertices`
|
||||||
|
// appears in an index, and we can safely remove the from the main storage
|
||||||
|
// after the last currently active transaction is finished.
|
||||||
|
RemoveObsoleteEntries(&indices_, oldest_active_start_timestamp);
|
||||||
|
|
||||||
{
|
{
|
||||||
uint64_t mark_timestamp;
|
|
||||||
std::unique_lock<utils::SpinLock> guard(engine_lock_);
|
std::unique_lock<utils::SpinLock> guard(engine_lock_);
|
||||||
mark_timestamp = timestamp_;
|
uint64_t mark_timestamp = timestamp_;
|
||||||
// Take garbage_undo_buffers lock while holding the engine lock to make
|
// Take garbage_undo_buffers lock while holding the engine lock to make
|
||||||
// sure that entries are sorted by mark timestamp in the list.
|
// sure that entries are sorted by mark timestamp in the list.
|
||||||
garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
|
garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
|
||||||
@ -716,6 +751,9 @@ void Storage::CollectGarbage() {
|
|||||||
garbage_undo_buffers.splice(garbage_undo_buffers.end(),
|
garbage_undo_buffers.splice(garbage_undo_buffers.end(),
|
||||||
unlinked_undo_buffers);
|
unlinked_undo_buffers);
|
||||||
});
|
});
|
||||||
|
for (auto vertex : current_deleted_vertices) {
|
||||||
|
garbage_vertices_.emplace_back(mark_timestamp, vertex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -730,8 +768,11 @@ void Storage::CollectGarbage() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto vertex_acc = vertices_.access();
|
auto vertex_acc = vertices_.access();
|
||||||
for (auto vertex : current_deleted_vertices) {
|
while (!garbage_vertices_.empty() &&
|
||||||
CHECK(vertex_acc.remove(vertex)) << "Invalid database state!";
|
garbage_vertices_.front().first < oldest_active_start_timestamp) {
|
||||||
|
CHECK(vertex_acc.remove(garbage_vertices_.front().second))
|
||||||
|
<< "Invalid database state!";
|
||||||
|
garbage_vertices_.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "storage/v2/commit_log.hpp"
|
#include "storage/v2/commit_log.hpp"
|
||||||
#include "storage/v2/edge.hpp"
|
#include "storage/v2/edge.hpp"
|
||||||
#include "storage/v2/edge_accessor.hpp"
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
|
#include "storage/v2/indices.hpp"
|
||||||
#include "storage/v2/mvcc.hpp"
|
#include "storage/v2/mvcc.hpp"
|
||||||
#include "storage/v2/name_id_mapper.hpp"
|
#include "storage/v2/name_id_mapper.hpp"
|
||||||
#include "storage/v2/result.hpp"
|
#include "storage/v2/result.hpp"
|
||||||
@ -50,6 +51,7 @@ class VerticesIterable final {
|
|||||||
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
||||||
Transaction *transaction_;
|
Transaction *transaction_;
|
||||||
View view_;
|
View view_;
|
||||||
|
Indices *indices_;
|
||||||
|
|
||||||
class Iterator final {
|
class Iterator final {
|
||||||
VerticesIterable *self_;
|
VerticesIterable *self_;
|
||||||
@ -71,10 +73,11 @@ class VerticesIterable final {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
VerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor,
|
VerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor,
|
||||||
Transaction *transaction, View view)
|
Transaction *transaction, View view, Indices *indices)
|
||||||
: vertices_accessor_(std::move(vertices_accessor)),
|
: vertices_accessor_(std::move(vertices_accessor)),
|
||||||
transaction_(transaction),
|
transaction_(transaction),
|
||||||
view_(view) {}
|
view_(view),
|
||||||
|
indices_(indices) {}
|
||||||
|
|
||||||
Iterator begin() { return Iterator(this, vertices_accessor_.begin()); }
|
Iterator begin() { return Iterator(this, vertices_accessor_.begin()); }
|
||||||
Iterator end() { return Iterator(this, vertices_accessor_.end()); }
|
Iterator end() { return Iterator(this, vertices_accessor_.end()); }
|
||||||
@ -109,10 +112,25 @@ class Storage final {
|
|||||||
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
|
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
|
||||||
|
|
||||||
VerticesIterable Vertices(View view) {
|
VerticesIterable Vertices(View view) {
|
||||||
return VerticesIterable(storage_->vertices_.access(), &transaction_,
|
return VerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
||||||
view);
|
&storage_->indices_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabelIndex::Iterable Vertices(LabelId label, View view);
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable Vertices(LabelId label, PropertyId property,
|
||||||
|
View view);
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable Vertices(LabelId label, PropertyId property,
|
||||||
|
const PropertyValue &value,
|
||||||
|
View view);
|
||||||
|
|
||||||
|
LabelPropertyIndex::Iterable Vertices(
|
||||||
|
LabelId label, PropertyId property,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &upper_bound,
|
||||||
|
View view);
|
||||||
|
|
||||||
Result<bool> DeleteVertex(VertexAccessor *vertex);
|
Result<bool> DeleteVertex(VertexAccessor *vertex);
|
||||||
|
|
||||||
Result<bool> DetachDeleteVertex(VertexAccessor *vertex);
|
Result<bool> DetachDeleteVertex(VertexAccessor *vertex);
|
||||||
@ -147,6 +165,21 @@ class Storage final {
|
|||||||
|
|
||||||
Accessor Access();
|
Accessor Access();
|
||||||
|
|
||||||
|
bool CreateIndex(LabelId label, PropertyId property) {
|
||||||
|
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
|
||||||
|
return indices_.label_property_index.CreateIndex(label, property,
|
||||||
|
vertices_.access());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DropIndex(LabelId label, PropertyId property) {
|
||||||
|
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
|
||||||
|
return indices_.label_property_index.DropIndex(label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LabelPropertyIndexExists(LabelId label, PropertyId property) {
|
||||||
|
return indices_.label_property_index.IndexExists(label, property);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CollectGarbage();
|
void CollectGarbage();
|
||||||
|
|
||||||
@ -166,6 +199,8 @@ class Storage final {
|
|||||||
|
|
||||||
NameIdMapper name_id_mapper_;
|
NameIdMapper name_id_mapper_;
|
||||||
|
|
||||||
|
Indices indices_;
|
||||||
|
|
||||||
// Transaction engine
|
// Transaction engine
|
||||||
utils::SpinLock engine_lock_;
|
utils::SpinLock engine_lock_;
|
||||||
uint64_t timestamp_{kTimestampInitialId};
|
uint64_t timestamp_{kTimestampInitialId};
|
||||||
@ -188,9 +223,16 @@ class Storage final {
|
|||||||
utils::SpinLock>
|
utils::SpinLock>
|
||||||
garbage_undo_buffers_;
|
garbage_undo_buffers_;
|
||||||
|
|
||||||
// Vertices that are logically deleted and now are waiting to be removed from
|
// Vertices that are logically deleted but still have to be removed from
|
||||||
// the main storage.
|
// indices before removing them from the main storage.
|
||||||
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_;
|
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_;
|
||||||
|
|
||||||
|
// Vertices that are logically deleted and removed from indices and now wait
|
||||||
|
// to be removed from the main storage.
|
||||||
|
std::list<std::pair<uint64_t, Gid>> garbage_vertices_;
|
||||||
|
|
||||||
|
// Edges that are logically deleted and wait to be removed from the main
|
||||||
|
// storage.
|
||||||
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_;
|
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "storage/v2/edge_accessor.hpp"
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
|
#include "storage/v2/indices.hpp"
|
||||||
#include "storage/v2/mvcc.hpp"
|
#include "storage/v2/mvcc.hpp"
|
||||||
|
|
||||||
namespace storage {
|
namespace storage {
|
||||||
|
|
||||||
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex,
|
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex,
|
||||||
Transaction *transaction,
|
Transaction *transaction,
|
||||||
|
Indices *indices,
|
||||||
View view) {
|
View view) {
|
||||||
bool is_visible = true;
|
bool is_visible = true;
|
||||||
Delta *delta = nullptr;
|
Delta *delta = nullptr;
|
||||||
@ -39,7 +41,7 @@ std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!is_visible) return std::nullopt;
|
if (!is_visible) return std::nullopt;
|
||||||
return VertexAccessor{vertex, transaction};
|
return VertexAccessor{vertex, transaction, indices};
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
||||||
@ -57,6 +59,9 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
|||||||
CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
|
CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
|
||||||
|
|
||||||
vertex_->labels.push_back(label);
|
vertex_->labels.push_back(label);
|
||||||
|
|
||||||
|
UpdateOnAddLabel(indices_, label, vertex_, *transaction_);
|
||||||
|
|
||||||
return Result<bool>{true};
|
return Result<bool>{true};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +208,8 @@ Result<bool> VertexAccessor::SetProperty(PropertyId property,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
||||||
|
|
||||||
return Result<bool>{existed};
|
return Result<bool>{existed};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +369,8 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(
|
|||||||
auto [edge_type, from_vertex, edge] = item;
|
auto [edge_type, from_vertex, edge] = item;
|
||||||
if (edge_types.empty() || std::find(edge_types.begin(), edge_types.end(),
|
if (edge_types.empty() || std::find(edge_types.begin(), edge_types.end(),
|
||||||
edge_type) != edge_types.end()) {
|
edge_type) != edge_types.end()) {
|
||||||
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_);
|
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_,
|
||||||
|
indices_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result<decltype(ret)>(std::move(ret));
|
return Result<decltype(ret)>(std::move(ret));
|
||||||
@ -428,7 +436,8 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(
|
|||||||
auto [edge_type, to_vertex, edge] = item;
|
auto [edge_type, to_vertex, edge] = item;
|
||||||
if (edge_types.empty() || std::find(edge_types.begin(), edge_types.end(),
|
if (edge_types.empty() || std::find(edge_types.begin(), edge_types.end(),
|
||||||
edge_type) != edge_types.end()) {
|
edge_type) != edge_types.end()) {
|
||||||
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_);
|
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_,
|
||||||
|
indices_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result<decltype(ret)>(std::move(ret));
|
return Result<decltype(ret)>(std::move(ret));
|
||||||
|
@ -12,18 +12,19 @@ namespace storage {
|
|||||||
|
|
||||||
class EdgeAccessor;
|
class EdgeAccessor;
|
||||||
class Storage;
|
class Storage;
|
||||||
|
struct Indices;
|
||||||
|
|
||||||
class VertexAccessor final {
|
class VertexAccessor final {
|
||||||
private:
|
private:
|
||||||
friend class Storage;
|
friend class Storage;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VertexAccessor(Vertex *vertex, Transaction *transaction)
|
VertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices)
|
||||||
: vertex_(vertex), transaction_(transaction) {}
|
: vertex_(vertex), transaction_(transaction), indices_(indices) {}
|
||||||
|
|
||||||
static std::optional<VertexAccessor> Create(Vertex *vertex,
|
static std::optional<VertexAccessor> Create(Vertex *vertex,
|
||||||
Transaction *transaction,
|
Transaction *transaction,
|
||||||
View view);
|
Indices *indices, View view);
|
||||||
|
|
||||||
Result<bool> AddLabel(LabelId label);
|
Result<bool> AddLabel(LabelId label);
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ class VertexAccessor final {
|
|||||||
private:
|
private:
|
||||||
Vertex *vertex_;
|
Vertex *vertex_;
|
||||||
Transaction *transaction_;
|
Transaction *transaction_;
|
||||||
|
Indices *indices_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace storage
|
} // namespace storage
|
||||||
|
@ -18,9 +18,6 @@ DEFINE_int32(num_iterations, kNumIterations, "number of iterations");
|
|||||||
std::pair<std::string, storage::StorageGcConfig> TestConfigurations[] = {
|
std::pair<std::string, storage::StorageGcConfig> TestConfigurations[] = {
|
||||||
{"NoGc",
|
{"NoGc",
|
||||||
storage::StorageGcConfig{.type = storage::StorageGcConfig::Type::NONE}},
|
storage::StorageGcConfig{.type = storage::StorageGcConfig::Type::NONE}},
|
||||||
{"OnFinishGc",
|
|
||||||
storage::StorageGcConfig{.type =
|
|
||||||
storage::StorageGcConfig::Type::ON_FINISH}},
|
|
||||||
{"100msPeriodicGc",
|
{"100msPeriodicGc",
|
||||||
storage::StorageGcConfig{.type = storage::StorageGcConfig::Type::PERIODIC,
|
storage::StorageGcConfig{.type = storage::StorageGcConfig::Type::PERIODIC,
|
||||||
.interval = std::chrono::milliseconds(100)}},
|
.interval = std::chrono::milliseconds(100)}},
|
||||||
|
@ -319,6 +319,9 @@ target_link_libraries(${test_prefix}storage_v2 mg-storage-v2)
|
|||||||
add_unit_test(storage_v2_gc.cpp)
|
add_unit_test(storage_v2_gc.cpp)
|
||||||
target_link_libraries(${test_prefix}storage_v2_gc mg-storage-v2)
|
target_link_libraries(${test_prefix}storage_v2_gc mg-storage-v2)
|
||||||
|
|
||||||
|
add_unit_test(storage_v2_indices.cpp)
|
||||||
|
target_link_libraries(${test_prefix}storage_v2_indices mg-storage-v2)
|
||||||
|
|
||||||
add_unit_test(storage_v2_name_id_mapper.cpp)
|
add_unit_test(storage_v2_name_id_mapper.cpp)
|
||||||
target_link_libraries(${test_prefix}storage_v2_name_id_mapper mg-storage-v2)
|
target_link_libraries(${test_prefix}storage_v2_name_id_mapper mg-storage-v2)
|
||||||
|
|
||||||
|
@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
using testing::UnorderedElementsAre;
|
using testing::UnorderedElementsAre;
|
||||||
|
|
||||||
// TODO: We should implement a more sophisticated stress test to verify that GC
|
// TODO: The point of these is not to test GC fully, these are just simple
|
||||||
// is working properly in a multithreaded environment.
|
// sanity checks. These will be superseded by a more sophisticated stress test
|
||||||
|
// which will verify that GC is working properly in a multithreaded environment.
|
||||||
|
|
||||||
// A simple test trying to get GC to run while a transaction is still alive and
|
// A simple test trying to get GC to run while a transaction is still alive and
|
||||||
// then verify that GC didn't delete anything it shouldn't have.
|
// then verify that GC didn't delete anything it shouldn't have.
|
||||||
@ -150,3 +151,45 @@ TEST(StorageV2Gc, Sanity) {
|
|||||||
acc.Commit();
|
acc.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A simple sanity check for index GC:
|
||||||
|
// 1. Start transaction 0, create some vertices, add a label to them and
|
||||||
|
// commit.
|
||||||
|
// 2. Start transaction 1.
|
||||||
|
// 3. Start transaction 2, remove the labels and commit;
|
||||||
|
// 4. Wait for GC. GC shouldn't remove the vertices from index because
|
||||||
|
// transaction 1 can still see them with that label.
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST(StorageV2Gc, Indices) {
|
||||||
|
storage::Storage storage(
|
||||||
|
storage::StorageGcConfig{.type = storage::StorageGcConfig::Type::PERIODIC,
|
||||||
|
.interval = std::chrono::milliseconds(100)});
|
||||||
|
|
||||||
|
{
|
||||||
|
auto acc0 = storage.Access();
|
||||||
|
for (uint64_t i = 0; i < 1000; ++i) {
|
||||||
|
auto vertex = acc0.CreateVertex();
|
||||||
|
ASSERT_TRUE(*vertex.AddLabel(acc0.NameToLabel("label")));
|
||||||
|
}
|
||||||
|
acc0.Commit();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto acc1 = storage.Access();
|
||||||
|
|
||||||
|
auto acc2 = storage.Access();
|
||||||
|
for (auto vertex : acc2.Vertices(storage::View::OLD)) {
|
||||||
|
ASSERT_TRUE(*vertex.RemoveLabel(acc2.NameToLabel("label")));
|
||||||
|
}
|
||||||
|
acc2.Commit();
|
||||||
|
|
||||||
|
// Wait for GC.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||||
|
|
||||||
|
std::set<storage::Gid> gids;
|
||||||
|
for (auto vertex :
|
||||||
|
acc1.Vertices(acc1.NameToLabel("label"), storage::View::OLD)) {
|
||||||
|
gids.insert(vertex.Gid());
|
||||||
|
}
|
||||||
|
EXPECT_EQ(gids.size(), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
468
tests/unit/storage_v2_indices.cpp
Normal file
468
tests/unit/storage_v2_indices.cpp
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "storage/v2/storage.hpp"
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(google-build-using-namespace)
|
||||||
|
using namespace storage;
|
||||||
|
|
||||||
|
LabelId nil_label = LabelId::FromUint(0);
|
||||||
|
PropertyId nil_property = PropertyId::FromUint(0);
|
||||||
|
|
||||||
|
using testing::IsEmpty;
|
||||||
|
using testing::UnorderedElementsAre;
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||||
|
#define ASSERT_NO_ERROR(result) ASSERT_FALSE((result).HasError())
|
||||||
|
|
||||||
|
class IndexTest : public testing::Test {
|
||||||
|
protected:
|
||||||
|
IndexTest()
|
||||||
|
: prop_id(nil_property),
|
||||||
|
prop_val(nil_property),
|
||||||
|
label1(nil_label),
|
||||||
|
label2(nil_label) {}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
auto acc = storage.Access();
|
||||||
|
prop_id = acc.NameToProperty("id");
|
||||||
|
prop_val = acc.NameToProperty("val");
|
||||||
|
label1 = acc.NameToLabel("label1");
|
||||||
|
label2 = acc.NameToLabel("label2");
|
||||||
|
vertex_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage storage;
|
||||||
|
PropertyId prop_id;
|
||||||
|
PropertyId prop_val;
|
||||||
|
LabelId label1;
|
||||||
|
LabelId label2;
|
||||||
|
|
||||||
|
VertexAccessor CreateVertex(Storage::Accessor *accessor) {
|
||||||
|
VertexAccessor vertex = accessor->CreateVertex();
|
||||||
|
CHECK(!vertex.SetProperty(prop_id, PropertyValue(vertex_id++)).HasError());
|
||||||
|
return vertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class TIterable>
|
||||||
|
std::vector<int64_t> GetIds(TIterable iterable, View view = View::OLD) {
|
||||||
|
std::vector<int64_t> ret;
|
||||||
|
for (auto vertex : iterable) {
|
||||||
|
ret.push_back(vertex.GetProperty(prop_id, view)->ValueInt());
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int vertex_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelIndexBasic) {
|
||||||
|
// The following steps are performed and index correctness is validated after
|
||||||
|
// each step:
|
||||||
|
// 1. Create 10 vertices numbered from 0 to 9.
|
||||||
|
// 2. Add Label1 to odd numbered, and Label2 to even numbered vertices.
|
||||||
|
// 3. Remove Label1 from odd numbered vertices, and add it to even numbered
|
||||||
|
// vertices.
|
||||||
|
// 4. Delete even numbered vertices.
|
||||||
|
auto acc = storage.Access();
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty());
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
|
||||||
|
acc.AdvanceCommand();
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt();
|
||||||
|
if (id % 2) {
|
||||||
|
ASSERT_NO_ERROR(vertex.RemoveLabel(label1));
|
||||||
|
} else {
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt();
|
||||||
|
if (id % 2 == 0) {
|
||||||
|
ASSERT_NO_ERROR(acc.DeleteVertex(&vertex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty());
|
||||||
|
|
||||||
|
acc.AdvanceCommand();
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelIndexDuplicateVersions) {
|
||||||
|
// By removing labels and adding them again we create duplicate entries for
|
||||||
|
// the same vertex in the index (they only differ by the timestamp). This test
|
||||||
|
// checks that duplicates are properly filtered out.
|
||||||
|
{
|
||||||
|
auto acc = storage.Access();
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
|
||||||
|
acc.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto acc = storage.Access();
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
ASSERT_NO_ERROR(vertex.RemoveLabel(label1));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty());
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
}
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelIndexTransactionalIsolation) {
|
||||||
|
// Check that transactions only see entries they are supposed to see.
|
||||||
|
auto acc_before = storage.Access();
|
||||||
|
auto acc = storage.Access();
|
||||||
|
auto acc_after = storage.Access();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
EXPECT_THAT(GetIds(acc_before.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc_after.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
|
||||||
|
acc.Commit();
|
||||||
|
|
||||||
|
auto acc_after_commit = storage.Access();
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc_before.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc_after.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc_after_commit.Vertices(label1, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelPropertyIndexCreateAndDrop) {
|
||||||
|
EXPECT_TRUE(storage.CreateIndex(label1, prop_id));
|
||||||
|
EXPECT_TRUE(storage.LabelPropertyIndexExists(label1, prop_id));
|
||||||
|
EXPECT_FALSE(storage.LabelPropertyIndexExists(label2, prop_id));
|
||||||
|
EXPECT_FALSE(storage.CreateIndex(label1, prop_id));
|
||||||
|
|
||||||
|
EXPECT_TRUE(storage.CreateIndex(label2, prop_id));
|
||||||
|
EXPECT_TRUE(storage.LabelPropertyIndexExists(label2, prop_id));
|
||||||
|
|
||||||
|
EXPECT_TRUE(storage.DropIndex(label1, prop_id));
|
||||||
|
EXPECT_FALSE(storage.LabelPropertyIndexExists(label1, prop_id));
|
||||||
|
EXPECT_FALSE(storage.DropIndex(label1, prop_id));
|
||||||
|
|
||||||
|
EXPECT_TRUE(storage.DropIndex(label2, prop_id));
|
||||||
|
EXPECT_FALSE(storage.LabelPropertyIndexExists(label2, prop_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following three tests are almost an exact copy-paste of the corresponding
|
||||||
|
// label index tests. We request all vertices with given label and property from
|
||||||
|
// the index, without range filtering. Range filtering is tested in a separate
|
||||||
|
// test.
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelPropertyIndexBasic) {
|
||||||
|
storage.CreateIndex(label1, prop_val);
|
||||||
|
storage.CreateIndex(label2, prop_val);
|
||||||
|
|
||||||
|
auto acc = storage.Access();
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
IsEmpty());
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
|
||||||
|
acc.AdvanceCommand();
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt();
|
||||||
|
if (id % 2) {
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue()));
|
||||||
|
} else {
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt();
|
||||||
|
if (id % 2 == 0) {
|
||||||
|
ASSERT_NO_ERROR(acc.DeleteVertex(&vertex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(1, 3, 5, 7, 9));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 2, 4, 6, 8));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
|
||||||
|
acc.AdvanceCommand();
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelPropertyIndexDuplicateVersions) {
|
||||||
|
storage.CreateIndex(label1, prop_val);
|
||||||
|
{
|
||||||
|
auto acc = storage.Access();
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
|
||||||
|
acc.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto acc = storage.Access();
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
|
||||||
|
for (auto vertex : acc.Vertices(View::OLD)) {
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(42)));
|
||||||
|
}
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelPropertyIndexTransactionalIsolation) {
|
||||||
|
storage.CreateIndex(label1, prop_val);
|
||||||
|
|
||||||
|
auto acc_before = storage.Access();
|
||||||
|
auto acc = storage.Access();
|
||||||
|
auto acc_after = storage.Access();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc_before.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc_after.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
|
||||||
|
acc.Commit();
|
||||||
|
|
||||||
|
auto acc_after_commit = storage.Access();
|
||||||
|
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc_before.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc_after.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
IsEmpty());
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc_after_commit.Vertices(label1, prop_val, View::NEW), View::NEW),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||||
|
TEST_F(IndexTest, LabelPropertyIndexFiltering) {
|
||||||
|
// We insert vertices with values:
|
||||||
|
// 0 0.0 1 1.0 2 2.0 3 3.0 4 4.0
|
||||||
|
// Then we check all combinations of inclusive and exclusive bounds.
|
||||||
|
// We also have a mix of doubles and integers to verify that they are sorted
|
||||||
|
// properly.
|
||||||
|
|
||||||
|
storage.CreateIndex(label1, prop_val);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto acc = storage.Access();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
auto vertex = CreateVertex(&acc);
|
||||||
|
ASSERT_NO_ERROR(vertex.AddLabel(label1));
|
||||||
|
ASSERT_NO_ERROR(vertex.SetProperty(
|
||||||
|
prop_val, i % 2 ? PropertyValue(i / 2) : PropertyValue(i / 2.0)));
|
||||||
|
}
|
||||||
|
acc.Commit();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto acc = storage.Access();
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc.Vertices(label1, prop_val, PropertyValue(i), View::OLD)),
|
||||||
|
UnorderedElementsAre(2 * i, 2 * i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// [1, +inf>
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val,
|
||||||
|
utils::MakeBoundInclusive(PropertyValue(1)),
|
||||||
|
std::nullopt, View::OLD)),
|
||||||
|
UnorderedElementsAre(2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
|
// <1, +inf>
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val,
|
||||||
|
utils::MakeBoundExclusive(PropertyValue(1)),
|
||||||
|
std::nullopt, View::OLD)),
|
||||||
|
UnorderedElementsAre(4, 5, 6, 7, 8, 9));
|
||||||
|
|
||||||
|
// <-inf, 3]
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, std::nullopt,
|
||||||
|
utils::MakeBoundInclusive(PropertyValue(3)),
|
||||||
|
View::OLD)),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4, 5, 6, 7));
|
||||||
|
// <-inf, 3>
|
||||||
|
EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, std::nullopt,
|
||||||
|
utils::MakeBoundExclusive(PropertyValue(3)),
|
||||||
|
View::OLD)),
|
||||||
|
UnorderedElementsAre(0, 1, 2, 3, 4, 5));
|
||||||
|
|
||||||
|
// [1, 3]
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc.Vertices(
|
||||||
|
label1, prop_val, utils::MakeBoundInclusive(PropertyValue(1)),
|
||||||
|
utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)),
|
||||||
|
UnorderedElementsAre(2, 3, 4, 5, 6, 7));
|
||||||
|
// <1, 3]
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc.Vertices(
|
||||||
|
label1, prop_val, utils::MakeBoundExclusive(PropertyValue(1)),
|
||||||
|
utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)),
|
||||||
|
UnorderedElementsAre(4, 5, 6, 7));
|
||||||
|
// [1, 3>
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc.Vertices(
|
||||||
|
label1, prop_val, utils::MakeBoundInclusive(PropertyValue(1)),
|
||||||
|
utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)),
|
||||||
|
UnorderedElementsAre(2, 3, 4, 5));
|
||||||
|
// <1, 3>
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetIds(acc.Vertices(
|
||||||
|
label1, prop_val, utils::MakeBoundExclusive(PropertyValue(1)),
|
||||||
|
utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)),
|
||||||
|
UnorderedElementsAre(4, 5));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user