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

tkalkirill 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 e167c685ab IGNITE-19693 getAll does not preserve order and does not 
return nulls for missing keys (#2233)
e167c685ab is described below

commit e167c685ab0088e78f628bc589ea6d1881971427
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Mon Jun 26 10:32:04 2023 +0300

    IGNITE-19693 getAll does not preserve order and does not return nulls for 
missing keys (#2233)
---
 .../java/org/apache/ignite/table/RecordView.java   | 11 ++--
 .../internal/client/table/ClientKeyValueView.java  | 11 ++--
 .../client/table/ClientRecordBinaryView.java       | 13 ++---
 .../client/table/ClientRecordSerializer.java       |  3 +-
 .../internal/client/table/ClientRecordView.java    | 13 ++---
 .../client/table/ClientTupleSerializer.java        |  3 +-
 .../apache/ignite/client/ClientRecordViewTest.java | 12 +++--
 .../ignite/client/fakes/FakeInternalTable.java     | 10 ++--
 .../ignite/client/fakes/FakeSchemaRegistry.java    | 10 ++--
 .../client-test/key_value_binary_view_test.cpp     | 61 ++++++++++++----------
 .../cpp/tests/client-test/key_value_view_test.cpp  | 47 +++++++++++------
 .../tests/client-test/record_binary_view_test.cpp  | 61 ++++++++++++----------
 .../cpp/tests/client-test/record_view_test.cpp     | 53 +++++++++++--------
 .../cpp/tests/client-test/transactions_test.cpp    |  6 ++-
 .../Table/RecordViewBinaryTests.cs                 | 13 +++--
 .../Table/RecordViewPocoTests.cs                   | 13 +++--
 .../Transactions/TransactionsTests.cs              |  2 +-
 .../runner/app/ItTableApiContractTest.java         | 13 +++--
 .../app/client/ItThinClientTransactionsTest.java   |  4 +-
 .../ignite/internal/schema/SchemaRegistry.java     |  5 +-
 .../schema/registry/SchemaRegistryImpl.java        | 13 ++---
 modules/table/build.gradle                         |  1 +
 .../ignite/internal/table/InternalTable.java       | 14 +++--
 .../internal/table/KeyValueBinaryViewImpl.java     |  4 +-
 .../ignite/internal/table/KeyValueViewImpl.java    |  4 +-
 .../internal/table/RecordBinaryViewImpl.java       | 22 ++++----
 .../ignite/internal/table/RecordViewImpl.java      | 25 +++++----
 .../distributed/storage/InternalTableImpl.java     |  6 +--
 .../table/RecordBinaryViewOperationsTest.java      | 24 +++------
 .../internal/table/RecordViewOperationsTest.java   |  6 +--
 .../ignite/internal/table/TxAbstractTest.java      | 24 ++++-----
 .../table/impl/DummySchemaManagerImpl.java         | 10 ++--
 32 files changed, 292 insertions(+), 225 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/table/RecordView.java 
b/modules/api/src/main/java/org/apache/ignite/table/RecordView.java
index 4d756edf0d..0906b5fb5c 100644
--- a/modules/api/src/main/java/org/apache/ignite/table/RecordView.java
+++ b/modules/api/src/main/java/org/apache/ignite/table/RecordView.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.table;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.tx.Transaction;
 import org.jetbrains.annotations.NotNull;
@@ -55,18 +56,20 @@ public interface RecordView<R> extends 
DataStreamerTarget<R> {
      * @param keyRecs Records with key columns set. The records cannot be 
{@code null}.
      * @return Records with all columns filled from the table. The order of 
collection elements is
      *     guaranteed to be the same as the order of {@code keyRecs}. If a 
record does not exist, the
-     *     element at the corresponding index of the resulting collection is 
null.
+     *     element at the corresponding index of the resulting collection is 
{@code null}.
      */
-    Collection<R> getAll(@Nullable Transaction tx, @NotNull Collection<R> 
keyRecs);
+    List<R> getAll(@Nullable Transaction tx, Collection<R> keyRecs);
 
     /**
      * Asynchronously gets records from a table.
      *
      * @param tx      Transaction or {@code null} to auto-commit.
      * @param keyRecs Records with the key columns set. The records cannot be 
{@code null}.
-     * @return Future that represents the pending completion of the operation.
+     * @return Future that will return records with all columns filled from 
the table. The order of collection elements is
+     *      guaranteed to be the same as the order of {@code keyRecs}. If a 
record does not exist, the
+     *      element at the corresponding index of the resulting collection is 
{@code null}.
      */
-    @NotNull CompletableFuture<Collection<R>> getAllAsync(@Nullable 
Transaction tx, @NotNull Collection<R> keyRecs);
+    CompletableFuture<List<R>> getAllAsync(@Nullable Transaction tx, 
Collection<R> keyRecs);
 
     /**
      * Inserts a record into a table, if it does not exist, or replaces an 
existing one.
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
index 408a71cfb4..09b3a764ec 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
@@ -464,11 +464,12 @@ public class ClientKeyValueView<K, V> implements 
KeyValueView<K, V> {
 
         try {
             for (int i = 0; i < cnt; i++) {
-                in.unpackBoolean(); // TODO: Optimize (IGNITE-16022).
-
-                var tupleReader = new 
BinaryTupleReader(schema.columns().length, in.readBinaryUnsafe());
-                var reader = new ClientMarshallerReader(tupleReader);
-                res.put((K) keyMarsh.readObject(reader, null), (V) 
valMarsh.readObject(reader, null));
+                // TODO: Optimize (IGNITE-16022).
+                if (in.unpackBoolean()) {
+                    var tupleReader = new 
BinaryTupleReader(schema.columns().length, in.readBinaryUnsafe());
+                    var reader = new ClientMarshallerReader(tupleReader);
+                    res.put((K) keyMarsh.readObject(reader, null), (V) 
valMarsh.readObject(reader, null));
+                }
             }
 
             return res;
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordBinaryView.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordBinaryView.java
index 40b4294f38..f48a2c2a18 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordBinaryView.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordBinaryView.java
@@ -17,10 +17,12 @@
 
 package org.apache.ignite.internal.client.table;
 
+import static java.util.concurrent.CompletableFuture.completedFuture;
 import static org.apache.ignite.internal.client.ClientUtils.sync;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow.Publisher;
@@ -76,19 +78,17 @@ public class ClientRecordBinaryView implements 
RecordView<Tuple> {
                 ClientTupleSerializer.getPartitionAwarenessProvider(tx, 
keyRec));
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<Tuple> getAll(@Nullable Transaction tx, @NotNull 
Collection<Tuple> keyRecs) {
+    public List<Tuple> getAll(@Nullable Transaction tx, Collection<Tuple> 
keyRecs) {
         return sync(getAllAsync(tx, keyRecs));
     }
 
-    /** {@inheritDoc} */
     @Override
-    public @NotNull CompletableFuture<Collection<Tuple>> getAllAsync(@Nullable 
Transaction tx, @NotNull Collection<Tuple> keyRecs) {
+    public CompletableFuture<List<Tuple>> getAllAsync(@Nullable Transaction 
tx, Collection<Tuple> keyRecs) {
         Objects.requireNonNull(keyRecs);
 
         if (keyRecs.isEmpty()) {
-            return CompletableFuture.completedFuture(Collections.emptyList());
+            return completedFuture(Collections.emptyList());
         }
 
         return tbl.doSchemaOutInOpAsync(
@@ -96,7 +96,8 @@ public class ClientRecordBinaryView implements 
RecordView<Tuple> {
                 (s, w) -> ser.writeTuples(tx, keyRecs, s, w, true),
                 ClientTupleSerializer::readTuplesNullable,
                 Collections.emptyList(),
-                ClientTupleSerializer.getPartitionAwarenessProvider(tx, 
keyRecs.iterator().next()));
+                ClientTupleSerializer.getPartitionAwarenessProvider(tx, 
keyRecs.iterator().next())
+        );
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordSerializer.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordSerializer.java
index ba0901f571..975606762b 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordSerializer.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordSerializer.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
 import org.apache.ignite.internal.binarytuple.BinaryTupleReader;
 import org.apache.ignite.internal.client.PayloadOutputChannel;
@@ -166,7 +167,7 @@ public class ClientRecordSerializer<R> {
         }
     }
 
-    Collection<R> readRecs(ClientSchema schema, ClientMessageUnpacker in, 
boolean nullable, TuplePart part) {
+    List<R> readRecs(ClientSchema schema, ClientMessageUnpacker in, boolean 
nullable, TuplePart part) {
         var cnt = in.unpackInt();
 
         if (cnt == 0) {
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordView.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordView.java
index 42f9358e65..35d6a68afa 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordView.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientRecordView.java
@@ -17,10 +17,12 @@
 
 package org.apache.ignite.internal.client.table;
 
+import static java.util.concurrent.CompletableFuture.completedFuture;
 import static org.apache.ignite.internal.client.ClientUtils.sync;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow.Publisher;
@@ -76,19 +78,17 @@ public class ClientRecordView<R> implements RecordView<R> {
                 ClientTupleSerializer.getPartitionAwarenessProvider(tx, 
ser.mapper(), keyRec));
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<R> getAll(@Nullable Transaction tx, @NotNull 
Collection<R> keyRecs) {
+    public List<R> getAll(@Nullable Transaction tx, Collection<R> keyRecs) {
         return sync(getAllAsync(tx, keyRecs));
     }
 
-    /** {@inheritDoc} */
     @Override
-    public @NotNull CompletableFuture<Collection<R>> getAllAsync(@Nullable 
Transaction tx, @NotNull Collection<R> keyRecs) {
+    public CompletableFuture<List<R>> getAllAsync(@Nullable Transaction tx, 
Collection<R> keyRecs) {
         Objects.requireNonNull(keyRecs);
 
         if (keyRecs.isEmpty()) {
-            return CompletableFuture.completedFuture(Collections.emptyList());
+            return completedFuture(Collections.emptyList());
         }
 
         return tbl.doSchemaOutInOpAsync(
@@ -96,7 +96,8 @@ public class ClientRecordView<R> implements RecordView<R> {
                 (s, w) -> ser.writeRecs(tx, keyRecs, s, w, TuplePart.KEY),
                 (s, r) -> ser.readRecs(s, r, true, TuplePart.KEY_AND_VAL),
                 Collections.emptyList(),
-                ClientTupleSerializer.getPartitionAwarenessProvider(tx, 
ser.mapper(), keyRecs.iterator().next()));
+                ClientTupleSerializer.getPartitionAwarenessProvider(tx, 
ser.mapper(), keyRecs.iterator().next())
+        );
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
index e6e9cc1738..c0ef66a94f 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleSerializer.java
@@ -31,6 +31,7 @@ import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
@@ -299,7 +300,7 @@ public class ClientTupleSerializer {
         return res;
     }
 
-    static Collection<Tuple> readTuplesNullable(ClientSchema schema, 
ClientMessageUnpacker in) {
+    static List<Tuple> readTuplesNullable(ClientSchema schema, 
ClientMessageUnpacker in) {
         var cnt = in.unpackInt();
         var res = new ArrayList<Tuple>(cnt);
 
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
 
b/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
index d98a924f6c..a6e52f354c 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/ClientRecordViewTest.java
@@ -251,16 +251,17 @@ public class ClientRecordViewTest extends 
AbstractClientTableTest {
 
         PersonPojo[] res = pojoView.getAll(null, keys).toArray(new 
PersonPojo[0]);
 
-        assertEquals(2, res.length);
+        assertEquals(3, res.length);
 
         assertNotNull(res[0]);
-        assertNotNull(res[1]);
+        assertNull(res[1]);
+        assertNotNull(res[2]);
 
         assertEquals(DEFAULT_ID, res[0].id);
         assertEquals(DEFAULT_NAME, res[0].name);
 
-        assertEquals(100L, res[1].id);
-        assertEquals("100", res[1].name);
+        assertEquals(100L, res[2].id);
+        assertEquals("100", res[2].name);
     }
 
     @Test
@@ -273,7 +274,8 @@ public class ClientRecordViewTest extends 
AbstractClientTableTest {
         String[] res = primitiveView.getAll(null, List.of("a", "b", 
"c")).toArray(new String[0]);
 
         assertEquals("a", res[0]);
-        assertEquals("c", res[1]);
+        assertNull(res[1]);
+        assertEquals("c", res[2]);
     }
 
     @Test
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
index a8e27e60e4..614e498676 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.client.fakes;
 
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.BitSet;
@@ -123,7 +125,7 @@ public class FakeInternalTable implements InternalTable {
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Collection<BinaryRow>> 
getAll(Collection<BinaryRowEx> keyRows,
+    public CompletableFuture<List<BinaryRow>> getAll(Collection<BinaryRowEx> 
keyRows,
             @Nullable InternalTransaction tx) {
         var res = new ArrayList<BinaryRow>();
 
@@ -132,15 +134,17 @@ public class FakeInternalTable implements InternalTable {
 
             if (val != null) {
                 res.add(val.getNow(null));
+            } else {
+                res.add(null);
             }
         }
 
         onDataAccess("getAll", keyRows);
-        return CompletableFuture.completedFuture(res);
+        return completedFuture(res);
     }
 
     @Override
-    public CompletableFuture<Collection<BinaryRow>> getAll(
+    public CompletableFuture<List<BinaryRow>> getAll(
             Collection<BinaryRowEx> keyRows,
             HybridTimestamp readTimestamp,
             ClusterNode recipientNode
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSchemaRegistry.java
 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSchemaRegistry.java
index 899bd0a32d..21b8e74a26 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSchemaRegistry.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSchemaRegistry.java
@@ -17,12 +17,13 @@
 
 package org.apache.ignite.client.fakes;
 
+import static java.util.stream.Collectors.toList;
+
 import java.util.Collection;
-import java.util.Objects;
+import java.util.List;
 import java.util.concurrent.ConcurrentNavigableMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
 import org.apache.ignite.internal.schema.SchemaRegistry;
@@ -127,9 +128,8 @@ public class FakeSchemaRegistry implements SchemaRegistry {
         return new Row(schema(row.schemaVersion()), row);
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<Row> resolve(Collection<BinaryRow> rows) {
-        return 
rows.stream().filter(Objects::nonNull).map(this::resolve).collect(Collectors.toList());
+    public List<Row> resolve(Collection<BinaryRow> rows) {
+        return rows.stream().map(binaryRow -> binaryRow == null ? null : 
resolve(binaryRow)).collect(toList());
     }
 }
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 e6ef6d7646..30930c8066 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
@@ -160,7 +160,10 @@ TEST_F(key_value_binary_view_test, get_all_empty) {
 TEST_F(key_value_binary_view_test, get_all_nonexisting) {
     auto res = kv_view.get_all(nullptr, {get_tuple(-42)});
 
-    ASSERT_TRUE(res.empty());
+    ASSERT_FALSE(res.empty());
+
+    EXPECT_EQ(res.size(), 1);
+    EXPECT_EQ(res.front(), std::nullopt);
 }
 
 TEST_F(key_value_binary_view_test, put_all_empty_no_throw) {
@@ -168,11 +171,11 @@ TEST_F(key_value_binary_view_test, 
put_all_empty_no_throw) {
 }
 
 TEST_F(key_value_binary_view_test, put_all_get_all) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_t records_num = 10;
 
     std::vector<std::pair<ignite_tuple, ignite_tuple>> records;
     records.reserve(records_num);
-    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(get_tuple(i), get_tuple("Val" + 
std::to_string(i)));
 
     std::vector<ignite_tuple> keys;
@@ -182,26 +185,28 @@ TEST_F(key_value_binary_view_test, put_all_get_all) {
     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(2, res[0]->column_count());
-    EXPECT_EQ(9, res[0]->get<int64_t>("key"));
-    EXPECT_EQ("Val9", res[0]->get<std::string>("val"));
+    ASSERT_EQ(res.size(), keys.size());
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i].get<std::int64_t>(0);
+        auto val = res[i];
 
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ(2, res[1]->column_count());
-    EXPECT_EQ(10, res[1]->get<int64_t>("key"));
-    EXPECT_EQ("Val10", res[1]->get<std::string>("val"));
+        if (key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key;
+            EXPECT_EQ(2, val->column_count());
+            EXPECT_EQ(key, val->get<std::int64_t>("key"));
+            EXPECT_EQ("Val" + std::to_string(key), 
val->get<std::string>("val"));
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key << ", Res = " << 
val->get<std::string>("val");
+        }
+    }
 }
 
 TEST_F(key_value_binary_view_test, put_all_get_all_async) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_t records_num = 10;
 
     std::vector<std::pair<ignite_tuple, ignite_tuple>> records;
     records.reserve(records_num);
-    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(get_tuple(i), get_tuple("Val" + 
std::to_string(i)));
 
     std::vector<ignite_tuple> keys;
@@ -214,23 +219,25 @@ TEST_F(key_value_binary_view_test, put_all_get_all_async) 
{
         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(2, res[0]->column_count());
-    EXPECT_EQ(9, res[0]->get<int64_t>("key"));
-    EXPECT_EQ("Val9", res[0]->get<std::string>("val"));
+    ASSERT_EQ(res.size(), keys.size());
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i].get<std::int64_t>(0);
+        auto val = res[i];
 
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ(2, res[1]->column_count());
-    EXPECT_EQ(10, res[1]->get<int64_t>("key"));
-    EXPECT_EQ("Val10", res[1]->get<std::string>("val"));
+        if (key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key;
+            EXPECT_EQ(2, val->column_count());
+            EXPECT_EQ(key, val->get<std::int64_t>("key"));
+            EXPECT_EQ("Val" + std::to_string(key), 
val->get<std::string>("val"));
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key << ", Res = " << 
val->get<std::string>("val");
+        }
+    }
 }
 
 TEST_F(key_value_binary_view_test, get_and_put_new_record) {
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
index 6aa892d873..f3a7dedc5a 100644
--- a/modules/platforms/cpp/tests/client-test/key_value_view_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/key_value_view_test.cpp
@@ -176,7 +176,10 @@ TEST_F(key_value_view_test, get_all_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());
+    ASSERT_FALSE(res.empty());
+
+    EXPECT_EQ(res.size(), 1);
+    EXPECT_EQ(res.front(), std::nullopt);
 }
 
 TEST_F(key_value_view_test, put_all_empty_no_throw) {
@@ -184,11 +187,11 @@ TEST_F(key_value_view_test, put_all_empty_no_throw) {
 }
 
 TEST_F(key_value_view_test, put_all_get_all) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_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)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(i, "Val" + std::to_string(i));
 
     std::vector<test_key_type> keys;
@@ -198,22 +201,27 @@ TEST_F(key_value_view_test, put_all_get_all) {
     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_EQ(res.size(), keys.size());
 
-    ASSERT_TRUE(res[0].has_value());
-    EXPECT_EQ("Val9", res[0]->val);
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i];
+        auto val = res[i];
 
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ("Val10", res[1]->val);
+        if (key.key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key.key;
+            EXPECT_EQ("Val" + std::to_string(key.key), val->val);
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key.key << ", Res = " 
<< val->val;
+        }
+    }
 }
 
 TEST_F(key_value_view_test, put_all_get_all_async) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_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)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(i, "Val" + std::to_string(i));
 
     std::vector<test_key_type> keys;
@@ -226,19 +234,24 @@ TEST_F(key_value_view_test, put_all_get_all_async) {
         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_EQ(res.size(), keys.size());
 
-    ASSERT_TRUE(res[0].has_value());
-    EXPECT_EQ("Val9", res[0]->val);
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i];
+        auto val = res[i];
 
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ("Val10", res[1]->val);
+        if (key.key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key.key;
+            EXPECT_EQ("Val" + std::to_string(key.key), val->val);
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key.key << ", Res = " 
<< val->val;
+        }
+    }
 }
 
 TEST_F(key_value_view_test, get_and_put_new_record) {
diff --git 
a/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp 
b/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp
index fd95e93e61..b2376df3c1 100644
--- a/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp
@@ -147,7 +147,10 @@ TEST_F(record_binary_view_test, get_all_empty) {
 TEST_F(record_binary_view_test, get_all_nonexisting) {
     auto res = tuple_view.get_all(nullptr, {get_tuple(-42)});
 
-    ASSERT_TRUE(res.empty());
+    ASSERT_FALSE(res.empty());
+
+    EXPECT_EQ(res.size(), 1);
+    EXPECT_EQ(res.front(), std::nullopt);
 }
 
 TEST_F(record_binary_view_test, upsert_all_empty_no_throw) {
@@ -155,11 +158,11 @@ TEST_F(record_binary_view_test, 
upsert_all_empty_no_throw) {
 }
 
 TEST_F(record_binary_view_test, upsert_all_get_all) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_t records_num = 10;
 
     std::vector<ignite_tuple> records;
     records.reserve(records_num);
-    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(get_tuple(i, "Val" + std::to_string(i)));
 
     std::vector<ignite_tuple> keys;
@@ -169,26 +172,28 @@ TEST_F(record_binary_view_test, upsert_all_get_all) {
     tuple_view.upsert_all(nullptr, records);
     auto res = tuple_view.get_all(nullptr, keys);
 
-    // TODO: Key order should be preserved by the server (IGNITE-16004).
-    EXPECT_EQ(res.size(), 2);
+    ASSERT_EQ(res.size(), keys.size());
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i].get<std::int64_t>(0);
+        auto val = res[i];
 
-    ASSERT_TRUE(res[0].has_value());
-    EXPECT_EQ(2, res[0]->column_count());
-    EXPECT_EQ(9, res[0]->get<int64_t>("key"));
-    EXPECT_EQ("Val9", res[0]->get<std::string>("val"));
-
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ(2, res[1]->column_count());
-    EXPECT_EQ(10, res[1]->get<int64_t>("key"));
-    EXPECT_EQ("Val10", res[1]->get<std::string>("val"));
+        if (key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key;
+            EXPECT_EQ(2, val->column_count());
+            EXPECT_EQ(key, val->get<std::int64_t>("key"));
+            EXPECT_EQ("Val" + std::to_string(key), 
val->get<std::string>("val"));
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key << ", Res = " << 
val->get<std::string>("val");
+        }
+    }
 }
 
 TEST_F(record_binary_view_test, upsert_all_get_all_async) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_t records_num = 10;
 
     std::vector<ignite_tuple> records;
     records.reserve(records_num);
-    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(get_tuple(i, "Val" + std::to_string(i)));
 
     std::vector<ignite_tuple> keys;
@@ -201,23 +206,25 @@ TEST_F(record_binary_view_test, upsert_all_get_all_async) 
{
         if (!check_and_set_operation_error(*all_done, res))
             return;
 
-        // TODO: Key order should be preserved by the server (IGNITE-16004).
         tuple_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_EQ(res.size(), keys.size());
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i].get<std::int64_t>(0);
+        auto val = res[i];
 
-    ASSERT_TRUE(res[0].has_value());
-    EXPECT_EQ(2, res[0]->column_count());
-    EXPECT_EQ(9, res[0]->get<int64_t>("key"));
-    EXPECT_EQ("Val9", res[0]->get<std::string>("val"));
-
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ(2, res[1]->column_count());
-    EXPECT_EQ(10, res[1]->get<int64_t>("key"));
-    EXPECT_EQ("Val10", res[1]->get<std::string>("val"));
+        if (key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key;
+            EXPECT_EQ(2, val->column_count());
+            EXPECT_EQ(key, val->get<std::int64_t>("key"));
+            EXPECT_EQ("Val" + std::to_string(key), 
val->get<std::string>("val"));
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key << ", Res = " << 
val->get<std::string>("val");
+        }
+    }
 }
 
 TEST_F(record_binary_view_test, get_and_upsert_new_record) {
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 91b7c24624..0fdcc5ab17 100644
--- a/modules/platforms/cpp/tests/client-test/record_view_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/record_view_test.cpp
@@ -286,7 +286,10 @@ TEST_F(record_view_test, get_all_empty) {
 TEST_F(record_view_test, get_all_nonexisting) {
     auto res = view.get_all(nullptr, {test_type(-42)});
 
-    ASSERT_TRUE(res.empty());
+    ASSERT_FALSE(res.empty());
+
+    EXPECT_EQ(res.size(), 1);
+    EXPECT_EQ(res.front(), std::nullopt);
 }
 
 TEST_F(record_view_test, upsert_all_empty_no_throw) {
@@ -294,11 +297,11 @@ TEST_F(record_view_test, upsert_all_empty_no_throw) {
 }
 
 TEST_F(record_view_test, upsert_all_get_all) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_t records_num = 10;
 
     std::vector<test_type> records;
     records.reserve(records_num);
-    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(i, "Val" + std::to_string(i));
 
     std::vector<test_type> keys;
@@ -308,24 +311,28 @@ TEST_F(record_view_test, upsert_all_get_all) {
     view.upsert_all(nullptr, records);
     auto res = view.get_all(nullptr, keys);
 
-    // TODO: Key order should be preserved by the server (IGNITE-16004).
-    EXPECT_EQ(res.size(), 2);
+    ASSERT_EQ(res.size(), keys.size());
 
-    ASSERT_TRUE(res[0].has_value());
-    EXPECT_EQ(9, res[0]->key);
-    EXPECT_EQ("Val9", res[0]->val);
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i];
+        auto val = res[i];
 
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ(10, res[1]->key);
-    EXPECT_EQ("Val10", res[1]->val);
+        if (key.key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key.key;
+            EXPECT_EQ(key.key, val->key);
+            EXPECT_EQ("Val" + std::to_string(key.key), val->val);
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key.key << ", Res = " 
<< val->val;
+        }
+    }
 }
 
 TEST_F(record_view_test, upsert_all_get_all_async) {
-    static constexpr std::size_t records_num = 10;
+    static constexpr std::int64_t records_num = 10;
 
     std::vector<test_type> records;
     records.reserve(records_num);
-    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
+    for (std::int64_t i = 1; i < 1 + records_num; ++i)
         records.emplace_back(i, "Val" + std::to_string(i));
 
     std::vector<test_type> keys;
@@ -338,21 +345,25 @@ TEST_F(record_view_test, upsert_all_get_all_async) {
         if (!check_and_set_operation_error(*all_done, res))
             return;
 
-        // TODO: Key order should be preserved by the server (IGNITE-16004).
         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_EQ(res.size(), keys.size());
 
-    ASSERT_TRUE(res[0].has_value());
-    EXPECT_EQ(9, res[0]->key);
-    EXPECT_EQ("Val9", res[0]->val);
+    for (std::size_t i = 0; i < keys.size(); ++i) {
+        auto key = keys[i];
+        auto val = res[i];
 
-    ASSERT_TRUE(res[1].has_value());
-    EXPECT_EQ(10, res[1]->key);
-    EXPECT_EQ("Val10", res[1]->val);
+        if (key.key <= records_num) {
+            ASSERT_TRUE(val.has_value()) << "Key = " << key.key;
+            EXPECT_EQ(key.key, val->key);
+            EXPECT_EQ("Val" + std::to_string(key.key), val->val);
+        } else {
+            ASSERT_FALSE(val.has_value()) << "Key = " << key.key << ", Res = " 
<< val->val;
+        }
+    }
 }
 
 TEST_F(record_view_test, get_and_upsert_new_record) {
diff --git a/modules/platforms/cpp/tests/client-test/transactions_test.cpp 
b/modules/platforms/cpp/tests/client-test/transactions_test.cpp
index 9b70c4b012..7c7243a40e 100644
--- a/modules/platforms/cpp/tests/client-test/transactions_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/transactions_test.cpp
@@ -211,7 +211,8 @@ TEST_F(transactions_test, record_view_upsert_all) {
 
     auto values2 = record_view.get_all(nullptr, {get_tuple(42)});
 
-    ASSERT_TRUE(values2.empty());
+    ASSERT_EQ(1, values2.size());
+    EXPECT_FALSE(values2.front().has_value());
 }
 
 TEST_F(transactions_test, record_view_get_and_upsert) {
@@ -279,7 +280,8 @@ TEST_F(transactions_test, record_view_insert_all) {
 
     auto values2 = record_view.get_all(nullptr, {get_tuple(42)});
 
-    ASSERT_TRUE(values2.empty());
+    ASSERT_EQ(1, values2.size());
+    EXPECT_FALSE(values2.front().has_value());
 }
 
 TEST_F(transactions_test, record_view_replace) {
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
index 59d972b141..1c82e273d7 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
@@ -360,25 +360,28 @@ namespace Apache.Ignite.Tests.Table
 
             await TupleView.UpsertAllAsync(null, records);
 
-            // TODO: Key order should be preserved by the server 
(IGNITE-16004).
             var res = await TupleView.GetAllAsync(null, Enumerable.Range(9, 
4).Select(x => GetTuple(x)));
-            var resArr = res.OrderBy(x => x.Value[0]).ToArray();
+            var resArr = res.ToArray();
 
-            Assert.AreEqual(2, res.Count);
+            Assert.AreEqual(4, res.Count);
 
             Assert.AreEqual(9, resArr[0].Value[0]);
             Assert.AreEqual("9", resArr[0].Value[1]);
 
             Assert.AreEqual(10, resArr[1].Value[0]);
             Assert.AreEqual("10", resArr[1].Value[1]);
+
+            Assert.IsFalse(resArr[2].HasValue);
+            Assert.IsFalse(resArr[3].HasValue);
         }
 
         [Test]
-        public async Task TestGetAllNonExistentKeysReturnsEmptyList()
+        public async Task TestGetAllNonExistentKeysReturnsListWithNoValue()
         {
             var res = await TupleView.GetAllAsync(null, new[] { GetTuple(-100) 
});
 
-            Assert.AreEqual(0, res.Count);
+            Assert.AreEqual(1, res.Count);
+            Assert.IsFalse(res[0].HasValue);
         }
 
         [Test]
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
index 98056ed6f1..ee2ba78e62 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
@@ -383,25 +383,28 @@ namespace Apache.Ignite.Tests.Table
 
             await PocoView.UpsertAllAsync(null, records);
 
-            // TODO: Key order should be preserved by the server 
(IGNITE-16004).
             var res = await PocoView.GetAllAsync(null, Enumerable.Range(9, 
4).Select(x => GetPoco(x)));
-            var resArr = res.OrderBy(x => x.Value.Key).ToArray();
+            var resArr = res.ToArray();
 
-            Assert.AreEqual(2, res.Count);
+            Assert.AreEqual(4, res.Count);
 
             Assert.AreEqual(9, resArr[0].Value.Key);
             Assert.AreEqual("9", resArr[0].Value.Val);
 
             Assert.AreEqual(10, resArr[1].Value.Key);
             Assert.AreEqual("10", resArr[1].Value.Val);
+
+            Assert.IsFalse(resArr[2].HasValue);
+            Assert.IsFalse(resArr[3].HasValue);
         }
 
         [Test]
-        public async Task TestGetAllNonExistentKeysReturnsEmptyList()
+        public async Task TestGetAllNonExistentKeysReturnsListWithNoValue()
         {
             var res = await PocoView.GetAllAsync(null, new[] { GetPoco(-100) 
});
 
-            Assert.AreEqual(0, res.Count);
+            Assert.AreEqual(1, res.Count);
+            Assert.IsFalse(res[0].HasValue);
         }
 
         [Test]
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
index fb499064bf..c493020f54 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Transactions/TransactionsTests.cs
@@ -58,7 +58,7 @@ namespace Apache.Ignite.Tests.Transactions
             Assert.IsFalse(await TupleView.DeleteAsync(tx, key));
 
             await TupleView.UpsertAllAsync(tx, new[] { GetTuple(1, "6"), 
GetTuple(2, "7") });
-            Assert.AreEqual(2, (await TupleView.GetAllAsync(tx, new[] { key, 
GetTuple(2), GetTuple(3) })).Count);
+            Assert.AreEqual(3, (await TupleView.GetAllAsync(tx, new[] { key, 
GetTuple(2), GetTuple(3) })).Count);
 
             var insertAllRes = await TupleView.InsertAllAsync(tx, new[] { 
GetTuple(1, "8"), GetTuple(3, "9") });
             Assert.AreEqual(GetTuple(1, "6"), (await TupleView.GetAsync(tx, 
key)).Value);
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableApiContractTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableApiContractTest.java
index d123b28903..91da7e94e7 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableApiContractTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableApiContractTest.java
@@ -23,6 +23,7 @@ import static 
org.apache.ignite.internal.schema.testutils.SchemaConfigurationCon
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -255,6 +256,11 @@ public class ItTableApiContractTest extends 
ClusterPerClassIntegrationTest {
     public void testGetAll() {
         RecordView<Tuple> tbl = ignite.tables().table(TABLE_NAME).recordView();
 
+        assertThat(
+                tbl.getAll(null, List.of(Tuple.create().set("name", "id_0"))),
+                contains(nullValue())
+        );
+
         var recs = IntStream.range(0, 5)
                 .mapToObj(i -> Tuple.create().set("name", "id_" + i * 
2).set("balance", i * 2))
                 .collect(toList());
@@ -265,12 +271,11 @@ public class ItTableApiContractTest extends 
ClusterPerClassIntegrationTest {
                 .mapToObj(i -> Tuple.create().set("name", "id_" + i))
                 .collect(toList());
 
-        List<Tuple> res = (List<Tuple>) tbl.getAll(null, keys);
+        List<Tuple> res = tbl.getAll(null, keys);
 
-        // TODO: IGNITE-19693 should be: "id_0", null, "id_2", null, "id_4", 
null, "id_6", null, "id_8", null
         assertThat(
-                res.stream().map(tuple -> 
tuple.stringValue(0)).collect(toList()),
-                contains("id_0", "id_2", "id_4", "id_6", "id_8")
+                res.stream().map(tuple -> tuple == null ? null : 
tuple.stringValue(0)).collect(toList()),
+                contains("id_0", null, "id_2", null, "id_4", null, "id_6", 
null, "id_8", null)
         );
     }
 
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
index 05aa8da742..b0ac1d5635 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
@@ -123,7 +123,7 @@ public class ItThinClientTransactionsTest extends 
ItAbstractThinClientTest {
         assertFalse(recordView.delete(tx, key));
 
         recordView.upsertAll(tx, List.of(rec(1, "6"), rec(2, "7")));
-        assertEquals(2, recordView.getAll(tx, List.of(key, rec(2, null), 
rec(3, null))).size());
+        assertEquals(3, recordView.getAll(tx, List.of(key, rec(2, null), 
rec(3, null))).size());
 
         tx.rollback();
         assertEquals(rec(1, "1"), recordView.get(null, key));
@@ -149,7 +149,7 @@ public class ItThinClientTransactionsTest extends 
ItAbstractThinClientTest {
         assertFalse(recordView.delete(tx, key));
 
         recordView.upsertAll(tx, List.of(kv(1, "6"), kv(2, "7")));
-        assertEquals(2, recordView.getAll(tx, List.of(key, key(2), 
key(3))).size());
+        assertEquals(3, recordView.getAll(tx, List.of(key, key(2), 
key(3))).size());
 
         tx.rollback();
         assertEquals(kv(1, "1"), recordView.get(null, key));
diff --git 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaRegistry.java
 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaRegistry.java
index fd279935f2..2e8ecd8918 100644
--- 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaRegistry.java
+++ 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaRegistry.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.schema;
 
 import java.util.Collection;
+import java.util.List;
 import org.apache.ignite.internal.schema.registry.SchemaRegistryException;
 import org.apache.ignite.internal.schema.row.Row;
 import org.jetbrains.annotations.Nullable;
@@ -93,7 +94,7 @@ public interface SchemaRegistry {
      * Resolves batch of binary row against the latest schema.
      *
      * @param rows Binary rows.
-     * @return Schema-aware rows.
+     * @return Schema-aware rows. Contains {@code null} at the same positions 
as in {@code rows}.
      */
-    Collection<Row> resolve(Collection<BinaryRow> rows);
+    List<Row> resolve(Collection<BinaryRow> rows);
 }
diff --git 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/SchemaRegistryImpl.java
 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/SchemaRegistryImpl.java
index 75e879cb86..5368222c72 100644
--- 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/SchemaRegistryImpl.java
+++ 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/SchemaRegistryImpl.java
@@ -148,17 +148,14 @@ public class SchemaRegistryImpl implements SchemaRegistry 
{
         return resolveInternal(row, schemaDescriptor);
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<Row> resolve(Collection<BinaryRow> binaryRows) {
-        final SchemaDescriptor curSchema = waitLatestSchema();
+    public List<Row> resolve(Collection<BinaryRow> binaryRows) {
+        SchemaDescriptor curSchema = waitLatestSchema();
 
-        List<Row> rows = new ArrayList<>(binaryRows.size());
+        var rows = new ArrayList<Row>(binaryRows.size());
 
-        for (BinaryRow r : binaryRows) {
-            if (r != null) {
-                rows.add(resolveInternal(r, curSchema));
-            }
+        for (BinaryRow row : binaryRows) {
+            rows.add(row == null ? null : resolveInternal(row, curSchema));
         }
 
         return rows;
diff --git a/modules/table/build.gradle b/modules/table/build.gradle
index 196ff37947..f16310bb56 100644
--- a/modules/table/build.gradle
+++ b/modules/table/build.gradle
@@ -91,6 +91,7 @@ dependencies {
     testFixturesImplementation libs.fastutil.core
     testFixturesImplementation libs.mockito.core
     testFixturesImplementation libs.mockito.junit
+    testFixturesImplementation libs.hamcrest.core
 
     integrationTestImplementation project(':ignite-replicator')
     integrationTestImplementation project(':ignite-raft-api')
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
index 3ebf468150..02f7474fe4 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
@@ -103,10 +103,12 @@ public interface InternalTable extends ManuallyCloseable {
      * Asynchronously get rows from the table.
      *
      * @param keyRows Rows with key columns set.
-     * @param tx      The transaction.
-     * @return Future representing pending completion of the operation.
+     * @param tx      Transaction or {@code null} to auto-commit.
+     * @return Future that will return rows with all columns filled from the 
table. The order of collection elements is
+     *      guaranteed to be the same as the order of {@code keyRows}. If a 
record does not exist, the
+     *      element at the corresponding index of the resulting collection is 
{@code null}.
      */
-    CompletableFuture<Collection<BinaryRow>> getAll(Collection<BinaryRowEx> 
keyRows, @Nullable InternalTransaction tx);
+    CompletableFuture<List<BinaryRow>> getAll(Collection<BinaryRowEx> keyRows, 
@Nullable InternalTransaction tx);
 
     /**
      * Asynchronously get rows from the table for the proposed read timestamp.
@@ -114,9 +116,11 @@ public interface InternalTable extends ManuallyCloseable {
      * @param keyRows       Rows with key columns set.
      * @param readTimestamp Read timestamp.
      * @param recipientNode Cluster node that will handle given get request.
-     * @return Future representing pending completion of the operation.
+     * @return Future that will return rows with all columns filled from the 
table. The order of collection elements is
+     *      guaranteed to be the same as the order of {@code keyRows}. If a 
record does not exist, the
+     *      element at the corresponding index of the resulting collection is 
{@code null}.
      */
-    CompletableFuture<Collection<BinaryRow>> getAll(
+    CompletableFuture<List<BinaryRow>> getAll(
             Collection<BinaryRowEx> keyRows,
             HybridTimestamp readTimestamp,
             ClusterNode recipientNode
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueBinaryViewImpl.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueBinaryViewImpl.java
index 25ed891f14..bf86b16991 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueBinaryViewImpl.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueBinaryViewImpl.java
@@ -429,7 +429,9 @@ public class KeyValueBinaryViewImpl extends 
AbstractTableView implements KeyValu
         Map<Tuple, Tuple> pairs = IgniteUtils.newHashMap(rows.size());
 
         for (Row row : schemaReg.resolve(rows)) {
-            pairs.put(TableRow.keyTuple(row), TableRow.valueTuple(row));
+            if (row != null) {
+                pairs.put(TableRow.keyTuple(row), TableRow.valueTuple(row));
+            }
         }
 
         return pairs;
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
index 76c39a31d5..94db24837e 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
@@ -529,7 +529,9 @@ public class KeyValueViewImpl<K, V> extends 
AbstractTableView implements KeyValu
 
         try {
             for (Row row : schemaReg.resolve(rows)) {
-                pairs.put(marsh.unmarshalKey(row), marsh.unmarshalValue(row));
+                if (row != null) {
+                    pairs.put(marsh.unmarshalKey(row), 
marsh.unmarshalValue(row));
+                }
             }
 
             return pairs;
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/RecordBinaryViewImpl.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/RecordBinaryViewImpl.java
index 6c34f013fa..b5fc8728bd 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/RecordBinaryViewImpl.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/RecordBinaryViewImpl.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.table;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow.Publisher;
@@ -74,18 +75,16 @@ public class RecordBinaryViewImpl extends AbstractTableView 
implements RecordVie
         return tbl.get(keyRow, (InternalTransaction) tx).thenApply(this::wrap);
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<Tuple> getAll(@Nullable Transaction tx, @NotNull 
Collection<Tuple> keyRecs) {
+    public List<Tuple> getAll(@Nullable Transaction tx, Collection<Tuple> 
keyRecs) {
         return sync(getAllAsync(tx, keyRecs));
     }
 
-    /** {@inheritDoc} */
     @Override
-    public @NotNull CompletableFuture<Collection<Tuple>> getAllAsync(@Nullable 
Transaction tx, @NotNull Collection<Tuple> keyRecs) {
+    public CompletableFuture<List<Tuple>> getAllAsync(@Nullable Transaction 
tx, Collection<Tuple> keyRecs) {
         Objects.requireNonNull(keyRecs);
 
-        return tbl.getAll(mapToBinary(keyRecs, true), (InternalTransaction) 
tx).thenApply(this::wrap);
+        return tbl.getAll(mapToBinary(keyRecs, true), (InternalTransaction) 
tx).thenApply(binaryRows -> wrap(binaryRows, true));
     }
 
     /** {@inheritDoc} */
@@ -161,7 +160,7 @@ public class RecordBinaryViewImpl extends AbstractTableView 
implements RecordVie
     public @NotNull CompletableFuture<Collection<Tuple>> 
insertAllAsync(@Nullable Transaction tx, @NotNull Collection<Tuple> recs) {
         Objects.requireNonNull(recs);
 
-        return tbl.insertAll(mapToBinary(recs, false), (InternalTransaction) 
tx).thenApply(this::wrap);
+        return tbl.insertAll(mapToBinary(recs, false), (InternalTransaction) 
tx).thenApply(rows -> wrap(rows, false));
     }
 
     /** {@inheritDoc} */
@@ -273,7 +272,7 @@ public class RecordBinaryViewImpl extends AbstractTableView 
implements RecordVie
     public @NotNull CompletableFuture<Collection<Tuple>> 
deleteAllAsync(@Nullable Transaction tx, @NotNull Collection<Tuple> keyRecs) {
         Objects.requireNonNull(keyRecs);
 
-        return tbl.deleteAll(mapToBinary(keyRecs, true), (InternalTransaction) 
tx).thenApply(this::wrap);
+        return tbl.deleteAll(mapToBinary(keyRecs, true), (InternalTransaction) 
tx).thenApply(rows -> wrap(rows, false));
     }
 
     /** {@inheritDoc} */
@@ -287,7 +286,7 @@ public class RecordBinaryViewImpl extends AbstractTableView 
implements RecordVie
     public @NotNull CompletableFuture<Collection<Tuple>> 
deleteAllExactAsync(@Nullable Transaction tx, @NotNull Collection<Tuple> recs) {
         Objects.requireNonNull(recs);
 
-        return tbl.deleteAllExact(mapToBinary(recs, false), 
(InternalTransaction) tx).thenApply(this::wrap);
+        return tbl.deleteAllExact(mapToBinary(recs, false), 
(InternalTransaction) tx).thenApply(rows -> wrap(rows, false));
     }
 
     /**
@@ -323,17 +322,20 @@ public class RecordBinaryViewImpl extends 
AbstractTableView implements RecordVie
      * Returns table rows.
      *
      * @param rows Binary rows.
+     * @param addNull {@code true} if {@code null} is added for missing rows.
      */
-    private Collection<Tuple> wrap(Collection<BinaryRow> rows) {
+    private List<Tuple> wrap(Collection<BinaryRow> rows, boolean addNull) {
         if (rows.isEmpty()) {
             return Collections.emptyList();
         }
 
-        Collection<Tuple> wrapped = new ArrayList<>(rows.size());
+        var wrapped = new ArrayList<Tuple>(rows.size());
 
         for (Row row : schemaReg.resolve(rows)) {
             if (row != null) {
                 wrapped.add(TableRow.tuple(row));
+            } else if (addNull) {
+                wrapped.add(null);
             }
         }
 
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/RecordViewImpl.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/RecordViewImpl.java
index 7c851a7e09..6222c64e32 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/RecordViewImpl.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/RecordViewImpl.java
@@ -80,18 +80,16 @@ public class RecordViewImpl<R> extends AbstractTableView 
implements RecordView<R
         return tbl.get(keyRow, (InternalTransaction) 
tx).thenApply(this::unmarshal);
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<R> getAll(@Nullable Transaction tx, @NotNull 
Collection<R> keyRecs) {
+    public List<R> getAll(@Nullable Transaction tx, Collection<R> keyRecs) {
         return sync(getAllAsync(tx, keyRecs));
     }
 
-    /** {@inheritDoc} */
     @Override
-    public @NotNull CompletableFuture<Collection<R>> getAllAsync(@Nullable 
Transaction tx, @NotNull Collection<R> keyRecs) {
+    public CompletableFuture<List<R>> getAllAsync(@Nullable Transaction tx, 
Collection<R> keyRecs) {
         Objects.requireNonNull(keyRecs);
 
-        return tbl.getAll(marshalKeys(keyRecs), (InternalTransaction) 
tx).thenApply(this::unmarshal);
+        return tbl.getAll(marshalKeys(keyRecs), (InternalTransaction) 
tx).thenApply(binaryRows -> unmarshal(binaryRows, true));
     }
 
     /** {@inheritDoc} */
@@ -161,7 +159,7 @@ public class RecordViewImpl<R> extends AbstractTableView 
implements RecordView<R
     public @NotNull CompletableFuture<Collection<R>> insertAllAsync(@Nullable 
Transaction tx, @NotNull Collection<R> recs) {
         Collection<BinaryRowEx> rows = marshal(Objects.requireNonNull(recs));
 
-        return tbl.insertAll(rows, (InternalTransaction) 
tx).thenApply(this::unmarshal);
+        return tbl.insertAll(rows, (InternalTransaction) 
tx).thenApply(binaryRows -> unmarshal(binaryRows, false));
     }
 
     /** {@inheritDoc} */
@@ -260,7 +258,7 @@ public class RecordViewImpl<R> extends AbstractTableView 
implements RecordView<R
     public @NotNull CompletableFuture<Collection<R>> deleteAllAsync(@Nullable 
Transaction tx, @NotNull Collection<R> keyRecs) {
         Collection<BinaryRowEx> rows = 
marshal(Objects.requireNonNull(keyRecs));
 
-        return tbl.deleteAll(rows, (InternalTransaction) 
tx).thenApply(this::unmarshal);
+        return tbl.deleteAll(rows, (InternalTransaction) 
tx).thenApply(binaryRows -> unmarshal(binaryRows, false));
     }
 
     /** {@inheritDoc} */
@@ -274,7 +272,7 @@ public class RecordViewImpl<R> extends AbstractTableView 
implements RecordView<R
     public @NotNull CompletableFuture<Collection<R>> 
deleteAllExactAsync(@Nullable Transaction tx, @NotNull Collection<R> keyRecs) {
         Collection<BinaryRowEx> rows = 
marshal(Objects.requireNonNull(keyRecs));
 
-        return tbl.deleteAllExact(rows, (InternalTransaction) 
tx).thenApply(this::unmarshal);
+        return tbl.deleteAllExact(rows, (InternalTransaction) 
tx).thenApply(binaryRows -> unmarshal(binaryRows, false));
     }
 
     /**
@@ -410,20 +408,25 @@ public class RecordViewImpl<R> extends AbstractTableView 
implements RecordView<R
      * Unmarshal records.
      *
      * @param rows Row collection.
+     * @param addNull {@code true} if {@code null} is added for missing rows.
      * @return Records collection.
      */
-    private Collection<R> unmarshal(Collection<BinaryRow> rows) {
+    private List<R> unmarshal(Collection<BinaryRow> rows, boolean addNull) {
         if (rows.isEmpty()) {
             return Collections.emptyList();
         }
 
         RecordMarshaller<R> marsh = marshaller();
 
-        List<R> recs = new ArrayList<>(rows.size());
+        var recs = new ArrayList<R>(rows.size());
 
         try {
             for (Row row : schemaReg.resolve(rows)) {
-                recs.add(marsh.unmarshal(row));
+                if (row != null) {
+                    recs.add(marsh.unmarshal(row));
+                } else if (addNull) {
+                    recs.add(null);
+                }
             }
 
             return recs;
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
index 06724d110b..98fc9148c4 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
@@ -559,7 +559,7 @@ public class InternalTableImpl implements InternalTable {
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Collection<BinaryRow>> 
getAll(Collection<BinaryRowEx> keyRows, InternalTransaction tx) {
+    public CompletableFuture<List<BinaryRow>> getAll(Collection<BinaryRowEx> 
keyRows, InternalTransaction tx) {
         if (tx != null && tx.isReadOnly()) {
             BinaryRowEx firstRow = keyRows.iterator().next();
 
@@ -589,7 +589,7 @@ public class InternalTableImpl implements InternalTable {
 
     /** {@inheritDoc} */
     @Override
-    public CompletableFuture<Collection<BinaryRow>> getAll(
+    public CompletableFuture<List<BinaryRow>> getAll(
             Collection<BinaryRowEx> keyRows,
             HybridTimestamp readTimestamp,
             ClusterNode recipientNode
@@ -1240,7 +1240,7 @@ public class InternalTableImpl implements InternalTable {
      * @param rowBatches Row batches by partition ID.
      * @return Future of collecting results.
      */
-    static CompletableFuture<Collection<BinaryRow>> 
collectMultiRowsResponsesWithRestoreOrder(Collection<RowBatch> rowBatches) {
+    static CompletableFuture<List<BinaryRow>> 
collectMultiRowsResponsesWithRestoreOrder(Collection<RowBatch> rowBatches) {
         return allResultFutures(rowBatches)
                 .thenApply(response -> {
                     var result = new 
BinaryRow[RowBatch.getTotalRequestedRowSize(rowBatches)];
diff --git 
a/modules/table/src/test/java/org/apache/ignite/internal/table/RecordBinaryViewOperationsTest.java
 
b/modules/table/src/test/java/org/apache/ignite/internal/table/RecordBinaryViewOperationsTest.java
index f1dbf99ceb..cbf9c65442 100644
--- 
a/modules/table/src/test/java/org/apache/ignite/internal/table/RecordBinaryViewOperationsTest.java
+++ 
b/modules/table/src/test/java/org/apache/ignite/internal/table/RecordBinaryViewOperationsTest.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.internal.table;
 
 import static 
org.apache.ignite.internal.schema.DefaultValueProvider.constantProvider;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -356,9 +358,7 @@ public class RecordBinaryViewOperationsTest {
                         Tuple.create().set("id", 3L)
                 ));
 
-        assertEquals(2, res.size());
-        assertTrue(res.contains(rec1));
-        assertTrue(res.contains(rec3));
+        assertThat(res, contains(rec1, null, rec3));
     }
 
     @Test
@@ -384,9 +384,7 @@ public class RecordBinaryViewOperationsTest {
                         Tuple.create().set("id", 3L)
                 ));
 
-        assertEquals(2, res.size());
-        assertTrue(res.contains(rec1));
-        assertTrue(res.contains(rec3));
+        assertThat(res, contains(rec1, null, rec3));
 
         Tuple upRec1 = Tuple.create().set("id", 1L).set("val", 112L);
         Tuple rec2 = Tuple.create().set("id", 2L).set("val", 22L);
@@ -402,11 +400,7 @@ public class RecordBinaryViewOperationsTest {
                         Tuple.create().set("id", 3L)
                 ));
 
-        assertEquals(3, res.size());
-
-        assertTrue(res.contains(upRec1));
-        assertTrue(res.contains(rec2));
-        assertTrue(res.contains(upRec3));
+        assertThat(res, contains(upRec1, rec2, upRec3));
     }
 
     @Test
@@ -532,9 +526,7 @@ public class RecordBinaryViewOperationsTest {
                         Tuple.create().set("id", 3L)
                 ));
 
-        assertEquals(1, current.size());
-
-        assertTrue(current.contains(tuple2));
+        assertThat(current, contains(null, tuple2, null));
     }
 
     @Test
@@ -588,9 +580,7 @@ public class RecordBinaryViewOperationsTest {
                         Tuple.create().set("id", 3L)
                 ));
 
-        assertEquals(1, current.size());
-
-        assertTrue(current.contains(tuple3Upsert));
+        assertThat(current, contains(null, null, tuple3Upsert));
     }
 
     @Test
diff --git 
a/modules/table/src/test/java/org/apache/ignite/internal/table/RecordViewOperationsTest.java
 
b/modules/table/src/test/java/org/apache/ignite/internal/table/RecordViewOperationsTest.java
index 5796c6df44..ba1ccb1e5b 100644
--- 
a/modules/table/src/test/java/org/apache/ignite/internal/table/RecordViewOperationsTest.java
+++ 
b/modules/table/src/test/java/org/apache/ignite/internal/table/RecordViewOperationsTest.java
@@ -29,6 +29,8 @@ import static 
org.apache.ignite.internal.schema.NativeTypes.STRING;
 import static org.apache.ignite.internal.schema.NativeTypes.datetime;
 import static org.apache.ignite.internal.schema.NativeTypes.time;
 import static org.apache.ignite.internal.schema.NativeTypes.timestamp;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -282,9 +284,7 @@ public class RecordViewOperationsTest {
 
         Collection<TestObjectWithAllTypes> res = tbl.getAll(null, 
List.of(key1, key2, key3));
 
-        assertEquals(2, res.size());
-        assertTrue(res.contains(val1));
-        assertTrue(res.contains(val3));
+        assertThat(res, contains(val1, null, val3));
     }
 
     /**
diff --git 
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
 
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
index 28cbc2e04d..32e3185a66 100644
--- 
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
+++ 
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
@@ -19,7 +19,10 @@ package org.apache.ignite.internal.table;
 
 import static java.util.concurrent.CompletableFuture.allOf;
 import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.stream.Collectors.toList;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -73,6 +76,7 @@ import org.apache.ignite.tx.IgniteTransactions;
 import org.apache.ignite.tx.Transaction;
 import org.apache.ignite.tx.TransactionException;
 import org.apache.ignite.tx.TransactionOptions;
+import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
@@ -763,7 +767,7 @@ public abstract class TxAbstractTest extends 
IgniteAbstractTest {
 
         Collection<Tuple> ret = accounts.recordView().getAll(null, keys);
 
-        assertEquals(0, ret.size());
+        assertThat(ret, contains(null, null));
 
         accounts.recordView().upsert(null, makeValue(1, 100.));
         accounts.recordView().upsert(null, makeValue(2, 200.));
@@ -1696,15 +1700,11 @@ public abstract class TxAbstractTest extends 
IgniteAbstractTest {
      * @param rows Rows.
      * @param expected Expected values.
      */
-    private void validateBalance(Collection<Tuple> rows, double... expected) {
-        List<Tuple> rows0 = new ArrayList<>(rows);
-
-        assertEquals(expected.length, rows.size());
-
-        for (int i = 0; i < expected.length; i++) {
-            double v = expected[i];
-            assertEquals(v, rows0.get(i).doubleValue("balance"));
-        }
+    private static void validateBalance(Collection<Tuple> rows, @Nullable 
Double... expected) {
+        assertThat(
+                rows.stream().map(tuple -> tuple == null ? null : 
tuple.doubleValue("balance")).collect(toList()),
+                contains(expected)
+        );
     }
 
     /**
@@ -1897,7 +1897,7 @@ public abstract class TxAbstractTest extends 
IgniteAbstractTest {
 
         Transaction readOnlyTx2 = igniteTransactions.begin(new 
TransactionOptions().readOnly(true));
         Collection<Tuple> retrievedKeys3 = 
accounts.recordView().getAll(readOnlyTx2, List.of(makeKey(1), makeKey(2)));
-        validateBalance(retrievedKeys3, 300.);
+        validateBalance(retrievedKeys3, null, 300.);
     }
 
     @Test
@@ -1964,7 +1964,7 @@ public abstract class TxAbstractTest extends 
IgniteAbstractTest {
         } else {
             res = accountsRv.getAll(null, List.of(makeKey(1), makeKey(2)));
 
-            assertTrue(CollectionUtils.nullOrEmpty(res));
+            assertThat(res, contains(null, null));
         }
     }
 }
diff --git 
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummySchemaManagerImpl.java
 
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummySchemaManagerImpl.java
index 7e0360802d..b9e919d901 100644
--- 
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummySchemaManagerImpl.java
+++ 
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummySchemaManagerImpl.java
@@ -17,9 +17,10 @@
 
 package org.apache.ignite.internal.table.impl;
 
+import static java.util.stream.Collectors.toList;
+
 import java.util.Collection;
-import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.List;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
 import org.apache.ignite.internal.schema.SchemaRegistry;
@@ -93,9 +94,8 @@ public class DummySchemaManagerImpl implements SchemaRegistry 
{
         return new Row(schema, row);
     }
 
-    /** {@inheritDoc} */
     @Override
-    public Collection<Row> resolve(Collection<BinaryRow> rows) {
-        return 
rows.stream().filter(Objects::nonNull).map(this::resolve).collect(Collectors.toList());
+    public List<Row> resolve(Collection<BinaryRow> rows) {
+        return rows.stream().map(binaryRow -> binaryRow == null ? null : 
resolve(binaryRow)).collect(toList());
     }
 }

Reply via email to