Merge changes I439374af,I2c978762,Ib9c0c4d9

* changes:
  Integrate DataQuery
  Implement DataQuery
  tests: Initialize schema once for the whole test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ccf0aeb..34c6fb2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -78,7 +78,7 @@
 add_library(utils STATIC
     src/utils.cpp
     )
-target_link_libraries(utils path Boost::boost)
+target_link_libraries(utils path ast_values Boost::boost)
 
 add_library(schemas STATIC
     src/static_schema.cpp
@@ -88,6 +88,7 @@
 
 add_library(datastoreaccess STATIC
     src/datastore_access.cpp
+    src/data_query.cpp
     )
 target_link_libraries(datastoreaccess PUBLIC Boost::boost)
 
@@ -112,7 +113,7 @@
     src/yang_schema.cpp
     src/libyang_utils.cpp
     )
-target_link_libraries(yangschema schemas ${LIBYANG_LIBRARIES})
+target_link_libraries(yangschema schemas utils ${LIBYANG_LIBRARIES})
 # Ensure that this doesn't override Boost's -isystem -- see the log for details.
 target_include_directories(yangschema SYSTEM PRIVATE ${LIBYANG_INCLUDEDIR})
 link_directories(${LIBYANG_LIBRARY_DIRS})
@@ -213,10 +214,17 @@
     function(setup_datastore_tests)
         add_test(NAME setup_netopeer COMMAND ${SYSREPOCFG_EXECUTABLE} ietf-netconf-server -i ${CMAKE_CURRENT_SOURCE_DIR}/tests/netopeer-test-config.xml --datastore=startup --format=xml)
         add_test(NAME start_daemons COMMAND ${FAKEROOT_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/start_daemons.sh)
+        add_test(NAME example-schema_init
+            COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/sysrepoctl-manage-module.sh ${SYSREPOCTL_EXECUTABLE} ${SYSREPOCFG_EXECUTABLE} install ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang )
+        add_test(NAME example-schema_cleanup
+            COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/sysrepoctl-manage-module.sh ${SYSREPOCTL_EXECUTABLE} ${SYSREPOCFG_EXECUTABLE} uninstall ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang )
         add_test(NAME kill_daemons COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/kill_daemons.sh)
-        set_tests_properties(setup_netopeer start_daemons kill_daemons PROPERTIES RESOURCE_LOCK sysrepo)
+        set_tests_properties(example-schema_init PROPERTIES FIXTURES_REQUIRED netopeer_running FIXTURES_SETUP example-schema_setup)
+        set_tests_properties(example-schema_cleanup PROPERTIES FIXTURES_CLEANUP example-schema_setup)
+        set_tests_properties(setup_netopeer start_daemons kill_daemons example-schema_init example-schema_cleanup PROPERTIES RESOURCE_LOCK sysrepo)
         set_tests_properties(setup_netopeer PROPERTIES FIXTURES_SETUP netopeer_configured)
         set_tests_properties(start_daemons PROPERTIES FIXTURES_REQUIRED netopeer_configured FIXTURES_SETUP netopeer_running)
+        set_property(TEST kill_daemons APPEND PROPERTY DEPENDS example-schema_cleanup)
     endfunction()
 
     function(cli_test name)
@@ -229,21 +237,18 @@
                 tests/${name}.cpp
                 )
         endif()
-        target_link_libraries(test_${name} DoctestIntegration parser)
+        target_link_libraries(test_${name} DoctestIntegration parser datastoreaccess)
         if(NOT CMAKE_CROSSCOMPILING)
             add_test(test_${name} test_${name})
         endif()
         target_include_directories(test_${name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
     endfunction()
 
-    function(datastore_test_impl name model_path backend)
+    function(datastore_test_impl name model backend)
         set(TESTNAME test_${name}_${backend})
         cli_test(${name}_${backend} ${name}.cpp)
-        add_test(NAME ${TESTNAME}_init COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/sysrepoctl-manage-module.sh ${SYSREPOCTL_EXECUTABLE} ${SYSREPOCFG_EXECUTABLE} install ${model_path})
-        add_test(NAME ${TESTNAME}_cleanup COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/sysrepoctl-manage-module.sh ${SYSREPOCTL_EXECUTABLE} ${SYSREPOCFG_EXECUTABLE} uninstall ${model_path})
-        set_tests_properties(${TESTNAME}_init PROPERTIES FIXTURES_REQUIRED netopeer_running FIXTURES_SETUP ${TESTNAME}_setup)
-        set_tests_properties(${TESTNAME} PROPERTIES FIXTURES_REQUIRED ${TESTNAME}_setup RESOURCE_LOCK sysrepo)
-        set_tests_properties(${TESTNAME}_cleanup PROPERTIES FIXTURES_CLEANUP ${TESTNAME}_setup)
+        set_tests_properties(${TESTNAME} PROPERTIES FIXTURES_REQUIRED ${model}_setup RESOURCE_LOCK sysrepo)
+        set_property(TEST ${TESTNAME} APPEND PROPERTY FIXTURES_REQUIRED netopeer_running)
         target_include_directories(${TESTNAME} PRIVATE ${PROJECT_SOURCE_DIR}/tests/mock)
         if (${backend} STREQUAL "sysrepo")
             target_link_libraries(${TESTNAME} sysrepoaccess)
@@ -257,13 +262,9 @@
         target_compile_definitions(${TESTNAME} PRIVATE ${backend}_BACKEND)
     endfunction()
 
-    function(datastore_test name model_path)
-        set(TESTNAME test_${name})
-        datastore_test_impl(${name} ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang sysrepo)
-        datastore_test_impl(${name} ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang netconf)
-        set_tests_properties(${TESTNAME}_netconf_init PROPERTIES DEPENDS ${TESTNAME}_sysrepo_cleanup)
-        set_property(TEST kill_daemons APPEND PROPERTY DEPENDS ${TESTNAME}_sysrepo_cleanup)
-        set_property(TEST kill_daemons APPEND PROPERTY DEPENDS ${TESTNAME}_netconf_cleanup)
+    function(datastore_test name model)
+        datastore_test_impl(${name} ${model} sysrepo)
+        datastore_test_impl(${name} ${model} netconf)
     endfunction()
 
     cli_test(cd)
@@ -280,9 +281,11 @@
     cli_test(parser_methods)
     cli_test(path_utils)
     target_link_libraries(test_path_utils path)
+    cli_test(keyvalue_completion)
 
     setup_datastore_tests()
-    datastore_test(datastore_access ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang)
+    datastore_test(datastore_access example-schema)
+    datastore_test(data_query example-schema)
 endif()
 
 option(WITH_PYTHON_BINDINGS "Create and install Python3 bindings for accessing datastores" OFF)
@@ -296,7 +299,7 @@
         configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/python_netconfaccess.py
             ${CMAKE_CURRENT_BINARY_DIR}/tests_python_netconfaccess.py @ONLY)
         add_test(NAME test_netconf_cli_py COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/tests_python_netconfaccess.py)
-        set_tests_properties(test_netconf_cli_py PROPERTIES FIXTURES_REQUIRED netopeer_running RESOURCE_LOCK sysrepo DEPENDS test_datastore_access_netconf_cleanup)
+        set_tests_properties(test_netconf_cli_py PROPERTIES FIXTURES_REQUIRED netopeer_running RESOURCE_LOCK sysrepo DEPENDS example-schema_cleanup)
         set_property(TEST kill_daemons APPEND PROPERTY DEPENDS test_netconf_cli_py)
 
         set(sanitizer_active OFF)
diff --git a/src/ast_handlers.cpp b/src/ast_handlers.cpp
index ca5fe25..8aa6470 100644
--- a/src/ast_handlers.cpp
+++ b/src/ast_handlers.cpp
@@ -1,10 +1,13 @@
 #include "ast_handlers.hpp"
-std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::set<std::string> currentSet)
+std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentKeys)
 {
     std::set<std::string> missingKeys;
-    std::set_difference(keysNeeded.begin(), keysNeeded.end(),
-            currentSet.begin(), currentSet.end(),
-            std::inserter(missingKeys, missingKeys.end()));
+
+    for (const auto& key : keysNeeded) {
+        if (currentKeys.find(key) == currentKeys.end()) {
+            missingKeys.insert(key);
+        }
+    }
 
     std::set<std::string> res;
 
@@ -13,3 +16,12 @@
                    [] (auto it) { return it + "="; });
     return res;
 }
+
+std::string leafDataToCompletion(const leaf_data_& value)
+{
+    // Only string-like values need to be quoted
+    if (value.type() == typeid(std::string)) {
+        return escapeListKeyString(leafDataToString(value));
+    }
+    return leafDataToString(value);
+}
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index e59fc36..6ecfbc5 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -30,7 +30,7 @@
             _pass(context) = false;
             parserContext.m_errorMsg = "Key \"" + ast.first + "\" was entered more than once.";
         } else {
-            parserContext.m_tmpListKeys.insert(ast.first);
+            parserContext.m_tmpListKeys.insert({ast.first, ast.second});
         }
     }
 
@@ -68,11 +68,11 @@
 
         if (schema.listHasKey(location, list, ast)) {
             schemaNode_ listNode;
-            listNode.m_prefix = parserContext.m_curModule ? boost::optional<module_>{{*parserContext.m_curModule}} : boost::none;
+            listNode.m_prefix = parserContext.m_curModule.flat_map([] (auto mod) { return boost::optional<module_>{{mod}}; });;
             listNode.m_suffix = list_{parserContext.m_tmpListName};
             location.m_nodes.push_back(listNode);
             parserContext.m_tmpListKeyLeafPath.m_location = location;
-            parserContext.m_tmpListKeyLeafPath.m_node = {boost::none, ast};
+            parserContext.m_tmpListKeyLeafPath.m_node = { parserContext.m_curModule, ast };
         } else {
             parserContext.m_errorMsg = parserContext.m_tmpListName + " is not indexed by \"" + ast + "\".";
             _pass(context) = false;
@@ -577,7 +577,7 @@
     }
 };
 
-std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::set<std::string> currentSet);
+std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentSet);
 
 struct createKeySuggestions_class {
     template <typename T, typename Iterator, typename Context>
@@ -593,6 +593,42 @@
     }
 };
 
+std::string leafDataToCompletion(const leaf_data_& value);
+
+struct createValueSuggestions_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        if (!parserContext.m_completing) {
+            return;
+        }
+        const auto& dataQuery = parserContext.m_dataquery;
+
+        parserContext.m_completionIterator = begin;
+        auto listInstances = dataQuery->listKeys(parserContext.currentDataPath(), {parserContext.m_curModule, parserContext.m_tmpListName});
+
+        decltype(listInstances) filteredInstances;
+        //This filters out instances, which don't correspond to the partial instance we have.
+        const auto partialFitsComplete = [&parserContext] (const auto& complete) {
+            const auto& partial = parserContext.m_tmpListKeys;
+            return std::all_of(partial.begin(), partial.end(), [&complete] (const auto& oneKV) {
+                    const auto& [k, v] = oneKV;
+                    return complete.at(k) == v;
+                    });
+        };
+        std::copy_if(listInstances.begin(), listInstances.end(), std::inserter(filteredInstances, filteredInstances.end()), partialFitsComplete);
+
+        std::set<std::string> validValues;
+
+        std::transform(filteredInstances.begin(), filteredInstances.end(), std::inserter(validValues, validValues.end()), [&parserContext] (const auto& instance) {
+            return leafDataToCompletion(instance.at(parserContext.m_tmpListKeyLeafPath.m_node.second));
+        });
+
+        parserContext.m_suggestions = validValues;
+    }
+};
+
 struct suggestKeysEnd_class {
     template <typename T, typename Iterator, typename Context>
     void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
diff --git a/src/ast_values.cpp b/src/ast_values.cpp
index 8d9c40c..ae5aa6c 100644
--- a/src/ast_values.cpp
+++ b/src/ast_values.cpp
@@ -33,26 +33,51 @@
 {
 }
 
+bool module_::operator<(const module_& b) const
+{
+    return this->m_name < b.m_name;
+}
+
 bool identityRef_::operator==(const identityRef_& b) const
 {
     return this->m_prefix == b.m_prefix && this->m_value == b.m_value;
 }
 
+bool identityRef_::operator<(const identityRef_& b) const
+{
+    return std::tie(this->m_prefix, this->m_value) < std::tie(b.m_prefix, b.m_value);
+}
+
 bool binary_::operator==(const binary_& b) const
 {
     return this->m_value == b.m_value;
 }
 
+bool binary_::operator<(const binary_& b) const
+{
+    return this->m_value < b.m_value;
+}
+
 bool enum_::operator==(const enum_& b) const
 {
     return this->m_value == b.m_value;
 }
 
+bool enum_::operator<(const enum_& b) const
+{
+    return this->m_value < b.m_value;
+}
+
 bool special_::operator==(const special_& b) const
 {
     return this->m_value == b.m_value;
 }
 
+bool special_::operator<(const special_& b) const
+{
+    return this->m_value < b.m_value;
+}
+
 std::string specialValueToString(const special_& value)
 {
     switch (value.m_value) {
diff --git a/src/ast_values.hpp b/src/ast_values.hpp
index b2f2c3a..b79ac84 100644
--- a/src/ast_values.hpp
+++ b/src/ast_values.hpp
@@ -14,6 +14,7 @@
     enum_();
     enum_(const std::string& value);
     bool operator==(const enum_& b) const;
+    bool operator<(const enum_& b) const;
     std::string m_value;
 };
 
@@ -21,11 +22,13 @@
     binary_();
     binary_(const std::string& value);
     bool operator==(const binary_& b) const;
+    bool operator<(const binary_& b) const;
     std::string m_value;
 };
 
 struct module_ {
     bool operator==(const module_& b) const;
+    bool operator<(const module_& b) const;
     std::string m_name;
 };
 
@@ -34,6 +37,7 @@
     identityRef_(const std::string& module, const std::string& value);
     identityRef_(const std::string& value);
     bool operator==(const identityRef_& b) const;
+    bool operator<(const identityRef_& b) const;
     boost::optional<module_> m_prefix;
     std::string m_value;
 };
@@ -46,6 +50,7 @@
 
 struct special_ {
     bool operator==(const special_& b) const;
+    bool operator<(const special_& b) const;
     SpecialValue m_value;
 };
 
diff --git a/src/data_query.cpp b/src/data_query.cpp
new file mode 100644
index 0000000..be979ff
--- /dev/null
+++ b/src/data_query.cpp
@@ -0,0 +1,18 @@
+#include "data_query.hpp"
+#include "datastore_access.hpp"
+#include "schema.hpp"
+#include "utils.hpp"
+
+
+DataQuery::DataQuery(DatastoreAccess& datastore)
+    : m_datastore(datastore)
+{
+    m_schema = m_datastore.schema();
+}
+
+std::vector<ListInstance> DataQuery::listKeys(const dataPath_& location, const ModuleNodePair& node) const
+{
+    auto listPath = joinPaths(pathToDataString(location, Prefixes::Always), fullNodeName(location, node));
+
+    return m_datastore.listInstances(listPath);
+}
diff --git a/src/data_query.hpp b/src/data_query.hpp
new file mode 100644
index 0000000..b2f4a33
--- /dev/null
+++ b/src/data_query.hpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <map>
+#include <memory>
+#include <set>
+#include "ast_values.hpp"
+#include "datastore_access.hpp"
+#include "schema.hpp"
+
+
+/*! \class DataQuery
+ *  \brief A class used for querying data from a datastore for usage in tab-completion.
+ *
+*/
+class DataQuery {
+public:
+    DataQuery(DatastoreAccess& datastore);
+    /*! \brief Lists all possible instances of key/value pairs for a list specified by the arguments.
+     *  \param location Location of the list.
+     *  \param node Name (and optional module name) of the list.
+     *  \return A vector of maps, which represent the instances. The map is keyed by the name of the list key. The values in the map, are values of the list keys.
+     */
+    std::vector<ListInstance> listKeys(const dataPath_& location, const ModuleNodePair& node) const;
+
+private:
+    DatastoreAccess& m_datastore;
+    std::shared_ptr<Schema> m_schema;
+};
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index ae0d12a..6686b92 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -36,6 +36,8 @@
 
 class Schema;
 
+using ListInstance = std::map<std::string, leaf_data_>;
+
 class DatastoreAccess {
 public:
     using Tree = std::map<std::string, leaf_data_>;
@@ -52,4 +54,8 @@
 
     virtual void commitChanges() = 0;
     virtual void discardChanges() = 0;
+
+private:
+    friend class DataQuery;
+    virtual std::vector<ListInstance> listInstances(const std::string& path) = 0;
 };
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 98ed8b7..11d1db0 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -71,6 +71,7 @@
 x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
 x3::rule<createPathSuggestions_class, x3::unused_type> const createPathSuggestions = "createPathSuggestions";
 x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
+x3::rule<createValueSuggestions_class, x3::unused_type> const createValueSuggestions = "createValueSuggestions";
 x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
 x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
 x3::rule<completing_class, x3::unused_type> const completing = "completing";
@@ -110,11 +111,14 @@
 auto const createKeySuggestions_def =
     x3::eps;
 
+auto const createValueSuggestions_def =
+    x3::eps;
+
 auto const suggestKeysEnd_def =
     x3::eps;
 
 auto const keyValue_def =
-    key_identifier > '=' > leaf_data;
+    key_identifier > '=' > createValueSuggestions > leaf_data;
 
 auto const keyValueWrapper =
     lexeme['[' > createKeySuggestions > keyValue > suggestKeysEnd > ']'];
@@ -396,6 +400,7 @@
 BOOST_SPIRIT_DEFINE(command)
 BOOST_SPIRIT_DEFINE(createPathSuggestions)
 BOOST_SPIRIT_DEFINE(createKeySuggestions)
+BOOST_SPIRIT_DEFINE(createValueSuggestions)
 BOOST_SPIRIT_DEFINE(suggestKeysEnd)
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
 BOOST_SPIRIT_DEFINE(completing)
diff --git a/src/main.cpp b/src/main.cpp
index d1b45e0..fd064ba 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -36,7 +36,8 @@
     std::cout << "Welcome to netconf-cli" << std::endl;
 
     SysrepoAccess datastore("netconf-cli");
-    Parser parser(datastore.schema());
+    auto dataQuery = std::make_shared<DataQuery>(datastore);
+    Parser parser(datastore.schema(), dataQuery);
 
     using replxx::Replxx;
 
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 7387231..91b1874 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -194,3 +194,36 @@
 {
     return m_schema;
 }
+
+std::vector<ListInstance> NetconfAccess::listInstances(const std::string& path)
+{
+    std::vector<ListInstance> res;
+    auto list = m_schema->dataNodeFromPath(path);
+
+    // This inserts selection nodes - I only want keys not other data
+    // To get the keys, I have to call find_path here - otherwise I would get keys of a top-level node (which might not even be a list)
+    auto keys = libyang::Schema_Node_List{(*(list->find_path(path.c_str())->data().begin()))->schema()}.keys();
+    for (const auto& keyLeaf : keys) {
+        // Have to call find_path here - otherwise I'll have the list, not the leaf inside it
+        auto selectionLeaf = *(m_schema->dataNodeFromPath(keyLeaf->path())->find_path(keyLeaf->path().c_str())->data().begin());
+        auto actualList = *(list->find_path(path.c_str())->data().begin());
+        actualList->insert(selectionLeaf);
+    }
+
+    auto instances = m_session->getConfig(NC_DATASTORE_RUNNING, list->print_mem(LYD_XML, 0));
+
+
+    for (const auto& instance : instances->find_path(path.c_str())->data()) {
+        ListInstance instanceRes;
+
+        // I take the first child here, because the first element (the parent of the child()) will be the list
+        for (const auto& keyLeaf : instance->child()->tree_for()) {
+            auto leafData = libyang::Data_Node_Leaf_List{keyLeaf};
+            auto leafSchema = libyang::Schema_Node_Leaf{leafData.schema()};
+            instanceRes.insert({ leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
+        }
+        res.push_back(instanceRes);
+    }
+
+    return res;
+}
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index f43976a..aa13312 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -46,6 +46,8 @@
     std::shared_ptr<Schema> schema() override;
 
 private:
+    std::vector<std::map<std::string, leaf_data_>> listInstances(const std::string& path) override;
+
     std::string fetchSchema(const std::string_view module, const
             std::optional<std::string_view> revision, const
             std::optional<std::string_view> submodule, const
diff --git a/src/parser.cpp b/src/parser.cpp
index c3dc088..ff262fc 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -15,8 +15,9 @@
 InvalidCommandException::~InvalidCommandException() = default;
 
 
-Parser::Parser(const std::shared_ptr<const Schema> schema)
+Parser::Parser(const std::shared_ptr<const Schema> schema, const std::shared_ptr<const DataQuery> dataQuery)
     : m_schema(schema)
+    , m_dataquery(dataQuery)
 {
     m_curDir.m_scope = Scope::Absolute;
 }
@@ -29,7 +30,7 @@
 command_ Parser::parseCommand(const std::string& line, std::ostream& errorStream)
 {
     command_ parsedCommand;
-    ParserContext ctx(*m_schema, m_curDir);
+    ParserContext ctx(*m_schema, nullptr, m_curDir);
     auto it = line.begin();
 
     boost::spirit::x3::error_handler<std::string::const_iterator> errorHandler(it, line.end(), errorStream);
@@ -51,7 +52,7 @@
 {
     std::set<std::string> completions;
     command_ parsedCommand;
-    ParserContext ctx(*m_schema, m_curDir);
+    ParserContext ctx(*m_schema, m_dataquery, m_curDir);
     ctx.m_completing = true;
     auto it = line.begin();
     boost::spirit::x3::error_handler<std::string::const_iterator> errorHandler(it, line.end(), errorStream);
diff --git a/src/parser.hpp b/src/parser.hpp
index c2cd8d6..7a94919 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -9,6 +9,7 @@
 #pragma once
 #include "ast_commands.hpp"
 #include "ast_path.hpp"
+#include "data_query.hpp"
 #include "schema.hpp"
 
 
@@ -32,7 +33,7 @@
 
 class Parser {
 public:
-    Parser(const std::shared_ptr<const Schema> schema);
+    Parser(const std::shared_ptr<const Schema> schema, const std::shared_ptr<const DataQuery> dataQuery = nullptr);
     command_ parseCommand(const std::string& line, std::ostream& errorStream);
     void changeNode(const dataPath_& name);
     std::string currentNode() const;
@@ -41,5 +42,6 @@
 
 private:
     const std::shared_ptr<const Schema> m_schema;
+    const std::shared_ptr<const DataQuery> m_dataquery;
     dataPath_ m_curDir;
 };
diff --git a/src/parser_context.cpp b/src/parser_context.cpp
index 7750301..26e6fa3 100644
--- a/src/parser_context.cpp
+++ b/src/parser_context.cpp
@@ -7,9 +7,10 @@
 */
 
 #include "parser_context.hpp"
-ParserContext::ParserContext(const Schema& schema, const dataPath_& curDir)
+ParserContext::ParserContext(const Schema& schema, const std::shared_ptr<const DataQuery> dataQuery, const dataPath_& curDir)
     : m_schema(schema)
     , m_curPathOrig(curDir)
+    , m_dataquery(dataQuery)
     , m_curPath(curDir)
 {
     if (!currentDataPath().m_nodes.empty() && currentDataPath().m_nodes.at(0).m_prefix)
diff --git a/src/parser_context.hpp b/src/parser_context.hpp
index 77357e7..98022db 100644
--- a/src/parser_context.hpp
+++ b/src/parser_context.hpp
@@ -6,9 +6,10 @@
  *
 */
 
+#include "data_query.hpp"
 #include "schema.hpp"
 struct ParserContext {
-    ParserContext(const Schema& schema, const dataPath_& curDir);
+    ParserContext(const Schema& schema, const std::shared_ptr<const DataQuery> dataQuery, const dataPath_& curDir);
     schemaPath_ currentSchemaPath();
     dataPath_ currentDataPath();
     void clearPath();
@@ -18,16 +19,17 @@
 
     const Schema& m_schema;
     const dataPath_ m_curPathOrig;
+    const std::shared_ptr<const DataQuery> m_dataquery;
     boost::optional<std::string> m_curModule;
     std::string m_errorMsg;
     std::string m_tmpListName;
     bool m_topLevelModulePresent = false;
-    std::set<std::string> m_tmpListKeys;
 
     struct {
         schemaPath_ m_location;
         ModuleNodePair m_node;
     } m_tmpListKeyLeafPath;
+    std::map<std::string, leaf_data_> m_tmpListKeys;
     bool m_errorHandled = false;
     bool m_completing = false;
     std::set<std::string> m_suggestions;
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index cf19407..b681893 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -6,7 +6,10 @@
  *
 */
 
+#include <libyang/Tree_Data.hpp>
+#include <libyang/Tree_Schema.hpp>
 #include <sysrepo-cpp/Session.hpp>
+#include "libyang_utils.hpp"
 #include "sysrepo_access.hpp"
 #include "utils.hpp"
 #include "yang_schema.hpp"
@@ -317,3 +320,54 @@
 
     throw DatastoreException(res);
 }
+
+std::vector<ListInstance> SysrepoAccess::listInstances(const std::string& path)
+{
+    std::vector<ListInstance> res;
+    auto lists = getItems(path);
+
+    decltype(lists) instances;
+    auto wantedTree = *(m_schema->dataNodeFromPath(path)->find_path(path.c_str())->data().begin());
+    std::copy_if(lists.begin(), lists.end(), std::inserter(instances, instances.end()), [this, pathToCheck=wantedTree->schema()->path()](const auto& item) {
+        // This filters out non-instances.
+        if (item.second.type() != typeid(special_) || boost::get<special_>(item.second).m_value != SpecialValue::List) {
+            return false;
+        }
+
+        // Now, getItems is recursive: it gives everything including nested lists. So I try create a tree from the instance...
+        auto instanceTree = *(m_schema->dataNodeFromPath(item.first)->find_path(item.first.c_str())->data().begin());
+        // And then check if its schema path matches the list we actually want. This filters out lists which are not the ones I requested.
+        return instanceTree->schema()->path() == pathToCheck;
+    });
+
+    // If there are no instances, then just return
+    if (instances.empty()) {
+        return res;
+    }
+
+    // I need to find out which keys does the list have. To do that, I create a
+    // tree from the first instance. This is gives me some top level node,
+    // which will be our list in case out list is a top-level node. In case it
+    // isn't, we have call find_path on the top level node. After that, I just
+    // retrieve the keys.
+    auto topLevelTree = m_schema->dataNodeFromPath(instances.begin()->first);
+    auto list = *(topLevelTree->find_path(path.c_str())->data().begin());
+    auto keys = libyang::Schema_Node_List{list->schema()}.keys();
+
+    // Creating a full tree at the same time from the values sysrepo gives me
+    // would be a pain (and after sysrepo switches to libyang meaningless), so
+    // I just use this algorithm to create data nodes one by one and get the
+    // key values from them.
+    for (const auto& instance : instances) {
+        auto wantedList = *(m_schema->dataNodeFromPath(instance.first)->find_path(path.c_str())->data().begin());
+        ListInstance instanceRes;
+        for (const auto& key : keys) {
+            auto vec = wantedList->find_path(key->name())->data();
+            auto leaf = libyang::Data_Node_Leaf_List{*(vec.begin())};
+            instanceRes.emplace(key->name(), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
+        }
+        res.push_back(instanceRes);
+    }
+
+    return res;
+}
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index c2ce909..cebb564 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -42,6 +42,7 @@
     void discardChanges() override;
 
 private:
+    std::vector<std::map<std::string, leaf_data_>> listInstances(const std::string& path) override;
     [[noreturn]] void reportErrors();
 
     std::string fetchSchema(const char* module, const char* revision, const char* submodule);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 06259b9..999f33a 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -399,7 +399,7 @@
                                                 path.c_str(),
                                                 value ? value.value().c_str() : nullptr,
                                                 LYD_ANYDATA_CONSTSTRING,
-                                                0);
+                                                LYD_PATH_OPT_EDIT);
 }
 
 std::shared_ptr<libyang::Module> YangSchema::getYangModule(const std::string& name)
diff --git a/tests/data_query.cpp b/tests/data_query.cpp
new file mode 100644
index 0000000..c62c240
--- /dev/null
+++ b/tests/data_query.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <experimental/iterator>
+#include "trompeloeil_doctest.h"
+
+#ifdef sysrepo_BACKEND
+#include "sysrepo_access.hpp"
+#elif defined(netconf_BACKEND)
+#include "netconf_access.hpp"
+#include "netopeer_vars.hpp"
+#else
+#error "Unknown backend"
+#endif
+#include "data_query.hpp"
+#include "sysrepo_subscription.hpp"
+#include "utils.hpp"
+
+namespace std {
+std::ostream& operator<<(std::ostream& s, const std::vector<std::map<std::string, leaf_data_>> set)
+{
+    s << std::endl << "{" << std::endl;
+    std::transform(set.begin(), set.end(), std::experimental::make_ostream_joiner(s, ", \n"), [](const auto& map) {
+        std::ostringstream ss;
+        ss << "    {" << std::endl << "        ";
+        std::transform(map.begin(), map.end(), std::experimental::make_ostream_joiner(ss, ", \n        "), [](const auto& keyValue){
+            return "{" + keyValue.first + "{" + boost::core::demangle(keyValue.second.type().name()) + "}" + ", " + leafDataToString(keyValue.second) + "}";
+        });
+        ss << std::endl << "    }";
+        return ss.str();
+    });
+    s << std::endl << "}" << std::endl;
+    return s;
+}
+}
+
+TEST_CASE("data query")
+{
+    trompeloeil::sequence seq1;
+    SysrepoSubscription subscriptionExample("example-schema");
+    SysrepoSubscription subscriptionOther("other-module");
+
+#ifdef sysrepo_BACKEND
+    SysrepoAccess datastore("netconf-cli-test");
+#elif defined(netconf_BACKEND)
+    NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+#else
+#error "Unknown backend"
+#endif
+
+    DataQuery dataquery(datastore);
+
+    SECTION("listKeys")
+    {
+        dataPath_ location;
+        location.m_scope = Scope::Absolute;
+        ModuleNodePair node;
+        std::vector<std::map<std::string, leaf_data_>> expected;
+
+        SECTION("example-schema:person")
+        {
+            datastore.createListInstance("/example-schema:person[name='Vaclav']");
+            datastore.createListInstance("/example-schema:person[name='Tomas']");
+            datastore.createListInstance("/example-schema:person[name='Jan Novak']");
+            node.first = "example-schema";
+            node.second = "person";
+            expected = {
+                {{"name", std::string{"Jan Novak"}}},
+                {{"name", std::string{"Tomas"}}},
+                {{"name", std::string{"Vaclav"}}}
+            };
+        }
+
+        SECTION("example-schema:selectedNumbers")
+        {
+            datastore.createListInstance("/example-schema:selectedNumbers[value='45']");
+            datastore.createListInstance("/example-schema:selectedNumbers[value='99']");
+            datastore.createListInstance("/example-schema:selectedNumbers[value='127']");
+            node.first = "example-schema";
+            node.second = "selectedNumbers";
+            expected = {
+                {{"value", int8_t{127}}},
+                {{"value", int8_t{45}}},
+                {{"value", int8_t{99}}}
+            };
+        }
+
+        SECTION("example-schema:animalWithColor")
+        {
+            datastore.createListInstance("/example-schema:animalWithColor[name='Dog'][color='brown']");
+            datastore.createListInstance("/example-schema:animalWithColor[name='Dog'][color='white']");
+            datastore.createListInstance("/example-schema:animalWithColor[name='Cat'][color='grey']");
+            node.first = "example-schema";
+            node.second = "animalWithColor";
+            expected = {
+                {{"name", std::string{"Cat"}}, {"color", std::string{"grey"}}},
+                {{"name", std::string{"Dog"}}, {"color", std::string{"brown"}}},
+                {{"name", std::string{"Dog"}}, {"color", std::string{"white"}}}
+            };
+        }
+
+        SECTION("example-schema:animalWithColor - quotes in values")
+        {
+            datastore.createListInstance("/example-schema:animalWithColor[name='D\"o\"g'][color=\"b'r'own\"]");
+            node.first = "example-schema";
+            node.second = "animalWithColor";
+            expected = {
+                {{"name", std::string{"D\"o\"g"}}, {"color", std::string{"b'r'own"}}}
+            };
+        }
+
+        SECTION("example-schema:ports")
+        {
+            datastore.createListInstance("/example-schema:ports[name='A']");
+            datastore.createListInstance("/example-schema:ports[name='B']");
+            datastore.createListInstance("/example-schema:ports[name='E']");
+            node.first = "example-schema";
+            node.second = "ports";
+            expected = {
+                {{"name", enum_{"A"}}},
+                {{"name", enum_{"B"}}},
+                {{"name", enum_{"E"}}},
+            };
+        }
+
+        SECTION("example-schema:org/example:people - nested list")
+        {
+            datastore.createListInstance("/example-schema:org[department='accounting']");
+            datastore.createListInstance("/example-schema:org[department='sales']");
+            datastore.createListInstance("/example-schema:org[department='programmers']");
+            datastore.createListInstance("/example-schema:org[department='accounting']/people[name='Alice']");
+            datastore.createListInstance("/example-schema:org[department='accounting']/people[name='Bob']");
+            datastore.createListInstance("/example-schema:org[department='sales']/people[name='Alice']");
+            datastore.createListInstance("/example-schema:org[department='sales']/people[name='Cyril']");
+            datastore.createListInstance("/example-schema:org[department='sales']/people[name='Alice']/computers[type='laptop']");
+            datastore.createListInstance("/example-schema:org[department='sales']/people[name='Alice']/computers[type='server']");
+            datastore.createListInstance("/example-schema:org[department='sales']/people[name='Cyril']/computers[type='PC']");
+            datastore.createListInstance("/example-schema:org[department='sales']/people[name='Cyril']/computers[type='server']");
+
+            SECTION("outer list")
+            {
+                node.first = "example-schema";
+                node.second = "org";
+                expected = {
+                    {{"department", std::string{"accounting"}}},
+                    {{"department", std::string{"sales"}}},
+                    {{"department", std::string{"programmers"}}},
+                };
+            }
+
+            SECTION("nested list")
+            {
+                listElement_ list;
+                list.m_name = "org";
+                node.second = "people";
+                SECTION("accounting department")
+                {
+                    list.m_keys = {
+                        {"department", std::string{"accounting"}}
+                    };
+                    expected = {
+                        {{"name", std::string{"Alice"}}},
+                        {{"name", std::string{"Bob"}}},
+                    };
+                }
+                SECTION("sales department")
+                {
+                    list.m_keys = {
+                        {"department", std::string{"sales"}}
+                    };
+                    expected = {
+                        {{"name", std::string{"Alice"}}},
+                        {{"name", std::string{"Cyril"}}},
+                    };
+                }
+                SECTION("programmers department")
+                {
+                    list.m_keys = {
+                        {"department", std::string{"programmers"}}
+                    };
+                    expected = {
+                    };
+                }
+                location.m_nodes.push_back(dataNode_{{"example-schema"}, list});
+            }
+
+            SECTION("THREE MF NESTED LISTS")
+            {
+                listElement_ listOrg;
+                listOrg.m_name = "org";
+                listOrg.m_keys = {
+                    {"department", std::string{"sales"}}
+                };
+
+                listElement_ listPeople;
+                node.second = "computers";
+
+                SECTION("alice computers")
+                {
+                    listPeople.m_name = "people";
+                    listPeople.m_keys = {
+                        {"name", std::string{"Alice"}}
+                    };
+                    expected = {
+                        {{"type", enum_{"laptop"}}},
+                        {{"type", enum_{"server"}}},
+                    };
+
+                }
+
+                SECTION("cyril computers")
+                {
+                    listPeople.m_name = "people";
+                    listPeople.m_keys = {
+                        {"name", std::string{"Cyril"}}
+                    };
+                    expected = {
+                        {{"type", enum_{"PC"}}},
+                        {{"type", enum_{"server"}}},
+                    };
+                }
+
+                location.m_nodes.push_back(dataNode_{{"example-schema"}, listOrg});
+                location.m_nodes.push_back(dataNode_{listPeople});
+
+            }
+        }
+
+        SECTION("/other-module:parking-lot/example-schema:cars - list coming from an augment")
+        {
+            datastore.createListInstance("/other-module:parking-lot/example-schema:cars[id='1']");
+            datastore.createListInstance("/other-module:parking-lot/example-schema:cars[id='2']");
+
+            location.m_nodes.push_back(dataNode_{{"other-module"}, container_{"parking-lot"}});
+            node.first = "example-schema";
+            node.second = "cars";
+            expected = {
+                {{"id", int32_t{1}}},
+                {{"id", int32_t{2}}},
+            };
+
+        }
+
+        datastore.commitChanges();
+        std::sort(expected.begin(), expected.end());
+        auto keys = dataquery.listKeys(location, node);
+        std::sort(keys.begin(), keys.end());
+        REQUIRE(keys == expected);
+    }
+
+    waitForCompletionAndBitMore(seq1);
+}
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 112ace9..88b05c3 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -50,7 +50,7 @@
 {
     trompeloeil::sequence seq1;
     MockRecorder mock;
-    SysrepoSubscription subscription(&mock);
+    SysrepoSubscription subscription("example-schema", &mock);
 
 #ifdef sysrepo_BACKEND
     SysrepoAccess datastore("netconf-cli-test");
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
new file mode 100644
index 0000000..289d919
--- /dev/null
+++ b/tests/datastoreaccess_mock.hpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#include <map>
+#include "datastore_access.hpp"
+#include "trompeloeil_doctest.h"
+#include "utils.hpp"
+
+namespace trompeloeil {
+    template <>
+    inline void print(std::ostream& os, const leaf_data_& data)
+    {
+        os << leafDataToString(data);
+    }
+}
+
+class MockDatastoreAccess : public trompeloeil::mock_interface<DatastoreAccess> {
+    IMPLEMENT_MOCK1(getItems);
+    IMPLEMENT_MOCK2(setLeaf);
+    IMPLEMENT_MOCK1(createPresenceContainer);
+    IMPLEMENT_MOCK1(deletePresenceContainer);
+    IMPLEMENT_MOCK1(createListInstance);
+    IMPLEMENT_MOCK1(deleteListInstance);
+    IMPLEMENT_MOCK2(executeRpc);
+
+    // Can't use IMPLEMENT_MOCK for private methods - IMPLEMENT_MOCK needs full visibility of the method
+    MAKE_MOCK1(listInstances, std::vector<ListInstance>(const std::string&), override);
+
+
+    IMPLEMENT_MOCK0(schema);
+
+    IMPLEMENT_MOCK0(commitChanges);
+    IMPLEMENT_MOCK0(discardChanges);
+};
+
+
diff --git a/tests/enum_completion.cpp b/tests/enum_completion.cpp
index 29f23a7..c6b31d8 100644
--- a/tests/enum_completion.cpp
+++ b/tests/enum_completion.cpp
@@ -8,6 +8,7 @@
  */
 
 #include "trompeloeil_doctest.h"
+#include "datastoreaccess_mock.hpp"
 #include "parser.hpp"
 #include "pretty_printers.hpp"
 #include "static_schema.hpp"
@@ -22,7 +23,16 @@
     schema->addList("/", "mod:list", {"number"});
     schema->addLeaf("/mod:list", "mod:number", yang::LeafDataTypes::Int32);
     schema->addLeafEnum("/mod:list", "mod:leafInList", {"ano", "anoda", "ne", "katoda"});
-    Parser parser(schema);
+    auto mockDatastore = std::make_shared<MockDatastoreAccess>();
+    // The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
+    ALLOW_CALL(*mockDatastore, listInstances("/mod:list"))
+        .RETURN(std::vector<ListInstance>{});
+
+    // DataQuery gets the schema from DatastoreAccess once
+    auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
+        .RETURN(schema);
+    auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
+    Parser parser(schema, dataQuery);
     std::string input;
     std::ostringstream errorStream;
 
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index d993781..a7ff3d8 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -2,6 +2,10 @@
     prefix aha;
     namespace "http://example.com";
 
+    import other-module {
+        prefix other;
+    }
+
     leaf leafUInt8 {
         type uint8;
     }
@@ -127,4 +131,68 @@
     }
 
     rpc noop {}
+
+    list selectedNumbers {
+        key 'value';
+        leaf value {
+            type int8;
+        }
+    }
+
+    list animalWithColor {
+        key 'name color';
+        leaf name {
+            type string;
+        }
+        leaf color {
+            type string;
+        }
+    }
+
+    list ports {
+        key 'name';
+        leaf name {
+            type enumeration {
+                enum A;
+                enum B;
+                enum C;
+                enum D;
+                enum E;
+            }
+        }
+    }
+
+    list org {
+        key 'department';
+        leaf department {
+            type string;
+        }
+
+        list people {
+            key 'name';
+            leaf name {
+                type string;
+            }
+
+            list computers {
+                key 'type';
+                leaf type {
+                    type enumeration {
+                        enum PC;
+                        enum laptop;
+                        enum server;
+                    }
+                }
+            }
+        }
+    }
+
+    augment "/other:parking-lot" {
+        list cars {
+            key 'id';
+            leaf id {
+                type int32;
+            }
+        }
+    }
 }
diff --git a/tests/keyvalue_completion.cpp b/tests/keyvalue_completion.cpp
new file mode 100644
index 0000000..56c7841
--- /dev/null
+++ b/tests/keyvalue_completion.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <experimental/iterator>
+#include <iostream>
+#include "trompeloeil_doctest.h"
+#include "ast_commands.hpp"
+#include "datastoreaccess_mock.hpp"
+#include "parser.hpp"
+#include "static_schema.hpp"
+
+namespace std {
+std::ostream& operator<<(std::ostream& s, const std::set<std::string> set)
+{
+    s << std::endl << "{";
+    std::copy(set.begin(), set.end(), std::experimental::make_ostream_joiner(s, ", "));
+    s << "}" << std::endl;
+    return s;
+}
+}
+
+TEST_CASE("keyvalue_completion")
+{
+    auto schema = std::make_shared<StaticSchema>();
+    schema->addModule("example");
+    schema->addContainer("/", "example:a");
+    schema->addContainer("/", "example:b");
+    schema->addList("/", "example:list", {"number"});
+    schema->addLeaf("/example:list", "example:number", yang::LeafDataTypes::Int32);
+    schema->addList("/", "example:twoKeyList", {"number", "name"});
+    schema->addLeaf("/example:twoKeyList", "example:number", yang::LeafDataTypes::Int32);
+    schema->addLeaf("/example:twoKeyList", "example:name", yang::LeafDataTypes::String);
+    auto mockDatastore = std::make_shared<MockDatastoreAccess>();
+
+    // DataQuery gets the schema from DatastoreAccess once
+    auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
+        .RETURN(schema);
+    auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
+    expectation.reset();
+    Parser parser(schema, dataQuery);
+    std::string input;
+    std::ostringstream errorStream;
+
+    std::set<std::string> expected;
+    std::vector<std::shared_ptr<trompeloeil::expectation>> queryExpectations;
+    std::vector<ListInstance> queryReturn;
+
+    SECTION("get example:list[number=")
+    {
+        input = "get example:list[number=";
+        queryReturn = {
+            {{"number", 1}},
+            {{"number", 7}},
+            {{"number", 9}},
+            {{"number", 42}}
+        };
+        expected = {
+            "1",
+            "7",
+            "9",
+            "42"
+        };
+        queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:list"))
+            .RETURN(queryReturn));
+    }
+
+    SECTION("get example:twoKeyList[number=")
+    {
+        queryReturn = {
+            {{"number", 1}, {"name", std::string{"Petr"}}},
+            {{"number", 7}, {"name", std::string{"Petr"}}},
+            {{"number", 10}, {"name", std::string{"Petr"}}},
+            {{"number", 10}, {"name", std::string{"Honza"}}},
+            {{"number", 100}, {"name", std::string{"Honza"}}},
+        };
+        input = "get example:twoKeyList[";
+        SECTION("no keys set")
+        {
+            SECTION("number")
+            {
+                input += "number=";
+                expected = { "1", "7", "10", "100" };
+            }
+            SECTION("name")
+            {
+                input += "name=";
+                expected = { "'Petr'", "'Honza'"};
+            }
+            queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .RETURN(queryReturn));
+        }
+
+        SECTION("name is set")
+        {
+            input += "name=";
+            SECTION("Petr")
+            {
+                input += "'Petr'";
+                expected = { "1", "7", "10"};
+            }
+            SECTION("Honza")
+            {
+                input += "'Honza'";
+                expected = { "10", "100" };
+            }
+            input += "][number=";
+            queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .TIMES(2)
+                    .RETURN(queryReturn));
+        }
+
+        SECTION("number is set")
+        {
+            input += "number=";
+            SECTION("1")
+            {
+                input += "1";
+                expected = { "'Petr'" };
+            }
+            SECTION("7")
+            {
+                input += "7";
+                expected = { "'Petr'" };
+            }
+            SECTION("10")
+            {
+                input += "10";
+                expected = { "'Honza'", "'Petr'" };
+            }
+            SECTION("100")
+            {
+                input += "100";
+                expected = { "'Honza'" };
+            }
+            input += "][name=";
+            queryExpectations.push_back(NAMED_REQUIRE_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .TIMES(2)
+                    .RETURN(queryReturn));
+        }
+
+        SECTION("both keys are set")
+        {
+            SECTION("get example:twoKeyList[number=123][name='Petr'")
+            {
+                input = "get example:twoKeyList[number=123][name='Petr'";
+                expected = {"]/"};
+            }
+            SECTION("get example:twoKeyList[number=123][name='Petr']")
+            {
+                input = "get example:twoKeyList[number=123][name='Petr']";
+                expected = {"]/"};
+            }
+            SECTION("get example:twoKeyList[number=123][name='Petr'][")
+            {
+                input = "get example:twoKeyList[number=123][name='Petr'][";
+            }
+            // I use ALLOW_CALL here, all this stuff calls it different number of times
+            queryExpectations.push_back(NAMED_ALLOW_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+                    .RETURN(queryReturn));
+        }
+
+    }
+
+    REQUIRE(parser.completeCommand(input, errorStream).m_completions == expected);
+    for (auto& it : queryExpectations) {
+        it.reset();
+    }
+}
diff --git a/tests/mock/sysrepo_subscription.cpp b/tests/mock/sysrepo_subscription.cpp
index 7cd5cf3..c50e19e 100644
--- a/tests/mock/sysrepo_subscription.cpp
+++ b/tests/mock/sysrepo_subscription.cpp
@@ -45,13 +45,16 @@
 
 Recorder::~Recorder() = default;
 
-SysrepoSubscription::SysrepoSubscription(Recorder* rec)
+SysrepoSubscription::SysrepoSubscription(const std::string& moduleName, Recorder* rec)
     : m_connection(new sysrepo::Connection("netconf-cli-test-subscription"))
 {
     m_session = std::make_shared<sysrepo::Session>(m_connection);
     m_subscription = std::make_shared<sysrepo::Subscribe>(m_session);
-    const char* modName = "example-schema";
-    m_callback = std::make_shared<MyCallback>(modName, rec);
+    if (rec) {
+        m_callback = std::make_shared<MyCallback>(moduleName, rec);
+    } else {
+        m_callback = std::make_shared<sysrepo::Callback>();
+    }
 
-    m_subscription->module_change_subscribe(modName, m_callback);
+    m_subscription->module_change_subscribe(moduleName.c_str(), m_callback);
 }
diff --git a/tests/mock/sysrepo_subscription.hpp b/tests/mock/sysrepo_subscription.hpp
index dce6506..0102d1b 100644
--- a/tests/mock/sysrepo_subscription.hpp
+++ b/tests/mock/sysrepo_subscription.hpp
@@ -27,7 +27,7 @@
 
 class SysrepoSubscription {
 public:
-    SysrepoSubscription(Recorder* rec);
+    SysrepoSubscription(const std::string& moduleName, Recorder* rec = nullptr);
 
 private:
     std::shared_ptr<sysrepo::Connection> m_connection;
diff --git a/tests/other-module.yang b/tests/other-module.yang
new file mode 100644
index 0000000..ca1ca06
--- /dev/null
+++ b/tests/other-module.yang
@@ -0,0 +1,7 @@
+module other-module {
+    prefix aha;
+    namespace "http://other.com";
+
+    container parking-lot;
+
+}
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 832110d..0ad6089 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -7,6 +7,7 @@
 */
 
 #include "trompeloeil_doctest.h"
+#include "datastoreaccess_mock.hpp"
 #include "parser.hpp"
 #include "pretty_printers.hpp"
 #include "static_schema.hpp"
@@ -36,7 +37,20 @@
     schema->addLeaf("/example:twoKeyList", "example:name", yang::LeafDataTypes::String);
     schema->addLeaf("/example:twoKeyList", "example:number", yang::LeafDataTypes::Int32);
     schema->addLeaf("/", "example:leafInt", yang::LeafDataTypes::Int32);
-    Parser parser(schema);
+    auto mockDatastore = std::make_shared<MockDatastoreAccess>();
+
+    // The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
+    ALLOW_CALL(*mockDatastore, listInstances("/example:list"))
+        .RETURN(std::vector<ListInstance>{});
+    ALLOW_CALL(*mockDatastore, listInstances("/example:twoKeyList"))
+        .RETURN(std::vector<ListInstance>{});
+
+    // DataQuery gets the schema from DatastoreAccess once
+    auto expectation = NAMED_REQUIRE_CALL(*mockDatastore, schema())
+        .RETURN(schema);
+    auto dataQuery = std::make_shared<DataQuery>(*mockDatastore);
+    expectation.reset();
+    Parser parser(schema, dataQuery);
     std::string input;
     std::ostringstream errorStream;
 
@@ -188,8 +202,8 @@
         SECTION("cd example:list[number=")
         {
             input = "cd example:list[number=";
-            expectedCompletions = {"number="};
-            expectedContextLength = 7;
+            expectedCompletions = {};
+            expectedContextLength = 0;
         }
 
         SECTION("cd example:list[number=12")