You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2022/05/24 10:46:14 UTC

[ignite] branch master updated: IGNITE-16877 C++ thin: Implement ScanQuery

This is an automated email from the ASF dual-hosted git repository.

isapego pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new b121a905850 IGNITE-16877 C++ thin: Implement ScanQuery
b121a905850 is described below

commit b121a905850c2398c82706cdcc164bf0bcaa815c
Author: Igor Sapego <is...@apache.org>
AuthorDate: Tue May 24 14:40:59 2022 +0400

    IGNITE-16877 C++ thin: Implement ScanQuery
    
    This closes #10033
---
 .../core/include/ignite/cache/query/query_scan.h   |   4 +-
 .../core/include/ignite/cache/query/query_sql.h    |   4 +-
 .../include/ignite/cache/query/query_sql_fields.h  |   4 +-
 .../core/include/ignite/cache/query/query_text.h   |   4 +-
 .../platforms/cpp/thin-client-test/CMakeLists.txt  |   1 +
 .../config/sql-query-fields-default.xml            |   6 +
 .../cpp/thin-client-test/src/scan_query_test.cpp   | 436 +++++++++++++++++++++
 modules/platforms/cpp/thin-client/CMakeLists.txt   |   1 +
 .../ignite/impl/thin/cache/cache_client_proxy.h    |  12 +-
 .../impl/thin/cache/query/query_cursor_proxy.h     |  88 +++++
 .../include/ignite/impl/thin/readable.h            | 130 +++++-
 .../include/ignite/thin/cache/cache_client.h       |  15 +-
 .../include/ignite/thin/cache/cache_entry.h        |   9 +
 .../include/ignite/thin/cache/query/query_cursor.h | 151 +++++++
 .../include/ignite/thin/cache/query/query_scan.h   | 144 +++++++
 .../ignite/thin/cache/query/query_sql_fields.h     |   8 +-
 .../src/cache/query/query_cursor_proxy.cpp         |  67 ++++
 .../src/impl/cache/cache_client_impl.cpp           |  18 +
 .../thin-client/src/impl/cache/cache_client_impl.h |  10 +
 .../src/impl/cache/cache_client_proxy.cpp          |  17 +-
 ...ry_fields_cursor_impl.h => query_cursor_impl.h} |  91 ++---
 .../impl/cache/query/query_fields_cursor_impl.h    |   6 +-
 .../platforms/cpp/thin-client/src/impl/message.cpp |  36 +-
 .../platforms/cpp/thin-client/src/impl/message.h   | 122 +++++-
 24 files changed, 1272 insertions(+), 112 deletions(-)

diff --git a/modules/platforms/cpp/core/include/ignite/cache/query/query_scan.h b/modules/platforms/cpp/core/include/ignite/cache/query/query_scan.h
index 4228ba5bc3e..3d931713773 100644
--- a/modules/platforms/cpp/core/include/ignite/cache/query/query_scan.h
+++ b/modules/platforms/cpp/core/include/ignite/cache/query/query_scan.h
@@ -111,7 +111,9 @@ namespace ignite
                 /**
                  * Set local flag.
                  *
-                 * @param loc Local flag.
+                 * @param val Value of the flag. If true, query will be
+                 *     executed only on local node, so only local entries
+                 *     will be returned as query result.
                  */
                 void SetLocal(bool loc)
                 {
diff --git a/modules/platforms/cpp/core/include/ignite/cache/query/query_sql.h b/modules/platforms/cpp/core/include/ignite/cache/query/query_sql.h
index b896c8aac03..46c0a98a2a0 100644
--- a/modules/platforms/cpp/core/include/ignite/cache/query/query_sql.h
+++ b/modules/platforms/cpp/core/include/ignite/cache/query/query_sql.h
@@ -201,7 +201,9 @@ namespace ignite
                 /**
                  * Set local flag.
                  *
-                 * @param loc Local flag.
+                 * @param val Value of the flag. If true, query will be
+                 *     executed only on local node, so only local entries
+                 *     will be returned as query result.
                  */
                 void SetLocal(bool loc)
                 {
diff --git a/modules/platforms/cpp/core/include/ignite/cache/query/query_sql_fields.h b/modules/platforms/cpp/core/include/ignite/cache/query/query_sql_fields.h
index 9c051a3dc89..8da9bedaa7b 100644
--- a/modules/platforms/cpp/core/include/ignite/cache/query/query_sql_fields.h
+++ b/modules/platforms/cpp/core/include/ignite/cache/query/query_sql_fields.h
@@ -205,7 +205,9 @@ namespace ignite
                 /**
                  * Set local flag.
                  *
-                 * @param loc Local flag.
+                 * @param val Value of the flag. If true, query will be
+                 *     executed only on local node, so only local entries
+                 *     will be returned as query result.
                  */
                 void SetLocal(bool loc)
                 {
diff --git a/modules/platforms/cpp/core/include/ignite/cache/query/query_text.h b/modules/platforms/cpp/core/include/ignite/cache/query/query_text.h
index ded8f220263..d73e73c00c6 100644
--- a/modules/platforms/cpp/core/include/ignite/cache/query/query_text.h
+++ b/modules/platforms/cpp/core/include/ignite/cache/query/query_text.h
@@ -125,7 +125,9 @@ namespace ignite
                 /**
                  * Set local flag.
                  *
-                 * @param loc Local flag.
+                 * @param val Value of the flag. If true, query will be
+                 *     executed only on local node, so only local entries
+                 *     will be returned as query result.
                  */
                 void SetLocal(bool loc)
                 {
diff --git a/modules/platforms/cpp/thin-client-test/CMakeLists.txt b/modules/platforms/cpp/thin-client-test/CMakeLists.txt
index 215871f9554..3b2f095a223 100644
--- a/modules/platforms/cpp/thin-client-test/CMakeLists.txt
+++ b/modules/platforms/cpp/thin-client-test/CMakeLists.txt
@@ -38,6 +38,7 @@ set(SOURCES
         src/test_utils.cpp
         src/ignite_client_test.cpp
         src/interop_test.cpp
+        src/scan_query_test.cpp
         src/sql_fields_query_test.cpp
         src/auth_test.cpp
         src/tx_test.cpp
diff --git a/modules/platforms/cpp/thin-client-test/config/sql-query-fields-default.xml b/modules/platforms/cpp/thin-client-test/config/sql-query-fields-default.xml
index 9a716a31721..5a2c6abd0a1 100644
--- a/modules/platforms/cpp/thin-client-test/config/sql-query-fields-default.xml
+++ b/modules/platforms/cpp/thin-client-test/config/sql-query-fields-default.xml
@@ -79,6 +79,12 @@
         <property name="cacheMode" value="PARTITIONED"/>
         <property name="atomicityMode" value="TRANSACTIONAL"/>
 
+        <property name="affinity">
+            <bean class="org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction">
+                <property name="partitions" value="256"/>
+            </bean>
+        </property>
+
         <!-- Configure type metadata to enable queries. -->
         <property name="queryEntities">
             <list>
diff --git a/modules/platforms/cpp/thin-client-test/src/scan_query_test.cpp b/modules/platforms/cpp/thin-client-test/src/scan_query_test.cpp
new file mode 100644
index 00000000000..c37c965c21e
--- /dev/null
+++ b/modules/platforms/cpp/thin-client-test/src/scan_query_test.cpp
@@ -0,0 +1,436 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <boost/test/unit_test.hpp>
+
+#include <ignite/ignite_error.h>
+#include <ignite/ignition.h>
+
+#include <ignite/thin/ignite_client_configuration.h>
+#include <ignite/thin/ignite_client.h>
+
+#include <ignite/test_type.h>
+#include <test_utils.h>
+
+using namespace ignite::thin;
+using namespace ignite::thin::cache::query;
+using namespace boost::unit_test;
+
+class ScanQueryTestSuiteFixture
+{
+public:
+    static ignite::Ignite StartNode(const char* name)
+    {
+        return ignite_test::StartCrossPlatformServerNode("sql-query-fields.xml", name);
+    }
+
+    ScanQueryTestSuiteFixture()
+    {
+        serverNode = StartNode("ServerNode");
+
+        IgniteClientConfiguration cfg;
+
+        cfg.SetEndPoints("127.0.0.1:11110");
+
+        client = IgniteClient::Start(cfg);
+
+        cacheAllFields = client.GetCache<int64_t, ignite::TestType>("cacheAllFields");
+    }
+
+    ~ScanQueryTestSuiteFixture()
+    {
+        ignite::Ignition::StopAll(false);
+    }
+
+protected:
+    /** Server node. */
+    ignite::Ignite serverNode;
+
+    /** Client. */
+    IgniteClient client;
+
+    /** Cache with TestType. */
+    cache::CacheClient<int64_t, ignite::TestType> cacheAllFields;
+};
+
+BOOST_AUTO_TEST_SUITE(ScanQueryBasicTestSuite)
+
+BOOST_AUTO_TEST_CASE(ScanQueryDefaults)
+{
+    ScanQuery qry;
+
+    BOOST_CHECK_EQUAL(qry.GetPartition(), -1);
+    BOOST_CHECK_EQUAL(qry.GetPageSize(), 1024);
+
+    BOOST_CHECK(!qry.IsLocal());
+}
+
+BOOST_AUTO_TEST_CASE(ScanQuerySetGet)
+{
+    ScanQuery qry;
+
+    qry.SetPageSize(4096);
+    qry.SetPartition(200);
+
+    qry.SetLocal(true);
+
+    BOOST_CHECK_EQUAL(qry.GetPageSize(), 4096);
+    BOOST_CHECK_EQUAL(qry.GetPartition(), 200);
+
+    BOOST_CHECK(qry.IsLocal());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+namespace
+{
+    /**
+     * Check that error empty cursor error.
+     *
+     * @param err Error.
+     */
+    bool IsCursorEmptyError(const ignite::IgniteError& err)
+    {
+        return err.GetCode() == ignite::IgniteError::IGNITE_ERR_GENERIC &&
+            std::string(err.GetText()) == "The cursor is empty";
+    }
+
+    /**
+     * Check that cursor is empty.
+     *
+     * @param cursor Cursor.
+     */
+    template<typename K, typename V>
+    void CheckCursorEmpty(QueryCursor<K, V>& cursor)
+    {
+        BOOST_CHECK(!cursor.HasNext());
+        BOOST_CHECK_EXCEPTION(cursor.GetNext(), ignite::IgniteError, IsCursorEmptyError);
+    }
+    /**
+     * Check empty result through GetAll().
+     *
+     * @param cur Cursor.
+     */
+    template<typename K, typename V>
+    void CheckEmptyGetAll(QueryCursor<K, V>& cur)
+    {
+        std::vector<cache::CacheEntry<K, V> > res;
+
+        cur.GetAll(res);
+
+        BOOST_REQUIRE(res.size() == 0);
+
+        CheckCursorEmpty(cur);
+    }
+
+    /**
+     * Check empty result through iter version of GetAll().
+     *
+     * @param cur Cursor.
+     */
+    template<typename K, typename V>
+    void CheckEmptyGetAllIter(QueryCursor<K, V>& cur)
+    {
+        std::vector<cache::CacheEntry<K, V> > res;
+
+        cur.GetAll(std::back_inserter(res));
+
+        BOOST_REQUIRE(res.size() == 0);
+
+        CheckCursorEmpty(cur);
+    }
+
+    /**
+     * Check single result through iteration.
+     *
+     * @param cur Cursor.
+     * @param key Key.
+     * @param value Value.
+     */
+    template<typename K, typename V>
+    void CheckSingle(QueryCursor<K, V>& cur, const K& key, const V& value)
+    {
+        BOOST_REQUIRE(cur.HasNext());
+
+        cache::CacheEntry<K, V> entry = cur.GetNext();
+
+        BOOST_REQUIRE(entry.GetKey() == key);
+        BOOST_REQUIRE(entry.GetValue() == value);
+
+        CheckCursorEmpty(cur);
+    }
+
+    /**
+     * Check single result through GetAll().
+     *
+     * @param cur Cursor.
+     * @param key Key.
+     * @param value Value.
+     */
+    template<typename K, typename V>
+    void CheckSingleGetAll(QueryCursor<K, V>& cur, const K& key, const V& value)
+    {
+        std::vector< cache::CacheEntry<K, V> > res;
+
+        cur.GetAll(res);
+
+        CheckCursorEmpty(cur);
+
+        BOOST_CHECK_EQUAL(res.size(), 1);
+
+        BOOST_CHECK_EQUAL(res[0].GetKey(), key);
+        BOOST_CHECK(res[0].GetValue() == value);
+    }
+
+    /**
+     * Check single result through iter version of GetAll().
+     *
+     * @param cur Cursor.
+     * @param key Key.
+     * @param value Value.
+     */
+    template<typename K, typename V>
+    void CheckSingleGetAllIter(QueryCursor<K, V>& cur, const K& key, const V& value)
+    {
+        std::vector< cache::CacheEntry<K, V> > res;
+
+        cur.GetAll(std::back_inserter(res));
+
+        CheckCursorEmpty(cur);
+
+        BOOST_CHECK_EQUAL(res.size(), 1);
+
+        BOOST_CHECK_EQUAL(res[0].GetKey(), key);
+        BOOST_CHECK(res[0].GetValue() == value);
+    }
+
+    /**
+     * Check multiple results through iteration.
+     *
+     * @param cur Cursor.
+     * @param key1 Key 1.
+     * @param value1 Value 1.
+     * @param key2 Key 2.
+     * @param value2 Value 2.
+     */
+    template<typename K, typename V>
+    void CheckMultiple(QueryCursor<K, V>& cur, const K& key1, const V& value1, const K& key2, const V& value2)
+    {
+        for (int i = 0; i < 2; i++)
+        {
+            BOOST_REQUIRE(cur.HasNext());
+
+            cache::CacheEntry<K, V> entry = cur.GetNext();
+
+            if (entry.GetKey() == key1)
+                BOOST_CHECK(entry.GetValue() == value1);
+            else if (entry.GetKey() == key2)
+                BOOST_CHECK(entry.GetValue() == value2);
+            else
+                BOOST_FAIL("Unexpected entry.");
+        }
+
+        CheckCursorEmpty(cur);
+    }
+
+    /**
+     * Check multiple results through GetAll().
+     *
+     * @param cur Cursor.
+     * @param key1 Key 1.
+     * @param value1 Value 1.
+     * @param key2 Key 2.
+     * @param value2 Value 2.
+     */
+    template<typename K, typename V>
+    void CheckMultipleGetAll(QueryCursor<K, V>& cur, const K& key1, const V& value1, const K& key2, const V& value2)
+    {
+        std::vector< cache::CacheEntry<K, V> > res;
+
+        cur.GetAll(res);
+
+        CheckCursorEmpty(cur);
+
+        BOOST_REQUIRE_EQUAL(res.size(), 2);
+
+        for (int i = 0; i < 2; i++)
+        {
+            cache::CacheEntry<K, V> entry = res[i];
+
+            if (entry.GetKey() == key1)
+                BOOST_CHECK(entry.GetValue() == value1);
+            else if (entry.GetKey() == key2)
+                BOOST_CHECK(entry.GetValue() == value2);
+            else
+                BOOST_FAIL("Unexpected entry.");
+        }
+    }
+
+    /**
+     * Check multiple results through GetAll().
+     *
+     * @param cur Cursor.
+     * @param key1 Key 1.
+     * @param value1 Value 1.
+     * @param key2 Key 2.
+     * @param value2 Value 2.
+     */
+    template<typename K, typename V>
+    void CheckMultipleGetAllIter(QueryCursor<K, V>& cur, const K& key1, const V& value1, const K& key2, const V& value2)
+    {
+        std::vector< cache::CacheEntry<K, V> > res;
+
+        cur.GetAll(std::back_inserter(res));
+
+        CheckCursorEmpty(cur);
+
+        BOOST_REQUIRE_EQUAL(res.size(), 2);
+
+        for (int i = 0; i < 2; i++)
+        {
+            cache::CacheEntry<K, V> entry = res[i];
+
+            if (entry.GetKey() == key1)
+                BOOST_CHECK(entry.GetValue() == value1);
+            else if (entry.GetKey() == key2)
+                BOOST_CHECK(entry.GetValue() == value2);
+            else
+                BOOST_FAIL("Unexpected entry.");
+        }
+    }
+
+    /**
+     * Make custom test value.
+     *
+     * @param seed Seed to generate value.
+     */
+    IGNORE_SIGNED_OVERFLOW
+    ignite::TestType MakeCustomTestValue(int64_t seed)
+    {
+        ignite::TestType val;
+
+        val.i8Field = static_cast<int8_t>(seed);
+        val.i16Field = static_cast<int16_t>(2 * seed);
+        val.i32Field = static_cast<int32_t>(4 * seed);
+        val.i64Field = 8 * seed;
+        val.strField = "Lorem ipsum";
+        val.floatField = 16.0f * seed;
+        val.doubleField = 32.0 * seed;
+        val.boolField = ((seed % 2) == 0);
+        val.guidField = ignite::Guid(0x1020304050607080 * seed, 0x9000A0B0C0D0E0F0 * seed);
+        val.dateField = ignite::Date(235682736 * seed);
+        val.timeField = ignite::Time((124523 * seed) % (24 * 60 * 60 * 1000));
+        val.timestampField = ignite::Timestamp(128341594123 * seed);
+
+        val.i8ArrayField.push_back(static_cast<int8_t>(9 * seed));
+        val.i8ArrayField.push_back(static_cast<int8_t>(6 * seed));
+        val.i8ArrayField.push_back(static_cast<int8_t>(3 * seed));
+        val.i8ArrayField.push_back(static_cast<int8_t>(42 * seed));
+
+        return val;
+    }
+
+} // anonymous namespace
+
+BOOST_FIXTURE_TEST_SUITE(ScanQueryTestSuite, ScanQueryTestSuiteFixture)
+
+/**
+ * Test scan query.
+ */
+BOOST_AUTO_TEST_CASE(TestScanQuery)
+{
+    // Test query with no results.
+    ScanQuery qry;
+
+    QueryCursor<int64_t, ignite::TestType> cursor = cacheAllFields.Query(qry);
+    CheckCursorEmpty(cursor);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckEmptyGetAll(cursor);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckEmptyGetAllIter(cursor);
+
+    int64_t key1 = 1;
+    ignite::TestType val1 = MakeCustomTestValue(1);
+
+    // Test simple query.
+    cacheAllFields.Put(key1, val1);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckSingle(cursor, key1, val1);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckSingleGetAll(cursor, key1, val1);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckSingleGetAllIter(cursor, key1, val1);
+
+    int64_t key2 = 2;
+    ignite::TestType val2 = MakeCustomTestValue(2);
+
+    // Test query returning multiple entries.
+    cacheAllFields.Put(key2, val2);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckMultiple(cursor, key1, val1, key2, val2);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckMultipleGetAll(cursor, key1, val1, key2, val2);
+
+    cursor = cacheAllFields.Query(qry);
+    CheckMultipleGetAllIter(cursor, key1, val1, key2, val2);
+}
+
+/**
+ * Test scan query over partitions.
+ */
+BOOST_AUTO_TEST_CASE(TestScanQueryPartitioned)
+{
+    // Populate cache with data.
+    int32_t partCnt = 256;   // Defined in configuration explicitly.
+    int64_t entryCnt = 1000; // Should be greater than partCnt.
+
+    for (int64_t i = 0; i < entryCnt; i++)
+        cacheAllFields.Put(i, MakeCustomTestValue(i));
+
+    // Iterate over all partitions and collect data.
+    std::set<int64_t> keys;
+
+    for (int32_t i = 0; i < partCnt; i++)
+    {
+        ScanQuery qry;
+        qry.SetPartition(i);
+
+        QueryCursor<int64_t, ignite::TestType> cur = cacheAllFields.Query(qry);
+
+        while (cur.HasNext())
+        {
+            cache::CacheEntry<int64_t, ignite::TestType> entry = cur.GetNext();
+
+            int64_t key = entry.GetKey();
+            keys.insert(key);
+
+            BOOST_REQUIRE(entry.GetValue() == MakeCustomTestValue(key));
+        }
+    }
+
+    // Ensure that all keys were read.
+    BOOST_CHECK_EQUAL(keys.size(), entryCnt);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/modules/platforms/cpp/thin-client/CMakeLists.txt b/modules/platforms/cpp/thin-client/CMakeLists.txt
index 20d52eca542..f6a742f97e3 100644
--- a/modules/platforms/cpp/thin-client/CMakeLists.txt
+++ b/modules/platforms/cpp/thin-client/CMakeLists.txt
@@ -41,6 +41,7 @@ set(SOURCES src/impl/data_channel.cpp
         src/impl/transactions/transactions_proxy.cpp
         src/compute/compute_client.cpp
         src/ignite_client.cpp
+        src/cache/query/query_cursor_proxy.cpp
         src/cache/query/query_fields_cursor.cpp
         src/cache/query/query_fields_row.cpp)
 
diff --git a/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/cache_client_proxy.h b/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/cache_client_proxy.h
index 01ee3953ae2..83f71023292 100644
--- a/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/cache_client_proxy.h
+++ b/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/cache_client_proxy.h
@@ -21,9 +21,11 @@
 #include <ignite/common/concurrent.h>
 
 #include <ignite/thin/cache/query/query_fields_cursor.h>
+#include <ignite/thin/cache/query/query_scan.h>
 #include <ignite/thin/cache/query/query_sql_fields.h>
 
 #include <ignite/impl/thin/cache/continuous/continuous_query_client_holder.h>
+#include <ignite/impl/thin/cache/query/query_cursor_proxy.h>
 
 namespace ignite
 {
@@ -59,7 +61,7 @@ namespace ignite
                     /**
                      * Constructor.
                      */
-                    CacheClientProxy(const common::concurrent::SharedPointer<void>& impl) :
+                    explicit CacheClientProxy(const common::concurrent::SharedPointer<void>& impl) :
                         impl(impl)
                     {
                         // No-op.
@@ -299,6 +301,14 @@ namespace ignite
                     ignite::thin::cache::query::QueryFieldsCursor Query(
                             const ignite::thin::cache::query::SqlFieldsQuery& qry);
 
+                    /**
+                     * Perform scan query.
+                     *
+                     * @param qry Query.
+                     * @return Query cursor proxy.
+                     */
+                    query::QueryCursorProxy Query(const ignite::thin::cache::query::ScanQuery& qry);
+
                     /**
                      * Starts the continuous query execution
                      *
diff --git a/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/query/query_cursor_proxy.h b/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/query/query_cursor_proxy.h
new file mode 100644
index 00000000000..3953bb2b7d3
--- /dev/null
+++ b/modules/platforms/cpp/thin-client/include/ignite/impl/thin/cache/query/query_cursor_proxy.h
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_CURSOR_PROXY
+#define _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_CURSOR_PROXY
+
+#include <vector>
+
+#include <ignite/common/concurrent.h>
+#include <ignite/ignite_error.h>
+
+#include <ignite/thin/cache/cache_entry.h>
+#include <ignite/impl/thin/readable.h>
+
+namespace ignite
+{
+    namespace impl
+    {
+        namespace thin
+        {
+            namespace cache
+            {
+                namespace query
+                {
+                    /**
+                     * Query cursor class implementation.
+                     */
+                    class IGNITE_IMPORT_EXPORT QueryCursorProxy
+                    {
+                    public:
+                        /**
+                         * Default constructor.
+                         */
+                        QueryCursorProxy()
+                        {
+                            // No-op.
+                        }
+
+                        /**
+                         * Constructor.
+                         *
+                         * @param impl Implementation.
+                         */
+                        explicit QueryCursorProxy(const common::concurrent::SharedPointer<void> &impl);
+
+                        /**
+                         * Check whether next entry exists.
+                         *
+                         * @return True if next entry exists.
+                         *
+                         * @throw IgniteError class instance in case of failure.
+                         */
+                        bool HasNext() const;
+
+                        /**
+                         * Get next entry.
+                         *
+                         * @param entry Entry.
+                         *
+                         * @throw IgniteError class instance in case of failure.
+                         */
+                        void GetNext(Readable& entry);
+
+                    private:
+                        /** Implementation delegate. */
+                        common::concurrent::SharedPointer<void> impl;
+                    };
+                }
+            }
+        }
+    }
+}
+
+#endif //_IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_CURSOR_PROXY
diff --git a/modules/platforms/cpp/thin-client/include/ignite/impl/thin/readable.h b/modules/platforms/cpp/thin-client/include/ignite/impl/thin/readable.h
index 2045c447cca..2caac1de839 100644
--- a/modules/platforms/cpp/thin-client/include/ignite/impl/thin/readable.h
+++ b/modules/platforms/cpp/thin-client/include/ignite/impl/thin/readable.h
@@ -18,7 +18,10 @@
 #ifndef _IGNITE_IMPL_THIN_READABLE
 #define _IGNITE_IMPL_THIN_READABLE
 
+#include <utility>
+
 #include <ignite/binary/binary_raw_reader.h>
+#include <ignite/thin/cache/cache_entry.h>
 
 namespace ignite
 {
@@ -88,36 +91,131 @@ namespace ignite
                 }
 
             private:
-                /** Data router. */
+                /** Value reference. */
                 ValueType& value;
             };
 
+            /**
+             * Implementation of the Readable class for the std::pair type.
+             */
+            template<typename T1, typename T2>
+            class ReadableImpl< std::pair<T1, T2> > : public Readable
+            {
+            public:
+                /** First value type. */
+                typedef T1 ValueType1;
+
+                /** Second value type. */
+                typedef T2 ValueType2;
+
+                /**
+                 * Constructor.
+                 *
+                 * @param value Value.
+                 */
+                ReadableImpl(std::pair<ValueType1, ValueType2>& pair) :
+                    pair(pair)
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Destructor.
+                 */
+                virtual ~ReadableImpl()
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Read value using reader.
+                 *
+                 * @param reader Reader to use.
+                 */
+                virtual void Read(binary::BinaryReaderImpl& reader)
+                {
+                    reader.ReadTopObject<ValueType1>(pair.first);
+                    reader.ReadTopObject<ValueType2>(pair.second);
+                }
+
+            private:
+                /** Pair reference. */
+                std::pair<ValueType1, ValueType2>& pair;
+            };
+
+            /**
+             * Implementation of the Readable class for the CacheEntry type.
+             */
+            template<typename K, typename V>
+            class ReadableImpl< ignite::thin::cache::CacheEntry<K, V> > : public Readable
+            {
+            public:
+                /** Key type. */
+                typedef K KeyType;
+
+                /** Value type. */
+                typedef V ValueType;
+
+                /**
+                 * Constructor.
+                 *
+                 * @param value Value.
+                 */
+                ReadableImpl(ignite::thin::cache::CacheEntry<KeyType, ValueType>& entry) :
+                    entry(entry)
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Destructor.
+                 */
+                virtual ~ReadableImpl()
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Read value using reader.
+                 *
+                 * @param reader Reader to use.
+                 */
+                virtual void Read(binary::BinaryReaderImpl& reader)
+                {
+                    reader.ReadTopObject<KeyType>(entry.key);
+                    reader.ReadTopObject<ValueType>(entry.val);
+                }
+
+            private:
+                /** Entry reference. */
+                ignite::thin::cache::CacheEntry<KeyType, ValueType>& entry;
+            };
+
             /**
              * Implementation of Readable interface for map.
              *
-             * @tparam T1 Type of the first element in the pair.
-             * @tparam T2 Type of the second element in the pair.
+             * @tparam T Type for the element in the container.
              * @tparam I Out iterator.
              */
-            template<typename T1, typename T2, typename I>
-            class ReadableMapImpl : public Readable
+            template<typename T, typename I>
+            class ReadableContainerImpl : public Readable
             {
             public:
-                /** Type of the first element in the pair. */
-                typedef T1 ElementType1;
+                /** Type of the element in the containers. */
+                typedef T ValueType;
 
-                /** Type of the second element in the pair. */
-                typedef T2 ElementType2;
-                
                 /** Type of the iterator. */
                 typedef I IteratorType;
 
+                /** Readable type for the element in the containers. */
+                typedef ReadableImpl<ValueType> ReadableType;
+
                 /**
                  * Constructor.
                  *
                  * @param iter Iterator.
                  */
-                ReadableMapImpl(IteratorType iter) :
+                ReadableContainerImpl(IteratorType iter) :
                     iter(iter)
                 {
                     // No-op.
@@ -126,7 +224,7 @@ namespace ignite
                 /**
                  * Destructor.
                  */
-                virtual ~ReadableMapImpl()
+                virtual ~ReadableContainerImpl()
                 {
                     // No-op.
                 }
@@ -144,13 +242,13 @@ namespace ignite
 
                     for (int32_t i = 0; i < cnt; ++i)
                     {
-                        std::pair<ElementType1, ElementType2> pair;
+                        ValueType value;
 
-                        reader.ReadTopObject<ElementType1>(pair.first);
-                        reader.ReadTopObject<ElementType2>(pair.second);
+                        ReadableType readable(value);
 
-                        iter = pair;
+                        readable.Read(reader);
 
+                        *iter = value;
                         ++iter;
                     }
                 }
diff --git a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_client.h b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_client.h
index 544010e4926..3ad14db3145 100644
--- a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_client.h
+++ b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_client.h
@@ -25,7 +25,9 @@
 
 #include <ignite/common/concurrent.h>
 
+#include <ignite/thin/cache/query/query_cursor.h>
 #include <ignite/thin/cache/query/query_fields_cursor.h>
+#include <ignite/thin/cache/query/query_scan.h>
 #include <ignite/thin/cache/query/query_sql_fields.h>
 #include <ignite/thin/cache/query/continuous/continuous_query_client.h>
 #include <ignite/thin/cache/query/continuous/continuous_query_handle.h>
@@ -181,7 +183,7 @@ namespace ignite
                 void GetAll(InIter begin, InIter end, OutIter dst)
                 {
                     impl::thin::WritableSetImpl<K, InIter> wrSeq(begin, end);
-                    impl::thin::ReadableMapImpl<K, V, OutIter> rdSeq(dst);
+                    impl::thin::ReadableContainerImpl< std::pair<K, V>, OutIter> rdSeq(dst);
 
                     proxy.GetAll(wrSeq, rdSeq);
                 }
@@ -599,6 +601,17 @@ namespace ignite
                     return proxy.Query(qry);
                 }
 
+                /**
+                 * Perform scan query.
+                 *
+                 * @param qry Query.
+                 * @return Query cursor.
+                 */
+                query::QueryCursor<KeyType, ValueType> Query(const query::ScanQuery& qry)
+                {
+                    return query::QueryCursor<KeyType, ValueType>(proxy.Query(qry));
+                }
+
                 /**
                  * Starts the continuous query execution
                  *
diff --git a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_entry.h b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_entry.h
index ef52814b5ab..cdaf9cac648 100644
--- a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_entry.h
+++ b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/cache_entry.h
@@ -28,6 +28,14 @@
 
 namespace ignite
 {
+    namespace impl
+    {
+        namespace thin
+        {
+            template<typename T>
+            class ReadableImpl;
+        }
+    }
     namespace thin
     {
         namespace cache
@@ -41,6 +49,7 @@ namespace ignite
             template<typename K, typename V>
             class CacheEntry
             {
+                friend class ignite::impl::thin::ReadableImpl< ignite::thin::cache::CacheEntry<K, V> >;
             public:
                 /**
                  * Default constructor.
diff --git a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_cursor.h b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_cursor.h
new file mode 100644
index 00000000000..e50cc83e236
--- /dev/null
+++ b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_cursor.h
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file
+ * Declares ignite::thin::cache::query::QueryCursor class template.
+ */
+
+#ifndef _IGNITE_THIN_CACHE_QUERY_QUERY_CURSOR
+#define _IGNITE_THIN_CACHE_QUERY_QUERY_CURSOR
+
+#include <vector>
+
+#include <ignite/common/concurrent.h>
+#include <ignite/ignite_error.h>
+
+#include <ignite/thin/cache/cache_entry.h>
+
+#include <ignite/impl/thin/readable.h>
+#include <ignite/impl/thin/cache/query/query_cursor_proxy.h>
+
+namespace ignite
+{
+    namespace thin
+    {
+        namespace cache
+        {
+            namespace query
+            {
+                /**
+                 * Query cursor class template.
+                 *
+                 * Both key and value types should be default-constructable, copy-constructable
+                 * and assignable. Also BinaryType class template should be specialized for both
+                 * types.
+                 *
+                 * This class is implemented as a reference to an implementation so copying
+                 * of this class instance will only create another reference to the same
+                 * underlying object. Underlying object will be released automatically once all
+                 * the instances are destructed.
+                 */
+                template<typename K, typename V>
+                class QueryCursor
+                {
+                public:
+                    /**
+                     * Default constructor.
+                     */
+                    QueryCursor()
+                    {
+                        // No-op.
+                    }
+
+                    /**
+                     * Constructor.
+                     *
+                     * @param impl Implementation.
+                     */
+                    explicit QueryCursor(const impl::thin::cache::query::QueryCursorProxy& impl) :
+                        impl(impl)
+                    {
+                        // No-op.
+                    }
+
+                    /**
+                     * Check whether next entry exists.
+                     *
+                     * @return True if next entry exists.
+                     *
+                     * @throw IgniteError class instance in case of failure.
+                     */
+                    bool HasNext() const
+                    {
+                        return impl.HasNext();
+                    }
+
+                    /**
+                     * Get next entry.
+                     *
+                     * @return Next entry.
+                     *
+                     * @throw IgniteError class instance in case of failure.
+                     */
+                    CacheEntry<K, V> GetNext()
+                    {
+                        CacheEntry<K, V> entry;
+                        impl::thin::ReadableImpl< CacheEntry<K, V> > readable(entry);
+
+                        impl.GetNext(readable);
+
+                        return entry;
+                    }
+
+                    /**
+                     * Get all entries.
+                     *
+                     * @param res Vector where query entries will be stored.
+                     *
+                     * @throw IgniteError class instance in case of failure.
+                     */
+                    void GetAll(std::vector<CacheEntry<K, V> >& res)
+                    {
+                        res.clear();
+                        GetAll(std::inserter(res, res.end()));
+                    }
+
+                    /**
+                     * Get all entries.
+                     *
+                     * @param iter Output iterator.
+                     *
+                     * @throw IgniteError class instance in case of failure.
+                     */
+                    template<typename OutIter>
+                    void GetAll(OutIter iter)
+                    {
+                        impl::thin::ReadableContainerImpl< CacheEntry<K, V>, OutIter > collection(iter);
+
+                        while (HasNext())
+                        {
+                            *iter = GetNext();
+                            ++iter;
+                        }
+                    }
+
+                private:
+                    /** Implementation delegate. */
+                    impl::thin::cache::query::QueryCursorProxy impl;
+                };
+            }
+        }
+
+
+    }
+}
+
+#endif //_IGNITE_THIN_CACHE_QUERY_QUERY_CURSOR
diff --git a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_scan.h b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_scan.h
new file mode 100644
index 00000000000..1d45806777d
--- /dev/null
+++ b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_scan.h
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file
+ * Declares ignite::thin::cache::query::ScanQuery class.
+ */
+
+#ifndef _IGNITE_THIN_CACHE_QUERY_QUERY_SCAN
+#define _IGNITE_THIN_CACHE_QUERY_QUERY_SCAN
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include <ignite/impl/thin/copyable_writable.h>
+
+namespace ignite
+{
+    namespace impl
+    {
+        namespace thin
+        {
+            // Forward declaration
+            class ScanQueryRequest;
+        }
+    }
+
+    namespace thin
+    {
+        namespace cache
+        {
+            namespace query
+            {
+               /**
+                * Scan query.
+                */
+                class ScanQuery
+                {
+                public:
+                    friend class ScanQueryRequest;
+
+                    /**
+                     * Default constructor.
+                     */
+                    ScanQuery() : part(-1), pageSize(1024), loc(false)
+                    {
+                        // No-op.
+                    }
+
+                    /**
+                     * Get partition to scan.
+                     *
+                     * @return Partition to scan.
+                     */
+                    int32_t GetPartition() const
+                    {
+                        return part;
+                    }
+
+                    /**
+                     * Set partition to scan.
+                     *
+                     * @param partition Partition to scan.
+                     */
+                    void SetPartition(int32_t partition)
+                    {
+                        this->part = partition;
+                    }
+
+                    /**
+                     * Get page size.
+                     *
+                     * @return Page size.
+                     */
+                    int32_t GetPageSize() const
+                    {
+                        return pageSize;
+                    }
+
+                    /**
+                     * Set the size of the result page.
+                     *
+                     * @param resultPageSize Result page size.
+                     */
+                    void SetPageSize(int32_t resultPageSize)
+                    {
+                        this->pageSize = resultPageSize;
+                    }
+
+                    /**
+                     * Get local flag.
+                     *
+                     * @return Local flag. If true, query will be executed only on the single connected node,
+                     *     so only local entries will be returned as query result. Should be used with caution because
+                     *     if the client connected to multiple nodes, a single node will be selected randomly.
+                     */
+                    bool IsLocal() const
+                    {
+                        return loc;
+                    }
+
+                    /**
+                     * Set local flag.
+                     *
+                     * @param localScan Local flag. If true, query will be executed only on the single connected node,
+                     *     so only local entries will be returned as query result. Should be used with caution because
+                     *     if the client connected to multiple nodes, a single node will be selected randomly.
+                     */
+                    void SetLocal(bool localScan)
+                    {
+                        loc = localScan;
+                    }
+
+                private:
+                    /** Partition. */
+                    int32_t part;
+
+                    /** Page size. */
+                    int32_t pageSize;
+
+                    /** Local flag. */
+                    bool loc;
+                };
+            }
+        }
+    }
+}
+
+#endif //_IGNITE_THIN_CACHE_QUERY_QUERY_SCAN
diff --git a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_sql_fields.h b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_sql_fields.h
index d3501386664..e9e0843a801 100644
--- a/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_sql_fields.h
+++ b/modules/platforms/cpp/thin-client/include/ignite/thin/cache/query/query_sql_fields.h
@@ -259,7 +259,9 @@ namespace ignite
                     /**
                      * Get local flag.
                      *
-                     * @return Local flag.
+                     * @return Local flag. If true, query will be executed only on the single connected node,
+                     *     so only local entries will be returned as query result. Should be used with caution because
+                     *     if the client connected to multiple nodes, a single node will be selected randomly.
                      */
                     bool IsLocal() const
                     {
@@ -269,7 +271,9 @@ namespace ignite
                     /**
                      * Set local flag.
                      *
-                     * @param loc Local flag.
+                     * @param loc Local flag. If true, query will be executed only on the single connected node,
+                     *     so only local entries will be returned as query result. Should be used with caution because
+                     *     if the client connected to multiple nodes, a single node will be selected randomly.
                      */
                     void SetLocal(bool loc)
                     {
diff --git a/modules/platforms/cpp/thin-client/src/cache/query/query_cursor_proxy.cpp b/modules/platforms/cpp/thin-client/src/cache/query/query_cursor_proxy.cpp
new file mode 100644
index 00000000000..f62af78de99
--- /dev/null
+++ b/modules/platforms/cpp/thin-client/src/cache/query/query_cursor_proxy.cpp
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ignite/impl/thin/cache/query/query_cursor_proxy.h>
+
+#include "impl/cache/query/query_cursor_impl.h"
+
+namespace
+{
+    using namespace ignite::common::concurrent;
+    using namespace ignite::impl::thin::cache::query;
+
+    QueryCursorImpl& GetQueryCursorImpl(SharedPointer<void>& ptr)
+    {
+        return *reinterpret_cast<QueryCursorImpl*>(ptr.Get());
+    }
+
+    const QueryCursorImpl& GetQueryCursorImpl(const SharedPointer<void>& ptr)
+    {
+        return *reinterpret_cast<const QueryCursorImpl*>(ptr.Get());
+    }
+}
+
+namespace ignite
+{
+    namespace impl
+    {
+        namespace thin
+        {
+            namespace cache
+            {
+                namespace query
+                {
+                    QueryCursorProxy::QueryCursorProxy(const common::concurrent::SharedPointer<void> &impl) :
+                        impl(impl)
+                    {
+                        // No-op.
+                    }
+
+                    bool QueryCursorProxy::HasNext() const
+                    {
+                        return GetQueryCursorImpl(impl).HasNext();
+                    }
+
+                    void QueryCursorProxy::GetNext(Readable& entry)
+                    {
+                        GetQueryCursorImpl(impl).GetNext(entry);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.cpp b/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.cpp
index 100645af911..3340e37d344 100644
--- a/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.cpp
+++ b/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.cpp
@@ -369,6 +369,24 @@ namespace ignite
                     return cursorImpl;
                 }
 
+                query::SP_QueryCursorImpl CacheClientImpl::Query(const ignite::thin::cache::query::ScanQuery& qry)
+                {
+                    ScanQueryRequest req(id, qry);
+                    ScanQueryResponse rsp;
+
+                    SP_DataChannel channel = SyncMessage(req, rsp);
+
+                    query::SP_QueryCursorImpl cursor(
+                        new query::QueryCursorImpl(
+                            rsp.GetCursorId(),
+                            rsp.GetCursorPage(),
+                            channel,
+                            router.Get()->GetIoTimeout()
+                    ));
+
+                    return cursor;
+                }
+
                 query::continuous::SP_ContinuousQueryHandleClientImpl CacheClientImpl::QueryContinuous(
                         const query::continuous::SP_ContinuousQueryClientHolderBase& continuousQuery)
                 {
diff --git a/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.h b/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.h
index 4c82592a020..5e9b5fb882d 100644
--- a/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.h
+++ b/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_impl.h
@@ -22,11 +22,13 @@
 #include <string>
 
 #include <ignite/thin/cache/query/query_sql_fields.h>
+#include <ignite/thin/cache/query/query_scan.h>
 
 #include <ignite/impl/thin/cache/continuous/continuous_query_client_holder.h>
 
 #include "impl/data_router.h"
 #include "impl/transactions/transactions_impl.h"
+#include "impl/cache/query/query_cursor_impl.h"
 #include "impl/cache/query/query_fields_cursor_impl.h"
 #include "impl/cache/query/continuous/continuous_query_handle_impl.h"
 
@@ -301,6 +303,14 @@ namespace ignite
                      */
                     query::SP_QueryFieldsCursorImpl Query(const ignite::thin::cache::query::SqlFieldsQuery &qry);
 
+                    /**
+                     * Perform scan query.
+                     *
+                     * @param qry Query.
+                     * @return Query cursor proxy.
+                     */
+                    query::SP_QueryCursorImpl Query(const ignite::thin::cache::query::ScanQuery& qry);
+
                     /**
                      * Starts the continuous query execution
                      *
diff --git a/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_proxy.cpp b/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_proxy.cpp
index eab6ba000eb..f97c1072409 100644
--- a/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_proxy.cpp
+++ b/modules/platforms/cpp/thin-client/src/impl/cache/cache_client_proxy.cpp
@@ -21,12 +21,10 @@
 #include <impl/cache/cache_client_impl.h>
 
 using namespace ignite::impl::thin;
+using namespace ignite::thin::cache::query;
+using namespace ignite::thin::cache::query::continuous;
 using namespace cache;
 
-using ignite::thin::cache::query::SqlFieldsQuery;
-using ignite::thin::cache::query::QueryFieldsCursor;
-using ignite::thin::cache::query::continuous::ContinuousQueryHandleClient;
-
 namespace
 {
     using namespace ignite::common::concurrent;
@@ -156,11 +154,18 @@ namespace ignite
                     GetCacheImpl(impl).GetAndPutIfAbsent(key, valIn, valOut);
                 }
 
-                QueryFieldsCursor CacheClientProxy::Query(const ignite::thin::cache::query::SqlFieldsQuery &qry)
+                QueryFieldsCursor CacheClientProxy::Query(const SqlFieldsQuery &qry)
                 {
                     query::SP_QueryFieldsCursorImpl cursorImpl = GetCacheImpl(impl).Query(qry);
 
-                    return ignite::thin::cache::query::QueryFieldsCursor(cursorImpl);
+                    return QueryFieldsCursor(cursorImpl);
+                }
+
+                query::QueryCursorProxy CacheClientProxy::Query(const ScanQuery& qry)
+                {
+                    query::SP_QueryCursorImpl cursorImpl = GetCacheImpl(impl).Query(qry);
+
+                    return query::QueryCursorProxy(cursorImpl);
                 }
 
                 ContinuousQueryHandleClient CacheClientProxy::QueryContinuous(
diff --git a/modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h b/modules/platforms/cpp/thin-client/src/impl/cache/query/query_cursor_impl.h
similarity index 65%
copy from modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h
copy to modules/platforms/cpp/thin-client/src/impl/cache/query/query_cursor_impl.h
index 6e03c71a80c..027609f5659 100644
--- a/modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h
+++ b/modules/platforms/cpp/thin-client/src/impl/cache/query/query_cursor_impl.h
@@ -15,15 +15,12 @@
  * limitations under the License.
  */
 
-#ifndef _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_FIELDS_CURSOR_IMPL
-#define _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_FIELDS_CURSOR_IMPL
+#ifndef _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_CURSOR_IMPL
+#define _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_CURSOR_IMPL
 
 #include <ignite/common/concurrent.h>
 
-#include <ignite/thin/cache/query/query_fields_row.h>
-
 #include "impl/cache/query/cursor_page.h"
-#include "impl/cache/query/query_fields_row_impl.h"
 #include "impl/data_router.h"
 #include "impl/message.h"
 
@@ -38,9 +35,9 @@ namespace ignite
                 namespace query
                 {
                     /**
-                     * Query Fields Cursor Implementation.
+                     * Query Cursor Implementation.
                      */
-                    class QueryFieldsCursorImpl
+                    class QueryCursorImpl
                     {
                     public:
                         /**
@@ -52,18 +49,16 @@ namespace ignite
                          * @param channel Data channel. Used to request new page.
                          * @param timeout Timeout.
                          */
-                        QueryFieldsCursorImpl(
-                                int64_t id,
-                                const std::vector<std::string>& columns,
-                                const SP_CursorPage &cursorPage,
-                                const SP_DataChannel& channel,
-                                int32_t timeout) :
+                        QueryCursorImpl(
+                            int64_t id,
+                            const SP_CursorPage &cursorPage,
+                            const SP_DataChannel& channel,
+                            int32_t timeout) :
                             id(id),
-                            columns(columns),
                             page(cursorPage),
                             channel(channel),
                             timeout(timeout),
-                            currentRow(0),
+                            currentElement(0),
                             stream(page.Get()->GetMemory()),
                             reader(&stream),
                             endReached(false)
@@ -76,7 +71,7 @@ namespace ignite
                         /**
                          * Destructor.
                          */
-                        virtual ~QueryFieldsCursorImpl()
+                        virtual ~QueryCursorImpl()
                         {
                             // No-op.
                         }
@@ -88,7 +83,7 @@ namespace ignite
                          *
                          * @throw IgniteError class instance in case of failure.
                          */
-                        bool HasNext()
+                        bool HasNext() const
                         {
                             return !endReached;
                         }
@@ -96,13 +91,11 @@ namespace ignite
                         /**
                          * Get next entry.
                          *
-                         * This method should only be used on the valid instance.
-                         *
-                         * @return Next entry.
+                         * @param entry Entry.
                          *
                          * @throw IgniteError class instance in case of failure.
                          */
-                        ignite::thin::cache::query::QueryFieldsRow GetNext()
+                        void GetNext(Readable& entry)
                         {
                             if (!HasNext())
                                 throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "The cursor is empty");
@@ -110,25 +103,11 @@ namespace ignite
                             if (IsUpdateNeeded())
                                 Update();
 
-                            SP_QueryFieldsRowImpl rowImpl(
-                                new QueryFieldsRowImpl(
-                                        static_cast<int32_t>(columns.size()),
-                                        page,
-                                        stream.Position()));
+                            entry.Read(reader);
 
-                            SkipRow();
+                            ++currentElement;
 
-                            return ignite::thin::cache::query::QueryFieldsRow(rowImpl);
-                        }
-
-                        /**
-                         * Get column names.
-                         *
-                         * @return Column names.
-                         */
-                        const std::vector<std::string>& GetColumns() const
-                        {
-                            return columns;
+                            CheckEnd();
                         }
 
                     private:
@@ -147,42 +126,30 @@ namespace ignite
                          */
                         void Update()
                         {
-                            SqlFieldsCursorGetPageRequest req(id);
-                            SqlFieldsCursorGetPageResponse rsp;
+                            QueryCursorGetPageRequest<MessageType::QUERY_SCAN_CURSOR_GET_PAGE> req(id);
+                            QueryCursorGetPageResponse rsp;
 
                             DataChannel* channel0 = channel.Get();
 
                             if (!channel0)
-                                throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "Connection is not established");
+                                throw IgniteError(IgniteError::IGNITE_ERR_GENERIC,
+                                    "Connection is not established");
 
                             channel0->SyncMessage(req, rsp, timeout);
 
                             page = rsp.GetCursorPage();
-                            currentRow = 0;
+                            currentElement = 0;
 
                             stream = interop::InteropInputStream(page.Get()->GetMemory());
                             stream.Position(page.Get()->GetStartPos());
                         }
 
-                        /**
-                         * Skip position to the next row.
-                         */
-                        void SkipRow()
-                        {
-                            for (size_t i = 0; i < columns.size(); ++i)
-                                reader.Skip();
-
-                            ++currentRow;
-
-                            CheckEnd();
-                        }
-
                         /**
                          * Check whether end is reached.
                          */
                         void CheckEnd()
                         {
-                            if (currentRow == page.Get()->GetRowNum())
+                            if (currentElement == page.Get()->GetRowNum())
                             {
                                 bool hasNextPage = reader.ReadBool();
                                 endReached = !hasNextPage;
@@ -194,9 +161,6 @@ namespace ignite
                         /** Cursor ID. */
                         int64_t id;
 
-                        /** Column names. */
-                        std::vector<std::string> columns;
-
                         /** Cursor page. */
                         SP_CursorPage page;
 
@@ -206,8 +170,8 @@ namespace ignite
                         /** Timeout in milliseconds. */
                         int32_t timeout;
 
-                        /** Current row in page. */
-                        int32_t currentRow;
+                        /** Current element in page. */
+                        int32_t currentElement;
 
                         /** Stream. */
                         interop::InteropInputStream stream;
@@ -219,11 +183,12 @@ namespace ignite
                         bool endReached;
                     };
 
-                    typedef common::concurrent::SharedPointer<QueryFieldsCursorImpl> SP_QueryFieldsCursorImpl;
+                    /** Shared pointer. */
+                    typedef common::concurrent::SharedPointer< QueryCursorImpl > SP_QueryCursorImpl;
                 }
             }
         }
     }
 }
 
-#endif // _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_FIELDS_CURSOR_IMPL
+#endif // _IGNITE_IMPL_THIN_CACHE_QUERY_QUERY_CURSOR_IMPL
diff --git a/modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h b/modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h
index 6e03c71a80c..a323043bf29 100644
--- a/modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h
+++ b/modules/platforms/cpp/thin-client/src/impl/cache/query/query_fields_cursor_impl.h
@@ -88,7 +88,7 @@ namespace ignite
                          *
                          * @throw IgniteError class instance in case of failure.
                          */
-                        bool HasNext()
+                        bool HasNext() const
                         {
                             return !endReached;
                         }
@@ -147,8 +147,8 @@ namespace ignite
                          */
                         void Update()
                         {
-                            SqlFieldsCursorGetPageRequest req(id);
-                            SqlFieldsCursorGetPageResponse rsp;
+                            QueryCursorGetPageRequest<MessageType::QUERY_SQL_FIELDS_CURSOR_GET_PAGE> req(id);
+                            QueryCursorGetPageResponse rsp;
 
                             DataChannel* channel0 = channel.Get();
 
diff --git a/modules/platforms/cpp/thin-client/src/impl/message.cpp b/modules/platforms/cpp/thin-client/src/impl/message.cpp
index 8390e2d94bb..15982b92eaf 100644
--- a/modules/platforms/cpp/thin-client/src/impl/message.cpp
+++ b/modules/platforms/cpp/thin-client/src/impl/message.cpp
@@ -313,6 +313,35 @@ namespace ignite
                 value = reader.ReadInt32();
             }
 
+
+            void ScanQueryResponse::ReadOnSuccess(binary::BinaryReaderImpl& reader, const ProtocolVersion&)
+            {
+                ignite::binary::BinaryRawReader rawReader(&reader);
+
+                cursorId = rawReader.ReadInt64();
+
+                cursorPage.Get()->Read(reader);
+            }
+
+            ScanQueryRequest::ScanQueryRequest(int32_t cacheId, const ignite::thin::cache::query::ScanQuery &qry) :
+                CacheRequest<MessageType::QUERY_SCAN>(cacheId, false),
+                qry(qry)
+            {
+                // No-op.
+            }
+
+            void ScanQueryRequest::Write(binary::BinaryWriterImpl &writer, const ProtocolVersion &ver) const
+            {
+                CacheRequest::Write(writer, ver);
+
+                // TODO: IGNITE-16995 Implement a RemoteFilter for ScanQuery
+                writer.WriteNull();
+
+                writer.WriteInt32(qry.GetPageSize());
+                writer.WriteInt32(qry.GetPartition());
+                writer.WriteBool(qry.IsLocal());
+            }
+
             SqlFieldsQueryRequest::SqlFieldsQueryRequest(
                 int32_t cacheId,
                 const ignite::thin::cache::query::SqlFieldsQuery &qry
@@ -372,12 +401,7 @@ namespace ignite
                 cursorPage.Get()->Read(reader);
             }
 
-            void SqlFieldsCursorGetPageRequest::Write(binary::BinaryWriterImpl& writer, const ProtocolVersion&) const
-            {
-                writer.WriteInt64(cursorId);
-            }
-
-            void SqlFieldsCursorGetPageResponse::ReadOnSuccess(binary::BinaryReaderImpl& reader, const ProtocolVersion&)
+            void QueryCursorGetPageResponse::ReadOnSuccess(binary::BinaryReaderImpl& reader, const ProtocolVersion&)
             {
                 cursorPage.Get()->Read(reader);
             }
diff --git a/modules/platforms/cpp/thin-client/src/impl/message.h b/modules/platforms/cpp/thin-client/src/impl/message.h
index dad00c701cd..c5ca32e8517 100644
--- a/modules/platforms/cpp/thin-client/src/impl/message.h
+++ b/modules/platforms/cpp/thin-client/src/impl/message.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <vector>
 
+#include <ignite/thin/cache/query/query_scan.h>
 #include <ignite/thin/cache/query/query_sql_fields.h>
 #include <ignite/thin/transactions/transaction_consts.h>
 
@@ -164,6 +165,12 @@ namespace ignite
                     /** Cache partitions request. */
                     CACHE_PARTITIONS = 1101,
 
+                    /** Scan query request. */
+                    QUERY_SCAN = 2000,
+
+                    /** Scan query get page request. */
+                    QUERY_SCAN_CURSOR_GET_PAGE = 2001,
+
                     /** SQL fields query request. */
                     QUERY_SQL_FIELDS = 2004,
 
@@ -961,9 +968,10 @@ namespace ignite
             };
 
             /**
-             * Cache SQL fields cursor get page request.
+             * Cache query cursor get page request.
              */
-            class SqlFieldsCursorGetPageRequest : public RequestAdapter<MessageType::QUERY_SQL_FIELDS_CURSOR_GET_PAGE>
+            template<int16_t OpCode>
+            class QueryCursorGetPageRequest : public RequestAdapter<OpCode>
             {
             public:
                 /**
@@ -971,7 +979,7 @@ namespace ignite
                  *
                  * @param cursorId Cursor ID.
                  */
-                explicit SqlFieldsCursorGetPageRequest(int64_t cursorId) :
+                explicit QueryCursorGetPageRequest(int64_t cursorId) :
                     cursorId(cursorId)
                 {
                     // No-op.
@@ -980,7 +988,7 @@ namespace ignite
                 /**
                  * Destructor.
                  */
-                virtual ~SqlFieldsCursorGetPageRequest()
+                virtual ~QueryCursorGetPageRequest()
                 {
                     // No-op.
                 }
@@ -988,15 +996,51 @@ namespace ignite
                 /**
                  * Write request using provided writer.
                  * @param writer Writer.
-                 * @param ver Version.
                  */
-                virtual void Write(binary::BinaryWriterImpl& writer, const ProtocolVersion& ver) const;
+                virtual void Write(binary::BinaryWriterImpl& writer, const ProtocolVersion&) const
+                {
+                    writer.WriteInt64(cursorId);
+                }
 
             private:
                 /** Cursor ID. */
                 const int64_t cursorId;
             };
 
+            /**
+             * Cache scan query request.
+             */
+            class ScanQueryRequest : public CacheRequest<MessageType::QUERY_SCAN>
+            {
+            public:
+                /**
+                 * Constructor.
+                 *
+                 * @param cacheId Cache ID.
+                 * @param qry SQL query.
+                 */
+                explicit ScanQueryRequest(int32_t cacheId, const ignite::thin::cache::query::ScanQuery &qry);
+
+                /**
+                 * Destructor.
+                 */
+                virtual ~ScanQueryRequest()
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Write request using provided writer.
+                 * @param writer Writer.
+                 * @param ver Version.
+                 */
+                virtual void Write(binary::BinaryWriterImpl& writer, const ProtocolVersion& ver) const;
+
+            private:
+                /** Query. */
+                const ignite::thin::cache::query::ScanQuery &qry;
+            };
+
             /**
              * Continuous query request.
              */
@@ -1481,6 +1525,64 @@ namespace ignite
                 int32_t value;
             };
 
+            /**
+             * Cache scan query response.
+             */
+            class ScanQueryResponse : public Response
+            {
+            public:
+                /**
+                 * Constructor.
+                 */
+                ScanQueryResponse() :
+                    cursorId(0),
+                    cursorPage(new cache::query::CursorPage())
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Destructor.
+                 */
+                virtual ~ScanQueryResponse()
+                {
+                    // No-op.
+                }
+
+                /**
+                 * Get cursor ID.
+                 *
+                 * @return Cursor ID.
+                 */
+                int64_t GetCursorId() const
+                {
+                    return cursorId;
+                }
+
+                /**
+                 * Get cursor page.
+                 * @return Cursor page.
+                 */
+                cache::query::SP_CursorPage GetCursorPage() const
+                {
+                    return cursorPage;
+                }
+
+                /**
+                 * Read data if response status is ResponseStatus::SUCCESS.
+                 *
+                 * @param reader Reader.
+                 */
+                virtual void ReadOnSuccess(binary::BinaryReaderImpl& reader, const ProtocolVersion&);
+
+            private:
+                /** Cursor ID. */
+                int64_t cursorId;
+
+                /** Cursor Page. */
+                cache::query::SP_CursorPage cursorPage;
+            };
+
             /**
              * Cache SQL fields query response.
              */
@@ -1553,15 +1655,15 @@ namespace ignite
             };
 
             /**
-             * Cache SQL fields cursor get page response.
+             * Query cursor get page response.
              */
-            class SqlFieldsCursorGetPageResponse : public Response
+            class QueryCursorGetPageResponse : public Response
             {
             public:
                 /**
                  * Constructor.
                  */
-                SqlFieldsCursorGetPageResponse() :
+                QueryCursorGetPageResponse() :
                     cursorPage(new cache::query::CursorPage())
                 {
                     // No-op.
@@ -1570,7 +1672,7 @@ namespace ignite
                 /**
                  * Destructor.
                  */
-                virtual ~SqlFieldsCursorGetPageResponse()
+                virtual ~QueryCursorGetPageResponse()
                 {
                     // No-op.
                 }