diff --git a/src/database/graph_db.hpp b/src/database/graph_db.hpp index 8ce84cf8e..6322a968f 100644 --- a/src/database/graph_db.hpp +++ b/src/database/graph_db.hpp @@ -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_; diff --git a/src/database/graph_db_accessor.cpp b/src/database/graph_db_accessor.cpp index 8e6176a2f..53618f62e 100644 --- a/src/database/graph_db_accessor.cpp +++ b/src/database/graph_db_accessor.cpp @@ -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); } diff --git a/src/database/graph_db_accessor.hpp b/src/database/graph_db_accessor.hpp index 33c66ba20..7190494db 100644 --- a/src/database/graph_db_accessor.hpp +++ b/src/database/graph_db_accessor.hpp @@ -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 */ diff --git a/src/database/indexes/index_utils.hpp b/src/database/indexes/index_utils.hpp new file mode 100644 index 000000000..1b63e96d8 --- /dev/null +++ b/src/database/indexes/index_utils.hpp @@ -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 diff --git a/src/database/indexes/key_index.hpp b/src/database/indexes/key_index.hpp index d9bc3a25c..f1bf00d44 100644 --- a/src/database/indexes/key_index.hpp +++ b/src/database/indexes/key_index.hpp @@ -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 diff --git a/src/database/indexes/label_property_index.hpp b/src/database/indexes/label_property_index.hpp new file mode 100644 index 000000000..a630a7b57 --- /dev/null +++ b/src/database/indexes/label_property_index.hpp @@ -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_; +}; diff --git a/src/storage/property_value.hpp b/src/storage/property_value.hpp index 3c61f69c2..8e470dc0f 100644 --- a/src/storage/property_value.hpp +++ b/src/storage/property_value.hpp @@ -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); diff --git a/src/storage/record_accessor.cpp b/src/storage/record_accessor.cpp index e712eea7b..6572b45b0 100644 --- a/src/storage/record_accessor.cpp +++ b/src/storage/record_accessor.cpp @@ -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>; diff --git a/src/storage/record_accessor.hpp b/src/storage/record_accessor.hpp index 00b31c26a..3b923d716 100644 --- a/src/storage/record_accessor.hpp +++ b/src/storage/record_accessor.hpp @@ -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. diff --git a/src/storage/vertex_accessor.cpp b/src/storage/vertex_accessor.cpp index bffc1e8d7..bc6f2933d 100644 --- a/src/storage/vertex_accessor.cpp +++ b/src/storage/vertex_accessor.cpp @@ -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; } diff --git a/src/transactions/commit_log.hpp b/src/transactions/commit_log.hpp index c0dfecfd4..d46e60f17 100644 --- a/src/transactions/commit_log.hpp +++ b/src/transactions/commit_log.hpp @@ -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; } diff --git a/src/transactions/snapshot.cpp b/src/transactions/snapshot.cpp index d30e02c84..e4b2c6b92 100644 --- a/src/transactions/snapshot.cpp +++ b/src/transactions/snapshot.cpp @@ -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; diff --git a/src/transactions/snapshot.hpp b/src/transactions/snapshot.hpp index 1919ea791..cd4926fa4 100644 --- a/src/transactions/snapshot.hpp +++ b/src/transactions/snapshot.hpp @@ -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(); } diff --git a/src/transactions/transaction.cpp b/src/transactions/transaction.cpp index 681eadd72..9e4a1e9ce 100644 --- a/src/transactions/transaction.cpp +++ b/src/transactions/transaction.cpp @@ -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); } } diff --git a/src/transactions/transaction.hpp b/src/transactions/transaction.hpp index e00909639..4b3e7a342 100644 --- a/src/transactions/transaction.hpp +++ b/src/transactions/transaction.hpp @@ -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; }; } diff --git a/tests/unit/database_key_index.cpp b/tests/unit/database_key_index.cpp index 92800944b..b3b9d776d 100644 --- a/tests/unit/database_key_index.cpp +++ b/tests/unit/database_key_index.cpp @@ -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" diff --git a/tests/unit/database_label_property_index.cpp b/tests/unit/database_label_property_index.cpp new file mode 100644 index 000000000..19e203b3b --- /dev/null +++ b/tests/unit/database_label_property_index.cpp @@ -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(); +} diff --git a/tests/unit/graph_db_accessor_index_api.cpp b/tests/unit/graph_db_accessor_index_api.cpp index 9f09a0e75..0fa6ac0e0 100644 --- a/tests/unit/graph_db_accessor_index_api.cpp +++ b/tests/unit/graph_db_accessor_index_api.cpp @@ -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(); +}