catalog: support autocompletion query

* use seaprate functions to for autocompletion query and component-based query
* add json-paramters as a name component in the query-results content
* set freshness for ACK data
* fix bug that catalog throws run-time error when malformed Json::value is used
* query-results data conforms the schema

Change-Id: I942c26888fa79c206f2fa27f6f0a92fc4a8bef79
refs: #3024, #2797, #3035, #3047
diff --git a/catalog.conf.sample.in b/catalog.conf.sample.in
index 11b11d0..72b4153 100644
--- a/catalog.conf.sample.in
+++ b/catalog.conf.sample.in
@@ -1,5 +1,5 @@
 ; The catalog section contains settings of catalog
-general
+general:
 {
   ; Set the catalog prefix, so that adapters can extend it as their own prefix
   ; e.g., suppose that the catalog has the prefix "ndn:/cmip5", so QueryAdapter has the prefix
@@ -7,6 +7,10 @@
   ; PublishAdapter has the prefix "ndn:/cmip5/catalog/publish"
 
   prefix /catalog/myUniqueName
+
+  ; name fileds conatin name components for the scientifi data, for example, the climate data
+  ; contains name fileds like activity, ..., time
+  nameFields activity,product,organization,model,experiment,frequency,modeling_realm,variable_name,ensemble,time
 }
 
 ; The queryAdapter section contains settings of queryAdapter
diff --git a/catalog/src/catalog/catalog.cpp b/catalog/src/catalog/catalog.cpp
index ebec2f9..7b6c0bb 100644
--- a/catalog/src/catalog/catalog.cpp
+++ b/catalog/src/catalog/catalog.cpp
@@ -56,6 +56,17 @@
                                  " in \"general\" section");
       }
     }
+    if (i->first == "nameFields") {
+      std::istringstream ss(i->second.get_value<std::string>());
+      std::string token;
+      while(std::getline(ss, token, ',')) {
+        m_nameFields.push_back(token);
+      }
+    }
+  }
+  if (m_nameFields.size() == 0) { // nameFields must not be empty
+    throw Error("Empty value for \"nameFields\""
+                             " in \"general\" section");
   }
 }
 
@@ -84,7 +95,7 @@
        i != m_adapters.end();
        ++ i)
   {
-    (*i)->setConfigFile(config, m_prefix);
+    (*i)->setConfigFile(config, m_prefix, m_nameFields);
   }
 
   config.parse(m_configFile, true);
diff --git a/catalog/src/catalog/catalog.hpp b/catalog/src/catalog/catalog.hpp
index b58129e..7041e82 100644
--- a/catalog/src/catalog/catalog.hpp
+++ b/catalog/src/catalog/catalog.hpp
@@ -108,6 +108,7 @@
 
   // Adapters that added by users
   std::vector<std::unique_ptr<util::CatalogAdapter>> m_adapters;
+  std::vector<std::string> m_nameFields;
 }; // class Catalog
 
 
diff --git a/catalog/src/publish/publish-adapter.hpp b/catalog/src/publish/publish-adapter.hpp
index 492bcff..9fb03db 100644
--- a/catalog/src/publish/publish-adapter.hpp
+++ b/catalog/src/publish/publish-adapter.hpp
@@ -65,7 +65,8 @@
    */
   void
   setConfigFile(util::ConfigFile& config,
-                const ndn::Name& prefix);
+                const ndn::Name& prefix,
+                const std::vector<std::string>& nameFields);
 
 protected:
   /**
@@ -163,8 +164,10 @@
 template <typename DatabaseHandler>
 void
 PublishAdapter<DatabaseHandler>::setConfigFile(util::ConfigFile& config,
-                                               const ndn::Name& prefix)
+                                               const ndn::Name& prefix,
+                                               const std::vector<std::string>& nameFields)
 {
+  m_nameFields = nameFields;
   config.addSectionHandler("publishAdapter",
                            bind(&PublishAdapter<DatabaseHandler>::onConfig, this,
                                 _1, _2, _3, prefix));
diff --git a/catalog/src/query/query-adapter.hpp b/catalog/src/query/query-adapter.hpp
index 107e6c4..2adb161 100644
--- a/catalog/src/query/query-adapter.hpp
+++ b/catalog/src/query/query-adapter.hpp
@@ -47,10 +47,12 @@
 #include <mutex>
 #include <sstream>
 #include <string>
+#include <array>
 
 namespace atmos {
 namespace query {
-static const size_t MAX_SEGMENT_SIZE = ndn::MAX_NDN_PACKET_SIZE >> 1;
+// todo: calculate payload limit by get the size of a signed empty Data packet
+static const size_t PAYLOAD_LIMIT = 7000;
 
 /**
  * QueryAdapter handles the Query usecases for the catalog
@@ -75,7 +77,8 @@
    */
   void
   setConfigFile(util::ConfigFile& config,
-                const ndn::Name& prefix);
+                const ndn::Name& prefix,
+                const std::vector<std::string>& nameFields);
 
 protected:
   /**
@@ -115,13 +118,15 @@
    * @param isFinalBlock:   bool to indicate whether this needs to be flagged in the Data as the
    *                         last entry
    * @param isAutocomplete: bool to indicate whether this is an autocomplete message
+   * @param resultCount:    the number of records in the query results
    */
   std::shared_ptr<ndn::Data>
   makeReplyData(const ndn::Name& segmentPrefix,
                 const Json::Value& value,
                 uint64_t segmentNo,
                 bool isFinalBlock,
-                bool isAutocomplete);
+                bool isAutocomplete,
+                uint64_t resultCount);
 
   /**
    * Helper function that generates query results from a Json query carried in the Interest
@@ -142,15 +147,21 @@
               const ndn::Name::Component& version);
 
   /**
-   * Helper function that generates the sqlQuery string and autocomplete flag
-   * @param sqlQuery:     stringstream to save the sqlQuery string
-   * @param jsonValue:    Json value that contains the query information
-   * @param autocomplete: Flag to indicate if the json contains autocomplete flag
+   * Helper function that sends NACK
+   *
+   * @param dataPrefix: prefix for the data packet
    */
   void
+  sendNack(const ndn::Name& dataPrefix);
+
+  /**
+   * Helper function that generates the sqlQuery string for component-based query
+   * @param sqlQuery:     stringstream to save the sqlQuery string
+   * @param jsonValue:    Json value that contains the query information
+   */
+  bool
   json2Sql(std::stringstream& sqlQuery,
-           Json::Value& jsonValue,
-           bool& autocomplete);
+           Json::Value& jsonValue);
 
   /**
    * Helper function that signs the data
@@ -178,6 +189,15 @@
   void
   setFilters();
 
+  /**
+   * Helper function that generates the sqlQuery string for autocomplete query
+   * @param sqlQuery:     stringstream to save the sqlQuery string
+   * @param jsonValue:    Json value that contains the query information
+   */
+  bool
+  json2AutocompletionSql(std::stringstream& sqlQuery,
+                         Json::Value& jsonValue);
+
 protected:
   typedef std::unordered_map<ndn::Name, const ndn::RegisteredPrefixId*> RegisteredPrefixList;
   // Handle to the Catalog's database
@@ -192,6 +212,8 @@
   ndn::util::InMemoryStorageLru m_cache;
   // @}
   RegisteredPrefixList m_registeredPrefixList;
+  //std::vector<std::string> m_atmosColumns;
+  ndn::Name m_catalogId; // should be replaced with the PK digest
 };
 
 template <typename DatabaseHandler>
@@ -199,6 +221,7 @@
                                             const std::shared_ptr<ndn::KeyChain>& keyChain)
   : util::CatalogAdapter(face, keyChain)
   , m_cache(250000)
+  , m_catalogId("catalogIdPlaceHolder")
 {
 }
 
@@ -228,8 +251,10 @@
 template <typename DatabaseHandler>
 void
 QueryAdapter<DatabaseHandler>::setConfigFile(util::ConfigFile& config,
-                                             const ndn::Name& prefix)
+                                             const ndn::Name& prefix,
+                                             const std::vector<std::string>& nameFields)
 {
+  m_nameFields = nameFields;
   config.addSectionHandler("queryAdapter", bind(&QueryAdapter<DatabaseHandler>::onConfig, this,
                                                 _1, _2, _3, prefix));
 }
@@ -340,9 +365,9 @@
     return;
   }
   std::shared_ptr<const ndn::Interest> interestPtr = interest.shared_from_this();
-  #ifndef NDEBUG
-    std::cout << "query interest : " << interestPtr->getName() << std::endl;
-  #endif
+
+  std::cout << "incoming query interest : " << interestPtr->getName() << std::endl;
+
   // @todo: use thread pool
   std::thread queryThread(&QueryAdapter<DatabaseHandler>::runJsonQuery,
                           this,
@@ -359,9 +384,9 @@
   // CS so we just ignore any retrieval Interests that hit us for
   // now. In the future, this should check some form of
   // InMemoryStorage.
-  #ifndef NDEBUG
-    std::cout << "query results interest : " << interest.toUri() << std::endl;
-  #endif
+
+  std::cout << "incoming query-results interest : " << interest.toUri() << std::endl;
+
   auto data = m_cache.find(interest.getName());
   if (data) {
     m_face->put(*data);
@@ -389,23 +414,53 @@
   // JSON parsed ok, so we can acknowledge successful receipt of the query
   ndn::Name ackName(interest->getName());
   ackName.append(version);
+  ackName.append(m_catalogId);
   ackName.append("OK");
 
   std::shared_ptr<ndn::Data> ack = std::make_shared<ndn::Data>(ackName);
+  ack->setFreshnessPeriod(ndn::time::milliseconds(10000));
+
   signData(*ack);
-  #ifndef NDEBUG
-    std::cout << "makeAckData : " << ackName << std::endl;
-  #endif
+#ifndef NDEBUG
+  std::cout << "makeAckData : " << ackName << std::endl;
+#endif
   return ack;
 }
 
 template <typename DatabaseHandler>
 void
-QueryAdapter<DatabaseHandler>::json2Sql(std::stringstream& sqlQuery,
-                                        Json::Value& jsonValue,
-                                        bool& autocomplete)
+QueryAdapter<DatabaseHandler>::sendNack(const ndn::Name& dataPrefix)
 {
-  // 3) Convert the JSON Query into a MySQL one
+  uint64_t segmentNo = 0;
+
+  std::shared_ptr<ndn::Data> nack =
+    std::make_shared<ndn::Data>(ndn::Name(dataPrefix).appendSegment(segmentNo));
+  nack->setFreshnessPeriod(ndn::time::milliseconds(10000));
+  nack->setFinalBlockId(ndn::Name::Component::fromSegment(segmentNo));
+
+  signData(*nack);
+
+  std::cout << "make NACK : " << ndn::Name(dataPrefix).appendSegment(segmentNo) << std::endl;
+
+  m_mutex.lock();
+  m_cache.insert(*nack);
+  m_mutex.unlock();
+}
+
+
+template <typename DatabaseHandler>
+bool
+QueryAdapter<DatabaseHandler>::json2Sql(std::stringstream& sqlQuery,
+                                        Json::Value& jsonValue)
+{
+#ifndef NDEBUG
+  std::cout << "jsonValue in json2Sql: " << jsonValue.toStyledString() << std::endl;
+#endif
+  if (jsonValue.type() != Json::objectValue) {
+    std::cout << jsonValue.toStyledString() << "is not json object" << std::endl;
+    return false;
+  }
+
   sqlQuery << "SELECT name FROM cmip5";
   bool input = false;
   for (Json::Value::iterator iter = jsonValue.begin(); iter != jsonValue.end(); ++iter)
@@ -413,28 +468,115 @@
     Json::Value key = iter.key();
     Json::Value value = (*iter);
 
+    if (key == Json::nullValue || value == Json::nullValue) {
+      std::cout << "null key or value in JsonValue: " << jsonValue.toStyledString() << std::endl;
+      return false;
+    }
+
+    // cannot convert to string
+    if (!key.isConvertibleTo(Json::stringValue) || !value.isConvertibleTo(Json::stringValue)) {
+      std::cout << "malformed JsonQuery string : " << jsonValue.toStyledString() << std::endl;
+      return false;
+    }
+
+    if (key.asString().compare("?") == 0) {
+      continue;
+    }
+
     if (input) {
       sqlQuery << " AND";
     } else {
       sqlQuery << " WHERE";
     }
 
-    // Auto-complete case
-    if (key.asString().compare("?") == 0) {
-      sqlQuery << " name REGEXP '^" << value.asString() << "'";
-      autocomplete = true;
-    }
-    // Component case
-    else {
-      sqlQuery << " " << key.asString() << "='" << value.asString() << "'";
-    }
+    sqlQuery << " " << key.asString() << "='" << value.asString() << "'";
     input = true;
   }
 
   if (!input) { // Force it to be the empty set
-    sqlQuery << " limit 0";
+     return false;
   }
   sqlQuery << ";";
+  return true;
+}
+
+template <typename DatabaseHandler>
+bool
+QueryAdapter<DatabaseHandler>::json2AutocompletionSql(std::stringstream& sqlQuery,
+                                                      Json::Value& jsonValue)
+{
+#ifndef NDEBUG
+  std::cout << "jsonValue in json2AutocompletionSql: " << jsonValue.toStyledString() << std::endl;
+#endif
+  if (jsonValue.type() != Json::objectValue) {
+    std::cout << jsonValue.toStyledString() << "is not json object" << std::endl;
+    return false;
+  }
+
+  std::string typedString;
+  // get the string in the jsonValue
+  for (Json::Value::iterator iter = jsonValue.begin(); iter != jsonValue.end(); ++iter)
+  {
+    Json::Value key = iter.key();
+    Json::Value value = (*iter);
+
+    if (key == Json::nullValue || value == Json::nullValue) {
+      std::cout << "null key or value in JsonValue: " << jsonValue.toStyledString() << std::endl;
+      return false;
+    }
+
+    // cannot convert to string
+    if (!key.isConvertibleTo(Json::stringValue) || !value.isConvertibleTo(Json::stringValue)) {
+      std::cout << "malformed JsonQuery string : " << jsonValue.toStyledString() << std::endl;
+      return false;
+    }
+
+    if (key.asString().compare("?") == 0) {
+      typedString.assign(value.asString());
+      // since the front end triggers the autocompletion when users typed '/',
+      // there must be a '/' at the end, and the first char must be '/'
+      if (typedString.at(typedString.length() - 1) != '/' || typedString.find("/") != 0)
+        return false;
+      break;
+    }
+  }
+
+  // 1. get the expected column number by parsing the typedString, so we can get the filed name
+  size_t pos = 0;
+  size_t start = 1; // start from the 1st char which is not '/'
+  size_t count = 0; // also the name to query for
+  std::string token;
+  std::string delimiter = "/";
+  std::map<std::string, std::string> typedComponents;
+  while ((pos = typedString.find(delimiter, start)) != std::string::npos) {
+    token = typedString.substr(start, pos - start);
+    if (count >= m_nameFields.size() - 1) {
+      return false;
+    }
+
+    // add column name and value (token) into map
+    typedComponents.insert(std::pair<std::string, std::string>(m_nameFields[count], token));
+    count ++;
+    start = pos + 1;
+  }
+
+  // 2. generate the sql string (append what appears in the typed string, like activity='xxx'),
+  // return true
+  bool more = false;
+  sqlQuery << "SELECT " << m_nameFields[count] << " FROM cmip5";
+  for (std::map<std::string, std::string>::iterator it = typedComponents.begin();
+       it != typedComponents.end(); ++it) {
+    if (more)
+      sqlQuery << " AND";
+    else
+      sqlQuery << " WHERE";
+
+    sqlQuery << " " << it->first << "='" << it->second << "'";
+
+    more = true;
+  }
+  sqlQuery << ";";
+  return true;
 }
 
 template <typename DatabaseHandler>
@@ -449,10 +591,11 @@
   const std::string jsonQuery(reinterpret_cast<const char*>(jsonStr.value()), jsonStr.value_size());
 
   if (jsonQuery.length() <= 0) {
-    // send Nack?
+    // no JSON query, send Nack?
     return;
   }
-  // ------------------
+  // check if the ACK is cached, if yes, respond with ACK
+  // ?? what if the results for now it NULL, but latter exist?
   // For efficiency, do a double check. Once without the lock, then with it.
   if (m_activeQueryToFirstResponse.find(jsonQuery) != m_activeQueryToFirstResponse.end()) {
     m_mutex.lock();
@@ -478,6 +621,7 @@
     return;
   }
 
+  // the version should be replaced with ChronoSync state digest
   const ndn::name::Component version
     = ndn::name::Component::fromVersion(ndn::time::toUnixTimestamp(
                                           ndn::time::system_clock::now()).count());
@@ -494,6 +638,8 @@
       return;
     }
     // This is where things are expensive so we save them for the lock
+    // note that we ack the query with the cached ACK messages, but we should remove the ACKs
+    // that conatin the old version when ChronoSync is updated
     m_activeQueryToFirstResponse.insert(std::pair<std::string,
                                         std::shared_ptr<ndn::Data>>(jsonQuery, ack));
     m_face->put(*ack);
@@ -503,13 +649,33 @@
   // 3) Convert the JSON Query into a MySQL one
   bool autocomplete = false;
   std::stringstream sqlQuery;
-  json2Sql(sqlQuery, parsedFromString, autocomplete);
 
-  // 4) Run the Query
+  // the server side should conform: http://redmine.named-data.net/projects/ndn-atmos/wiki/Query
+  // for now, should be /<prefix>/query-results/<query-parameters>/<version>/, latter add catalog-id
   ndn::Name segmentPrefix(m_prefix);
   segmentPrefix.append("query-results");
+  segmentPrefix.append(jsonStr);
   segmentPrefix.append(version);
+  segmentPrefix.append(m_catalogId);
 
+  Json::Value tmp;
+  // expect the autocomplete and the component-based query are separate
+  // if JSON::Value contains ? as key, is autocompletion
+  if (parsedFromString.get("?", tmp) != tmp) {
+    autocomplete = true;
+    if (!json2AutocompletionSql(sqlQuery, parsedFromString)) {
+      sendNack(segmentPrefix);
+      return;
+    }
+  }
+  else {
+    if (!json2Sql(sqlQuery, parsedFromString)) {
+      sendNack(segmentPrefix);
+      return;
+    }
+  }
+
+  // 4) Run the Query
   prepareSegments(segmentPrefix, sqlQuery.str(), autocomplete);
 }
 
@@ -529,32 +695,29 @@
                                      const std::string& sqlString,
                                      bool autocomplete)
 {
-#ifndef NDEBUG
-  std::cout << "sqlString in prepareSegments : " << sqlString << std::endl;
-#endif
+  std::cout << "prepareSegments() executes sql : " << sqlString << std::endl;
+
   // 4) Run the Query
   std::shared_ptr<MYSQL_RES> results
     = atmos::util::MySQLPerformQuery(m_databaseHandler, sqlString);
 
   if (!results) {
-#ifndef NDEBUG
     std::cout << "null MYSQL_RES for query : " << sqlString << std::endl;
-#endif
+
     // @todo: throw runtime error or log the error message?
     return;
   }
 
-#ifndef NDEBUG
+  uint64_t resultCount = mysql_num_rows(results.get());
+
   std::cout << "Query results for \""
             << sqlString
             << "\" contain "
-            << mysql_num_rows(results.get())
+            << resultCount
             << " rows" << std::endl;
-#endif
 
   MYSQL_ROW row;
   size_t usedBytes = 0;
-  const size_t PAYLOAD_LIMIT = 7000;
   uint64_t segmentNo = 0;
   Json::Value array;
   while ((row = mysql_fetch_row(results.get())))
@@ -562,7 +725,7 @@
     size_t size = strlen(row[0]) + 1;
     if (usedBytes + size > PAYLOAD_LIMIT) {
       std::shared_ptr<ndn::Data> data
-        = makeReplyData(segmentPrefix, array, segmentNo, false, autocomplete);
+        = makeReplyData(segmentPrefix, array, segmentNo, false, autocomplete, resultCount);
       m_mutex.lock();
       m_cache.insert(*data);
       m_mutex.unlock();
@@ -574,7 +737,7 @@
     usedBytes += size;
   }
   std::shared_ptr<ndn::Data> data
-    = makeReplyData(segmentPrefix, array, segmentNo, true, autocomplete);
+    = makeReplyData(segmentPrefix, array, segmentNo, true, autocomplete, resultCount);
   m_mutex.lock();
   m_cache.insert(*data);
   m_mutex.unlock();
@@ -586,10 +749,13 @@
                                              const Json::Value& value,
                                              uint64_t segmentNo,
                                              bool isFinalBlock,
-                                             bool isAutocomplete)
+                                             bool isAutocomplete,
+                                             uint64_t resultCount)
 {
   Json::Value entry;
   Json::FastWriter fastWriter;
+  Json::UInt64 count(resultCount);
+  entry["resultCount"] = count;
   if (isAutocomplete) {
     entry["next"] = value;
   } else {
diff --git a/catalog/src/util/catalog-adapter.hpp b/catalog/src/util/catalog-adapter.hpp
index 2b03358..863f29a 100644
--- a/catalog/src/util/catalog-adapter.hpp
+++ b/catalog/src/util/catalog-adapter.hpp
@@ -72,7 +72,8 @@
    */
   virtual void
   setConfigFile(util::ConfigFile& config,
-                const ndn::Name& prefix) = 0;
+                const ndn::Name& prefix,
+                const std::vector<std::string>& nameFields) = 0;
 
 protected:
 
@@ -120,6 +121,7 @@
   ndn::Name m_prefix;
   // Name for the signing key
   ndn::Name m_signingId;
+  std::vector<std::string> m_nameFields;
 }; // class CatalogAdapter
 
 
diff --git a/catalog/tests/unit-tests/query/test-query-adapter.cpp b/catalog/tests/unit-tests/query/test-query-adapter.cpp
index 1e8d0c1..3be4544 100644
--- a/catalog/tests/unit-tests/query/test-query-adapter.cpp
+++ b/catalog/tests/unit-tests/query/test-query-adapter.cpp
@@ -45,6 +45,11 @@
     {
     }
 
+    void setNameFields(const std::vector<std::string>& nameFields)
+    {
+      m_nameFields = nameFields;
+    }
+
     void setPrefix(const ndn::Name& prefix)
     {
       m_prefix = prefix;
@@ -73,14 +78,11 @@
       return makeAckData(interest, version);
     }
 
-    void
-    parseJsonTest(std::string& targetSql,
-                  Json::Value& parsedFromString,
-                  bool& autocomplete)
+    bool
+    json2SqlTest(std::stringstream& ss,
+                 Json::Value& parsedFromString)
     {
-      std::stringstream resultSql;
-      json2Sql(resultSql, parsedFromString, autocomplete);
-      targetSql.assign(resultSql.str());
+      return json2Sql(ss, parsedFromString);
     }
 
     std::shared_ptr<ndn::Data>
@@ -88,9 +90,11 @@
                  const Json::Value& value,
                  uint64_t segmentNo,
                  bool isFinalBlock,
-                 bool isAutocomplete)
+                 bool isAutocomplete,
+                 uint64_t resultCount)
     {
-      return makeReplyData(segmentPrefix, value, segmentNo, isFinalBlock, isAutocomplete);
+      return makeReplyData(segmentPrefix, value, segmentNo, isFinalBlock,
+                           isAutocomplete, resultCount);
     }
 
     void
@@ -111,10 +115,7 @@
       fileList.append("/ndn/test3");
 
       std::shared_ptr<ndn::Data> data = makeReplyData(segmentPrefix,
-                                                      fileList,
-                                                      0,
-                                                      true,
-                                                      false);
+                                                      fileList, 0, true, false, 3);
       m_mutex.lock();
       m_cache.insert(*data);
       m_mutex.unlock();
@@ -147,6 +148,13 @@
     {
       onConfig(section, false, std::string("test.txt"), prefix);
     }
+
+    bool
+    json2AutocompletionSqlTest(std::stringstream& sqlQuery,
+                               Json::Value& jsonValue)
+    {
+      return json2AutocompletionSql(sqlQuery, jsonValue);
+    }
   };
 
   class QueryAdapterFixture : public UnitTestTimeFixture
@@ -158,6 +166,19 @@
       , queryAdapterTest1(face, keyChain)
       , queryAdapterTest2(face, keyChain)
     {
+      std::string c1("activity"), c2("product"), c3("organization"), c4("model");
+      std::string c5("experiment"), c6("frequency"), c7("modeling_realm"), c8("variable_name");
+      std::string c9("ensemble"), c10("time");
+      nameFields.push_back(c1);
+      nameFields.push_back(c2);
+      nameFields.push_back(c3);
+      nameFields.push_back(c4);
+      nameFields.push_back(c5);
+      nameFields.push_back(c6);
+      nameFields.push_back(c7);
+      nameFields.push_back(c8);
+      nameFields.push_back(c9);
+      nameFields.push_back(c10);
     }
 
     virtual
@@ -185,6 +206,7 @@
       catch (boost::property_tree::info_parser_error &e) {
         std::cout << "Failed to read config file " << e.what() << std::endl;;
       }
+      queryAdapterTest1.setNameFields(nameFields);
       queryAdapterTest1.configAdapter(section, ndn::Name("/test"));
     }
 
@@ -206,6 +228,7 @@
       catch (boost::property_tree::info_parser_error &e) {
         std::cout << "Failed to read config file " << e.what() << std::endl;;
       }
+      queryAdapterTest2.setNameFields(nameFields);
       queryAdapterTest2.configAdapter(section, ndn::Name("/test"));
     }
 
@@ -214,6 +237,7 @@
     std::shared_ptr<ndn::KeyChain> keyChain;
     QueryAdapterTest queryAdapterTest1;
     QueryAdapterTest queryAdapterTest2;
+    std::vector<std::string> nameFields;
   };
 
   BOOST_FIXTURE_TEST_SUITE(QueryAdapterTestSuite, QueryAdapterFixture)
@@ -238,23 +262,18 @@
     testJson["activity"] = "testActivity";
     testJson["product"] = "testProduct";
 
-    std::string dstString;
-    bool autocomplete = false;
-    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
-    BOOST_CHECK_EQUAL(dstString, "SELECT name FROM cmip5 WHERE\
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(true, queryAdapterTest1.json2SqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL(ss.str(), "SELECT name FROM cmip5 WHERE\
  activity=\'testActivity\' AND name='test\' AND product=\'testProduct\';");
-    BOOST_CHECK_EQUAL(autocomplete, false);
   }
 
   BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseEmptyTest)
   {
     Json::Value testJson;
 
-    std::string dstString;
-    bool autocomplete = false;
-    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
-    BOOST_CHECK_EQUAL(dstString, "SELECT name FROM cmip5 limit 0;");
-    BOOST_CHECK_EQUAL(autocomplete, false);
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest1.json2SqlTest(ss, testJson));
   }
 
   BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseAllItemsTest)
@@ -279,10 +298,9 @@
     testJson["output type"] = "testOutputType";
     testJson["timestamp"] = "testTimestamp";
 
-    std::string dstString;
-    bool autocomplete = false;
-    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
-    BOOST_CHECK_EQUAL(dstString, "SELECT name FROM cmip5 WHERE activity=\'testActivity\' AND \
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(true, queryAdapterTest1.json2SqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL(ss.str(), "SELECT name FROM cmip5 WHERE activity=\'testActivity\' AND \
 ensemble=\'testEnsemble\' AND ensemble member=\'testEnsembleMember\' AND \
 experiment=\'testExperiment\' AND field campaign=\'testFieldCampaign\' AND \
 frequency=\'testFrenquency\' AND grid resolution=\'testGridResolution\' AND \
@@ -291,21 +309,65 @@
 output type=\'testOutputType\' AND product=\'testProduct\' AND sample \
 granularity=\'testSampleGranularity\' AND start time=\'testStartTime\' AND \
 timestamp=\'testTimestamp\' AND variable name=\'testVarName\';");
-    BOOST_CHECK_EQUAL(autocomplete, false);
   }
 
   BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseSearchTest)
   {
+    // incorrect autocompletion is ok for sql conversion
     Json::Value testJson;
     testJson["name"] = "test";
     testJson["?"] = "serchTest";
 
-    std::string dstString;
-    bool autocomplete = false;
-    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
-    BOOST_CHECK_EQUAL(dstString,
-      "SELECT name FROM cmip5 WHERE name REGEXP \'^serchTest\' AND name=\'test\';");
-    BOOST_CHECK_EQUAL(autocomplete, true);
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(true, queryAdapterTest1.json2SqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL(ss.str(),
+      "SELECT name FROM cmip5 WHERE name=\'test\';");
+  }
+
+   BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseFailTest1)
+   {
+     Json::Value testJson;
+    testJson["name"] = Json::nullValue;
+
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest1.json2SqlTest(ss, testJson));
+   }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseFailTest2)
+  {
+    Json::Value testJson;
+
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest1.json2SqlTest(ss, testJson));
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseFailTest3)
+  {
+    Json::Value testJson;
+    testJson = Json::Value(Json::arrayValue);
+
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest1.json2SqlTest(ss, testJson));
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseFailTest4)
+  {
+    Json::Value testJson;
+    testJson[0] = "test";
+
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest1.json2SqlTest(ss, testJson));
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseFailTest5)
+  {
+    Json::Value testJson;
+    Json::Value param;
+    param[0] = "test";
+    testJson["name"] = param;
+
+    std::stringstream ss;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest1.json2SqlTest(ss, testJson));
   }
 
   BOOST_AUTO_TEST_CASE(QueryAdapterMakeAckDataTest)
@@ -319,7 +381,7 @@
       = ndn::name::Component::fromVersion(1);
 
     std::shared_ptr<ndn::Data> data = queryAdapterTest2.getAckData(interestPtr, version);
-    BOOST_CHECK_EQUAL(data->getName().toUri(), "/test/ack/data/json/%FD%01/OK");
+    BOOST_CHECK_EQUAL(data->getName().toUri(), "/test/ack/data/json/%FD%01/catalogIdPlaceHolder/OK");
     BOOST_CHECK_EQUAL(data->getContent().value_size(), 0);
   }
 
@@ -331,17 +393,15 @@
 
     const ndn::Name prefix("/atmos/test/prefix");
 
-    std::shared_ptr<ndn::Data> data = queryAdapterTest2.getReplyData(prefix,
-      fileList,
-      1,
-      false,
-      false);
+    std::shared_ptr<ndn::Data> data = queryAdapterTest2.getReplyData(prefix, fileList,
+                                                                     1, false, false, 2);
     BOOST_CHECK_EQUAL(data->getName().toUri(), "/atmos/test/prefix/%00%01");
     BOOST_CHECK_EQUAL(data->getFinalBlockId(), ndn::Name::Component(""));
     const std::string jsonRes(reinterpret_cast<const char*>(data->getContent().value()));
     Json::Value parsedFromString;
     Json::Reader reader;
     BOOST_CHECK_EQUAL(reader.parse(jsonRes, parsedFromString), true);
+    BOOST_CHECK_EQUAL(parsedFromString["resultCount"], 2);
     BOOST_CHECK_EQUAL(parsedFromString["results"].size(), 2);
     BOOST_CHECK_EQUAL(parsedFromString["results"][0], "/ndn/test1");
     BOOST_CHECK_EQUAL(parsedFromString["results"][1], "/ndn/test2");
@@ -354,17 +414,15 @@
     const ndn::Name prefix("/atmos/test/prefix");
 
     std::shared_ptr<ndn::Data> data = queryAdapterTest2.getReplyData(prefix,
-      fileList,
-      2,
-      true,
-      true);
-    // the finalBlock does not work for jsNDN
+                                                                     fileList, 2, true, true, 1);
+
     BOOST_CHECK_EQUAL(data->getName().toUri(), "/atmos/test/prefix/%00%02");
     BOOST_CHECK_EQUAL(data->getFinalBlockId(), ndn::Name::Component::fromSegment(2));
     const std::string jsonRes(reinterpret_cast<const char*>(data->getContent().value()));
     Json::Value parsedFromString;
     Json::Reader reader;
     BOOST_CHECK_EQUAL(reader.parse(jsonRes, parsedFromString), true);
+    BOOST_CHECK_EQUAL(parsedFromString["resultCount"], 1);
     BOOST_CHECK_EQUAL(parsedFromString["next"].size(), 1);
     BOOST_CHECK_EQUAL(parsedFromString["next"][0], "/ndn/test1");
   }
@@ -387,7 +445,8 @@
     if (ackData) {
       BOOST_CHECK_EQUAL(ackData->getName().getPrefix(3),
       ndn::Name("/test/query/%7B%22name%22%3A%22test%22%7D"));
-      BOOST_CHECK_EQUAL(ackData->getName().getSubName(4, 1), ndn::Name("OK"));
+      BOOST_CHECK_EQUAL(ackData->getName().at(ackData->getName().size() - 1),
+                        ndn::Name::Component("OK"));
       BOOST_CHECK_EQUAL(ackData->getContent().value_size(), 0);
     }
 
@@ -401,6 +460,7 @@
       Json::Value parsedFromString;
       Json::Reader reader;
       BOOST_CHECK_EQUAL(reader.parse(jsonRes, parsedFromString), true);
+      BOOST_CHECK_EQUAL(parsedFromString["resultCount"], 3);
       BOOST_CHECK_EQUAL(parsedFromString["results"].size(), 3);
       BOOST_CHECK_EQUAL(parsedFromString["results"][0], "/ndn/test1");
       BOOST_CHECK_EQUAL(parsedFromString["results"][1], "/ndn/test2");
@@ -408,6 +468,78 @@
     }
   }
 
+  BOOST_AUTO_TEST_CASE(QueryAdapterAutocompletionSqlSuccessTest)
+  {
+    initializeQueryAdapterTest2();
+
+    std::stringstream ss;
+    Json::Value testJson;
+    testJson["?"] = "/";
+    BOOST_CHECK_EQUAL(true, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL("SELECT activity FROM cmip5;", ss.str());
+
+    ss.str("");
+    ss.clear();
+    testJson.clear();
+    testJson["?"] = "/Activity/";
+    BOOST_CHECK_EQUAL(true, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL("SELECT product FROM cmip5 WHERE activity='Activity';", ss.str());
+
+    ss.str("");
+    ss.clear();
+    testJson.clear();
+    testJson["?"] = "/Activity/Product/Organization/Model/Experiment/";
+    BOOST_CHECK_EQUAL(true, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL("SELECT frequency FROM cmip5 WHERE activity='Activity' AND experiment=\
+'Experiment' AND model='Model' AND organization='Organization' AND product='Product';", ss.str());
+
+    ss.str("");
+    ss.clear();
+    testJson.clear();
+    testJson["?"] = "/Activity/Product/Organization/Model/Experiment/Frequency/Modeling/\
+Variable/Ensemble/";
+    BOOST_CHECK_EQUAL(true, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson));
+    BOOST_CHECK_EQUAL("SELECT time FROM cmip5 WHERE activity='Activity' AND ensemble='Ensemble' AND\
+ experiment='Experiment' AND frequency='Frequency' AND model='Model' AND modeling_realm='Modeling' \
+AND organization='Organization' AND product='Product' AND variable_name='Variable';",ss.str());
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterAutocompletionSqlFailTest)
+  {
+    initializeQueryAdapterTest2();
+
+    std::stringstream ss;
+    Json::Value testJson;
+    testJson["?"] = "serchTest";
+    BOOST_CHECK_EQUAL(false, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson));
+
+    ss.str("");
+    ss.clear();
+    testJson.clear();
+    testJson["?"] = "/cmip5";
+    BOOST_CHECK_EQUAL(false, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson));
+
+    ss.str("");
+    ss.clear();
+    Json::Value testJson2; //simply clear does not work
+    testJson2[0] = "test";
+    BOOST_CHECK_EQUAL(false, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson2));
+
+    ss.str("");
+    ss.clear();
+    Json::Value testJson3;
+    testJson3 = Json::Value(Json::arrayValue);
+    BOOST_CHECK_EQUAL(false, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson3));
+
+    ss.str("");
+    ss.clear();
+    Json::Value testJson4;
+    Json::Value param;
+    param[0] = "test";
+    testJson4["name"] = param;
+    BOOST_CHECK_EQUAL(false, queryAdapterTest2.json2AutocompletionSqlTest(ss, testJson4));
+}
+
   BOOST_AUTO_TEST_SUITE_END()
 
 }//tests