LabelProperty index.

Summary:
Add return values.

After merge.

Inital working version. Still missing comments.

Update documentation.

Add checking for previous vlist and value equality.

After merge.

Remove functor, add boolean ffunction.

Build index.

More functionality. Start implementing tests.

Add tests.

Reviewers: matej.gradicek, mislav.bradac, mferencevic, buda, florijan

Reviewed By: mislav.bradac, buda, florijan

Subscribers: lion, florijan, teon.banek, buda, pullbot

Differential Revision: https://phabricator.memgraph.io/D355
This commit is contained in:
Dominik Gleich 2017-05-16 14:47:03 +02:00
parent 4b712595d9
commit 842901ecd2
18 changed files with 1128 additions and 133 deletions

View File

@ -2,10 +2,14 @@
#include <thread>
#include "cppitertools/filter.hpp"
#include "cppitertools/imap.hpp"
#include "data_structures/concurrent/concurrent_set.hpp"
#include "data_structures/concurrent/skiplist.hpp"
#include "database/graph_db_datatypes.hpp"
#include "database/indexes/key_index.hpp"
#include "database/indexes/label_property_index.hpp"
#include "mvcc/version_list.hpp"
#include "storage/deferred_deleter.hpp"
#include "storage/edge.hpp"
@ -85,6 +89,7 @@ class GraphDb {
// indexes
KeyIndex<GraphDbTypes::Label, Vertex> labels_index_;
KeyIndex<GraphDbTypes::EdgeType, Edge> edge_types_index_;
LabelPropertyIndex label_property_index_;
// Schedulers
Scheduler<std::mutex> gc_scheduler_;

View File

@ -45,10 +45,19 @@ VertexAccessor GraphDbAccessor::insert_vertex() {
throw CreationException("Unable to create a Vertex.");
}
void GraphDbAccessor::update_label_index(const GraphDbTypes::Label &label,
const VertexAccessor &vertex_accessor,
const Vertex *vertex) {
void GraphDbAccessor::update_label_indices(
const GraphDbTypes::Label &label, const VertexAccessor &vertex_accessor,
const Vertex *const vertex) {
this->db_.labels_index_.Update(label, vertex_accessor.vlist_, vertex);
this->db_.label_property_index_.UpdateOnLabel(label, vertex_accessor.vlist_,
vertex);
}
void GraphDbAccessor::update_property_index(
const GraphDbTypes::Property &property,
const RecordAccessor<Vertex> &record_accessor, const Vertex *const vertex) {
this->db_.label_property_index_.UpdateOnProperty(
property, record_accessor.vlist_, vertex);
}
size_t GraphDbAccessor::vertices_count() const {
@ -59,6 +68,15 @@ size_t GraphDbAccessor::vertices_count(const GraphDbTypes::Label &label) const {
return db_.labels_index_.Count(label);
}
size_t GraphDbAccessor::vertices_count(
const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property) const {
const LabelPropertyIndex::Key key(label, property);
debug_assert(db_.label_property_index_.IndexExists(key),
"Index doesn't exist.");
return db_.label_property_index_.Count(key);
}
bool GraphDbAccessor::remove_vertex(VertexAccessor &vertex_accessor) {
vertex_accessor.SwitchNew();
// it's possible the vertex was removed already in this transaction
@ -111,7 +129,7 @@ EdgeAccessor GraphDbAccessor::insert_edge(VertexAccessor &from,
void GraphDbAccessor::update_edge_type_index(
const GraphDbTypes::EdgeType &edge_type, const EdgeAccessor &edge_accessor,
const Edge *edge) {
const Edge *const edge) {
this->db_.edge_types_index_.Update(edge_type, edge_accessor.vlist_, edge);
}

View File

@ -24,6 +24,13 @@
*/
class GraphDbAccessor {
// We need to make friends with this guys since they need to access private
// methods for updating indices.
friend class RecordAccessor<Vertex>;
friend class RecordAccessor<Edge>;
friend class VertexAccessor;
friend class EdgeAccessor;
public:
/**
* Creates an accessor for the given database.
@ -120,6 +127,30 @@ class GraphDbAccessor {
db_.labels_index_.GetVlists(label, *transaction_, current_state));
}
/**
* Return VertexAccessors which contain the current label and property for the
* given transaction visibility.
* @param label - label for which to return VertexAccessors
* @param property - property for which to return VertexAccessors
* @param current_state If true then the graph state for the
* current transaction+command is returned (insertions, updates and
* deletions performed in the current transaction+command are not
* ignored).
* @return iterable collection
*/
auto vertices(const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property,
bool current_state = false) {
debug_assert(db_.label_property_index_.IndexExists(
LabelPropertyIndex::Key(label, property)),
"Label+property index doesn't exist.");
return iter::imap([this, current_state](
auto vlist) { return VertexAccessor(*vlist, *this); },
db_.label_property_index_.GetVlists(
LabelPropertyIndex::Key(label, property),
*transaction_, current_state));
}
/**
* Creates a new Edge and returns an accessor to it.
*
@ -184,24 +215,58 @@ class GraphDbAccessor {
}
/**
* Insert this vertex into corresponding label index.
* @param label - label index into which to insert vertex label record
* @param vertex_accessor - vertex_accessor to insert
* @param vertex - vertex record to insert
* @brief - Build the given label/property Index. BuildIndex shouldn't be
* called in the same transaction where you are creating/or modifying
* vertices/edges. It should be a part of a separate transaction. Blocks the
* caller until the index is ready for use. Throws exception if index already
* exists or is being created by another transaction.
* @param label - label to build for
* @param property - property to build for
*/
void update_label_index(const GraphDbTypes::Label &label,
const VertexAccessor &vertex_accessor,
const Vertex *vertex);
void BuildIndex(const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property) {
const LabelPropertyIndex::Key key(label, property);
if (db_.label_property_index_.CreateIndex(key) == false) {
throw utils::BasicException(
"Index is either being created by another transaction or already "
"exists.");
}
// Everything that happens after the line above ended will be added to the
// index automatically, but we still have to add to index everything that
// happened earlier. We have to first wait for every transaction that
// happend before, or a bit later than CreateIndex to end.
auto wait_transaction = db_.tx_engine.begin();
wait_transaction->wait_for_active_except(transaction_->id);
wait_transaction->commit();
// This transaction surely sees everything that happened before CreateIndex.
auto transaction = db_.tx_engine.begin();
for (auto vertex_vlist : db_.vertices_.access()) {
auto vertex_record = vertex_vlist->find(*transaction);
// Check if visible record exists, if it exists apply function on it.
if (vertex_record == nullptr) continue;
db_.label_property_index_.UpdateOnLabelProperty(vertex_vlist,
vertex_record);
}
// Commit transaction as we finished applying method on newest visible
// records.
transaction->commit();
// After these two operations we are certain that everything is contained in
// the index under the assumption that this transaction contained no
// vertex/edge insert/update before this method was invoked.
db_.label_property_index_.IndexFinishedBuilding(key);
}
/**
* Insert this edge into corresponding edge_type index.
* @param edge_type - edge_type index into which to insert record
* @param edge_accessor - edge_accessor to insert
* @param edge - edge record to insert
* @brief - Returns true if the given label+property index already exists and
* is ready for use.
*/
void update_edge_type_index(const GraphDbTypes::EdgeType &edge_type,
const EdgeAccessor &edge_accessor,
const Edge *edge);
bool LabelPropertyIndexExists(const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property) {
return db_.label_property_index_.IndexExists(
LabelPropertyIndex::Key(label, property));
}
/**
* Return approximate number of all vertices in the database.
@ -209,10 +274,10 @@ class GraphDbAccessor {
*/
size_t vertices_count() const;
/*
* Return approximate number of all edges in the database.
* Note that this is always an over-estimate and never an under-estimate.
*/
/*
* Return approximate number of all edges in the database.
* Note that this is always an over-estimate and never an under-estimate.
*/
size_t edges_count() const;
/**
@ -223,6 +288,18 @@ class GraphDbAccessor {
*/
size_t vertices_count(const GraphDbTypes::Label &label) const;
/**
* Return approximate number of vertices under indexes with the given label
* and property.
* Note that this is always an over-estimate and never an under-estimate.
* @param label - label to check for
* @param property - property to check for
* @return number of vertices with the given label, fails if no such
* label+property index exists.
*/
size_t vertices_count(const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property) const;
/**
* Return approximate number of edges under indexes with the given edge_type.
* Note that this is always an over-estimate and never an under-estimate.
@ -339,6 +416,37 @@ class GraphDbAccessor {
}
private:
/**
* Insert this vertex into corresponding label and label+property (if it
* exists) index.
* @param label - label with which to insert vertex label record
* @param vertex_accessor - vertex_accessor to insert
* @param vertex - vertex record to insert
*/
void update_label_indices(const GraphDbTypes::Label &label,
const VertexAccessor &vertex_accessor,
const Vertex *const vertex);
/**
* Insert this edge into corresponding edge_type index.
* @param edge_type - edge_type index into which to insert record
* @param edge_accessor - edge_accessor to insert
* @param edge - edge record to insert
*/
void update_edge_type_index(const GraphDbTypes::EdgeType &edge_type,
const EdgeAccessor &edge_accessor,
const Edge *const edge);
/**
* Insert this vertex into corresponding any label + 'property' index.
* @param property - vertex will be inserted into indexes which contain this
* property
* @param record_accessor - record_accessor to insert
* @param vertex - vertex to insert
*/
void update_property_index(const GraphDbTypes::Property &property,
const RecordAccessor<Vertex> &record_accessor,
const Vertex *const vertex);
GraphDb &db_;
/** The current transaction */

View File

@ -0,0 +1,128 @@
#pragma once
#include "cppitertools/filter.hpp"
#include "cppitertools/imap.hpp"
#include "data_structures/concurrent/concurrent_map.hpp"
#include "mvcc/version_list.hpp"
#include "transactions/transaction.hpp"
namespace IndexUtils {
/**
* @brief - Get all inserted vlists in TKey specific storage which
* still return true for the 'exists' function.
* @param index - index from which to get vlists
* @param t - current transaction, which determines visibility.
* @param exists - method which determines visibility of entry and version
* (record) of the underlying objects (vertex/edge)
* @param current_state If true then the graph state for the
* current transaction+command is returned (insertions, updates and
* deletions performed in the current transaction+command are not
* ignored).
* @Tparam TIndexEntry - index entry inside skiplist
* @Tparam TRecord - type of record under index (edge/vertex usually.)
* @return iterable collection of distinct vlist records<TRecord> for which
* exists function evaluates as true
*/
template <class TIndexEntry, class TRecord>
static auto GetVlists(
SkipList<TIndexEntry> &index, const tx::Transaction &t,
const std::function<bool(const TIndexEntry &, const TRecord *)> exists,
bool current_state = false) {
TIndexEntry *prev = nullptr;
auto filtered = iter::filter(
[&t, exists, prev, current_state](TIndexEntry &entry) mutable {
// Check if the current entry could offer new possible return value
// with respect to the previous entry we evaluated.
// We do this to guarantee uniqueness, and also as an optimization to
// avoid checking same vlist twice when we can.
if (prev && entry.IsAlreadyChecked(*prev)) return false;
prev = &entry;
// TODO when refactoring MVCC reconsider the return-value-arg idiom
// here
TRecord *old_record, *new_record;
entry.vlist_->find_set_old_new(t, old_record, new_record);
// filtering out records not visible to the current
// transaction+command
// taking into account the current_state flag
bool visible =
(old_record && !(current_state && old_record->is_deleted_by(t))) ||
(current_state && new_record && !new_record->is_deleted_by(t));
if (!visible) return false;
// if current_state is true and we have the new record, then that's
// the reference value, and that needs to be compared with the index
// predicate
return (current_state && new_record) ? exists(entry, new_record)
: exists(entry, old_record);
},
index.access());
return iter::imap([](auto entry) { return entry.vlist_; },
std::move(filtered));
}
/**
* @brief - Removes from the index all entries for which records don't contain
* the given label/edge type/label + property anymore. Also update (remove)
* all record which are not visible for any transaction with an id larger or
* equal to `id`. This method assumes that the MVCC GC has been run with the
* same 'id'.
* @param indices - map of index entries (TIndexKey, skiplist<TIndexEntry>)
* @param id - oldest active id, safe to remove everything deleted before this
* id.
* @param engine - transaction engine to see which records are commited
* @param exists - function which checks 'key' and 'entry' if the entry still
* contains required properties (key + optional value (in case of label_property
* index))
* @Tparam Tkey - index key
* @Tparam TIndexEntry - index entry inside skiplist
* @Tparam TRecord - type of record under index (edge/vertex usually.)
*/
template <class TKey, class TIndexEntry, class TRecord>
static void Refresh(
ConcurrentMap<TKey, SkipList<TIndexEntry> *> &indices, const Id &id,
tx::Engine &engine,
const std::function<bool(const TKey &, const TIndexEntry &)> exists) {
for (auto &key_indices_pair : indices.access()) {
auto indices_entries_accessor = key_indices_pair.second->access();
for (auto indices_entry : indices_entries_accessor) {
// Remove it from index if it's deleted before the current id, or it
// doesn't have that label/edge_type/label+property anymore. We need to
// be careful when we are deleting the record which is not visible
// anymore because even though that record is not visible, some other,
// newer record could be visible, and might contain the label, and might
// not be in the index - that's why we can't remove it completely, but
// just update it's record value.
// We also need to be extra careful when checking for existance of
// label/edge_type because that record could still be under the update
// operation and as such could be modified while we are reading the
// label/edge_type - that's why we have to wait for it's creation id to
// be lower than ours `id` as that means it's surely either commited or
// aborted as we always refresh with the oldest active id.
if (indices_entry.record_->is_not_visible_from(id, engine)) {
// We first have to insert and then remove, because otherwise we might
// have a situation where there exists a vlist with the record
// containg the label/edge_type/label+property but it's not in our
// index.
// Get the oldest not deleted version for current 'id' (MVCC GC takes
// care of this.)
auto new_record = indices_entry.vlist_->Oldest();
if (new_record != nullptr)
indices_entries_accessor.insert(
TIndexEntry(indices_entry, new_record));
auto success = indices_entries_accessor.remove(indices_entry);
debug_assert(success == true, "Not able to delete entry.");
} else if (indices_entry.record_->tx.cre() < id &&
!exists(key_indices_pair.first, indices_entry)) {
// Since id is the oldest active id, if the record has been created
// before it we are sure that it won't be modified anymore and that
// the creating transaction finished, that's why it's safe to check
// it, and potentially remove it from index.
indices_entries_accessor.remove(indices_entry);
}
}
}
}
}; // IndexUtils

View File

@ -1,11 +1,9 @@
#pragma once
#include "cppitertools/filter.hpp"
#include "cppitertools/imap.hpp"
#include "data_structures/concurrent/concurrent_map.hpp"
#include "database/graph_db.hpp"
#include "database/graph_db_datatypes.hpp"
#include "database/indexes/index_utils.hpp"
#include "mvcc/version_list.hpp"
#include "storage/edge.hpp"
#include "storage/vertex.hpp"
@ -20,13 +18,19 @@
template <typename TKey, typename TRecord>
class KeyIndex {
public:
KeyIndex(){};
KeyIndex(const KeyIndex &other) = delete;
KeyIndex(KeyIndex &&other) = delete;
KeyIndex &operator=(const KeyIndex &other) = delete;
KeyIndex &operator=(KeyIndex &&other) = delete;
/**
* @brief - Clear all indexes so that we don't leak memory.
*/
~KeyIndex() {
for (auto key_indices_pair : indices_.access())
for (auto key_indices_pair : indices_.access()) {
// Delete skiplist because we created it with a new operator.
delete key_indices_pair.second;
}
}
/**
* @brief - Add record, vlist, if new, to TKey specific storage.
@ -35,7 +39,7 @@ class KeyIndex {
* @param record - pointer to record entry to add (contained in vlist)
*/
void Update(const TKey &key, mvcc::VersionList<TRecord> *vlist,
const TRecord *record) {
const TRecord *const record) {
GetKeyStorage(key)->access().insert(IndexEntry(vlist, record));
}
@ -53,35 +57,12 @@ class KeyIndex {
*/
auto GetVlists(const TKey &key, tx::Transaction &t,
bool current_state = false) {
auto index = GetKeyStorage(key);
mvcc::VersionList<TRecord> *prev = nullptr;
auto filtered = iter::filter(
[this, &key, &t, prev, current_state](auto entry) mutable {
// we check for previous match first as an optimization
// it's legal because if the first v-list pair does not
// pass, neither will any other identical one
if (entry.vlist_ == prev) return false;
prev = entry.vlist_;
// TODO when refactoring MVCC reconsider the return-value-arg idiom here
TRecord *old_record, *new_record;
entry.vlist_->find_set_old_new(t, old_record, new_record);
// filtering out records not visible to the current
// transaction+command
// taking into account the current_state flag
bool visible =
(old_record &&
!(current_state && old_record->is_deleted_by(t))) ||
(current_state && new_record && !new_record->is_deleted_by(t));
if (!visible) return false;
// if we current_state and we have the new record, then that's the
// reference value, and that needs to be compared with the index
// predicate
return (current_state && new_record) ? Exists(key, new_record)
: Exists(key, old_record);
return IndexUtils::GetVlists<IndexEntry, TRecord>(
*GetKeyStorage(key), t,
[this, key](const IndexEntry &, const TRecord *record) {
return Exists(key, record);
},
index->access());
return iter::imap([this](auto entry) { return entry.vlist_; },
std::move(filtered));
current_state);
}
/**
@ -105,38 +86,10 @@ class KeyIndex {
* @param engine - transaction engine to see which records are commited
*/
void Refresh(const Id &id, tx::Engine &engine) {
for (auto &key_indices_pair : indices_.access()) {
auto indices_entries_accessor = key_indices_pair.second->access();
for (auto indices_entry : indices_entries_accessor) {
// Remove it from index if it's deleted before the current id, or it
// doesn't have that label/edge_type anymore. We need to be careful when
// we are deleting the record which is not visible anymore because even
// though that record is not visible, some other, newer record could be
// visible, and might contain the label, and might not be in the index -
// that's why we can't remove it completely, but just update it's record
// value.
// We also need to be extra careful when checking for existance of
// label/edge_type because that record could still be under the update
// operation and as such could be modified while we are reading the
// label/edge_type - that's why we have to wait for it's creation id to
// be lower than ours `id` as that means it's surely either commited or
// aborted as we always refresh with the oldest active id.
if (indices_entry.record_->is_not_visible_from(id, engine)) {
// We first have to insert and then remove, because otherwise we might
// have a situation where there exists a vlist with the record
// containg the label/edge_type but it's not in our index.
auto new_record = indices_entry.vlist_->Oldest();
if (new_record != nullptr)
indices_entries_accessor.insert(
IndexEntry(indices_entry.vlist_, new_record));
auto success = indices_entries_accessor.remove(indices_entry);
debug_assert(success == true, "Not able to delete entry.");
} else if (indices_entry.record_->tx.cre() < id &&
!Exists(key_indices_pair.first, indices_entry.record_)) {
indices_entries_accessor.remove(indices_entry);
}
}
}
return IndexUtils::Refresh<TKey, IndexEntry, TRecord>(
indices_, id, engine, [this](const TKey &key, const IndexEntry &entry) {
return Exists(key, entry.record_);
});
}
private:
@ -145,7 +98,10 @@ class KeyIndex {
*/
class IndexEntry : public TotalOrdering<IndexEntry> {
public:
IndexEntry(mvcc::VersionList<TRecord> *vlist, const TRecord *record)
IndexEntry(const IndexEntry &entry, const TRecord *const new_record)
: IndexEntry(entry.vlist_, new_record) {}
IndexEntry(mvcc::VersionList<TRecord> *const vlist,
const TRecord *const record)
: vlist_(vlist), record_(record) {}
// Comparision operators - we need them to keep this sorted inside
@ -162,8 +118,17 @@ class KeyIndex {
return this->vlist_ == other.vlist_ && this->record_ == other.record_;
}
mvcc::VersionList<TRecord> *vlist_;
const TRecord *record_;
/**
* @brief - Checks if previous IndexEntry has the same vlist as this
* IndexEntry.
* @return - true if the vlists match.
*/
bool IsAlreadyChecked(const IndexEntry &previous) const {
return previous.vlist_ == this->vlist_;
}
mvcc::VersionList<TRecord> *const vlist_;
const TRecord *const record_;
};
/**
@ -192,12 +157,12 @@ class KeyIndex {
* @param label - label to check for.
* @return true if it contains, false otherwise.
*/
bool Exists(const GraphDbTypes::Label &label, const Vertex *v) const {
bool Exists(const GraphDbTypes::Label &label, const Vertex *const v) const {
debug_assert(v != nullptr, "Vertex is nullptr.");
// We have to check for existance of label because the transaction
// might not see the label, or the label was deleted and not yet
// removed from the index.
auto labels = v->labels_;
const auto &labels = v->labels_;
return std::find(labels.begin(), labels.end(), label) != labels.end();
}
@ -206,7 +171,8 @@ class KeyIndex {
* @param edge_type - edge_type to check for.
* @return true if it has that edge_type, false otherwise.
*/
bool Exists(const GraphDbTypes::EdgeType &edge_type, const Edge *e) const {
bool Exists(const GraphDbTypes::EdgeType &edge_type,
const Edge *const e) const {
debug_assert(e != nullptr, "Edge is nullptr.");
// We have to check for equality of edge types because the transaction
// might not see the edge type, or the edge type was deleted and not yet

View File

@ -0,0 +1,371 @@
#pragma once
#include "data_structures/concurrent/concurrent_map.hpp"
#include "database/graph_db.hpp"
#include "database/graph_db_datatypes.hpp"
#include "database/indexes/index_utils.hpp"
#include "mvcc/version_list.hpp"
#include "storage/edge.hpp"
#include "storage/vertex.hpp"
#include "transactions/transaction.hpp"
#include "utils/total_ordering.hpp"
/**
* @brief Implements LabelPropertyIndex.
*/
class LabelPropertyIndex {
public:
LabelPropertyIndex(){};
LabelPropertyIndex(const LabelPropertyIndex &other) = delete;
LabelPropertyIndex(LabelPropertyIndex &&other) = delete;
LabelPropertyIndex &operator=(const LabelPropertyIndex &other) = delete;
LabelPropertyIndex &operator=(LabelPropertyIndex &&other) = delete;
/**
* @brief - Clear all indices so that we don't leak memory.
*/
~LabelPropertyIndex() {
for (auto key_indices_pair : indices_.access()) {
// Delete skiplist because we created it with a new operator.
delete key_indices_pair.second;
}
}
/**
* @brief - Contain Label + property, to be used as an index key.
*/
class Key : public TotalOrdering<Key> {
public:
const GraphDbTypes::Label label_;
const GraphDbTypes::Property property_;
Key(const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property)
: label_(label), property_(property) {}
// Comparison operators - we need them to keep this sorted inside skiplist.
bool operator<(const Key &other) const {
if (this->label_ != other.label_) return this->label_ < other.label_;
return this->property_ < other.property_;
}
bool operator==(const Key &other) const {
return this->label_ == other.label_ && this->property_ == other.property_;
}
};
/**
* @brief - Creates index with the given key if it doesn't exist. Note that
* you still need to populate the index with existing records.
* @return - True if it created the index, false if it already exists.
*/
bool CreateIndex(const Key &key) {
auto access = indices_.access();
// Avoid creation if it already exists.
auto iter = access.find(key);
if (iter != access.end()) return false;
auto skiplist = new SkipList<IndexEntry>;
auto ret = access.insert(key, skiplist);
// Avoid multithreaded memory leak if we don't delete skiplist and fail the
// insert (some other thread already inserted)
if (ret.second == false) delete skiplist;
return ret.second;
}
/**
* @brief - Notify that the index has been populated with everything it should
* be populated with, and can be used from this moment forward without missing
* any records.
* @param key - index which finished being populated.
*/
void IndexFinishedBuilding(const Key &key) {
ready_for_use_.access().insert(key);
}
/**
* @brief - Updates all indexes which should contain this vertex.
* @param vlist - pointer to vlist entry to add
* @param vertex - pointer to vertex record entry to add (contained in vlist)
*/
void UpdateOnLabelProperty(mvcc::VersionList<Vertex> *const vlist,
const Vertex *const vertex) {
const auto &labels = vertex->labels_;
for (auto index : indices_.access()) {
// Vertex has the given label
if (std::find(labels.begin(), labels.end(), index.first.label_) ==
labels.end())
continue;
auto prop = vertex->properties_.at(index.first.property_);
if (prop.type() != PropertyValue::Type::Null) {
// Property exists and vertex should be added to skiplist.
Insert(*index.second, prop, vlist, vertex);
}
}
}
/**
* @brief - Updates all indexes with `label` and any property in `vertex` that
* exists.
* @param label - indexes with this label might be updated if vertex contains
* the corresponding property.
* @param vlist - pointer to vlist entry to add
* @param vertex - pointer to vertex record entry to add (contained in vlist)
*/
void UpdateOnLabel(const GraphDbTypes::Label &label,
mvcc::VersionList<Vertex> *const vlist,
const Vertex *const vertex) {
for (auto index : indices_.access()) {
if (index.first.label_ != label) continue;
auto prop = vertex->properties_.at(index.first.property_);
if (prop.type() != PropertyValue::Type::Null) {
// Property exists and vertex should be added to skiplist.
Insert(*index.second, prop, vlist, vertex);
}
}
}
/**
* @brief - Updates all indexes with `property` and any label in `vertex` that
* exists.
* @param property - indexes with this property might be updated if vertex
* contains the corresponding label.
* @param vlist - pointer to vlist entry to add
* @param vertex - pointer to vertex record entry to add (contained in vlist)
*/
void UpdateOnProperty(const GraphDbTypes::Property &property,
mvcc::VersionList<Vertex> *const vlist,
const Vertex *const vertex) {
const auto &labels = vertex->labels_;
for (auto index : indices_.access()) {
if (index.first.property_ != property) continue;
if (std::find(labels.begin(), labels.end(), index.first.label_) !=
labels.end()) {
// Label exists and vertex should be added to skiplist.
Insert(*index.second, vertex->properties_.at(property), vlist, vertex);
}
}
}
/**
* @brief - Get all the inserted vlists in key specific storage which still
* have that label and property visible in this transaction.
* @param key - Label+Property to query.
* @param t - current transaction, which determines visibility.
* @param current_state If true then the graph state for the
* current transaction+command is returned (insertions, updates and
* deletions performed in the current transaction+command are not
* ignored).
* @return iterable collection of vlists of vertex records with the requested
* key sorted ascendingly by the property value.
*/
auto GetVlists(const Key &key, const tx::Transaction &t,
bool current_state = false) {
debug_assert(ready_for_use_.access().contains(key), "Index not yet ready.");
return IndexUtils::GetVlists<IndexEntry, Vertex>(
*GetKeyStorage(key), t,
[this, key](const IndexEntry &entry, const Vertex *const vertex) {
return Exists(key, entry.value_, vertex);
},
current_state);
}
/**
* @brief - Check for existance of index.
* @param key - Index key
* @return true if the index with that key exists
*/
bool IndexExists(const Key &key) {
return ready_for_use_.access().contains(key);
}
/**
* @brief - Return number of items in skiplist associated with the given
* key. This number could be imprecise because of the underlying skiplist
* storage. Use this as a hint, and not as a rule. Fails if index doesn't
* exist.
* Moreover, some transaction probably sees only part of the skiplist since
* not all versions are visible for it. Also, garbage collection might now
* have been run for some time so the index might have accumulated garbage.
* @param key - key to query for.
* @return number of items
*/
size_t Count(const Key &key) {
auto index = GetKeyStorage(key);
permanent_assert(index != nullptr, "Index doesn't exist.");
debug_assert(ready_for_use_.access().contains(key), "Index not yet ready.");
return index->access().size();
}
/**
* @brief - Removes from the index all entries for which records don't contain
* the given label anymore, or the record was deleted before this transaction
* id.
* @param id - oldest active id, safe to remove everything deleted before this
* id.
*/
void Refresh(const Id &id, tx::Engine &engine) {
return IndexUtils::Refresh<Key, IndexEntry, Vertex>(
indices_, id, engine, [this](const Key &key, const IndexEntry &entry) {
return Exists(key, entry.value_, entry.record_);
});
}
private:
/**
* @brief - Contains value, vlist and vertex record to distinguish between
* index entries.
*/
class IndexEntry : public TotalOrdering<IndexEntry> {
public:
IndexEntry(const IndexEntry &entry, const Vertex *new_record)
: IndexEntry(entry.value_, entry.vlist_, new_record) {}
IndexEntry(const PropertyValue &value, mvcc::VersionList<Vertex> *vlist,
const Vertex *record)
: value_(value), vlist_(vlist), record_(record) {}
// Comparision operators - we need them to keep this sorted inside
// skiplist.
bool operator<(const IndexEntry &other) const {
bool this_value_smaller = Cmp(this->value_, other.value_);
if (this_value_smaller || Cmp(other.value_, this->value_))
return this_value_smaller;
if (this->vlist_ != other.vlist_) return this->vlist_ < other.vlist_;
return this->record_ < other.record_;
}
bool operator==(const IndexEntry &other) const {
return !(*this < other) && !(other < *this);
}
/**
* @brief - For two property values - orders the records by type and then by
* value. Except for integers and doubles - those are both converted to
* double and then compared.
* @return true if the first property value is smaller( should be before)
* than the second one
*/
static bool Cmp(const PropertyValue &a, const PropertyValue &b) {
if (a.type() != b.type() &&
!(IsCastableToDouble(a) && IsCastableToDouble(b)))
return a.type() < b.type();
if (a.type() == b.type()) {
switch (a.type()) {
case PropertyValue::Type::Null:
return false;
case PropertyValue::Type::String:
return a.Value<std::string>() < b.Value<std::string>();
case PropertyValue::Type::Bool:
return a.Value<bool>() < b.Value<bool>();
case PropertyValue::Type::Int:
return a.Value<int64_t>() < b.Value<int64_t>();
case PropertyValue::Type::Double:
return a.Value<double>() < b.Value<double>();
case PropertyValue::Type::List: {
auto va = a.Value<std::vector<PropertyValue>>();
auto vb = b.Value<std::vector<PropertyValue>>();
if (va.size() != vb.size()) return va.size() < vb.size();
return lexicographical_compare(va.begin(), va.end(), vb.begin(),
vb.end(), Cmp);
}
default:
permanent_fail("Unimplemented type operator.");
}
}
// Types are int and double - convert int to double
return GetDouble(a) < GetDouble(b);
}
/**
* @brief - Return value casted to double. This is only possible for
* integers and doubles.
*/
static double GetDouble(const PropertyValue &value) {
debug_assert(value.type() == PropertyValue::Type::Int ||
value.type() == PropertyValue::Type::Double,
"Invalid data type.");
if (value.type() == PropertyValue::Type::Int)
return static_cast<double>(value.Value<int64_t>());
return value.Value<double>();
}
/**
* @brief - Return if this value is castable to double (returns true for
* integers and doubles).
*/
static bool IsCastableToDouble(const PropertyValue &value) {
return value.type() == PropertyValue::Type::Int ||
value.type() == PropertyValue::Type::Double;
}
/**
* @brief - Check if previous IndexEntry represents the same vlist/value
* pair.
* @return - true if IndexEntries are equal by the vlist/value pair.
*/
bool IsAlreadyChecked(const IndexEntry &previous) const {
return previous.vlist_ == this->vlist_ &&
!Cmp(previous.value_, this->value_) &&
!Cmp(this->value_, previous.value_);
}
const PropertyValue value_;
mvcc::VersionList<Vertex> *const vlist_{nullptr};
const Vertex *const record_{nullptr};
};
/**
* @brief - Insert value, vlist, vertex into corresponding index (key) if the
* index exists.
* @param index - into which index to add
* @param value - value which to add
* @param vlist - pointer to vlist entry to add
* @param vertex - pointer to vertex record entry to add (contained in vlist)
*/
void Insert(SkipList<IndexEntry> &index, const PropertyValue &value,
mvcc::VersionList<Vertex> *const vlist,
const Vertex *const vertex) {
index.access().insert(IndexEntry(value, vlist, vertex));
}
/**
* @brief - Get storage for this key.
* @param key - Label and and property for which to query.
* @return pointer to skiplist of IndexEntries, if none which matches key
* exists return nullptr
*/
SkipList<IndexEntry> *GetKeyStorage(const Key &key) {
auto access = indices_.access();
auto iter = access.find(key);
if (iter == access.end()) return nullptr;
return iter->second;
}
/**
* @brief - Check if Vertex contains label and property with the given
* value.
* @param key - label and parameter to check for.
* @param value - value of parameter to compare
* @return true if it contains, false otherwise.
*/
bool Exists(const Key &key, const PropertyValue &value,
const Vertex *const v) const {
debug_assert(v != nullptr, "Vertex is nullptr.");
// We have to check for existance of label because the transaction
// might not see the label, or the label was deleted and not yet
// removed from the index.
const auto &labels = v->labels_;
if (std::find(labels.begin(), labels.end(), key.label_) == labels.end())
return false;
auto prop = v->properties_.at(key.property_);
// Property doesn't exists.
if (prop.type() == PropertyValue::Type::Null) return false;
// Property value is the same as expected.
return !IndexEntry::Cmp(prop, value) && !IndexEntry::Cmp(value, prop);
}
ConcurrentMap<Key, SkipList<IndexEntry> *> indices_;
ConcurrentSet<Key> ready_for_use_;
};

View File

@ -35,21 +35,21 @@ class PropertyValue {
PropertyValue(double value) : type_(Type::Double) { double_v = value; }
/// constructors for non-primitive types (shared pointers)
PropertyValue(const std::string& value) : type_(Type::String) {
PropertyValue(const std::string &value) : type_(Type::String) {
new (&string_v) std::shared_ptr<std::string>(new std::string(value));
}
PropertyValue(const char* value) : type_(Type::String) {
PropertyValue(const char *value) : type_(Type::String) {
new (&string_v) std::shared_ptr<std::string>(new std::string(value));
}
PropertyValue(const std::vector<PropertyValue>& value) : type_(Type::List) {
PropertyValue(const std::vector<PropertyValue> &value) : type_(Type::List) {
new (&list_v) std::shared_ptr<std::vector<PropertyValue>>(
new std::vector<PropertyValue>(value));
}
// assignment op
PropertyValue& operator=(const PropertyValue& other);
PropertyValue &operator=(const PropertyValue &other);
PropertyValue(const PropertyValue& other);
PropertyValue(const PropertyValue &other);
~PropertyValue();
Type type() const { return type_; }
@ -64,8 +64,8 @@ class PropertyValue {
template <typename T>
T Value() const;
friend std::ostream& operator<<(std::ostream& stream,
const PropertyValue& prop);
friend std::ostream &operator<<(std::ostream &stream,
const PropertyValue &prop);
private:
// storage for the value of the property
@ -96,4 +96,4 @@ class PropertyValueException : public utils::StacktraceException {
};
// stream output
std::ostream& operator<<(std::ostream& os, const PropertyValue::Type type);
std::ostream &operator<<(std::ostream &os, const PropertyValue::Type type);

View File

@ -92,5 +92,18 @@ const TRecord &RecordAccessor<TRecord>::current() const {
return *current_;
}
template <>
void RecordAccessor<Vertex>::PropsSet(GraphDbTypes::Property key,
PropertyValue value) {
Vertex &vertex = update();
vertex.properties_.set(key, value);
this->db_accessor().update_property_index(key, *this, &vertex);
}
template <>
void RecordAccessor<Edge>::PropsSet(GraphDbTypes::Property key,
PropertyValue value) {
update().properties_.set(key, value);
}
template class RecordAccessor<Vertex>;
template class RecordAccessor<Edge>;

View File

@ -52,16 +52,12 @@ class RecordAccessor {
const PropertyValue &PropsAt(GraphDbTypes::Property key) const;
/**
* Sets a value on the record for the given property.
* Sets a value on the record for the given property, operates on edge.
*
* @tparam TValue Type of the value being set.
* @param key Property key.
* @param value The value to set.
*/
template <typename TValue>
void PropsSet(GraphDbTypes::Property key, TValue value) {
update().properties_.set(key, value);
}
void PropsSet(GraphDbTypes::Property key, PropertyValue value);
/**
* Erases the property for the given key.

View File

@ -18,7 +18,7 @@ bool VertexAccessor::add_label(GraphDbTypes::Label label) {
// not a duplicate label, add it
Vertex &vertex = update();
vertex.labels_.emplace_back(label);
this->db_accessor().update_label_index(label, *this, &vertex);
this->db_accessor().update_label_indices(label, *this, &vertex);
return true;
}

View File

@ -14,7 +14,7 @@ class CommitLog {
ABORTED = 2, // 10
};
bool is_active() const { return flags & ACTIVE; }
bool is_active() const { return flags == ACTIVE; }
bool is_committed() const { return flags & COMMITTED; }

View File

@ -3,7 +3,7 @@
#include "transactions/engine.hpp"
template <class id_t>
bool tx::Snapshot<id_t>::all_finished(Engine &engine) {
bool tx::Snapshot<id_t>::all_finished(Engine &engine) const {
for (auto &sid : active) {
if (engine.clog.is_active(sid)) {
return false;

View File

@ -22,7 +22,7 @@ class Snapshot {
Snapshot(Snapshot &&other) { active = std::move(other.active); }
// True if all transaction from snapshot have finished.
bool all_finished(Engine &engine);
bool all_finished(Engine &engine) const;
bool is_active(id_t xid) const {
return std::binary_search(active.begin(), active.end(), xid);
@ -30,7 +30,7 @@ class Snapshot {
// Return id of oldest transaction. None if there is no transactions in
// snapshot.
Option<Id> oldest_active() {
Option<Id> oldest_active() const {
auto n = active.size();
if (n > 0) {
Id min = active[0];
@ -54,9 +54,9 @@ class Snapshot {
active.erase(last, active.end());
}
const id_t &front() { return active.front(); }
const id_t &front() const { return active.front(); }
const id_t &back() { return active.back(); }
const id_t &back() const { return active.back(); }
size_t size() { return active.size(); }

View File

@ -18,13 +18,15 @@ Transaction::Transaction(const Id &id, const Snapshot<Id> &snapshot,
Engine &engine)
: id(id), cid(1), engine(engine), snapshot(snapshot) {}
void Transaction::wait_for_active() {
while (snapshot.size() > 0) {
auto sid = snapshot.back();
void Transaction::wait_for_active_except(const Id &id) const {
Snapshot<Id> local_snapshot = snapshot;
local_snapshot.remove(id);
while (local_snapshot.size() > 0) {
auto sid = local_snapshot.front();
while (engine.clog.fetch_info(sid).is_active()) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
snapshot.remove(sid);
local_snapshot.remove(sid);
}
}

View File

@ -24,9 +24,10 @@ class Transaction {
Transaction(const Transaction &) = delete;
Transaction(Transaction &&) = default;
// Blocks until all transactions from snapshot finish. After this method,
// snapshot will be empty.
void wait_for_active();
// Blocks until all transactions from snapshot finish, except the 'id' one.
// After this method, snapshot will be either empty or contain transaction
// with Id 'id'.
void wait_for_active_except(const Id &id) const;
void take_lock(RecordLock &lock);
void commit();
@ -52,7 +53,7 @@ class Transaction {
private:
// a snapshot of currently active transactions
Snapshot<Id> snapshot;
const Snapshot<Id> snapshot;
LockStore<RecordLock> locks;
};
}

View File

@ -2,8 +2,8 @@
#include "gtest/gtest.h"
#include "data_structures/ptr_int.hpp"
#include "database/graph_db_datatypes.hpp"
#include "database/graph_db_accessor.hpp"
#include "database/graph_db_datatypes.hpp"
#include "dbms/dbms.hpp"
#include "storage/vertex.hpp"

View File

@ -0,0 +1,197 @@
#include "gtest/gtest.h"
#include "database/graph_db.hpp"
#include "database/graph_db_datatypes.hpp"
#include "database/indexes/label_property_index.hpp"
#include "dbms/dbms.hpp"
class LabelPropertyIndexComplexTest : public ::testing::Test {
protected:
virtual void SetUp() {
auto accessor = dbms.active();
label = accessor->label("label");
property = accessor->property("property");
label2 = accessor->label("label2");
property2 = accessor->property("property2");
key = new LabelPropertyIndex::Key(label, property);
EXPECT_EQ(index.CreateIndex(*key), true);
index.IndexFinishedBuilding(*key);
t = engine.begin();
vlist = new mvcc::VersionList<Vertex>(*t);
engine.advance(t->id);
vertex = vlist->find(*t);
ASSERT_NE(vertex, nullptr);
vertex->labels_.push_back(label);
vertex->properties_.set(property, 0);
EXPECT_EQ(index.Count(*key), 0);
}
virtual void TearDown() {
delete key;
delete vlist;
}
public:
Dbms dbms;
LabelPropertyIndex index;
LabelPropertyIndex::Key *key;
tx::Engine engine;
tx::Transaction *t{nullptr};
mvcc::VersionList<Vertex> *vlist;
Vertex *vertex;
GraphDbTypes::Label label;
GraphDbTypes::Property property;
GraphDbTypes::Label label2;
GraphDbTypes::Property property2;
};
TEST(LabelPropertyIndex, CreateIndex) {
Dbms dbms;
auto accessor = dbms.active();
LabelPropertyIndex::Key key(accessor->label("test"),
accessor->property("test2"));
LabelPropertyIndex index;
EXPECT_EQ(index.CreateIndex(key), true);
EXPECT_EQ(index.CreateIndex(key), false);
}
TEST(LabelPropertyIndex, IndexExistance) {
Dbms dbms;
auto accessor = dbms.active();
LabelPropertyIndex::Key key(accessor->label("test"),
accessor->property("test2"));
LabelPropertyIndex index;
EXPECT_EQ(index.CreateIndex(key), true);
// Index doesn't exist - and can't be used untill it's been notified as built.
EXPECT_EQ(index.IndexExists(key), false);
index.IndexFinishedBuilding(key);
EXPECT_EQ(index.IndexExists(key), true);
}
TEST(LabelPropertyIndex, Count) {
Dbms dbms;
auto accessor = dbms.active();
auto label = accessor->label("label");
auto property = accessor->property("property");
LabelPropertyIndex::Key key(label, property);
LabelPropertyIndex index;
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(index.Count(key), "Index doesn't exist.");
EXPECT_EQ(index.CreateIndex(key), true);
EXPECT_DEATH(index.Count(key), "Index not yet ready.");
index.IndexFinishedBuilding(key);
EXPECT_EQ(index.Count(key), 0);
}
// Add on label+property to index.
TEST_F(LabelPropertyIndexComplexTest, UpdateOnLabelPropertyTrue) {
index.UpdateOnLabelProperty(vlist, vertex);
EXPECT_EQ(index.Count(*key), 1);
}
// Try adding on label+property but fail because labels are clear.
TEST_F(LabelPropertyIndexComplexTest, UpdateOnLabelPropertyFalse) {
vertex->labels_.clear();
index.UpdateOnLabelProperty(vlist, vertex);
EXPECT_EQ(index.Count(*key), 0);
}
// Add on label to index.
TEST_F(LabelPropertyIndexComplexTest, UpdateOnLabelTrue) {
index.UpdateOnLabel(label, vlist, vertex);
EXPECT_EQ(index.Count(*key), 1);
}
// Try adding on label but fail because label is wrong.
TEST_F(LabelPropertyIndexComplexTest, UpdateOnLabelFalse) {
index.UpdateOnLabel(label2, vlist, vertex);
EXPECT_EQ(index.Count(*key), 0);
}
// Add on property to index.
TEST_F(LabelPropertyIndexComplexTest, UpdateOnPropertyTrue) {
index.UpdateOnProperty(property, vlist, vertex);
EXPECT_EQ(index.Count(*key), 1);
}
// Try adding on property but fail because property is wrong.
TEST_F(LabelPropertyIndexComplexTest, UpdateOnPropertyFalse) {
index.UpdateOnProperty(property2, vlist, vertex);
EXPECT_EQ(index.Count(*key), 0);
}
// Test index does it insert everything uniquely
TEST_F(LabelPropertyIndexComplexTest, UniqueInsert) {
index.UpdateOnLabelProperty(vlist, vertex);
index.UpdateOnLabelProperty(vlist, vertex);
EXPECT_EQ(index.Count(*key), 1);
}
// Check if index filters duplicates.
TEST_F(LabelPropertyIndexComplexTest, UniqueFilter) {
index.UpdateOnLabelProperty(vlist, vertex);
t->commit();
auto t2 = engine.begin();
auto vertex2 = vlist->update(*t2);
t2->commit();
index.UpdateOnLabelProperty(vlist, vertex2);
EXPECT_EQ(index.Count(*key), 2);
auto t3 = engine.begin();
auto iter = index.GetVlists(*key, *t3);
EXPECT_EQ(std::distance(iter.begin(), iter.end()), 1);
t3->commit();
}
// Remove label and check if index vertex is not returned now.
TEST_F(LabelPropertyIndexComplexTest, RemoveLabel) {
index.UpdateOnLabelProperty(vlist, vertex);
auto iter1 = index.GetVlists(*key, *t);
EXPECT_EQ(std::distance(iter1.begin(), iter1.end()), 1);
vertex->labels_.clear();
auto iter2 = index.GetVlists(*key, *t);
EXPECT_EQ(std::distance(iter2.begin(), iter2.end()), 0);
}
// Remove property and check if vertex is not returned now.
TEST_F(LabelPropertyIndexComplexTest, RemoveProperty) {
index.UpdateOnLabelProperty(vlist, vertex);
auto iter1 = index.GetVlists(*key, *t);
EXPECT_EQ(std::distance(iter1.begin(), iter1.end()), 1);
vertex->properties_.clear();
auto iter2 = index.GetVlists(*key, *t);
EXPECT_EQ(std::distance(iter2.begin(), iter2.end()), 0);
}
// Refresh with a vertex that looses its labels and properties.
TEST_F(LabelPropertyIndexComplexTest, Refresh) {
index.UpdateOnLabelProperty(vlist, vertex);
t->commit();
EXPECT_EQ(index.Count(*key), 1);
vertex->labels_.clear();
vertex->properties_.clear();
index.Refresh(engine.count() + 1, engine);
auto iter = index.GetVlists(*key, *t);
EXPECT_EQ(std::distance(iter.begin(), iter.end()), 0);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,9 +1,12 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
#include "data_structures/ptr_int.hpp"
#include "database/graph_db_accessor.hpp"
#include "dbms/dbms.hpp"
#include "logging/streams/stdout.hpp"
using testing::UnorderedElementsAreArray;
@ -12,7 +15,7 @@ auto Count(TIterable iterable) {
return std::distance(iterable.begin(), iterable.end());
}
TEST(GraphDbAccessor, VertexCount) {
TEST(GraphDbAccessor, VertexByLabelCount) {
Dbms dbms;
auto dba = dbms.active();
auto lab1 = dba->label("lab1");
@ -30,7 +33,57 @@ TEST(GraphDbAccessor, VertexCount) {
EXPECT_EQ(dba->vertices_count(), 28);
}
TEST(GraphDbAccessor, EdgeCount) {
TEST(GraphDbAccessor, VertexByLabelPropertyCount) {
Dbms dbms;
auto dba = dbms.active();
auto lab1 = dba->label("lab1");
auto lab2 = dba->label("lab2");
auto prop1 = dba->property("prop1");
auto prop2 = dba->property("prop2");
dba->BuildIndex(lab1, prop1);
dba->BuildIndex(lab1, prop2);
dba->BuildIndex(lab2, prop1);
dba->BuildIndex(lab2, prop2);
EXPECT_EQ(dba->vertices_count(lab1, prop1), 0);
EXPECT_EQ(dba->vertices_count(lab1, prop2), 0);
EXPECT_EQ(dba->vertices_count(lab2, prop1), 0);
EXPECT_EQ(dba->vertices_count(lab2, prop2), 0);
EXPECT_EQ(dba->vertices_count(), 0);
for (int i = 0; i < 14; ++i) {
VertexAccessor vertex = dba->insert_vertex();
vertex.add_label(lab1);
vertex.PropsSet(prop1, 1);
}
for (int i = 0; i < 15; ++i) {
auto vertex = dba->insert_vertex();
vertex.add_label(lab1);
vertex.PropsSet(prop2, 2);
}
for (int i = 0; i < 16; ++i) {
auto vertex = dba->insert_vertex();
vertex.add_label(lab2);
vertex.PropsSet(prop1, 3);
}
for (int i = 0; i < 17; ++i) {
auto vertex = dba->insert_vertex();
vertex.add_label(lab2);
vertex.PropsSet(prop2, 4);
}
// even though xxx_count functions in GraphDbAccessor can over-estimate
// in this situation they should be exact (nothing was ever deleted)
EXPECT_EQ(dba->vertices_count(lab1, prop1), 14);
EXPECT_EQ(dba->vertices_count(lab1, prop2), 15);
EXPECT_EQ(dba->vertices_count(lab2, prop1), 16);
EXPECT_EQ(dba->vertices_count(lab2, prop2), 17);
EXPECT_EQ(dba->vertices_count(), 14 + 15 + 16 + 17);
}
TEST(GraphDbAccessor, EdgeByEdgeTypeCount) {
Dbms dbms;
auto dba = dbms.active();
auto t1 = dba->edge_type("t1");
@ -50,8 +103,144 @@ TEST(GraphDbAccessor, EdgeCount) {
EXPECT_EQ(dba->edges_count(), 28);
}
TEST(GraphDbAccessor, VisibilityAfterInsertion) {
// Check if build index adds old vertex entries (ones before the index was
// created)
TEST(GraphDbAccessor, BuildIndexOnOld) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
auto vertex_accessor = dba->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, 0);
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(dba->vertices_count(label, property), "Index doesn't exist.");
dba->commit();
auto dba2 = dbms.active();
dba2->BuildIndex(label, property);
dba2->commit();
auto dba3 = dbms.active();
// Index is built and vertex is automatically added inside
EXPECT_EQ(dba3->vertices_count(label, property), 1);
EXPECT_EQ(Count(dba3->vertices(label, property)), 1);
dba3->commit();
}
// Try to build index two times
TEST(GraphDbAccessor, BuildIndexDouble) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
dba->BuildIndex(label, property);
EXPECT_THROW(dba->BuildIndex(label, property), utils::BasicException);
}
// Inserts integers, double, lists, booleans into index and check if they are
// sorted as they should be sorted.
TEST(GraphDbAccessor, SortedLabelPropertyEntries) {
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("lab1");
auto property = dba->property("prop1");
dba->BuildIndex(label, property);
dba->commit();
auto dba2 = dbms.active();
std::vector<PropertyValue> expected_property_value(50, 0);
// strings
for (int i = 0; i < 10; ++i) {
auto vertex_accessor = dba2->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property,
static_cast<std::string>(std::to_string(i)));
expected_property_value[i] = vertex_accessor.PropsAt(property);
}
// bools - insert in reverse to check for comparison between values.
for (int i = 9; i >= 0; --i) {
auto vertex_accessor = dba2->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, static_cast<bool>(i / 5));
expected_property_value[10 + i] = vertex_accessor.PropsAt(property);
}
// integers
for (int i = 0; i < 10; ++i) {
auto vertex_accessor = dba2->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, i);
expected_property_value[20 + 2 * i] = vertex_accessor.PropsAt(property);
}
// doubles
for (int i = 0; i < 10; ++i) {
auto vertex_accessor = dba2->insert_vertex();
vertex_accessor.add_label(label);
vertex_accessor.PropsSet(property, static_cast<double>(i + 0.5));
expected_property_value[20 + 2 * i + 1] = vertex_accessor.PropsAt(property);
}
// lists of ints - insert in reverse to check for comparision between lists.
for (int i = 9; i >= 0; --i) {
auto vertex_accessor = dba2->insert_vertex();
vertex_accessor.add_label(label);
std::vector<PropertyValue> value;
value.push_back(PropertyValue(i));
vertex_accessor.PropsSet(property, value);
expected_property_value[40 + i] = vertex_accessor.PropsAt(property);
}
EXPECT_EQ(Count(dba2->vertices(label, property, false)), 0);
EXPECT_EQ(Count(dba2->vertices(label, property, true)), 50);
int cnt = 0;
for (auto vertex : dba2->vertices(label, property, true)) {
const PropertyValue &property_value = vertex.PropsAt(property);
EXPECT_EQ(property_value.type(), expected_property_value[cnt].type());
switch (property_value.type()) {
case PropertyValue::Type::Bool:
EXPECT_EQ(property_value.Value<bool>(),
expected_property_value[cnt].Value<bool>());
break;
case PropertyValue::Type::Double:
EXPECT_EQ(property_value.Value<double>(),
expected_property_value[cnt].Value<double>());
break;
case PropertyValue::Type::Int:
EXPECT_EQ(property_value.Value<int64_t>(),
expected_property_value[cnt].Value<int64_t>());
break;
case PropertyValue::Type::String:
EXPECT_EQ(property_value.Value<std::string>(),
expected_property_value[cnt].Value<std::string>());
break;
case PropertyValue::Type::List: {
auto received_value =
property_value.Value<std::vector<PropertyValue>>();
auto expected_value =
expected_property_value[cnt].Value<std::vector<PropertyValue>>();
EXPECT_EQ(received_value.size(), expected_value.size());
EXPECT_EQ(received_value.size(), 1);
EXPECT_EQ(received_value[0].Value<int64_t>(),
expected_value[0].Value<int64_t>());
break;
}
case PropertyValue::Type::Null:
ASSERT_FALSE("Invalid value type.");
}
++cnt;
}
}
TEST(GraphDbAccessor, VisibilityAfterInsertion) {
Dbms dbms;
auto dba = dbms.active();
auto v1 = dba->insert_vertex();
@ -85,12 +274,10 @@ TEST(GraphDbAccessor, VisibilityAfterInsertion) {
}
TEST(GraphDbAccessor, VisibilityAfterDeletion) {
Dbms dbms;
auto dba = dbms.active();
auto lab = dba->label("lab");
for (int i = 0; i < 5; ++i)
dba->insert_vertex().add_label(lab);
for (int i = 0; i < 5; ++i) dba->insert_vertex().add_label(lab);
dba->advance_command();
auto type = dba->edge_type("type");
for (int j = 0; j < 3; ++j) {
@ -106,8 +293,7 @@ TEST(GraphDbAccessor, VisibilityAfterDeletion) {
// delete two edges
auto edges_it = dba->edges().begin();
for (int k = 0; k < 2; ++k)
dba->remove_edge(*edges_it++);
for (int k = 0; k < 2; ++k) dba->remove_edge(*edges_it++);
EXPECT_EQ(Count(dba->edges(type)), 3);
EXPECT_EQ(Count(dba->edges(type, true)), 1);
dba->advance_command();
@ -116,11 +302,15 @@ TEST(GraphDbAccessor, VisibilityAfterDeletion) {
// detach-delete 2 vertices
auto vertices_it = dba->vertices().begin();
for (int k = 0; k < 2; ++k)
dba->detach_remove_vertex(*vertices_it++);
for (int k = 0; k < 2; ++k) dba->detach_remove_vertex(*vertices_it++);
EXPECT_EQ(Count(dba->vertices(lab)), 5);
EXPECT_EQ(Count(dba->vertices(lab, true)), 3);
dba->advance_command();
EXPECT_EQ(Count(dba->vertices(lab)), 3);
EXPECT_EQ(Count(dba->vertices(lab, true)), 3);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}