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

ptupitsyn 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 a12385a  IGNITE-16093 Thin client: Handle absent and null values 
differently (#513)
a12385a is described below

commit a12385a9c89186cc362fba6f3e29be45de477077
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Fri Dec 17 14:19:56 2021 +0300

    IGNITE-16093 Thin client: Handle absent and null values differently (#513)
    
    Ignite Table API handles "column set to null" (1) and "column not set" (2) 
differently.
    * Non-nullable column does not allow (1), but allows (2) as long as there 
is a default value.
    * Nullable column will be set to null in (1) and to default value in (2).
    
    Add a new data type `NO_VALUE` to the protocol to reflect this distinction.
---
 .../internal/client/proto/ClientMessageCommon.java |  3 +
 .../internal/client/proto/ClientMessagePacker.java | 18 ++++++
 .../client/proto/ClientMessageUnpacker.java        | 23 ++++++++
 .../internal/client/proto/ClientMsgPackType.java   |  3 +
 .../proto/ClientMessagePackerUnpackerTest.java     | 25 ++++++++
 .../handler/requests/table/ClientTableCommon.java  | 11 +++-
 .../ignite/internal/client/table/ClientTable.java  | 68 +++++++---------------
 .../ignite/client/AbstractClientTableTest.java     |  7 +++
 .../org/apache/ignite/client/ClientTableTest.java  | 43 ++++++++++++++
 .../ignite/client/fakes/FakeIgniteTables.java      | 25 ++++++++
 .../Proto/MessagePackExtensionsTest.cs             | 27 +++++++++
 .../Internal/Proto/ClientMessagePackType.cs        |  7 ++-
 .../Internal/Proto/MessagePackReaderExtensions.cs  | 24 ++++++++
 .../Internal/Proto/MessagePackWriterExtensions.cs  | 12 ++++
 .../Apache.Ignite/Internal/Proto/ProtoCommon.cs    |  7 ---
 .../dotnet/Apache.Ignite/Internal/Table/Table.cs   | 12 +++-
 16 files changed, 258 insertions(+), 57 deletions(-)

diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageCommon.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageCommon.java
index 73a840c..8435c86 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageCommon.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageCommon.java
@@ -26,4 +26,7 @@ public class ClientMessageCommon {
 
     /** Magic bytes before handshake. */
     public static final byte[] MAGIC_BYTES = new byte[]{0x49, 0x47, 0x4E, 
0x49}; // IGNI
+
+    /** Special "no value" object. */
+    public static final Object NO_VALUE = new Object();
 }
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
index 9ca5549..3902654 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.client.proto;
 
 import static 
org.apache.ignite.internal.client.proto.ClientMessageCommon.HEADER_SIZE;
+import static 
org.apache.ignite.internal.client.proto.ClientMessageCommon.NO_VALUE;
 import static org.msgpack.core.MessagePack.Code;
 
 import io.netty.buffer.ByteBuf;
@@ -82,6 +83,17 @@ public class ClientMessagePacker implements AutoCloseable {
     }
 
     /**
+     * Writes a "no value" value.
+     */
+    public void packNoValue() {
+        assert !closed : "Packer is closed";
+
+        buf.writeByte(Code.FIXEXT1);
+        buf.writeByte(ClientMsgPackType.NO_VALUE);
+        buf.writeByte(0);
+    }
+
+    /**
      * Writes a boolean value.
      *
      * @param b the value to be written.
@@ -648,6 +660,12 @@ public class ClientMessagePacker implements AutoCloseable {
             return;
         }
 
+        if (val == NO_VALUE) {
+            packNoValue();
+
+            return;
+        }
+
         if (val instanceof Byte) {
             packByte((byte) val);
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
index f31ec61..551c22c 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
@@ -548,6 +548,29 @@ public class ClientMessageUnpacker implements 
AutoCloseable {
     }
 
     /**
+     * Tries to read a "no value" value.
+     *
+     * @return True when there was a "no value" value, false otherwise.
+     */
+    public boolean tryUnpackNoValue() {
+        assert refCnt > 0 : "Unpacker is closed";
+
+        int idx = buf.readerIndex();
+        byte code = buf.getByte(idx);
+
+        if (code == Code.FIXEXT1) {
+            byte extCode = buf.getByte(idx + 1);
+
+            if (extCode == ClientMsgPackType.NO_VALUE) {
+                buf.readerIndex(idx + 3);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Reads a payload.
      *
      * @param length Payload size.
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMsgPackType.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMsgPackType.java
index 4c4fc99..80950d7 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMsgPackType.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMsgPackType.java
@@ -47,4 +47,7 @@ public class ClientMsgPackType {
 
     /** Ignite UUID. */
     public static final byte IGNITE_UUID = 9;
+
+    /** Absent value for a column. */
+    public static final byte NO_VALUE = 10;
 }
diff --git 
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
 
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
index 96a6be4..db97c9c 100644
--- 
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
+++ 
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
@@ -20,6 +20,8 @@ package org.apache.ignite.internal.client.proto;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.randomBytes;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import io.netty.buffer.PooledByteBufAllocator;
 import io.netty.buffer.Unpooled;
@@ -321,4 +323,27 @@ public class ClientMessagePackerUnpackerTest {
             }
         }
     }
+
+    @Test
+    public void testNoValue() {
+        try (var packer = new 
ClientMessagePacker(PooledByteBufAllocator.DEFAULT.directBuffer())) {
+            packer.packInt(1);
+            packer.packNoValue();
+            packer.packString("s");
+
+            var buf = packer.getBuffer();
+
+            byte[] data = new byte[buf.readableBytes()];
+            buf.readBytes(data);
+
+            try (var unpacker = new 
ClientMessageUnpacker(Unpooled.wrappedBuffer(data))) {
+                unpacker.skipValues(4);
+
+                assertFalse(unpacker.tryUnpackNoValue());
+                assertEquals(1, unpacker.unpackInt());
+                assertTrue(unpacker.tryUnpackNoValue());
+                assertEquals("s", unpacker.unpackString());
+            }
+        }
+    }
 }
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
index 4cd7d73..e462bac 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.client.handler.requests.table;
 
+import static 
org.apache.ignite.internal.client.proto.ClientMessageCommon.NO_VALUE;
+
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.time.Instant;
@@ -311,7 +313,7 @@ class ClientTableCommon {
         var tuple = Tuple.create(cnt);
 
         for (int i = 0; i < cnt; i++) {
-            if (unpacker.tryUnpackNil()) {
+            if (unpacker.tryUnpackNoValue()) {
                 continue;
             }
 
@@ -437,13 +439,18 @@ class ClientTableCommon {
     }
 
     private static void writeColumnValue(ClientMessagePacker packer, Tuple 
tuple, Column col) {
-        var val = tuple.valueOrDefault(col.name(), null);
+        var val = tuple.valueOrDefault(col.name(), NO_VALUE);
 
         if (val == null) {
             packer.packNil();
             return;
         }
 
+        if (val == NO_VALUE) {
+            packer.packNoValue();
+            return;
+        }
+
         switch (col.type().spec()) {
             case INT8:
                 packer.packByte((byte) val);
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
index 0f350c0..1c84100 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.client.table;
 
+import static 
org.apache.ignite.internal.client.proto.ClientMessageCommon.NO_VALUE;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -249,29 +251,20 @@ public class ClientTable implements Table {
             boolean keyOnly,
             boolean skipHeader
     ) {
-        // TODO: Special case for ClientTupleBuilder - it has columns in order
-        // TODO: Optimize (IGNITE-16082).
-        var vals = new Object[keyOnly ? schema.keyColumnCount() : 
schema.columns().length];
-        var tupleSize = tuple.columnCount();
-
-        for (var i = 0; i < tupleSize; i++) {
-            var colName = tuple.columnName(i);
-            var col = schema.column(colName);
-
-            if (keyOnly && !col.key()) {
-                continue;
-            }
-
-            vals[col.schemaIndex()] = tuple.value(i);
-        }
-
         if (!skipHeader) {
             out.packIgniteUuid(id);
             out.packInt(schema.version());
         }
 
-        for (var val : vals) {
-            out.packObject(val);
+        var columns = schema.columns();
+        var count = keyOnly ? schema.keyColumnCount() : columns.length;
+
+        for (var i = 0; i < count; i++) {
+            var col = columns[i];
+
+            Object v = tuple.valueOrDefault(col.name(), NO_VALUE);
+
+            out.packObject(v);
         }
     }
 
@@ -291,39 +284,22 @@ public class ClientTable implements Table {
             ClientMessagePacker out,
             boolean skipHeader
     ) {
-        // TODO: Handle missing values and null values differently 
(IGNITE-16093).
-        var vals = new Object[schema.columns().length];
-
-        for (var i = 0; i < key.columnCount(); i++) {
-            var colName = key.columnName(i);
-            var col = schema.column(colName);
-
-            if (!col.key()) {
-                continue;
-            }
-
-            vals[col.schemaIndex()] = key.value(i);
-        }
-
-        if (val != null) {
-            for (var i = 0; i < val.columnCount(); i++) {
-                var colName = val.columnName(i);
-                var col = schema.column(colName);
-
-                if (col.key()) {
-                    continue;
-                }
-
-                vals[col.schemaIndex()] = val.value(i);
-            }
-        }
-
         if (!skipHeader) {
             out.packIgniteUuid(id);
             out.packInt(schema.version());
         }
 
-        for (var v : vals) {
+        var columns = schema.columns();
+
+        for (var i = 0; i < columns.length; i++) {
+            var col = columns[i];
+
+            Object v = col.key()
+                    ? key.valueOrDefault(col.name(), NO_VALUE)
+                    : val != null
+                            ? val.valueOrDefault(col.name(), NO_VALUE)
+                            : NO_VALUE;
+
             out.packObject(v);
         }
     }
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTableTest.java
 
b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTableTest.java
index e42c71a..42e9d88 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTableTest.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTableTest.java
@@ -19,6 +19,7 @@ package org.apache.ignite.client;
 
 import static 
org.apache.ignite.client.fakes.FakeIgniteTables.TABLE_ALL_COLUMNS;
 import static org.apache.ignite.client.fakes.FakeIgniteTables.TABLE_ONE_COLUMN;
+import static 
org.apache.ignite.client.fakes.FakeIgniteTables.TABLE_WITH_DEFAULT_VALUES;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -93,6 +94,12 @@ public class AbstractClientTableTest extends 
AbstractClientTest {
         return client.tables().table(DEFAULT_TABLE);
     }
 
+    protected Table tableWithDefaultValues() {
+        server.tables().createTableIfNotExists(TABLE_WITH_DEFAULT_VALUES, tbl 
-> tbl.changeReplicas(1));
+
+        return client.tables().table(TABLE_WITH_DEFAULT_VALUES);
+    }
+
     protected static Tuple allClumnsTableKey(long id) {
         return Tuple.create().set("gid", id).set("id", String.valueOf(id));
     }
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/ClientTableTest.java 
b/modules/client/src/test/java/org/apache/ignite/client/ClientTableTest.java
index 20b8819..a588e8b 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientTableTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ClientTableTest.java
@@ -329,4 +329,47 @@ public class ClientTableTest extends 
AbstractClientTableTest {
         assertEquals(3L, skippedTuples[1].longValue("id"));
         assertEquals("z", skippedTuples[1].stringValue("name"));
     }
+
+    @Test
+    public void testColumnWithDefaultValueNotSetReturnsDefault() {
+        RecordView<Tuple> table = tableWithDefaultValues().recordView();
+
+        var tuple = Tuple.create()
+                .set("id", 1);
+
+        table.upsert(tuple);
+
+        var res = table.get(tuple);
+
+        assertEquals("def_str", res.stringValue("str"));
+        assertEquals("def_str2", res.stringValue("str_non_null"));
+    }
+
+    @Test
+    public void testNullableColumnWithDefaultValueSetNullReturnsNull() {
+        RecordView<Tuple> table = tableWithDefaultValues().recordView();
+
+        var tuple = Tuple.create()
+                .set("id", 1)
+                .set("str", null);
+
+        table.upsert(tuple);
+
+        var res = table.get(tuple);
+
+        assertNull(res.stringValue("str"));
+    }
+
+    @Test
+    public void testNonNullableColumnWithDefaultValueSetNullThrowsException() {
+        RecordView<Tuple> table = tableWithDefaultValues().recordView();
+
+        var tuple = Tuple.create()
+                .set("id", 1)
+                .set("str_non_null", null);
+
+        var ex = assertThrows(CompletionException.class, () -> 
table.upsert(tuple));
+
+        assertTrue(ex.getMessage().contains("null was passed, but column is 
not nullable"), ex.getMessage());
+    }
 }
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
index aef3812..2f9a602 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteTables.java
@@ -46,6 +46,8 @@ public class FakeIgniteTables implements IgniteTables, 
IgniteTablesInternal {
 
     public static final String TABLE_ONE_COLUMN = "one-column";
 
+    public static final String TABLE_WITH_DEFAULT_VALUES = "default-columns";
+
     private final ConcurrentHashMap<String, TableImpl> tables = new 
ConcurrentHashMap<>();
 
     private final ConcurrentHashMap<IgniteUuid, TableImpl> tablesById = new 
ConcurrentHashMap<>();
@@ -172,6 +174,10 @@ public class FakeIgniteTables implements IgniteTables, 
IgniteTablesInternal {
                 history = this::getOneColumnSchema;
                 break;
 
+            case TABLE_WITH_DEFAULT_VALUES:
+                history = this::getDefaultColumnValuesSchema;
+                break;
+
             default:
                 history = this::getSchema;
                 break;
@@ -249,6 +255,25 @@ public class FakeIgniteTables implements IgniteTables, 
IgniteTablesInternal {
      * @param v Version.
      * @return Schema descriptor.
      */
+    private SchemaDescriptor getDefaultColumnValuesSchema(Integer v) {
+        return new SchemaDescriptor(
+                v,
+                new Column[]{
+                        new Column("id", NativeTypes.INT32, false)
+                },
+                new Column[]{
+                        new Column("num", NativeTypes.INT8, true, () -> (byte) 
42),
+                        new Column("str", NativeTypes.STRING, true, () -> 
"def_str"),
+                        new Column("str_non_null", NativeTypes.STRING, false, 
() -> "def_str2"),
+                });
+    }
+
+    /**
+     * Gets the schema.
+     *
+     * @param v Version.
+     * @return Schema descriptor.
+     */
     @SuppressWarnings("ZeroLengthArrayAllocation")
     private SchemaDescriptor getOneColumnSchema(Integer v) {
         return new SchemaDescriptor(
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/MessagePackExtensionsTest.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/MessagePackExtensionsTest.cs
index 88a0952..0197e20 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/MessagePackExtensionsTest.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/MessagePackExtensionsTest.cs
@@ -122,6 +122,33 @@ namespace Apache.Ignite.Tests.Proto
             CollectionAssert.AreEqual(JavaUuidBytes, bytes);
         }
 
+        [Test]
+        public void TestNoValue()
+        {
+            var res1 = WriteRead(
+                buf =>
+                {
+                    var w = buf.GetMessageWriter();
+
+                    w.Write(3);
+                    w.WriteNoValue();
+                    w.Write("abc");
+
+                    w.Flush();
+                },
+                m =>
+                {
+                    var r = new MessagePackReader(m);
+
+                    return (r.TryReadNoValue(), r.ReadInt32(), 
r.TryReadNoValue(), r.ReadString());
+                });
+
+            Assert.IsFalse(res1.Item1);
+            Assert.AreEqual(3, res1.Item2);
+            Assert.IsTrue(res1.Item3);
+            Assert.AreEqual("abc", res1.Item4);
+        }
+
         private static T WriteRead<T>(Action<PooledArrayBufferWriter> write, 
Func<ReadOnlyMemory<byte>, T> read)
         {
             var bufferWriter = new PooledArrayBufferWriter();
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientMessagePackType.cs
 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientMessagePackType.cs
index c629bbf..2484353 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientMessagePackType.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientMessagePackType.cs
@@ -65,6 +65,11 @@ namespace Apache.Ignite.Internal.Proto
         /// <summary>
         /// Ignite UUID.
         /// </summary>
-        IgniteUuid = 9
+        IgniteUuid = 9,
+
+        /// <summary>
+        /// Absent value for a column.
+        /// </summary>
+        NoValue = 10
     }
 }
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
index 0d4a706..4f773cd 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
@@ -143,6 +143,30 @@ namespace Apache.Ignite.Internal.Proto
             return res;
         }
 
+        /// <summary>
+        /// Reads <see cref="ClientMessagePackType.NoValue"/> if it is the 
next token.
+        /// </summary>
+        /// <param name="reader">Reader.</param>
+        /// <returns><c>true</c> if the next token was NoValue; <c>false</c> 
otherwise.</returns>
+        public static bool TryReadNoValue(this ref MessagePackReader reader)
+        {
+            if (reader.NextCode != MessagePackCode.FixExt1)
+            {
+                return false;
+            }
+
+            var header = reader.CreatePeekReader().ReadExtensionFormatHeader();
+
+            if (header.TypeCode != (sbyte)ClientMessagePackType.NoValue)
+            {
+                return false;
+            }
+
+            reader.ReadRaw(3);
+
+            return true;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static void ValidateExtensionType(
             ref MessagePackReader reader,
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
index 23b1b81..ca87fb3 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
@@ -83,6 +83,18 @@ namespace Apache.Ignite.Internal.Proto
         }
 
         /// <summary>
+        /// Writes Ignite UUID.
+        /// </summary>
+        /// <param name="writer">Writer.</param>
+        public static void WriteNoValue(this ref MessagePackWriter writer)
+        {
+            writer.WriteExtensionFormatHeader(
+                new ExtensionHeader((sbyte)ClientMessagePackType.NoValue, 1));
+
+            writer.Advance(1);
+        }
+
+        /// <summary>
         /// Writes an object.
         /// </summary>
         /// <param name="writer">Writer.</param>
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ProtoCommon.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ProtoCommon.cs
index fc8f35e..f290e53 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ProtoCommon.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ProtoCommon.cs
@@ -17,8 +17,6 @@
 
 namespace Apache.Ignite.Internal.Proto
 {
-    using System.Text;
-
     /// <summary>
     /// Common protocol data.
     /// </summary>
@@ -28,10 +26,5 @@ namespace Apache.Ignite.Internal.Proto
         /// Magic bytes.
         /// </summary>
         public static readonly byte[] MagicBytes = { (byte)'I', (byte)'G', 
(byte)'N', (byte)'I' };
-
-        /// <summary>
-        /// String encoding.
-        /// </summary>
-        public static readonly Encoding Encoding = new 
UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
index 4e2eb18..407fba0 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
@@ -352,6 +352,11 @@ namespace Apache.Ignite.Internal.Table
                 }
                 else
                 {
+                    if (r.TryReadNoValue())
+                    {
+                        continue;
+                    }
+
                     tuple[column.Name] = r.ReadObject(column.Type);
                 }
             }
@@ -367,6 +372,11 @@ namespace Apache.Ignite.Internal.Table
 
             for (var index = 0; index < count; index++)
             {
+                if (r.TryReadNoValue())
+                {
+                    continue;
+                }
+
                 var column = columns[index];
                 tuple[column.Name] = r.ReadObject(column.Type);
             }
@@ -443,7 +453,7 @@ namespace Apache.Ignite.Internal.Table
 
                 if (colIdx < 0)
                 {
-                    w.WriteNil();
+                    w.WriteNoValue();
                 }
                 else
                 {

Reply via email to