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

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


The following commit(s) were added to refs/heads/main by this push:
     new cafb8d3a10 IGNITE-18255 Add key_value_view in C++ (#1949)
cafb8d3a10 is described below

commit cafb8d3a102f82a0a74eb7adca198ae4234224df
Author: Igor Sapego <[email protected]>
AuthorDate: Tue Apr 18 12:08:00 2023 +0400

    IGNITE-18255 Add key_value_view in C++ (#1949)
---
 modules/platforms/cpp/ignite/client/CMakeLists.txt |   1 +
 .../cpp/ignite/client/detail/type_mapping_utils.h  | 130 ++++
 .../cpp/ignite/client/table/key_value_view.h       | 524 ++++++++++++-
 .../cpp/ignite/client/table/record_view.h          | 112 +--
 modules/platforms/cpp/ignite/client/table/table.h  |  15 +-
 .../platforms/cpp/tests/client-test/CMakeLists.txt |   1 +
 .../cpp/tests/client-test/all_fields_type.h        | 123 +++
 .../client-test/key_value_binary_view_test.cpp     |   4 +-
 .../cpp/tests/client-test/key_value_view_test.cpp  | 825 +++++++++++++++++++++
 .../cpp/tests/client-test/record_view_test.cpp     |  91 +--
 10 files changed, 1612 insertions(+), 214 deletions(-)

diff --git a/modules/platforms/cpp/ignite/client/CMakeLists.txt 
b/modules/platforms/cpp/ignite/client/CMakeLists.txt
index fc28d4a98c..614708bd0f 100644
--- a/modules/platforms/cpp/ignite/client/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/client/CMakeLists.txt
@@ -47,6 +47,7 @@ set(PUBLIC_HEADERS
     primitive.h
     type_mapping.h
     compute/compute.h
+    detail/type_mapping_utils.h
     network/cluster_node.h
     sql/sql.h
     table/ignite_tuple.h
diff --git a/modules/platforms/cpp/ignite/client/detail/type_mapping_utils.h 
b/modules/platforms/cpp/ignite/client/detail/type_mapping_utils.h
new file mode 100644
index 0000000000..1f1286683b
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/detail/type_mapping_utils.h
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <ignite/client/table/ignite_tuple.h>
+#include <ignite/client/type_mapping.h>
+
+#include <ignite/common/ignite_result.h>
+
+namespace ignite {
+/**
+ * Convert values to tuples.
+ * @param vals Values.
+ * @return Tuples.
+ */
+template<typename T>
+std::vector<ignite_tuple> values_to_tuples(std::vector<T> values) {
+    //TODO: Optimize memory usage (IGNITE-19198)
+    std::vector<ignite_tuple> tuples;
+    tuples.reserve(values.size());
+    for (auto &&value : std::move(values)) {
+        tuples.push_back(convert_to_tuple(std::move(value)));
+    }
+    return tuples;
+}
+
+/**
+ * Convert key-value pairs to tuples.
+ * @param vals Values.
+ * @return Tuples.
+ */
+template<typename K, typename V>
+std::vector<std::pair<ignite_tuple, ignite_tuple>> 
values_to_tuples(std::vector<std::pair<K, V>> values) {
+    //TODO: Optimize memory usage (IGNITE-19198)
+    std::vector<std::pair<ignite_tuple, ignite_tuple>> tuples;
+    tuples.reserve(values.size());
+    for (auto &&pair : std::move(values)) {
+        tuples.emplace_back(convert_to_tuple(std::move(pair.first)), 
convert_to_tuple(std::move(pair.second)));
+    }
+    return tuples;
+}
+
+/**
+ * Tuples to values.
+ * @param tuples Tuples.
+ * @return Values.
+ */
+template<typename T>
+std::vector<T> tuples_to_values(std::vector<ignite_tuple> tuples) {
+    //TODO: Optimize memory usage (IGNITE-19198)
+    std::vector<T> values;
+    values.reserve(tuples.size());
+    for (auto &&tuple : std::move(tuples)) {
+        values.emplace_back(convert_from_tuple<T>(std::move(tuple)));
+    }
+    return values;
+}
+
+/**
+ * Optional tuples to optional values.
+ * @param tuples Tuples.
+ * @return Values.
+ */
+template<typename T>
+std::vector<std::optional<T>> 
tuples_to_values(std::vector<std::optional<ignite_tuple>> tuples) {
+    //TODO: Optimize memory usage (IGNITE-19198)
+    std::vector<std::optional<T>> values;
+    values.reserve(tuples.size());
+    for (auto &&tuple : std::move(tuples)) {
+        values.emplace_back(convert_from_tuple<T>(std::move(tuple)));
+    }
+    return values;
+}
+
+/**
+ * Convert result from tuple-based type to user type.
+ * @param res Result to convert.
+ * @return Converted result.
+ */
+template<typename T>
+ignite_result<std::optional<T>> 
convert_result(ignite_result<std::optional<ignite_tuple>> &&res) {
+    if (res.has_error())
+        return {std::move(res).error()};
+
+    return {convert_from_tuple<T>(std::move(res).value())};
+}
+
+/**
+ * Convert result from tuple-based type to user type.
+ * @param res Result to convert.
+ * @return Converted result.
+ */
+template<typename T>
+ignite_result<std::vector<std::optional<T>>> convert_result(
+    ignite_result<std::vector<std::optional<ignite_tuple>>> &&res) {
+    if (res.has_error())
+        return {std::move(res).error()};
+
+    return {tuples_to_values<T>(std::move(res).value())};
+}
+
+/**
+ * Convert result from tuple-based type to user type.
+ * @param res Result to convert.
+ * @return Converted result.
+ */
+template<typename T>
+ignite_result<std::vector<T>> 
convert_result(ignite_result<std::vector<ignite_tuple>> &&res) {
+    if (res.has_error())
+        return {std::move(res).error()};
+
+    return {tuples_to_values<T>(std::move(res).value())};
+}
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/table/key_value_view.h 
b/modules/platforms/cpp/ignite/client/table/key_value_view.h
index 26fbafc0a7..04daa2b37e 100644
--- a/modules/platforms/cpp/ignite/client/table/key_value_view.h
+++ b/modules/platforms/cpp/ignite/client/table/key_value_view.h
@@ -19,6 +19,8 @@
 
 #include "ignite/client/table/ignite_tuple.h"
 #include "ignite/client/transaction/transaction.h"
+#include "ignite/client/type_mapping.h"
+#include "ignite/client/detail/type_mapping_utils.h"
 
 #include "ignite/common/config.h"
 #include "ignite/common/ignite_result.h"
@@ -36,24 +38,8 @@ namespace detail {
 class table_impl;
 }
 
-/**
- * Key-Value view interface provides methods to access table records in form 
of separate key and value parts.
- */
 template<typename K, typename V>
-class key_value_view {
-public:
-    typedef typename std::decay<K>::type key_type;
-    typedef typename std::decay<V>::type value_type;
-
-    // Deleted
-    key_value_view(const key_value_view &) = delete;
-    key_value_view &operator=(const key_value_view &) = delete;
-
-    // Default
-    key_value_view() = default;
-    key_value_view(key_value_view &&) noexcept = default;
-    key_value_view &operator=(key_value_view &&) noexcept = default;
-};
+class key_value_view;
 
 /**
  * Key-Value view interface provides methods to access table records in form 
of separate key and value parts.
@@ -76,7 +62,7 @@ public:
     key_value_view &operator=(key_value_view &&) noexcept = default;
 
     /**
-     * Gets a record by key asynchronously.
+     * Gets a value by key asynchronously.
      *
      * @param tx Optional transaction. If nullptr implicit transaction for this
      *   single operation is used.
@@ -88,7 +74,7 @@ public:
         transaction *tx, const key_type &key, 
ignite_callback<std::optional<value_type>> callback);
 
     /**
-     * Gets a record by key.
+     * Gets a value by key.
      *
      * @param tx Optional transaction. If nullptr implicit transaction for this
      *   single operation is used.
@@ -276,7 +262,7 @@ public:
      * @param key Key.
      * @return A value indicating whether a record with the specified key was 
deleted.
      */
-    IGNITE_API bool remove(transaction *tx, const value_type &key) {
+    IGNITE_API bool remove(transaction *tx, const key_type &key) {
         return sync<bool>([this, tx, &key](auto callback) { remove_async(tx, 
key, std::move(callback)); });
     }
 
@@ -319,7 +305,7 @@ public:
      *   records from @c keys that did not exist.
      */
     IGNITE_API void remove_all_async(
-        transaction *tx, std::vector<key_type> keys, 
ignite_callback<std::vector<value_type>> callback);
+        transaction *tx, std::vector<key_type> keys, 
ignite_callback<std::vector<key_type>> callback);
 
     /**
      * Removes values with given keys from the table. If one or more keys
@@ -330,8 +316,8 @@ public:
      * @param keys Keys.
      * @return Records from @c keys that did not exist.
      */
-    IGNITE_API std::vector<value_type> remove_all(transaction *tx, 
std::vector<key_type> keys) {
-        return sync<std::vector<value_type>>([this, tx, keys = 
std::move(keys)](auto callback) mutable {
+    IGNITE_API std::vector<key_type> remove_all(transaction *tx, 
std::vector<key_type> keys) {
+        return sync<std::vector<key_type>>([this, tx, keys = 
std::move(keys)](auto callback) mutable {
             remove_all_async(tx, std::move(keys), std::move(callback));
         });
     }
@@ -347,7 +333,7 @@ public:
      *   records from @c records that did not exist.
      */
     IGNITE_API void remove_all_async(transaction *tx, const 
std::vector<std::pair<key_type, value_type>> &pairs,
-        ignite_callback<std::vector<value_type>> callback);
+        ignite_callback<std::vector<key_type>> callback);
 
     /**
      * Removes records with given keys and values from the table. If one or 
more
@@ -358,8 +344,8 @@ public:
      * @param pairs Pairs to remove.
      * @return Records from @c records that did not exist.
      */
-    IGNITE_API std::vector<value_type> remove_all(transaction *tx, 
std::vector<std::pair<key_type, value_type>> pairs) {
-        return sync<std::vector<value_type>>([this, tx, pairs = 
std::move(pairs)](auto callback) mutable {
+    IGNITE_API std::vector<key_type> remove_all(transaction *tx, 
std::vector<std::pair<key_type, value_type>> pairs) {
+        return sync<std::vector<key_type>>([this, tx, pairs = 
std::move(pairs)](auto callback) mutable {
             remove_all_async(tx, std::move(pairs), std::move(callback));
         });
     }
@@ -495,4 +481,490 @@ private:
     std::shared_ptr<detail::table_impl> m_impl;
 };
 
+/**
+ * Key-Value view interface provides methods to access table records in form 
of separate key and value parts.
+ */
+template<typename K, typename V>
+class key_value_view {
+    friend class table;
+
+public:
+    typedef typename std::decay<K>::type key_type;
+    typedef typename std::decay<V>::type value_type;
+
+    // Deleted
+    key_value_view(const key_value_view &) = delete;
+    key_value_view &operator=(const key_value_view &) = delete;
+
+    // Default
+    key_value_view() = default;
+    key_value_view(key_value_view &&) noexcept = default;
+    key_value_view &operator=(key_value_view &&) noexcept = default;
+
+    /**
+     * Gets a value by key asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param callback Callback which is called on success with value if it
+     *   exists and @c std::nullopt otherwise
+     */
+    void get_async(transaction *tx, const key_type &key, 
ignite_callback<std::optional<value_type>> callback) {
+        m_delegate.get_async(tx, convert_to_tuple(key), [callback = 
std::move(callback)] (auto res) {
+            callback(convert_result<value_type>(std::move(res)));
+        });
+    }
+
+    /**
+     * Gets a value by key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @return Value if exists and @c std::nullopt otherwise.
+     */
+    [[nodiscard]] std::optional<value_type> get(transaction *tx, const 
key_type &key) {
+        return sync<std::optional<value_type>>(
+            [this, tx, &key](auto callback) { get_async(tx, key, 
std::move(callback)); });
+    }
+
+    /**
+     * Gets multiple values by keys asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param keys Keys.
+     * @param callback Callback that is called on operation completion. Called 
with
+     *   resulting records with all columns filled from the table. The order of
+     *   elements is guaranteed to be the same as the order of keys. If a 
record
+     *   does not exist, the resulting element of the corresponding order is
+     *   @c std::nullopt.
+     */
+    void get_all_async(
+        transaction *tx, std::vector<key_type> keys, 
ignite_callback<std::vector<std::optional<value_type>>> callback) {
+        m_delegate.get_all_async(tx, 
values_to_tuples<key_type>(std::move(keys)),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<value_type>(std::move(res)));
+            });
+    }
+
+    /**
+     * Gets multiple values by keys.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param keys Keys.
+     * @return Resulting records with all columns filled from the table.
+     *   The order of elements is guaranteed to be the same as the order of
+     *   keys. If a record does not exist, the resulting element of the
+     *   corresponding order is @c std::nullopt.
+     */
+    [[nodiscard]] std::vector<std::optional<value_type>> get_all(
+        transaction *tx, std::vector<key_type> keys) {
+        return sync<std::vector<std::optional<value_type>>>([this, tx, keys = 
std::move(keys)](auto callback) mutable {
+            get_all_async(tx, std::move(keys), std::move(callback));
+        });
+    }
+
+    /**
+     * Asynchronously determines if the table contains a value for the 
specified key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param callback Callback which is called on success with value
+     *   indicating whether value exists or not.
+     */
+    void contains_async(transaction *tx, const key_type &key, 
ignite_callback<bool> callback) {
+        m_delegate.contains_async(tx, convert_to_tuple(key), 
std::move(callback));
+    }
+
+    /**
+     * Determines if the table contains an entry for the specified key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @return Value indicating whether value exists or not.
+     */
+    [[nodiscard]] bool contains(transaction *tx, const key_type &key) {
+        return sync<bool>([this, tx, &key](auto callback) { contains_async(tx, 
key, std::move(callback)); });
+    }
+
+    /**
+     * Puts a value with a given key asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *  single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback Callback.
+     */
+    void put_async(transaction *tx, const key_type &key, const value_type 
&value, ignite_callback<void> callback) {
+        m_delegate.put_async(tx, convert_to_tuple(key), 
convert_to_tuple(value), std::move(callback));
+    }
+
+    /**
+     * Puts a value with a given key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *  single operation is used.
+     * @param key Key.
+     * @param value Value.
+     */
+    void put(transaction *tx, const key_type &key, const value_type &value) {
+        sync<void>([this, tx, &key, &value](auto callback) { put_async(tx, 
key, value, std::move(callback)); });
+    }
+
+    /**
+     * Puts multiple key-value pairs asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param pairs Pairs to put.
+     * @param callback Callback that is called on operation completion.
+     */
+    void put_all_async(
+        transaction *tx, const std::vector<std::pair<key_type, value_type>> 
&pairs, ignite_callback<void> callback) {
+        m_delegate.put_all_async(tx, values_to_tuples<key_type, 
value_type>(std::move(pairs)), std::move(callback));
+    }
+
+    /**
+     * Puts multiple key-value pairs.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param pairs Pairs to put.
+     */
+    void put_all(transaction *tx, const std::vector<std::pair<key_type, 
value_type>> &pairs) {
+        sync<void>([this, tx, pairs](auto callback) mutable { 
put_all_async(tx, pairs, std::move(callback)); });
+    }
+
+    /**
+     * Puts a value with a given key and returns previous value for the key 
asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback Callback. Called with a value which contains replaced
+     *   value or @c std::nullopt if it did not exist.
+     */
+    void get_and_put_async(transaction *tx, const key_type &key, const 
value_type &value,
+        ignite_callback<std::optional<value_type>> callback) {
+        m_delegate.get_and_put_async(tx, convert_to_tuple(key), 
convert_to_tuple(value),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<value_type>(std::move(res)));
+            });
+    }
+
+    /**
+     * Puts a value with a given key and returns previous value for the key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @return A replaced value or @c std::nullopt if it did not exist.
+     */
+    [[nodiscard]] std::optional<value_type> get_and_put(
+        transaction *tx, const key_type &key, const value_type &value) {
+        return sync<std::optional<value_type>>(
+            [this, tx, &key, &value](auto callback) { get_and_put_async(tx, 
key, value, std::move(callback)); });
+    }
+
+    /**
+     * Asynchronously puts a value with a given key if the specified key is 
not present in the table.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback Callback. Called with a value indicating whether the
+     *   record was inserted. Equals @c false if a record with the same key
+     *   already exists.
+     */
+    void put_if_absent_async(
+        transaction *tx, const key_type &key, const value_type &value, 
ignite_callback<bool> callback) {
+        m_delegate.put_if_absent_async(tx, convert_to_tuple(key), 
convert_to_tuple(value), std::move(callback));
+    }
+
+    /**
+     * Puts a value with a given key if the specified key is not present in 
the table.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     */
+    bool put_if_absent(transaction *tx, const key_type &key, const value_type 
&value) {
+        return sync<bool>(
+            [this, tx, &key, &value](auto callback) { put_if_absent_async(tx, 
key, value, std::move(callback)); });
+    }
+
+    /**
+     * Removes a value with the specified key asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param callback Callback that is called on operation completion. Called 
with
+     *   a value indicating whether a record with the specified key was 
deleted.
+     */
+    void remove_async(transaction *tx, const key_type &key, 
ignite_callback<bool> callback) {
+        m_delegate.remove_async(tx, convert_to_tuple(key), 
std::move(callback));
+    }
+
+    /**
+     * Removes a value with the specified key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @return A value indicating whether a record with the specified key was 
deleted.
+     */
+    bool remove(transaction *tx, const key_type &key) {
+        return sync<bool>([this, tx, &key](auto callback) { remove_async(tx, 
key, std::move(callback)); });
+    }
+
+    /**
+     * Asynchronously removes a value with a given key from the table only if 
it is equal to the specified value.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback Callback that is called on operation completion. Called 
with
+     *   a value indicating whether a record with the specified key was 
deleted.
+     */
+    void remove_async(
+        transaction *tx, const key_type &key, const value_type &value, 
ignite_callback<bool> callback) {
+        m_delegate.remove_async(tx, convert_to_tuple(key), 
convert_to_tuple(value), std::move(callback));
+    }
+
+    /**
+     * Removes a value with a given key from the table only if it is equal to 
the specified value.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @return A value indicating whether a record with the specified key was
+     *   deleted.
+     */
+    bool remove(transaction *tx, const key_type &key, const value_type &value) 
{
+        return sync<bool>(
+            [this, tx, &key, &value](auto callback) { remove_async(tx, key, 
value, std::move(callback)); });
+    }
+
+    /**
+     * Removes values with given keys from the table asynchronously. If one or
+     * more keys do not exist, other values are still removed
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param keys Keys.
+     * @param callback Callback that is called on operation completion. Called 
with
+     *   records from @c keys that did not exist.
+     */
+    void remove_all_async(
+        transaction *tx, std::vector<key_type> keys, 
ignite_callback<std::vector<key_type>> callback) {
+        m_delegate.remove_all_async(tx, 
values_to_tuples<key_type>(std::move(keys)),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<key_type>(std::move(res)));
+            });
+    }
+
+    /**
+     * Removes values with given keys from the table. If one or more keys
+     * do not exist, other values are still removed
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param keys Keys.
+     * @return Records from @c keys that did not exist.
+     */
+    std::vector<key_type> remove_all(transaction *tx, std::vector<key_type> 
keys) {
+        return sync<std::vector<key_type>>([this, tx, keys = 
std::move(keys)](auto callback) mutable {
+            remove_all_async(tx, std::move(keys), std::move(callback));
+        });
+    }
+
+    /**
+     * Removes records with given keys and values from the table 
asynchronously.
+     * If one or more records do not exist, other records are still removed.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param pairs Pairs to remove.
+     * @param callback Callback that is called on operation completion. Called 
with
+     *   records from @c records that did not exist.
+     */
+    void remove_all_async(transaction *tx, const 
std::vector<std::pair<key_type, value_type>> &pairs,
+        ignite_callback<std::vector<key_type>> callback) {
+        m_delegate.remove_all_async(tx, values_to_tuples<key_type, 
value_type>(std::move(pairs)),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<key_type>(std::move(res)));
+            });
+    }
+
+    /**
+     * Removes records with given keys and values from the table. If one or 
more
+     * records do not exist, other records are still removed.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param pairs Pairs to remove.
+     * @return Records from @c records that did not exist.
+     */
+    std::vector<key_type> remove_all(transaction *tx, 
std::vector<std::pair<key_type, value_type>> pairs) {
+        return sync<std::vector<key_type>>([this, tx, pairs = 
std::move(pairs)](auto callback) mutable {
+            remove_all_async(tx, std::move(pairs), std::move(callback));
+        });
+    }
+
+    /**
+     * Gets and removes a value associated with the given key asynchronously.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param callback Callback that is called on operation completion. Called 
with
+     *   a removed record or @c std::nullopt if it did not exist.
+     */
+    void get_and_remove_async(
+        transaction *tx, const key_type &key, 
ignite_callback<std::optional<value_type>> callback) {
+        m_delegate.get_and_remove_async(tx, convert_to_tuple(key),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<value_type>(std::move(res)));
+            });
+    }
+
+    /**
+     * Gets and removes a value associated with the given key.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key key.
+     * @return A removed record or @c std::nullopt if it did not exist.
+     */
+    std::optional<value_type> get_and_remove(transaction *tx, const key_type 
&key) {
+        return sync<std::optional<value_type>>(
+            [this, tx, &key](auto callback) { get_and_remove_async(tx, key, 
std::move(callback)); });
+    }
+
+    /**
+     * Asynchronously replaces a record with the specified key if it exists, 
otherwise does nothing.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback Callback. Called with a value indicating whether a 
record
+     *   with the specified key was replaced.
+     */
+    void replace_async(transaction *tx, const key_type &key, const value_type 
&value, ignite_callback<bool> callback) {
+        m_delegate.replace_async(tx, convert_to_tuple(key), 
convert_to_tuple(value), std::move(callback));
+    }
+
+    /**
+     * Replaces a record with the same key columns if it exists, otherwise does
+     * nothing.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @return A value indicating whether a record with the specified key was
+     *   replaced.
+     */
+    bool replace(transaction *tx, const key_type &key, const value_type 
&value) {
+        return sync<bool>(
+            [this, tx, &key, &value](auto callback) { replace_async(tx, key, 
value, std::move(callback)); });
+    }
+
+    /**
+     * Asynchronously replaces a value with a @c new_value one only if 
existing value equals to the specified
+     * @c old_value.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param old_value Value to be replaced.
+     * @param new_value New value.
+     * @param callback Callback. Called with a value indicating whether a
+     *   specified record was replaced.
+     */
+    void replace_async(transaction *tx, const key_type &key, const value_type 
&old_value,
+        const value_type &new_value, ignite_callback<bool> callback) {
+        m_delegate.replace_async(tx, convert_to_tuple(key), 
convert_to_tuple(old_value), convert_to_tuple(new_value),
+            std::move(callback));
+    }
+
+    /**
+     * Replaces a value with a @c new_value one only if existing value equals
+     * to the specified @c old_value.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param old_value Value to be replaced.
+     * @param new_value New value.
+     * @return A value indicating whether a specified record was replaced.
+     */
+    bool replace(
+        transaction *tx, const key_type &key, const value_type &old_value, 
const value_type &new_value) {
+        return sync<bool>([this, tx, &key, &old_value, &new_value](
+                              auto callback) { replace_async(tx, key, 
old_value, new_value, std::move(callback)); });
+    }
+
+    /**
+     * Asynchronously replaces a record with the given key if it exists
+     * returning previous value.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback Callback. Called with a previous value for the given 
key,
+     *   or @c std::nullopt if it did not exist.
+     */
+    void get_and_replace_async(transaction *tx, const key_type &key, const 
value_type &value,
+        ignite_callback<std::optional<value_type>> callback) {
+        m_delegate.get_and_replace_async(tx, convert_to_tuple(key), 
convert_to_tuple(value),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<value_type>(std::move(res)));
+            });
+    }
+
+    /**
+     * Replaces a record with the given key if it exists returning previous
+     * value.
+     *
+     * @param tx Optional transaction. If nullptr implicit transaction for this
+     *   single operation is used.
+     * @param key Key.
+     * @param value Value.
+     * @param callback A previous value for the given key, or @c std::nullopt 
if
+     *   it did not exist.
+     */
+    [[nodiscard]] std::optional<value_type> get_and_replace(
+        transaction *tx, const key_type &key, const value_type &value) {
+        return sync<std::optional<value_type>>(
+            [this, tx, &key, &value](auto callback) { 
get_and_replace_async(tx, key, value, std::move(callback)); });
+    }
+private:
+    /**
+     * Constructor
+     *
+     * @param impl Implementation
+     */
+    explicit key_value_view(key_value_view<ignite_tuple, ignite_tuple> 
delegate)
+        : m_delegate(std::move(delegate)) {}
+
+    /** Delegate. */
+    key_value_view<ignite_tuple, ignite_tuple> m_delegate;
+};
+
 } // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/table/record_view.h 
b/modules/platforms/cpp/ignite/client/table/record_view.h
index 8c7b3c5db5..57eb7c65ed 100644
--- a/modules/platforms/cpp/ignite/client/table/record_view.h
+++ b/modules/platforms/cpp/ignite/client/table/record_view.h
@@ -20,6 +20,7 @@
 #include <ignite/client/table/ignite_tuple.h>
 #include <ignite/client/transaction/transaction.h>
 #include <ignite/client/type_mapping.h>
+#include <ignite/client/detail/type_mapping_utils.h>
 
 #include <ignite/common/config.h>
 #include <ignite/common/ignite_result.h>
@@ -492,7 +493,7 @@ public:
      */
     void get_async(transaction *tx, const value_type &key, 
ignite_callback<std::optional<value_type>> callback) {
         m_delegate.get_async(tx, convert_to_tuple(key), [callback = 
std::move(callback)] (auto res) {
-            callback(convert_result(std::move(res)));
+            callback(convert_result<value_type>(std::move(res)));
         });
     }
 
@@ -523,9 +524,10 @@ public:
      */
     void get_all_async(transaction *tx, std::vector<value_type> keys,
         ignite_callback<std::vector<std::optional<value_type>>> callback) {
-        m_delegate.get_all_async(tx, values_to_tuples(std::move(keys)), 
[callback = std::move(callback)] (auto res) {
-            callback(convert_result(std::move(res)));
-        });
+        m_delegate.get_all_async(tx, 
values_to_tuples<value_type>(std::move(keys)),
+            [callback = std::move(callback)] (auto res) {
+                callback(convert_result<value_type>(std::move(res)));
+            });
     }
 
     /**
@@ -578,7 +580,7 @@ public:
      * @param callback Callback that is called on operation completion.
      */
     void upsert_all_async(transaction *tx, std::vector<value_type> records, 
ignite_callback<void> callback) {
-        m_delegate.upsert_all_async(tx, values_to_tuples(std::move(records)), 
std::move(callback));
+        m_delegate.upsert_all_async(tx, 
values_to_tuples<value_type>(std::move(records)), std::move(callback));
     }
 
     /**
@@ -606,7 +608,7 @@ public:
     void get_and_upsert_async(
         transaction *tx, const value_type &record, 
ignite_callback<std::optional<value_type>> callback) {
         m_delegate.get_and_upsert_async(tx, convert_to_tuple(record), 
[callback = std::move(callback)] (auto res) {
-            callback(convert_result(std::move(res)));
+            callback(convert_result<value_type>(std::move(res)));
         });
     }
 
@@ -659,9 +661,9 @@ public:
      */
     void insert_all_async(
         transaction *tx, std::vector<value_type> records, 
ignite_callback<std::vector<value_type>> callback) {
-        m_delegate.insert_all_async(tx, values_to_tuples(std::move(records)),
+        m_delegate.insert_all_async(tx, 
values_to_tuples<value_type>(std::move(records)),
             [callback = std::move(callback)] (auto res) {
-                callback(convert_result(std::move(res)));
+                callback(convert_result<value_type>(std::move(res)));
             }
         );
     }
@@ -753,7 +755,7 @@ public:
     void get_and_replace_async(
         transaction *tx, const value_type &record, 
ignite_callback<std::optional<value_type>> callback) {
         m_delegate.get_and_replace_async(tx, convert_to_tuple(record), 
[callback = std::move(callback)] (auto res) {
-            callback(convert_result(std::move(res)));
+            callback(convert_result<value_type>(std::move(res)));
         });
     }
 
@@ -837,7 +839,7 @@ public:
     void get_and_remove_async(
         transaction *tx, const value_type &key, 
ignite_callback<std::optional<value_type>> callback) {
         m_delegate.get_and_remove_async(tx, convert_to_tuple(key), [callback = 
std::move(callback)] (auto res) {
-            callback(convert_result(std::move(res)));
+            callback(convert_result<value_type>(std::move(res)));
         });
     }
 
@@ -866,8 +868,8 @@ public:
      */
     void remove_all_async(
         transaction *tx, std::vector<value_type> keys, 
ignite_callback<std::vector<value_type>> callback) {
-        m_delegate.remove_all_async(tx, values_to_tuples(std::move(keys)), 
[callback = std::move(callback)] (auto res) {
-            callback(convert_result(std::move(res)));
+        m_delegate.remove_all_async(tx, 
values_to_tuples<value_type>(std::move(keys)), [callback = std::move(callback)] 
(auto res) {
+            callback(convert_result<value_type>(std::move(res)));
         });
     }
 
@@ -898,9 +900,9 @@ public:
      */
     void remove_all_exact_async(
         transaction *tx, std::vector<value_type> records, 
ignite_callback<std::vector<value_type>> callback) {
-        m_delegate.remove_all_exact_async(tx, 
values_to_tuples(std::move(records)),
+        m_delegate.remove_all_exact_async(tx, 
values_to_tuples<value_type>(std::move(records)),
             [callback = std::move(callback)] (auto res) {
-                callback(convert_result(std::move(res)));
+                callback(convert_result<value_type>(std::move(res)));
             }
         );
     }
@@ -921,88 +923,6 @@ public:
     }
 
 private:
-    /**
-     * Convert values to tuples.
-     * @param vals Values.
-     * @return Tuples.
-     */
-    static std::vector<ignite_tuple> values_to_tuples(std::vector<value_type> 
values) {
-        //TODO: Optimize memory usage (IGNITE-19198)
-        std::vector<ignite_tuple> tuples;
-        tuples.reserve(values.size());
-        for (auto &&value : std::move(values)) {
-            tuples.push_back(convert_to_tuple(std::move(value)));
-        }
-        return tuples;
-    }
-
-    /**
-     * Tuples to values.
-     * @param tuples Tuples.
-     * @return Values.
-     */
-    static std::vector<value_type> tuples_to_values(std::vector<ignite_tuple> 
tuples) {
-        //TODO: Optimize memory usage (IGNITE-19198)
-        std::vector<value_type> values;
-        values.reserve(tuples.size());
-        for (auto &&tuple : std::move(tuples)) {
-            
values.emplace_back(convert_from_tuple<value_type>(std::move(tuple)));
-        }
-        return values;
-    }
-
-    /**
-     * Optional tuples to optional values.
-     * @param tuples Tuples.
-     * @return Values.
-     */
-    static std::vector<std::optional<value_type>> 
tuples_to_values(std::vector<std::optional<ignite_tuple>> tuples) {
-        //TODO: Optimize memory usage (IGNITE-19198)
-        std::vector<std::optional<value_type>> values;
-        values.reserve(tuples.size());
-        for (auto &&tuple : std::move(tuples)) {
-            
values.emplace_back(convert_from_tuple<value_type>(std::move(tuple)));
-        }
-        return values;
-    }
-
-    /**
-     * Convert result from tuple-based type to user type.
-     * @param res Result to convert.
-     * @return Converted result.
-     */
-    static ignite_result<std::optional<value_type>> 
convert_result(ignite_result<std::optional<ignite_tuple>> &&res) {
-        if (res.has_error())
-            return {std::move(res).error()};
-
-        return {convert_from_tuple<value_type>(std::move(res).value())};
-    }
-
-    /**
-     * Convert result from tuple-based type to user type.
-     * @param res Result to convert.
-     * @return Converted result.
-     */
-    static ignite_result<std::vector<std::optional<value_type>>> 
convert_result(
-        ignite_result<std::vector<std::optional<ignite_tuple>>> &&res) {
-        if (res.has_error())
-            return {std::move(res).error()};
-
-        return {tuples_to_values(std::move(res).value())};
-    }
-
-    /**
-     * Convert result from tuple-based type to user type.
-     * @param res Result to convert.
-     * @return Converted result.
-     */
-    static ignite_result<std::vector<value_type>> 
convert_result(ignite_result<std::vector<ignite_tuple>> &&res) {
-        if (res.has_error())
-            return {std::move(res).error()};
-
-        return {tuples_to_values(std::move(res).value())};
-    }
-
     /**
      * Constructor
      *
diff --git a/modules/platforms/cpp/ignite/client/table/table.h 
b/modules/platforms/cpp/ignite/client/table/table.h
index e1097ebb2c..23d17cf22a 100644
--- a/modules/platforms/cpp/ignite/client/table/table.h
+++ b/modules/platforms/cpp/ignite/client/table/table.h
@@ -81,10 +81,23 @@ public:
     /**
      * Gets the key-value binary view.
      *
-     * @return Record binary view.
+     * @return Key-value binary view.
      */
     [[nodiscard]] IGNITE_API key_value_view<ignite_tuple, ignite_tuple> 
get_key_value_binary_view() const noexcept;
 
+    /**
+     * Gets the key-value view.
+     *
+     * Template functions @c convert_to_tuple() and @c convert_from_tuple() 
should be specialized for the types K and V.
+     * @see See type_mapping.h for details.
+     *
+     * @return Key-value view.
+     */
+    template<typename K, typename V>
+    [[nodiscard]] key_value_view<K, V> get_key_value_view() const noexcept {
+        return key_value_view<K, V>{get_key_value_binary_view()};
+    }
+
 private:
     /**
      * Constructor
diff --git a/modules/platforms/cpp/tests/client-test/CMakeLists.txt 
b/modules/platforms/cpp/tests/client-test/CMakeLists.txt
index c8772c72e5..521b028675 100644
--- a/modules/platforms/cpp/tests/client-test/CMakeLists.txt
+++ b/modules/platforms/cpp/tests/client-test/CMakeLists.txt
@@ -25,6 +25,7 @@ set(SOURCES
     ignite_client_test.cpp
     ignite_runner_suite.h
     key_value_binary_view_test.cpp
+    key_value_view_test.cpp
     main.cpp
     record_binary_view_test.cpp
     record_view_test.cpp
diff --git a/modules/platforms/cpp/tests/client-test/all_fields_type.h 
b/modules/platforms/cpp/tests/client-test/all_fields_type.h
new file mode 100644
index 0000000000..409e2077a1
--- /dev/null
+++ b/modules/platforms/cpp/tests/client-test/all_fields_type.h
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <ignite/client/table/ignite_tuple.h>
+#include <ignite/client/type_mapping.h>
+
+#include <ignite/common/ignite_date.h>
+#include <ignite/common/ignite_date_time.h>
+#include <ignite/common/ignite_time.h>
+#include <ignite/common/ignite_timestamp.h>
+#include <ignite/common/uuid.h>
+#include <ignite/common/bit_array.h>
+#include <ignite/common/big_decimal.h>
+
+#include <string>
+#include <cstdint>
+
+
+/**
+ * All columns type table mapping (@see 
ignite_runner_suite::TABLE_NAME_ALL_COLUMNS).
+ */
+struct all_fields_type {
+    all_fields_type() = default;
+    explicit all_fields_type(std::int64_t key) : m_key(key) {}
+
+    std::int64_t m_key{0};
+    std::string m_str;
+    std::int8_t m_int8{0};
+    std::int16_t m_int16{0};
+    std::int32_t m_int32{0};
+    std::int64_t m_int64{0};
+    float m_float{.0f};
+    double m_double{.0};
+    ignite::uuid m_uuid;
+    ignite::ignite_date m_date;
+    ignite::bit_array m_bitmask;
+    ignite::ignite_time m_time;
+    ignite::ignite_time m_time2;
+    ignite::ignite_date_time m_datetime;
+    ignite::ignite_date_time m_datetime2;
+    ignite::ignite_timestamp m_timestamp;
+    ignite::ignite_timestamp m_timestamp2;
+    std::vector<std::byte> m_blob;
+    ignite::big_decimal m_decimal;
+};
+
+namespace ignite {
+
+template<>
+inline ignite_tuple convert_to_tuple(all_fields_type &&value) {
+    ignite_tuple tuple;
+
+    tuple.set("key", value.m_key);
+    tuple.set("str", value.m_str);
+    tuple.set("int8", value.m_int8);
+    tuple.set("int16", value.m_int16);
+    tuple.set("int32", value.m_int32);
+    tuple.set("int64", value.m_int64);
+    tuple.set("float", value.m_float);
+    tuple.set("double", value.m_double);
+    tuple.set("uuid", value.m_uuid);
+    tuple.set("date", value.m_date);
+    tuple.set("bitmask", value.m_bitmask);
+    tuple.set("time", value.m_time);
+    tuple.set("time2", value.m_time2);
+    tuple.set("datetime", value.m_datetime);
+    tuple.set("datetime2", value.m_datetime2);
+    tuple.set("timestamp", value.m_timestamp);
+    tuple.set("timestamp2", value.m_timestamp2);
+    tuple.set("blob", value.m_blob);
+    tuple.set("decimal", value.m_decimal);
+
+    return tuple;
+}
+
+template<>
+inline all_fields_type convert_from_tuple(ignite_tuple&& value) {
+    all_fields_type res;
+
+    res.m_key = value.get<std::int64_t>("key");
+
+    if (value.column_count() > 1) {
+        res.m_str = value.get<std::string>("str");
+        res.m_int8 = value.get<std::int8_t>("int8");
+        res.m_int16 = value.get<std::int16_t>("int16");
+        res.m_int32 = value.get<std::int32_t>("int32");
+        res.m_int64 = value.get<std::int64_t>("int64");
+        res.m_float = value.get<float>("float");
+        res.m_double = value.get<double>("double");
+        res.m_uuid = value.get<uuid>("uuid");
+        res.m_date = value.get<ignite_date>("date");
+        res.m_bitmask = value.get<bit_array>("bitmask");
+        res.m_time = value.get<ignite_time>("time");
+        res.m_time2 = value.get<ignite_time>("time2");
+        res.m_datetime = value.get<ignite_date_time>("datetime");
+        res.m_datetime2 = value.get<ignite_date_time>("datetime2");
+        res.m_timestamp = value.get<ignite_timestamp>("timestamp");
+        res.m_timestamp2 = value.get<ignite_timestamp>("timestamp2");
+        res.m_blob = value.get<std::vector<std::byte>>("blob");
+        res.m_decimal = value.get<big_decimal>("decimal");
+    }
+
+    return res;
+}
+
+} // namespace ignite
+
diff --git 
a/modules/platforms/cpp/tests/client-test/key_value_binary_view_test.cpp 
b/modules/platforms/cpp/tests/client-test/key_value_binary_view_test.cpp
index a0627cc657..e6ef6d7646 100644
--- a/modules/platforms/cpp/tests/client-test/key_value_binary_view_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/key_value_binary_view_test.cpp
@@ -54,7 +54,7 @@ protected:
     /** Ignite client. */
     ignite_client m_client;
 
-    /** Record binary view. */
+    /** Key-Value binary view. */
     key_value_view<ignite_tuple, ignite_tuple> kv_view;
 };
 
@@ -770,7 +770,7 @@ TEST_F(key_value_binary_view_test, 
remove_exact_empty_throws) {
 }
 
 TEST_F(key_value_binary_view_test, get_and_remove_nonexisting) {
-    auto res = kv_view.get_and_replace(nullptr, get_tuple(42), 
get_tuple("foo"));
+    auto res = kv_view.get_and_remove(nullptr, get_tuple(42));
     ASSERT_FALSE(res.has_value());
 
     auto res_tuple = kv_view.get(nullptr, get_tuple(42));
diff --git a/modules/platforms/cpp/tests/client-test/key_value_view_test.cpp 
b/modules/platforms/cpp/tests/client-test/key_value_view_test.cpp
new file mode 100644
index 0000000000..6aa892d873
--- /dev/null
+++ b/modules/platforms/cpp/tests/client-test/key_value_view_test.cpp
@@ -0,0 +1,825 @@
+/*
+ * 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 "all_fields_type.h"
+#include "ignite_runner_suite.h"
+
+#include "tests/test-common/test_utils.h"
+
+#include "ignite/client/ignite_client.h"
+#include "ignite/client/ignite_client_configuration.h"
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+
+using namespace ignite;
+
+/**
+ * Test key type mapping (@see ignite_runner_suite::TABLE_1).
+ */
+struct test_key_type {
+    test_key_type() = default;
+    explicit test_key_type(std::int64_t key) : key(key) {}
+
+    std::int64_t key{0};
+};
+
+/**
+ * Test value type mapping (@see ignite_runner_suite::TABLE_1).
+ */
+struct test_value_type {
+    test_value_type() = default;
+    explicit test_value_type(std::string val) : val(std::move(val)) {}
+
+    std::string val;
+};
+
+
+namespace ignite {
+
+template<>
+ignite_tuple convert_to_tuple(test_key_type &&value) {
+    ignite_tuple tuple;
+
+    tuple.set("key", value.key);
+
+    return tuple;
+}
+
+template<>
+test_key_type convert_from_tuple(ignite_tuple&& value) {
+    test_key_type res;
+
+    res.key = value.get<std::int64_t>("key");
+
+    return res;
+}
+
+template<>
+ignite_tuple convert_to_tuple(test_value_type &&value) {
+    ignite_tuple tuple;
+
+    tuple.set("val", value.val);
+
+    return tuple;
+}
+
+template<>
+test_value_type convert_from_tuple(ignite_tuple&& value) {
+    test_value_type res;
+
+    res.val = value.get<std::string>("val");
+
+    return res;
+}
+
+}
+
+/**
+ * Test suite.
+ */
+class key_value_view_test : public ignite_runner_suite {
+protected:
+    void SetUp() override {
+        ignite_client_configuration cfg{get_node_addrs()};
+        cfg.set_logger(get_logger());
+
+        m_client = ignite_client::start(cfg, std::chrono::seconds(30));
+        auto table = m_client.get_tables().get_table(TABLE_1);
+
+        kv_view = table->get_key_value_view<test_key_type, test_value_type>();
+    }
+
+    void TearDown() override {
+        std::vector<test_key_type> work_range;
+        work_range.reserve(100);
+        for (int i = -50; i < 50; ++i)
+            work_range.emplace_back(i);
+
+        kv_view.remove_all(nullptr, work_range);
+    }
+
+    /** Ignite client. */
+    ignite_client m_client;
+
+    /** Key-Value view. */
+    key_value_view<test_key_type, test_value_type> kv_view;
+};
+
+TEST_F(key_value_view_test, put_get) {
+    auto key = test_key_type(1);
+    auto val = test_value_type("foo");
+
+    kv_view.put(nullptr, key, val);
+    auto res = kv_view.get(nullptr, key);
+
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("foo", res->val);
+}
+
+TEST_F(key_value_view_test, put_get_async) {
+    auto key = test_key_type(1);
+    auto val = test_value_type("foo");
+
+    auto all_done = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.put_async(nullptr, key, val, [&](ignite_result<void> &&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        kv_view.get_async(nullptr, key, [&](auto res) { 
result_set_promise(*all_done, std::move(res)); });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("foo", res->val);
+}
+
+TEST_F(key_value_view_test, put_overrides_value) {
+    auto key = test_key_type(1);
+    auto val = test_value_type("foo");
+
+    kv_view.put(nullptr, key, val);
+    auto res = kv_view.get(nullptr, key);
+
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("foo", res->val);
+
+    val.val = "bar";
+    kv_view.put(nullptr, key, val);
+    res = kv_view.get(nullptr, key);
+
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("bar", res->val);
+}
+
+TEST_F(key_value_view_test, get_all_empty) {
+    auto res = kv_view.get_all(nullptr, {});
+    EXPECT_TRUE(res.empty());
+}
+
+TEST_F(key_value_view_test, get_all_nonexisting) {
+    auto res = kv_view.get_all(nullptr, {test_key_type(-42)});
+
+    ASSERT_TRUE(res.empty());
+}
+
+TEST_F(key_value_view_test, put_all_empty_no_throw) {
+    kv_view.put_all(nullptr, {});
+}
+
+TEST_F(key_value_view_test, put_all_get_all) {
+    static constexpr std::size_t records_num = 10;
+
+    std::vector<std::pair<test_key_type, test_value_type>> records;
+    records.reserve(records_num);
+    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+        records.emplace_back(i, "Val" + std::to_string(i));
+
+    std::vector<test_key_type> keys;
+    for (std::int64_t i = 9; i < 13; ++i)
+        keys.emplace_back(i);
+
+    kv_view.put_all(nullptr, records);
+    auto res = kv_view.get_all(nullptr, keys);
+
+    // TODO: Key order should be preserved by the server (IGNITE-16004).
+    EXPECT_EQ(res.size(), 2);
+
+    ASSERT_TRUE(res[0].has_value());
+    EXPECT_EQ("Val9", res[0]->val);
+
+    ASSERT_TRUE(res[1].has_value());
+    EXPECT_EQ("Val10", res[1]->val);
+}
+
+TEST_F(key_value_view_test, put_all_get_all_async) {
+    static constexpr std::size_t records_num = 10;
+
+    std::vector<std::pair<test_key_type, test_value_type>> records;
+    records.reserve(records_num);
+    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+        records.emplace_back(i, "Val" + std::to_string(i));
+
+    std::vector<test_key_type> keys;
+    for (std::int64_t i = 9; i < 13; ++i)
+        keys.emplace_back(i);
+
+    auto all_done = 
std::make_shared<std::promise<std::vector<std::optional<test_value_type>>>>();
+
+    kv_view.put_all_async(nullptr, records, [&](ignite_result<void> &&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        // TODO: Key order should be preserved by the server (IGNITE-16004).
+        kv_view.get_all_async(nullptr, keys, [&](auto res) { 
result_set_promise(*all_done, std::move(res)); });
+    });
+
+    auto res = all_done->get_future().get();
+
+    EXPECT_EQ(res.size(), 2);
+
+    ASSERT_TRUE(res[0].has_value());
+    EXPECT_EQ("Val9", res[0]->val);
+
+    ASSERT_TRUE(res[1].has_value());
+    EXPECT_EQ("Val10", res[1]->val);
+}
+
+TEST_F(key_value_view_test, get_and_put_new_record) {
+    auto key = test_key_type(42);
+    auto val = test_value_type("foo");
+    auto res = kv_view.get_and_put(nullptr, key, val);
+
+    ASSERT_FALSE(res.has_value());
+}
+
+TEST_F(key_value_view_test, get_and_put_existing_record) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto res = kv_view.get_and_put(nullptr, key, val1);
+
+    ASSERT_FALSE(res.has_value());
+
+    auto val2 = test_value_type("bar");
+    res = kv_view.get_and_put(nullptr, key, val2);
+
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("foo", res->val);
+
+    res = kv_view.get(nullptr, key);
+
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("bar", res->val);
+}
+
+TEST_F(key_value_view_test, get_and_put_existing_record_async) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto val2 = test_value_type("bar");
+
+    auto first = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.get_and_put_async(nullptr, key, val1, [&](auto res) {
+        if (!check_and_set_operation_error(*first, res))
+            return;
+
+        if (res.value().has_value())
+            
first->set_exception(std::make_exception_ptr(ignite_error("Expected nullopt on 
first insertion")));
+
+        kv_view.get_and_put_async(
+            nullptr, key, val2, [&](auto res) { result_set_promise(*first, 
std::move(res)); });
+    });
+
+    auto res = first->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val1.val, res->val);
+
+    auto second = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+    kv_view.get_async(nullptr, key, [&](auto res) { 
result_set_promise(*second, std::move(res)); });
+
+    res = second->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val2.val, res->val);
+}
+
+TEST_F(key_value_view_test, insert_new_record) {
+    auto key = test_key_type(42);
+    auto val = test_value_type("foo");
+    auto res = kv_view.put_if_absent(nullptr, key, val);
+
+    ASSERT_TRUE(res);
+}
+
+TEST_F(key_value_view_test, put_if_absent_existing_record) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto put_res = kv_view.put_if_absent(nullptr, key, val1);
+    ASSERT_TRUE(put_res);
+
+    auto val2 = test_value_type("bar");
+    put_res = kv_view.put_if_absent(nullptr, key, val2);
+    ASSERT_FALSE(put_res);
+
+    auto res = kv_view.get(nullptr, key);
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ("foo", res->val);
+}
+
+TEST_F(key_value_view_test, put_if_absent_existing_record_async) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto val2 = test_value_type("bar");
+
+    auto all_done = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> 
&&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (!res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
first insertion")));
+
+        kv_view.put_if_absent_async(nullptr, key, val2, 
[&](ignite_result<bool> &&res) {
+            if (!check_and_set_operation_error(*all_done, res))
+                return;
+
+            if (res.value())
+                
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected false on 
second insertion")));
+
+            kv_view.get_async(nullptr, key, [&](auto res) { 
result_set_promise(*all_done, std::move(res)); });
+        });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val1.val, res->val);
+}
+
+TEST_F(key_value_view_test, replace_nonexisting) {
+    auto key = test_key_type(42);
+    auto val = test_value_type("foo");
+
+    auto res1 = kv_view.replace(nullptr, key, val);
+    ASSERT_FALSE(res1);
+
+    auto res2 = kv_view.get(nullptr, key);
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, replace_existing) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+
+    auto res = kv_view.put_if_absent(nullptr, key, val1);
+    ASSERT_TRUE(res);
+
+    auto val2 = test_value_type("bar");
+    res = kv_view.replace(nullptr, key, val2);
+    ASSERT_TRUE(res);
+
+    auto res2 = kv_view.get(nullptr, key);
+    ASSERT_TRUE(res2.has_value());
+    EXPECT_EQ("bar", res2->val);
+}
+
+TEST_F(key_value_view_test, replace_existing_async) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto val2 = test_value_type("bar");
+
+    auto all_done = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> 
&&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (!res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
insertion")));
+
+        kv_view.replace_async(nullptr, key, val2, [&](ignite_result<bool> 
&&res) {
+            if (!check_and_set_operation_error(*all_done, res))
+                return;
+
+            if (!res.value())
+                
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
replace")));
+
+            kv_view.get_async(nullptr, key, [&](auto res) { 
result_set_promise(*all_done, std::move(res)); });
+        });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val2.val, res->val);
+}
+
+TEST_F(key_value_view_test, replace_exact_nonexisting) {
+    auto res1 = kv_view.replace(nullptr, test_key_type(42), 
test_value_type("foo"), test_value_type("bar"));
+    ASSERT_FALSE(res1);
+
+    auto res2 = kv_view.get(nullptr, test_key_type(42));
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, replace_exact_existing_wrong) {
+    auto res1 = kv_view.put_if_absent(nullptr, test_key_type(42), 
test_value_type("foo"));
+    ASSERT_TRUE(res1);
+
+    res1 = kv_view.replace(nullptr, test_key_type(42), test_value_type("bar"), 
test_value_type("baz"));
+    ASSERT_FALSE(res1);
+
+    auto res2 = kv_view.get(nullptr, test_key_type(42));
+    ASSERT_TRUE(res2.has_value());
+    EXPECT_EQ("foo", res2->val);
+}
+
+TEST_F(key_value_view_test, replace_exact_existing_right) {
+    auto res1 = kv_view.put_if_absent(nullptr, test_key_type(42), 
test_value_type("foo"));
+    ASSERT_TRUE(res1);
+
+    res1 = kv_view.replace(nullptr, test_key_type(42), test_value_type("foo"), 
test_value_type("baz"));
+    ASSERT_TRUE(res1);
+
+    auto res2 = kv_view.get(nullptr, test_key_type(42));
+    ASSERT_TRUE(res2.has_value());
+    EXPECT_EQ("baz", res2->val);
+}
+
+TEST_F(key_value_view_test, replace_exact_existing_right_async) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto val2 = test_value_type("bar");
+
+    auto all_done = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> 
&&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (!res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
insertion")));
+
+        kv_view.replace_async(nullptr, key, val1, val2, 
[&](ignite_result<bool> &&res) {
+            if (!check_and_set_operation_error(*all_done, res))
+                return;
+
+            if (!res.value())
+                
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
replace")));
+
+            kv_view.get_async(nullptr, key, [&](auto res) { 
result_set_promise(*all_done, std::move(res)); });
+        });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val2.val, res->val);
+}
+
+TEST_F(key_value_view_test, get_and_replace_nonexisting) {
+    auto key = test_key_type(42);
+    auto val = test_value_type("foo");
+    auto res1 = kv_view.get_and_replace(nullptr, key, val);
+    ASSERT_FALSE(res1.has_value());
+
+    auto res2 = kv_view.get(nullptr, key);
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, get_and_replace_existing) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto res1 = kv_view.put_if_absent(nullptr, key, val1);
+    ASSERT_TRUE(res1);
+
+    auto val2 = test_value_type("bar");
+    auto res2 = kv_view.get_and_replace(nullptr, key, val2);
+
+    ASSERT_TRUE(res2.has_value());
+    EXPECT_EQ("foo", res2->val);
+
+    res2 = kv_view.get(nullptr, test_key_type(42));
+    ASSERT_TRUE(res2.has_value());
+    EXPECT_EQ("bar", res2->val);
+}
+
+TEST_F(key_value_view_test, get_and_replace_existing_async) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+    auto val2 = test_value_type("bar");
+
+    auto all_done = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> 
&&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (!res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
insertion")));
+
+        kv_view.get_and_replace_async(
+            nullptr, key, val2, [&](auto res) { result_set_promise(*all_done, 
std::move(res)); });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val1.val, res->val);
+}
+
+TEST_F(key_value_view_test, remove_nonexisting) {
+    auto res1 = kv_view.remove(nullptr, test_key_type(1));
+    ASSERT_FALSE(res1);
+
+    auto res2 = kv_view.get(nullptr, test_key_type(1));
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, remove_existing) {
+    auto res = kv_view.put_if_absent(nullptr, test_key_type(1), 
test_value_type("foo"));
+    ASSERT_TRUE(res);
+
+    res = kv_view.remove(nullptr, test_key_type(1));
+    ASSERT_TRUE(res);
+
+    auto res2 = kv_view.get(nullptr, test_key_type(1));
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, remove_existing_async) {
+    auto all_done = std::make_shared<std::promise<bool>>();
+
+    kv_view.put_if_absent_async(nullptr, test_key_type(42), 
test_value_type("foo"), [&](ignite_result<bool> &&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (!res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
insertion")));
+
+        kv_view.remove_async(nullptr, test_key_type(42), [&](auto res) { 
result_set_promise(*all_done, std::move(res)); });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res);
+}
+
+TEST_F(key_value_view_test, remove_exact_nonexisting) {
+    auto res = kv_view.remove(nullptr, test_key_type(1), 
test_value_type("foo"));
+    ASSERT_FALSE(res);
+}
+
+TEST_F(key_value_view_test, remove_exact_existing) {
+    auto res = kv_view.put_if_absent(nullptr, test_key_type(1), 
test_value_type("foo"));
+    ASSERT_TRUE(res);
+
+    res = kv_view.remove(nullptr, test_key_type(1), test_value_type("bar"));
+    ASSERT_FALSE(res);
+
+    res = kv_view.remove(nullptr, test_key_type(1), test_value_type("foo"));
+    ASSERT_TRUE(res);
+
+    auto res2 = kv_view.get(nullptr, test_key_type(1));
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, remove_exact_existing_async) {
+    auto key = test_key_type(42);
+    auto val = test_value_type("foo");
+
+    auto all_done = std::make_shared<std::promise<bool>>();
+
+    auto res = kv_view.put_if_absent(nullptr, key, val);
+    ASSERT_TRUE(res);
+
+    kv_view.remove_async(nullptr, key, test_value_type("bar"), [&](auto res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected false on 
second remove")));
+
+        kv_view.remove_async(
+            nullptr, key, val, [&](auto res) { result_set_promise(*all_done, 
std::move(res)); });
+    });
+
+    auto res2 = all_done->get_future().get();
+    ASSERT_TRUE(res2);
+}
+
+TEST_F(key_value_view_test, get_and_remove_nonexisting) {
+    auto res1 = kv_view.get_and_remove(nullptr, test_key_type(42));
+    ASSERT_FALSE(res1.has_value());
+
+    auto res2 = kv_view.get(nullptr, test_key_type(42));
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, get_and_remove_existing) {
+    auto res1 = kv_view.put_if_absent(nullptr, test_key_type(42), 
test_value_type("foo"));
+    ASSERT_TRUE(res1);
+
+    auto res2 = kv_view.get_and_remove(nullptr, test_key_type(42));
+
+    ASSERT_TRUE(res2.has_value());
+    EXPECT_EQ("foo", res2->val);
+
+    res2 = kv_view.get(nullptr, test_key_type(42));
+    ASSERT_FALSE(res2.has_value());
+}
+
+TEST_F(key_value_view_test, get_and_remove_existing_async) {
+    auto key = test_key_type(42);
+    auto val1 = test_value_type("foo");
+
+    auto all_done = 
std::make_shared<std::promise<std::optional<test_value_type>>>();
+
+    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> 
&&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (!res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
insertion")));
+
+        kv_view.get_and_remove_async(
+            nullptr, key, [&](auto res) { result_set_promise(*all_done, 
std::move(res)); });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res.has_value());
+    EXPECT_EQ(val1.val, res->val);
+}
+
+TEST_F(key_value_view_test, contains) {
+    auto res = kv_view.contains(nullptr, test_key_type(1));
+    ASSERT_FALSE(res);
+
+    res = kv_view.put_if_absent(nullptr, test_key_type(1), 
test_value_type("foo"));
+    ASSERT_TRUE(res);
+
+    res = kv_view.contains(nullptr, test_key_type(1));
+    ASSERT_TRUE(res);
+}
+
+TEST_F(key_value_view_test, contains_async) {
+    auto all_done = std::make_shared<std::promise<bool>>();
+
+    kv_view.contains_async(nullptr, test_key_type(1), [&](ignite_result<bool> 
&&res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        if (res.value())
+            
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected to not 
contain")));
+
+        kv_view.put_if_absent_async(nullptr, test_key_type(1), 
test_value_type("foo"), [&](ignite_result<bool> &&res) {
+            if (!check_and_set_operation_error(*all_done, res))
+                return;
+
+            if (!res.value())
+                
all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on 
insert")));
+
+            kv_view.contains_async(nullptr, test_key_type(1), 
[&](ignite_result<bool> &&res) {
+                result_set_promise(*all_done, std::move(res));
+            });
+        });
+    });
+
+    auto res = all_done->get_future().get();
+    ASSERT_TRUE(res);
+}
+
+TEST_F(key_value_view_test, remove_all_nonexisting_keys_return_all) {
+    std::vector<test_key_type> non_existing = {test_key_type(1), 
test_key_type(2)};
+    auto res = kv_view.remove_all(nullptr, non_existing);
+
+    EXPECT_EQ(res.size(), 2);
+
+    // TODO: Key order should be preserved by the server (IGNITE-16004).
+    EXPECT_EQ(2, res[0].key);
+    EXPECT_EQ(1, res[1].key);
+}
+
+TEST_F(key_value_view_test, remove_all_only_existing) {
+    std::vector<std::pair<test_key_type, test_value_type>> to_insert = {
+        {test_key_type(1), test_value_type("foo")}, {test_key_type(2), 
test_value_type("bar")}};
+    kv_view.put_all(nullptr, to_insert);
+
+    auto res = kv_view.remove_all(nullptr, {test_key_type(1), 
test_key_type(2)});
+
+    EXPECT_TRUE(res.empty());
+}
+
+TEST_F(key_value_view_test, remove_all_overlapped) {
+    static constexpr std::size_t records_num = 10;
+
+    std::vector<std::pair<test_key_type, test_value_type>> to_insert;
+    to_insert.reserve(records_num);
+    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+        to_insert.emplace_back(i, "Val" + std::to_string(i));
+
+    kv_view.put_all(nullptr, to_insert);
+
+    std::vector<test_key_type> to_remove;
+    for (std::int64_t i = 9; i < 13; ++i)
+        to_remove.emplace_back(i);
+
+    auto res = kv_view.remove_all(nullptr, to_remove);
+
+    EXPECT_EQ(res.size(), 2);
+
+    // TODO: Key order should be preserved by the server (IGNITE-16004).
+    EXPECT_EQ(12, res[0].key);
+    EXPECT_EQ(11, res[1].key);
+}
+
+TEST_F(key_value_view_test, remove_all_empty) {
+    auto res = kv_view.remove_all(nullptr, std::vector<test_key_type>{});
+    EXPECT_TRUE(res.empty());
+}
+
+TEST_F(key_value_view_test, remove_all_exact_nonexisting) {
+    auto res = kv_view.remove_all(nullptr, {
+        {test_key_type(1), test_value_type("foo")}, {test_key_type(2), 
test_value_type("bar")}});
+
+    // TODO: Key order should be preserved by the server (IGNITE-16004).
+    ASSERT_EQ(2, res.size());
+}
+
+TEST_F(key_value_view_test, remove_all_exact_overlapped) {
+    kv_view.put_all(nullptr, {{test_key_type(1), test_value_type("foo")}, 
{test_key_type(2), test_value_type("bar")}});
+
+    auto res = kv_view.remove_all(nullptr, {
+        {test_key_type(1), test_value_type("baz")}, {test_key_type(2), 
test_value_type("bar")}});
+
+    EXPECT_EQ(res.size(), 1);
+    EXPECT_EQ(1, res.front().key);
+
+    auto tuple2 = kv_view.get(nullptr, test_key_type(2));
+
+    ASSERT_FALSE(tuple2.has_value());
+}
+
+TEST_F(key_value_view_test, remove_all_exact_overlapped_async) {
+    auto all_done = 
std::make_shared<std::promise<std::vector<test_key_type>>>();
+
+    kv_view.put_all_async(nullptr, {{test_key_type(1), 
test_value_type("foo")}, {test_key_type(2), test_value_type("bar")}}, [&](auto 
res) {
+        if (!check_and_set_operation_error(*all_done, res))
+            return;
+
+        kv_view.remove_all_async(nullptr, {{test_key_type(1), 
test_value_type("baz")}, {test_key_type(2), test_value_type("bar")}},
+            [&](auto res) { result_set_promise(*all_done, std::move(res)); });
+    });
+
+    auto res = all_done->get_future().get();
+    EXPECT_EQ(res.size(), 1);
+    EXPECT_EQ(1, res.front().key);
+}
+
+TEST_F(key_value_view_test, remove_all_exact_empty) {
+    auto res = kv_view.remove_all(nullptr, 
std::vector<std::pair<test_key_type, test_value_type>>{});
+    EXPECT_TRUE(res.empty());
+}
+
+TEST_F(key_value_view_test, types_test) {
+    auto table = m_client.get_tables().get_table(TABLE_NAME_ALL_COLUMNS);
+    auto kv_view = table->get_key_value_view<test_key_type, all_fields_type>();
+
+    all_fields_type inserted;
+    inserted.m_key = 42;
+    inserted.m_str = "test";
+    inserted.m_int8 = 1;
+    inserted.m_int16 = 2;
+    inserted.m_int32 = 3;
+    inserted.m_int64 = 4;
+    inserted.m_float = .5f;
+    inserted.m_double = .6f;
+    inserted.m_uuid = {0x123e4567e89b12d3, 0x7456426614174000};
+    inserted.m_date = {2023, 2, 7};
+    inserted.m_bitmask = bit_array{16, true};
+    inserted.m_time = {17, 4, 12, 3543634};
+    inserted.m_time2 = {17, 4, 12, 3543634};
+    inserted.m_datetime = {{2020, 7, 28}, {2, 15, 52, 6349879}};
+    inserted.m_datetime2 = {{2020, 7, 28}, {2, 15, 52, 6349879}};
+    inserted.m_timestamp = {3875238472, 248760634};
+    inserted.m_timestamp2 = {3875238472, 248760634};
+    inserted.m_blob = {std::byte(1), std::byte(2), std::byte(42)};
+    inserted.m_decimal = big_decimal{123456789098765};
+
+    kv_view.put(nullptr, test_key_type(42), inserted);
+    auto res = kv_view.get(nullptr, test_key_type(42));
+
+    ASSERT_TRUE(res.has_value());
+
+    EXPECT_EQ(inserted.m_key, res->m_key);
+    EXPECT_EQ(inserted.m_str, res->m_str);
+    EXPECT_EQ(inserted.m_int8, res->m_int8);
+    EXPECT_EQ(inserted.m_int16, res->m_int16);
+    EXPECT_EQ(inserted.m_int32, res->m_int32);
+    EXPECT_EQ(inserted.m_int64, res->m_int64);
+    EXPECT_EQ(inserted.m_float, res->m_float);
+    EXPECT_EQ(inserted.m_double, res->m_double);
+    EXPECT_EQ(inserted.m_uuid, res->m_uuid);
+    EXPECT_EQ(inserted.m_date, res->m_date);
+    EXPECT_EQ(inserted.m_bitmask, res->m_bitmask);
+    EXPECT_EQ(inserted.m_time, res->m_time);
+    EXPECT_EQ(inserted.m_datetime, res->m_datetime);
+    EXPECT_EQ(inserted.m_timestamp, res->m_timestamp);
+    EXPECT_EQ(inserted.m_blob, res->m_blob);
+    EXPECT_EQ(inserted.m_decimal, res->m_decimal);
+
+    EXPECT_EQ(ignite_time(17, 4, 12), res->m_time2);
+    EXPECT_EQ(ignite_date_time({2020, 7, 28}, {2, 15, 52, 6000000}), 
res->m_datetime2);
+    EXPECT_EQ(ignite_timestamp(3875238472, 248700000), res->m_timestamp2);
+}
diff --git a/modules/platforms/cpp/tests/client-test/record_view_test.cpp 
b/modules/platforms/cpp/tests/client-test/record_view_test.cpp
index a8f882eb5c..91b7c24624 100644
--- a/modules/platforms/cpp/tests/client-test/record_view_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/record_view_test.cpp
@@ -16,6 +16,8 @@
  */
 
 #include "ignite_runner_suite.h"
+#include "all_fields_type.h"
+
 #include "tests/test-common/test_utils.h"
 
 #include "ignite/client/ignite_client.h"
@@ -940,95 +942,6 @@ TEST_F(record_view_test, remove_all_exact_empty) {
     EXPECT_TRUE(res.empty());
 }
 
-/**
- * All columns type table mapping (@see 
ignite_runner_suite::TABLE_NAME_ALL_COLUMNS).
- */
-struct all_fields_type {
-    all_fields_type() = default;
-    explicit all_fields_type(std::int64_t key) : m_key(key) {}
-
-    std::int64_t m_key{0};
-    std::string m_str;
-    std::int8_t m_int8{0};
-    std::int16_t m_int16{0};
-    std::int32_t m_int32{0};
-    std::int64_t m_int64{0};
-    float m_float{.0f};
-    double m_double{.0};
-    uuid m_uuid;
-    ignite_date m_date;
-    bit_array m_bitmask;
-    ignite_time m_time;
-    ignite_time m_time2;
-    ignite_date_time m_datetime;
-    ignite_date_time m_datetime2;
-    ignite_timestamp m_timestamp;
-    ignite_timestamp m_timestamp2;
-    std::vector<std::byte> m_blob;
-    big_decimal m_decimal;
-};
-
-namespace ignite {
-
-template<>
-ignite_tuple convert_to_tuple(all_fields_type &&value) {
-    ignite_tuple tuple;
-
-    tuple.set("key", value.m_key);
-    tuple.set("str", value.m_str);
-    tuple.set("int8", value.m_int8);
-    tuple.set("int16", value.m_int16);
-    tuple.set("int32", value.m_int32);
-    tuple.set("int64", value.m_int64);
-    tuple.set("float", value.m_float);
-    tuple.set("double", value.m_double);
-    tuple.set("uuid", value.m_uuid);
-    tuple.set("date", value.m_date);
-    tuple.set("bitmask", value.m_bitmask);
-    tuple.set("time", value.m_time);
-    tuple.set("time2", value.m_time2);
-    tuple.set("datetime", value.m_datetime);
-    tuple.set("datetime2", value.m_datetime2);
-    tuple.set("timestamp", value.m_timestamp);
-    tuple.set("timestamp2", value.m_timestamp2);
-    tuple.set("blob", value.m_blob);
-    tuple.set("decimal", value.m_decimal);
-
-    return tuple;
-}
-
-template<>
-all_fields_type convert_from_tuple(ignite_tuple&& value) {
-    all_fields_type res;
-
-    res.m_key = value.get<std::int64_t>("key");
-
-    if (value.column_count() > 1) {
-        res.m_str = value.get<std::string>("str");
-        res.m_int8 = value.get<std::int8_t>("int8");
-        res.m_int16 = value.get<std::int16_t>("int16");
-        res.m_int32 = value.get<std::int32_t>("int32");
-        res.m_int64 = value.get<std::int64_t>("int64");
-        res.m_float = value.get<float>("float");
-        res.m_double = value.get<double>("double");
-        res.m_uuid = value.get<uuid>("uuid");
-        res.m_date = value.get<ignite_date>("date");
-        res.m_bitmask = value.get<bit_array>("bitmask");
-        res.m_time = value.get<ignite_time>("time");
-        res.m_time2 = value.get<ignite_time>("time2");
-        res.m_datetime = value.get<ignite_date_time>("datetime");
-        res.m_datetime2 = value.get<ignite_date_time>("datetime2");
-        res.m_timestamp = value.get<ignite_timestamp>("timestamp");
-        res.m_timestamp2 = value.get<ignite_timestamp>("timestamp2");
-        res.m_blob = value.get<std::vector<std::byte>>("blob");
-        res.m_decimal = value.get<big_decimal>("decimal");
-    }
-
-    return res;
-}
-
-}
-
 TEST_F(record_view_test, types_test) {
     auto table = m_client.get_tables().get_table(TABLE_NAME_ALL_COLUMNS);
     auto view2 = table->get_record_view<all_fields_type>();

Reply via email to