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

[ignite-3] branch main updated: IGNITE-14759: Fix wrong offset calculation for fix-sized column when nullmap is omitted. (#140)

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

amashenkov 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 ed4788a  IGNITE-14759: Fix wrong offset calculation for fix-sized column when nullmap is omitted.  (#140)
ed4788a is described below

commit ed4788a271ee5c8c8be277378fac6e8fa980bea4
Author: Andrew V. Mashenkov <AM...@users.noreply.github.com>
AuthorDate: Mon May 24 10:12:02 2021 +0300

    IGNITE-14759: Fix wrong offset calculation for fix-sized column when nullmap is omitted.  (#140)
---
 .../table/IndexColumnConfigurationSchema.java      |   4 +-
 .../runner/app/DynamicTableCreationTest.java       |  99 +++++++++++++-----
 .../internal/runner/app/TableCreationTest.java     |  48 +++++++--
 .../org/apache/ignite/internal/schema/Row.java     |  42 +++++---
 .../ignite/internal/schema/SchemaManager.java      |  12 ++-
 .../ignite/internal/schema/SchemaRegistry.java     |   6 +-
 .../org/apache/ignite/internal/schema/RowTest.java | 113 ++++++++++++++++-----
 7 files changed, 240 insertions(+), 84 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/table/IndexColumnConfigurationSchema.java b/modules/api/src/main/java/org/apache/ignite/configuration/schemas/table/IndexColumnConfigurationSchema.java
index e915040..3cf584c 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/table/IndexColumnConfigurationSchema.java
+++ b/modules/api/src/main/java/org/apache/ignite/configuration/schemas/table/IndexColumnConfigurationSchema.java
@@ -30,6 +30,6 @@ public class IndexColumnConfigurationSchema {
     String name;
 
     /** Ascending flag. */
-    @Value
-    boolean asc;
+    @Value(hasDefault = true)
+    boolean asc = true;
 }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/DynamicTableCreationTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/DynamicTableCreationTest.java
index 385d1f7..9cf1e02 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/DynamicTableCreationTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/DynamicTableCreationTest.java
@@ -24,6 +24,7 @@ import org.apache.ignite.app.Ignite;
 import org.apache.ignite.app.IgnitionManager;
 import org.apache.ignite.internal.schema.SchemaManager;
 import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.table.KeyValueBinaryView;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
 import org.junit.jupiter.api.Disabled;
@@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
  * Ignition interface tests.
@@ -82,21 +84,21 @@ class DynamicTableCreationTest {
      */
     @Test
     void testDynamicSimpleTableCreation() {
-        List<Ignite> clusterNodex = new ArrayList<>();
+        List<Ignite> clusterNodes = new ArrayList<>();
 
         for (String nodeBootstrapCfg : nodesBootstrapCfg)
-            clusterNodex.add(IgnitionManager.start(nodeBootstrapCfg));
+            clusterNodes.add(IgnitionManager.start(nodeBootstrapCfg));
 
-        assertEquals(3, clusterNodex.size());
+        assertEquals(3, clusterNodes.size());
 
         // Create table on node 0.
-        clusterNodex.get(0).tables().createTable("tbl1", tbl -> tbl
+        clusterNodes.get(0).tables().createTable("tbl1", tbl -> tbl
             .changeName("tbl1")
             .changeReplicas(1)
             .changePartitions(10)
             .changeColumns(cols -> cols
-                .create("key", c -> c.changeName("key").changeNullable(false).changeType(t -> t.changeType("int")))
-                .create("val", c -> c.changeName("val").changeNullable(true).changeType(t -> t.changeType("int")))
+                .create("key", c -> c.changeName("key").changeNullable(false).changeType(t -> t.changeType("INT64")))
+                .create("val", c -> c.changeName("val").changeNullable(true).changeType(t -> t.changeType("INT32")))
             )
             .changeIndices(idxs -> idxs
                 .create("PK", idx -> idx
@@ -104,17 +106,33 @@ class DynamicTableCreationTest {
                     .changeType("PRIMARY")
                     .changeColNames(new String[] {"key"})
                     .changeColumns(c -> c
-                        .create("key", t -> t.changeName("key").changeAsc(true)))
+                        .create("key", t -> t.changeName("key")))
                     .changeAffinityColumns(new String[] {"key"}))
             ));
 
         // Put data on node 1.
-        Table tbl1 = waitForTable(clusterNodex.get(1));
-        tbl1.insert(tbl1.tupleBuilder().set("key", 1).set("val", 111).build());
+        Table tbl1 = waitForTable(clusterNodes.get(1));
+        KeyValueBinaryView kvView1 = tbl1.kvView();
+
+        tbl1.insert(tbl1.tupleBuilder().set("key", 1L).set("val", 111).build());
+        kvView1.put(tbl1.tupleBuilder().set("key", 2L).build(), tbl1.tupleBuilder().set("val", 222).build());
 
         // Get data on node 2.
-        Table tbl2 = waitForTable(clusterNodex.get(2));
-        assertEquals(111, (int)tbl2.get(tbl2.tupleBuilder().set("key", 1).build()).value("val"));
+        Table tbl2 = waitForTable(clusterNodes.get(2));
+        KeyValueBinaryView kvView2 = tbl2.kvView();
+
+        final Tuple keyTuple1 = tbl2.tupleBuilder().set("key", 1L).build();
+        final Tuple keyTuple2 = kvView2.tupleBuilder().set("key", 2L).build();
+
+        assertThrows(IllegalArgumentException.class, () -> kvView2.get(keyTuple1).value("key"));
+        assertThrows(IllegalArgumentException.class, () -> kvView2.get(keyTuple1).value("key"));
+        assertEquals(1, (Long)tbl2.get(keyTuple1).value("key"));
+        assertEquals(2, (Long)tbl2.get(keyTuple2).value("key"));
+
+        assertEquals(111, (Integer)tbl2.get(keyTuple1).value("val"));
+        assertEquals(111, (Integer)kvView2.get(keyTuple1).value("val"));
+        assertEquals(222, (Integer)tbl2.get(keyTuple2).value("val"));
+        assertEquals(222, (Integer)kvView2.get(keyTuple2).value("val"));
     }
 
     /**
@@ -154,11 +172,11 @@ class DynamicTableCreationTest {
             .changeReplicas(1)
             .changePartitions(10)
             .changeColumns(cols -> cols
-                .create("key", c -> c.changeName("key").changeNullable(false).changeType(t -> t.changeType("uuid")))
-                .create("affKey", c -> c.changeName("affKey").changeNullable(false).changeType(t -> t.changeType("int")))
-                .create("valStr", c -> c.changeName("valStr").changeNullable(true).changeType(t -> t.changeType("string")))
-                .create("valInt", c -> c.changeName("valInt").changeNullable(true).changeType(t -> t.changeType("int")))
-                .create("valNullable", c -> c.changeName("valNullable").changeNullable(true).changeType(t -> t.changeType("short")).changeNullable(true))
+                .create("key", c -> c.changeName("key").changeNullable(false).changeType(t -> t.changeType("UUID")))
+                .create("affKey", c -> c.changeName("affKey").changeNullable(false).changeType(t -> t.changeType("INT64")))
+                .create("valStr", c -> c.changeName("valStr").changeNullable(true).changeType(t -> t.changeType("STRING")))
+                .create("valInt", c -> c.changeName("valInt").changeNullable(true).changeType(t -> t.changeType("INT32")))
+                .create("valNull", c -> c.changeName("valNull").changeNullable(true).changeType(t -> t.changeType("INT16")).changeNullable(true))
             )
             .changeIndices(idxs -> idxs
                 .create("PK", idx -> idx
@@ -167,23 +185,56 @@ class DynamicTableCreationTest {
                     .changeColNames(new String[] {"key", "affKey"})
                     .changeColumns(c -> c
                         .create("key", t -> t.changeName("key").changeAsc(true))
-                        .create("affKey", t -> t.changeName("affKey").changeAsc(true)))
+                        .create("affKey", t -> t.changeName("affKey").changeAsc(false)))
                     .changeAffinityColumns(new String[] {"affKey"}))
             ));
 
         final UUID uuid = UUID.randomUUID();
+        final UUID uuid2 = UUID.randomUUID();
 
         // Put data on node 1.
         Table tbl1 = waitForTable(clusterNodes.get(1));
-        tbl1.insert(tbl1.tupleBuilder().set("key", uuid).set("affKey", 42)
-            .set("valStr", "String value").set("valInt", 73).set("valNullable", null).build());
+        KeyValueBinaryView kvView1 = tbl1.kvView();
+
+        tbl1.insert(tbl1.tupleBuilder().set("key", uuid).set("affKey", 42L)
+            .set("valStr", "String value").set("valInt", 73).set("valNull", null).build());
+
+        kvView1.put(kvView1.tupleBuilder().set("key", uuid2).set("affKey", 4242L).build(),
+            kvView1.tupleBuilder().set("valStr", "String value 2").set("valInt", 7373).set("valNull", null).build());
 
         // Get data on node 2.
         Table tbl2 = waitForTable(clusterNodes.get(2));
-        final Tuple val = tbl2.get(tbl2.tupleBuilder().set("key", uuid).set("affKey", 42).build());
-
-        assertEquals("String value", val.value("valStr"));
-        assertEquals(73, (int)val.value("valInt"));
-        assertNull(val.value("valNullable"));
+        KeyValueBinaryView kvView2 = tbl2.kvView();
+
+        final Tuple keyTuple1 = tbl2.tupleBuilder().set("key", uuid).set("affKey", 42L).build();
+        final Tuple keyTuple2 = tbl2.tupleBuilder().set("key", uuid2).set("affKey", 4242L).build();
+
+        // KV view must NOT return key columns in value.
+        assertThrows(IllegalArgumentException.class, () -> kvView2.get(keyTuple1).value("key"));
+        assertThrows(IllegalArgumentException.class, () -> kvView2.get(keyTuple1).value("affKey"));
+        assertThrows(IllegalArgumentException.class, () -> kvView2.get(keyTuple2).value("key"));
+        assertThrows(IllegalArgumentException.class, () -> kvView2.get(keyTuple2).value("affKey"));
+
+        // Record binary view MUST return key columns in value.
+        assertEquals(uuid, tbl2.get(keyTuple1).value("key"));
+        assertEquals(42L, (Long)tbl2.get(keyTuple1).value("affKey"));
+        assertEquals(uuid2, tbl2.get(keyTuple2).value("key"));
+        assertEquals(4242L, (Long)tbl2.get(keyTuple2).value("affKey"));
+
+        assertEquals("String value", tbl2.get(keyTuple1).value("valStr"));
+        assertEquals(73, (Integer)tbl2.get(keyTuple1).value("valInt"));
+        assertNull(tbl2.get(keyTuple1).value("valNull"));
+
+        assertEquals("String value 2", tbl2.get(keyTuple2).value("valStr"));
+        assertEquals(7373, (Integer)tbl2.get(keyTuple2).value("valInt"));
+        assertNull(tbl2.get(keyTuple2).value("valNull"));
+
+        assertEquals("String value", kvView2.get(keyTuple1).value("valStr"));
+        assertEquals(73, (Integer)kvView2.get(keyTuple1).value("valInt"));
+        assertNull(kvView2.get(keyTuple1).value("valNull"));
+
+        assertEquals("String value 2", kvView2.get(keyTuple2).value("valStr"));
+        assertEquals(7373, (Integer)kvView2.get(keyTuple2).value("valInt"));
+        assertNull(kvView2.get(keyTuple2).value("valNull"));
     }
 }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/TableCreationTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/TableCreationTest.java
index 035caad..e9834ae 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/TableCreationTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/TableCreationTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.UUID;
 import org.apache.ignite.app.Ignite;
 import org.apache.ignite.app.IgnitionManager;
+import org.apache.ignite.table.KeyValueBinaryView;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
 import org.junit.jupiter.api.Assertions;
@@ -162,29 +163,58 @@ class TableCreationTest {
 
         clusterNodes.forEach(Assertions::assertNotNull);
 
-        { /* Table 1.*/
+        { /* Table 1. */
             Table tbl1 = clusterNodes.get(1).tables().table("tbl1");
-            tbl1.insert(tbl1.tupleBuilder().set("key", 1L).set("val", 111L).build());
+            KeyValueBinaryView kvView1 = tbl1.kvView();
+
+            tbl1.insert(tbl1.tupleBuilder().set("key", 1L).set("val", 111).build());
+            kvView1.put(tbl1.tupleBuilder().set("key", 2L).build(), tbl1.tupleBuilder().set("val", 222).build());
 
             Table tbl2 = clusterNodes.get(2).tables().table("tbl1");
-            assertEquals(111L, tbl2.get(tbl2.tupleBuilder().set("key", 1L).build()));
+            KeyValueBinaryView kvView2 = tbl2.kvView();
+
+            final Tuple keyTuple1 = tbl2.tupleBuilder().set("key", 1L).build();
+            final Tuple keyTuple2 = kvView2.tupleBuilder().set("key", 2L).build();
+
+            assertEquals(111, (Integer)tbl2.get(keyTuple1).value("val"));
+            assertEquals(111, (Integer)kvView1.get(keyTuple1).value("val"));
+            assertEquals(222, (Integer)tbl2.get(keyTuple2).value("val"));
+            assertEquals(222, (Integer)kvView1.get(keyTuple2).value("val"));
         }
 
         { /* Table 2. */
             final UUID uuid = UUID.randomUUID();
+            final UUID uuid2 = UUID.randomUUID();
 
             // Put data on node 1.
             Table tbl1 = clusterNodes.get(1).tables().table("tbl1");
+            KeyValueBinaryView kvView1 = tbl1.kvView();
+
             tbl1.insert(tbl1.tupleBuilder().set("key", uuid).set("affKey", 42L)
-                .set("valStr", "String value").set("valInt", 73L).set("valNullable", null).build());
+                .set("valStr", "String value").set("valInt", 73).set("valNullable", null).build());
+
+            kvView1.put(kvView1.tupleBuilder().set("key", uuid2).set("affKey", 4242L).build(),
+                kvView1.tupleBuilder().set("valStr", "String value 2").set("valInt", 7373).set("valNullable", null).build());
 
             // Get data on node 2.
             Table tbl2 = clusterNodes.get(2).tables().table("tbl1");
-            final Tuple val = tbl2.get(tbl1.tupleBuilder().set("key", uuid).set("affKey", 42L).build());
-
-            assertEquals("String value", val.value("valStr"));
-            assertEquals(73L, (Long)val.value("valInt"));
-            assertNull(val.value("valNullable"));
+            KeyValueBinaryView kvView2 = tbl2.kvView();
+
+            final Tuple keyTuple1 = tbl2.tupleBuilder().set("key", uuid).set("affKey", 42L).build();
+            final Tuple keyTuple2 = kvView2.tupleBuilder().set("key", uuid2).set("affKey", 4242L).build();
+
+            assertEquals("String value", tbl2.get(keyTuple1).value("valStr"));
+            assertEquals("String value", kvView2.get(keyTuple1).value("valStr"));
+            assertEquals("String value 2", tbl2.get(keyTuple2).value("valStr"));
+            assertEquals("String value 2", kvView2.get(keyTuple2).value("valStr"));
+            assertEquals(73, (Integer)tbl2.get(keyTuple1).value("valInt"));
+            assertEquals(73, (Integer)kvView2.get(keyTuple1).value("valInt"));
+            assertEquals(7373, (Integer)tbl2.get(keyTuple2).value("valInt"));
+            assertEquals(7373, (Integer)kvView2.get(keyTuple2).value("valInt"));
+            assertNull(tbl2.get(keyTuple1).value("valNullable"));
+            assertNull(kvView2.get(keyTuple1).value("valNullable"));
+            assertNull(tbl2.get(keyTuple2).value("valNullable"));
+            assertNull(kvView2.get(keyTuple2).value("valNullable"));
         }
     }
 }
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/Row.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/Row.java
index 9d967d3..c3bb8c5 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/Row.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/Row.java
@@ -492,33 +492,43 @@ public class Row implements BinaryRow {
      * @return Encoded offset (from the row start) of the requested fixlen column.
      */
     int fixlenColumnOffset(Columns cols, int baseOff, int idx, boolean hasVarTbl, boolean hasNullMap) {
-        int off = 0;
+        int colOff = 0;
 
         int payloadOff = baseOff + CHUNK_LEN_FIELD_SIZE;
 
-        if (hasNullMap) {
-            payloadOff += cols.nullMapSize();
-
-            int nullMapOff = nullMapOffset(baseOff);
-
-            int nullMapIdx = idx / 8;
-
-            // Fold offset based on the whole map bytes in the schema
-            for (int i = 0; i < nullMapIdx; i++)
-                off += cols.foldFixedLength(i, readByte(nullMapOff + i));
+        // Calculate fixlen column offset.
+        {
+            int colByteIdx = idx / 8;
 
             // Set bits starting from posInByte, inclusive, up to either the end of the byte or the last column index, inclusive
             int startBit = idx % 8;
-            int endBit = nullMapIdx == cols.nullMapSize() - 1 ? ((cols.numberOfFixsizeColumns() - 1) % 8) : 7;
+            int endBit = colByteIdx == (cols.length() + 7) / 8 - 1 ? ((cols.numberOfFixsizeColumns() - 1) % 8) : 7;
             int mask = (0xFF >> (7 - endBit)) & (0xFF << startBit);
 
-            off += cols.foldFixedLength(nullMapIdx, readByte(nullMapOff + nullMapIdx) | mask);
+            if (hasNullMap) {
+                payloadOff += cols.nullMapSize();
+
+                // Fold offset based on the whole map bytes in the schema
+                for (int i = 0; i < colByteIdx; i++)
+                    colOff += cols.foldFixedLength(i, readByte(nullMapOffset(baseOff) + i));
+
+                colOff += cols.foldFixedLength(colByteIdx, readByte(nullMapOffset(baseOff) + colByteIdx) | mask);
+            }
+            else {
+                for (int i = 0; i < colByteIdx; i++)
+                    colOff += cols.foldFixedLength(i, 0);
+
+                colOff += cols.foldFixedLength(colByteIdx, mask);
+            }
         }
 
-        if (hasVarTbl)
-            payloadOff += varlenItemOffset(readShort(payloadOff));
+        if (hasVarTbl) {
+            short verlenItems = readShort(payloadOff);
+
+            payloadOff += varlenItemOffset(verlenItems);
+        }
 
-        return payloadOff + off;
+        return payloadOff + colOff;
     }
 
     /**
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java
index 83233a9..b1ae96a 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaManager.java
@@ -252,13 +252,13 @@ public class SchemaManager extends Producer<SchemaEvent, SchemaEventParameters>
      */
     private NativeType createType(ColumnTypeView type) {
         switch (type.type().toLowerCase()) {
-            case "byte":
+            case "int8":
                 return NativeTypes.BYTE;
-            case "short":
+            case "int16":
                 return NativeTypes.SHORT;
-            case "int":
+            case "int32":
                 return NativeTypes.INTEGER;
-            case "long":
+            case "int64":
                 return NativeTypes.LONG;
             case "float":
                 return NativeTypes.FLOAT;
@@ -267,11 +267,13 @@ public class SchemaManager extends Producer<SchemaEvent, SchemaEventParameters>
             case "uuid":
                 return NativeTypes.UUID;
             case "bitmask":
+                assert type.length() > 0;
+
                 return NativeTypes.bitmaskOf(type.length());
             case "string":
                 return type.length() == 0 ? NativeTypes.STRING : NativeTypes.stringOf(type.length());
             case "bytes":
-                return NativeTypes.blobOf(type.length());
+                return type.length() == 0 ? NativeTypes.BYTES : NativeTypes.blobOf(type.length());
 
             default:
                 throw new IllegalStateException("Unsupported column type: " + type.type());
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 8da7278..777adf1 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
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.schema;
 
+import org.apache.ignite.internal.schema.registry.SchemaRegistryException;
+import org.jetbrains.annotations.NotNull;
+
 /**
  * Table schema registry interface.
  */
@@ -29,6 +32,7 @@ public interface SchemaRegistry {
     /**
      * @param ver Schema version.
      * @return Schema of given version.
+     * @throws SchemaRegistryException If schema was not found.
      */
-    SchemaDescriptor schema(int ver);
+    @NotNull SchemaDescriptor schema(int ver) throws SchemaRegistryException;
 }
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
index cdb1d31..659dd5f 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
@@ -40,11 +40,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
  * Tests row assembling and reading.
  */
 public class RowTest {
-    /** */
+    /** Random. */
     private Random rnd;
 
     /**
-     *
+     * Initialization.
      */
     @BeforeEach
     public void initRandom() {
@@ -56,10 +56,42 @@ public class RowTest {
     }
 
     /**
-     *
+     * Check row serialization for schema with nullable fix-sized columns only.
      */
     @Test
-    public void fixedSizes() {
+    public void nullableFixSizedColumns() {
+        Column[] keyCols = new Column[] {
+            new Column("keyByteCol", BYTE, false),
+            new Column("keyShortCol", SHORT, false),
+            new Column("keyIntCol", INTEGER, false),
+            new Column("keyLongCol", LONG, false),
+            new Column("keyFloatCol", FLOAT, false),
+            new Column("keyDoubleCol", DOUBLE, false),
+            new Column("keyUuidCol", UUID, false),
+            new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), false),
+            new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), false)
+        };
+
+        Column[] valCols = new Column[] {
+            new Column("valByteCol", BYTE, false),
+            new Column("valShortCol", SHORT, false),
+            new Column("valIntCol", INTEGER, false),
+            new Column("valLongCol", LONG, false),
+            new Column("valFloatCol", FLOAT, false),
+            new Column("valDoubleCol", DOUBLE, false),
+            new Column("valUuidCol", UUID, false),
+            new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), false),
+            new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), false)
+        };
+
+        checkSchema(keyCols, valCols);
+    }
+
+    /**
+     * Check row serialization for schema with non-nullable fix-sized columns only.
+     */
+    @Test
+    public void fixSizedColumns() {
         Column[] keyCols = new Column[] {
             new Column("keyByteCol", BYTE, true),
             new Column("keyShortCol", SHORT, true),
@@ -88,17 +120,17 @@ public class RowTest {
     }
 
     /**
-     *
+     * Check row serialization for schema with various columns.
      */
     @Test
-    public void variableSizes() {
+    public void mixedColumns() {
         Column[] keyCols = new Column[] {
-            new Column("keyByteCol", BYTE, true),
-            new Column("keyShortCol", SHORT, true),
-            new Column("keyIntCol", INTEGER, true),
-            new Column("keyLongCol", LONG, true),
-            new Column("keyBytesCol", BYTES, true),
-            new Column("keyStringCol", STRING, true),
+            new Column("keyByteCol", BYTE, false),
+            new Column("keyShortCol", SHORT, false),
+            new Column("keyIntCol", INTEGER, false),
+            new Column("keyLongCol", LONG, false),
+            new Column("keyBytesCol", BYTES, false),
+            new Column("keyStringCol", STRING, false),
         };
 
         Column[] valCols = new Column[] {
@@ -114,10 +146,28 @@ public class RowTest {
     }
 
     /**
-     *
+     * Check row serialization for schema with non-nullable varlen columns only.
+     */
+    @Test
+    public void varlenColumns() {
+        Column[] keyCols = new Column[] {
+            new Column("keyBytesCol", BYTES, false),
+            new Column("keyStringCol", STRING, false),
+        };
+
+        Column[] valCols = new Column[] {
+            new Column("valBytesCol", BYTES, false),
+            new Column("valStringCol", STRING, false),
+        };
+
+        checkSchema(keyCols, valCols);
+    }
+
+    /**
+     * Check row serialization for schema with nullable varlen columns only.
      */
     @Test
-    public void mixedSizes() {
+    public void nullableVarlenColumns() {
         Column[] keyCols = new Column[] {
             new Column("keyBytesCol", BYTES, true),
             new Column("keyStringCol", STRING, true),
@@ -132,7 +182,10 @@ public class RowTest {
     }
 
     /**
+     * Checks schema is independent from prodived column order.
      *
+     * @param keyCols Key columns.
+     * @param valCols Value columns.
      */
     private void checkSchema(Column[] keyCols, Column[] valCols) {
         checkSchemaShuffled(keyCols, valCols);
@@ -144,38 +197,38 @@ public class RowTest {
     }
 
     /**
+     * Checks schema for given columns.
      *
+     * @param keyCols Key columns.
+     * @param valCols Value columns.
      */
     private void checkSchemaShuffled(Column[] keyCols, Column[] valCols) {
         SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols);
 
-        Object[] checkArr = sequence(sch);
+        Object[] checkArr = generateRowValues(sch);
 
         checkValues(sch, checkArr);
 
-        while (checkArr[0] != null) {
-            int idx = 0;
+        for (int idx = 0; idx < checkArr.length; idx++) {
+            if (!sch.column(idx).nullable())
+                continue;
 
             Object prev = checkArr[idx];
             checkArr[idx] = null;
 
             checkValues(sch, checkArr);
 
-            while (idx < checkArr.length - 1 && checkArr[idx + 1] != null) {
-                checkArr[idx] = prev;
-                prev = checkArr[idx + 1];
-                checkArr[idx + 1] = null;
-                idx++;
-
-                checkValues(sch, checkArr);
-            }
+            checkArr[idx] = prev;
         }
     }
 
     /**
+     * Generate row values for given row schema.
      *
+     * @param schema Row schema.
+     * @return Row values.
      */
-    private Object[] sequence(SchemaDescriptor schema) {
+    private Object[] generateRowValues(SchemaDescriptor schema) {
         Object[] res = new Object[schema.length()];
 
         for (int i = 0; i < res.length; i++) {
@@ -188,14 +241,20 @@ public class RowTest {
     }
 
     /**
+     * Generates random value of a given type.
      *
+     * @param type Value type.
+     * @return Random value of requested type.
      */
     private Object generateRandomValue(NativeType type) {
         return TestUtils.generateRandomValue(rnd, type);
     }
 
     /**
+     * Validates row values after serialization-then-deserialization.
      *
+     * @param schema Row schema.
+     * @param vals Row values.
      */
     private void checkValues(SchemaDescriptor schema, Object... vals) {
         assertEquals(schema.keyColumns().length() + schema.valueColumns().length(), vals.length);
@@ -312,7 +371,7 @@ public class RowTest {
     }
 
     /**
-     *
+     * Shuffle columns.
      */
     private void shuffle(Column[] cols) {
         Collections.shuffle(Arrays.asList(cols));