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();
+}