ibessonov commented on code in PR #898:
URL: https://github.com/apache/ignite-3/pull/898#discussion_r905896545


##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTuple.java:
##########
@@ -0,0 +1,58 @@
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.schema.row.InternalTuple;
+
+/**
+ * Utility for access to binary tuple elements as typed values and with schema 
knowledge that allows to read
+ * elements as objects.
+ */
+public class BinaryTuple extends BinaryTupleReader implements InternalTuple {
+    /** Tuple schema. */
+    private final BinaryTupleSchema schema;
+
+    /**
+     * Constructor.
+     *
+     * @param schema Tuple schema.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTuple(BinaryTupleSchema schema, byte[] bytes) {
+        super(schema.elementCount(), bytes);
+        this.schema = schema;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param schema Tuple schema.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTuple(BinaryTupleSchema schema, ByteBuffer buffer) {
+        super(schema.elementCount(), buffer);
+        this.schema = schema;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int count() {
+        return elementCount();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BigDecimal decimalValue(int index) {

Review Comment:
   I have an idea. We should add "scale" parameter to the method's signature. 
This way BinaryTupleReader could implement InternalTuple. For me, this would be 
a preferred way of using tuples. In most cases, external schema object is 
totally fine, we don't usually need a "value(i)" method



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleBuilder.java:
##########
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utility to construct a binary tuple.
+ */
+public class BinaryTupleBuilder {
+    /** The buffer size allocated for values when we do not know anything 
better. */
+    private static final int DEFAULT_BUFFER_SIZE = 4000;
+
+    /** Current element. */
+    private int elementIndex = 0;
+
+    /** Number of elements in the tuple. */
+    private final int numElements;
+
+    /** Size of an offset table entry. */
+    private final int entrySize;
+
+    /** Position of the varlen offset table. */
+    private final int entryBase;
+
+    /** Starting position of variable-length values. */
+    private final int valueBase;
+
+    /** Buffer for tuple content. */
+    private ByteBuffer buffer;
+
+    /** Charset encoder for strings. Initialized lazily. */
+    private CharsetEncoder cachedEncoder;
+
+    /** Flag indicating if any NULL values were really put here. */
+    private boolean hasNullValues = false;
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     */
+    private BinaryTupleBuilder(int numElements, boolean allowNulls, int 
totalValueSize) {
+        this.numElements = numElements;
+
+        int base = BinaryTupleSchema.HEADER_SIZE;
+        if (allowNulls) {
+            base += BinaryTupleSchema.nullMapSize(numElements);
+        }
+
+        entryBase = base;
+
+        if (totalValueSize < 0) {
+            entrySize = 4;
+        } else {
+            entrySize = 
BinaryTupleSchema.flagsToEntrySize(BinaryTupleSchema.valueSizeToFlags(totalValueSize));
+        }
+
+        valueBase = base + entrySize * numElements;
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema) {
+        return create(schema.elementCount(), schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls) {
+        return create(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema, int 
totalValueSize) {
+        return create(schema.elementCount(), schema.hasNullableElements(), 
totalValueSize);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls, int 
totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocate(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema) 
{
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls) {
+        return createWithDirectBuffer(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema, 
int totalValueSize) {
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements(), totalValueSize);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls, int totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocateDirect(totalValueSize);

Review Comment:
   Maybe it would be better to pass already allocated buffer instead of making 
a new one. "allocateDirect" should likely be avoided, since it can cause 
immediate GC and might even fail.
   We should keep that in mind, maybe manual memory allocations and 
"GridUnsafe#wrapPointer" will be a better choice for table operations. This is 
a topic for another day.



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleBuilder.java:
##########
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utility to construct a binary tuple.
+ */
+public class BinaryTupleBuilder {
+    /** The buffer size allocated for values when we do not know anything 
better. */
+    private static final int DEFAULT_BUFFER_SIZE = 4000;
+
+    /** Current element. */
+    private int elementIndex = 0;
+
+    /** Number of elements in the tuple. */
+    private final int numElements;
+
+    /** Size of an offset table entry. */
+    private final int entrySize;
+
+    /** Position of the varlen offset table. */
+    private final int entryBase;
+
+    /** Starting position of variable-length values. */
+    private final int valueBase;
+
+    /** Buffer for tuple content. */
+    private ByteBuffer buffer;
+
+    /** Charset encoder for strings. Initialized lazily. */
+    private CharsetEncoder cachedEncoder;
+
+    /** Flag indicating if any NULL values were really put here. */
+    private boolean hasNullValues = false;
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     */
+    private BinaryTupleBuilder(int numElements, boolean allowNulls, int 
totalValueSize) {
+        this.numElements = numElements;
+
+        int base = BinaryTupleSchema.HEADER_SIZE;
+        if (allowNulls) {
+            base += BinaryTupleSchema.nullMapSize(numElements);
+        }
+
+        entryBase = base;
+
+        if (totalValueSize < 0) {
+            entrySize = 4;
+        } else {
+            entrySize = 
BinaryTupleSchema.flagsToEntrySize(BinaryTupleSchema.valueSizeToFlags(totalValueSize));
+        }
+
+        valueBase = base + entrySize * numElements;
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema) {
+        return create(schema.elementCount(), schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls) {
+        return create(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema, int 
totalValueSize) {
+        return create(schema.elementCount(), schema.hasNullableElements(), 
totalValueSize);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls, int 
totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocate(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema) 
{
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls) {
+        return createWithDirectBuffer(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema, 
int totalValueSize) {
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements(), totalValueSize);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls, int totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocateDirect(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Check if the binary tuple contains a null map.
+     */
+    public boolean hasNullMap() {
+        return entryBase > BinaryTupleSchema.HEADER_SIZE;
+    }
+
+    /**
+     * Append a NULL value for the current element.
+     *
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendNull() {
+        if (!hasNullMap()) {
+            throw new IgniteInternalException("Appending a NULL value in 
binary tuple builder with disabled NULLs");
+        }
+
+        hasNullValues = true;
+
+        int nullIndex = BinaryTupleSchema.HEADER_SIZE + elementIndex / 8;
+        byte nullMask = (byte) (1 << (elementIndex % 8));
+        buffer.put(nullIndex, (byte) (buffer.get(nullIndex) | nullMask));
+
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(byte value) {
+        if (value != 0) {
+            putByte(value);
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(Byte value) {
+        return value == null ? appendNull() : appendByte(value.byteValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendShort(short value) {
+        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+            if (value != 0) {

Review Comment:
   We could save some code by returning a `appendByte((byte) value)` here



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleBuilder.java:
##########
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utility to construct a binary tuple.
+ */
+public class BinaryTupleBuilder {
+    /** The buffer size allocated for values when we do not know anything 
better. */
+    private static final int DEFAULT_BUFFER_SIZE = 4000;
+
+    /** Current element. */
+    private int elementIndex = 0;
+
+    /** Number of elements in the tuple. */
+    private final int numElements;
+
+    /** Size of an offset table entry. */
+    private final int entrySize;
+
+    /** Position of the varlen offset table. */
+    private final int entryBase;
+
+    /** Starting position of variable-length values. */
+    private final int valueBase;
+
+    /** Buffer for tuple content. */
+    private ByteBuffer buffer;
+
+    /** Charset encoder for strings. Initialized lazily. */
+    private CharsetEncoder cachedEncoder;
+
+    /** Flag indicating if any NULL values were really put here. */
+    private boolean hasNullValues = false;
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     */
+    private BinaryTupleBuilder(int numElements, boolean allowNulls, int 
totalValueSize) {
+        this.numElements = numElements;
+
+        int base = BinaryTupleSchema.HEADER_SIZE;
+        if (allowNulls) {
+            base += BinaryTupleSchema.nullMapSize(numElements);
+        }
+
+        entryBase = base;
+
+        if (totalValueSize < 0) {
+            entrySize = 4;
+        } else {
+            entrySize = 
BinaryTupleSchema.flagsToEntrySize(BinaryTupleSchema.valueSizeToFlags(totalValueSize));
+        }
+
+        valueBase = base + entrySize * numElements;
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema) {
+        return create(schema.elementCount(), schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls) {
+        return create(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema, int 
totalValueSize) {
+        return create(schema.elementCount(), schema.hasNullableElements(), 
totalValueSize);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls, int 
totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocate(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema) 
{
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls) {
+        return createWithDirectBuffer(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema, 
int totalValueSize) {
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements(), totalValueSize);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls, int totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocateDirect(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Check if the binary tuple contains a null map.
+     */
+    public boolean hasNullMap() {
+        return entryBase > BinaryTupleSchema.HEADER_SIZE;
+    }
+
+    /**
+     * Append a NULL value for the current element.
+     *
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendNull() {
+        if (!hasNullMap()) {
+            throw new IgniteInternalException("Appending a NULL value in 
binary tuple builder with disabled NULLs");
+        }
+
+        hasNullValues = true;
+
+        int nullIndex = BinaryTupleSchema.HEADER_SIZE + elementIndex / 8;
+        byte nullMask = (byte) (1 << (elementIndex % 8));
+        buffer.put(nullIndex, (byte) (buffer.get(nullIndex) | nullMask));
+
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(byte value) {
+        if (value != 0) {
+            putByte(value);
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(Byte value) {
+        return value == null ? appendNull() : appendByte(value.byteValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendShort(short value) {
+        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+            if (value != 0) {
+                putByte((byte) value);
+            }
+        } else {
+            putShort(value);
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendShort(Short value) {
+        return value == null ? appendNull() : appendShort(value.shortValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendInt(int value) {
+        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+            if (value != 0) {

Review Comment:
   Same goes here



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleSchema.java:
##########
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+
+/**
+ * Description of a binary tuple.
+ */
+public class BinaryTupleSchema {
+    /** Size of a tuple header, in bytes. */
+    public static int HEADER_SIZE = 1;
+
+    /** Mask for size of entries in variable-length offset table. */
+    public static final int VARSIZE_MASK = 0x011;
+
+    /** Flag that indicates null map presence. */
+    public static final int NULLMAP_FLAG = 0b100;
+
+    /** Default value for UUID elements. */
+    public static final UUID DEFAULT_UUID = new UUID(0, 0);
+
+    /** Default value for Date elements (Jan 1st 1 BC). */
+    public static final LocalDate DEFAULT_DATE = LocalDate.of(0, 1, 1);
+
+    /** Default value for Time elements (00:00:00). */
+    public static final LocalTime DEFAULT_TIME = LocalTime.of(0, 0);
+
+    /** Default value for DateTime elements (Jan 1st 1 BC, 00:00:00). */
+    public static final LocalDateTime DEFAULT_DATE_TIME = LocalDateTime.of(0, 
1, 1, 0, 0);
+
+    /** Default value for Timestamp elements. */
+    public static final Instant DEFAULT_TIMESTAMP = Instant.EPOCH;
+
+    /**
+     * Tuple element description used for tuple parsing and building.
+     *
+     * For binary tuples encoding of values is determined by its basic type 
and the value itself. Parameters
+     * like precision and scale defined for columns in schema are not taken 
into account. The only exception
+     * is the Decimal type where the scale parameter is required for decoding.
+     *
+     * To keep things simple we have the scale parameter everywhere but really 
use it only for Decimals.
+     */
+    public static final class Element {
+        final NativeTypeSpec typeSpec;
+
+        final int decimalScale;
+
+        final boolean nullable;
+
+        public Element(NativeType type, boolean nullable) {
+            typeSpec = type.spec();
+
+            if (typeSpec == NativeTypeSpec.DECIMAL) {
+                DecimalNativeType decimalType = (DecimalNativeType) type;
+                decimalScale = decimalType.scale();
+            } else {
+                decimalScale = 0;
+            }
+
+            this.nullable = nullable;
+        }
+    }
+
+    /** Descriptors of all tuple elements. */
+    private final Element[] elements;
+
+    /** Indicates if the schema contains one or more nullable elements. */
+    private final boolean hasNullables;
+
+    /**
+     * Constructor.
+     *
+     * @param elements Tuple element descriptors.
+     */
+    private BinaryTupleSchema(Element[] elements) {
+        this.elements = elements;
+        this.hasNullables = Arrays.stream(elements).anyMatch(element -> 
element.nullable);
+    }
+
+    /**
+     * Create a tuple schema based on a range of row columns.
+     *
+     * @param descriptor Row schema.
+     * @param colBegin First columns in the range.
+     * @param colEnd Last column in the range (exclusive).
+     * @return Tuple schema.
+     */
+    private static BinaryTupleSchema createSchema(SchemaDescriptor descriptor, 
int colBegin, int colEnd) {
+        int numCols = colEnd - colBegin;
+
+        Element[] elements = new Element[numCols];
+
+        for (int i = 0; i < numCols; i++) {
+            Column column = descriptor.column(i);
+            elements[i] = new Element(column.type(), column.nullable());
+        }
+
+        return new BinaryTupleSchema(elements);
+    }
+
+    /**
+     * Create a tuple schema with specified elements.
+     *
+     * @param elements Tuple elements.
+     * @return Tuple schema.
+     */
+    public static BinaryTupleSchema create(Element[] elements) {
+        return new BinaryTupleSchema(elements.clone());
+    }
+
+    /**
+     * Create a schema for binary tuples with all columns of a row.
+     *
+     * @param descriptor Row schema.
+     * @return Tuple schema.
+     */
+    public static BinaryTupleSchema createRowSchema(SchemaDescriptor 
descriptor) {
+        return createSchema(descriptor, 0, descriptor.length());
+    }
+
+    /**
+     * Create a schema for binary tuples with key-only columns of a row.
+     *
+     * @param descriptor Row schema.
+     * @return Tuple schema.
+     */
+    public static BinaryTupleSchema createKeySchema(SchemaDescriptor 
descriptor) {
+        return createSchema(descriptor, 0, descriptor.keyColumns().length());
+    }
+
+    /**
+     * Create a schema for binary tuples with value-only columns of a row.
+     *
+     * @param descriptor Row schema.
+     * @return Tuple schema.
+     */
+    public static BinaryTupleSchema createValueSchema(SchemaDescriptor 
descriptor) {
+        return createSchema(descriptor, descriptor.keyColumns().length(), 
descriptor.length());
+    }
+
+    /**
+     * Create a schema for binary tuples with selected row columns.
+     *
+     * @param descriptor Row schema.
+     * @param indexes List of columns.
+     * @return Tuple schema.
+     */
+    public static BinaryTupleSchema createValueSchema(SchemaDescriptor 
descriptor, int[] indexes) {
+        Element[] elements = new Element[indexes.length];
+
+        for (int i : indexes) {

Review Comment:
   Does it really matter for arrays? I think compiler produces the same 
index-based loop



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleBuilder.java:
##########
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utility to construct a binary tuple.
+ */
+public class BinaryTupleBuilder {
+    /** The buffer size allocated for values when we do not know anything 
better. */
+    private static final int DEFAULT_BUFFER_SIZE = 4000;
+
+    /** Current element. */
+    private int elementIndex = 0;
+
+    /** Number of elements in the tuple. */
+    private final int numElements;
+
+    /** Size of an offset table entry. */
+    private final int entrySize;
+
+    /** Position of the varlen offset table. */
+    private final int entryBase;
+
+    /** Starting position of variable-length values. */
+    private final int valueBase;
+
+    /** Buffer for tuple content. */
+    private ByteBuffer buffer;
+
+    /** Charset encoder for strings. Initialized lazily. */
+    private CharsetEncoder cachedEncoder;
+
+    /** Flag indicating if any NULL values were really put here. */
+    private boolean hasNullValues = false;
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     */
+    private BinaryTupleBuilder(int numElements, boolean allowNulls, int 
totalValueSize) {
+        this.numElements = numElements;
+
+        int base = BinaryTupleSchema.HEADER_SIZE;
+        if (allowNulls) {
+            base += BinaryTupleSchema.nullMapSize(numElements);
+        }
+
+        entryBase = base;
+
+        if (totalValueSize < 0) {
+            entrySize = 4;
+        } else {
+            entrySize = 
BinaryTupleSchema.flagsToEntrySize(BinaryTupleSchema.valueSizeToFlags(totalValueSize));
+        }
+
+        valueBase = base + entrySize * numElements;
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema) {
+        return create(schema.elementCount(), schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls) {
+        return create(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema, int 
totalValueSize) {
+        return create(schema.elementCount(), schema.hasNullableElements(), 
totalValueSize);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls, int 
totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocate(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema) 
{
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls) {
+        return createWithDirectBuffer(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema, 
int totalValueSize) {
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements(), totalValueSize);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls, int totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocateDirect(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Check if the binary tuple contains a null map.
+     */
+    public boolean hasNullMap() {
+        return entryBase > BinaryTupleSchema.HEADER_SIZE;
+    }
+
+    /**
+     * Append a NULL value for the current element.
+     *
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendNull() {
+        if (!hasNullMap()) {
+            throw new IgniteInternalException("Appending a NULL value in 
binary tuple builder with disabled NULLs");
+        }
+
+        hasNullValues = true;
+
+        int nullIndex = BinaryTupleSchema.HEADER_SIZE + elementIndex / 8;
+        byte nullMask = (byte) (1 << (elementIndex % 8));
+        buffer.put(nullIndex, (byte) (buffer.get(nullIndex) | nullMask));
+
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(byte value) {
+        if (value != 0) {
+            putByte(value);
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(Byte value) {
+        return value == null ? appendNull() : appendByte(value.byteValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendShort(short value) {
+        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+            if (value != 0) {
+                putByte((byte) value);
+            }
+        } else {
+            putShort(value);
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendShort(Short value) {
+        return value == null ? appendNull() : appendShort(value.shortValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendInt(int value) {
+        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+            if (value != 0) {
+                putByte((byte) value);
+            }
+        } else {
+            if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
+                putShort((short) value);
+            } else {
+                putInt(value);
+            }
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendInt(Integer value) {
+        return value == null ? appendNull() : appendInt(value.intValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendLong(long value) {

Review Comment:
   And this method can be drastically shortened, if you wish



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleReader.java:
##########
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+
+/**
+ * Utility for access to binary tuple elements as typed values.
+ */
+public class BinaryTupleReader extends BinaryTupleParser {
+    /** A helper to locate and handle tuple elements. */
+    private class ElementSink implements BinaryTupleParser.Sink {
+        int offset;
+        int length;
+
+        /** {@inheritDoc} */
+        @Override
+        public void nextElement(int index, int begin, int end) {
+            offset = begin;
+            length = end - begin;
+        }
+
+        boolean isNull() {
+            return offset == 0;
+        }
+
+        byte asByte() {
+            if (length == 0) {
+                return 0;
+            }
+            assert length == Byte.BYTES;
+            return buffer.get(offset);
+        }
+
+        short asShort() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            assert length == Short.BYTES;
+            return buffer.getShort(offset);
+        }
+
+        int asInt() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            assert length == Integer.BYTES;
+            return buffer.getInt(offset);
+        }
+
+        long asLong() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            if (length == Integer.BYTES) {
+                return buffer.getInt(offset);
+            }
+            assert length == Long.BYTES;
+            return buffer.getLong(offset);
+        }
+
+        float asFloat() {
+            if (length == 0) {
+                return 0.0F;
+            }
+            assert length == Float.BYTES;
+            return buffer.getFloat(offset);
+        }
+
+        double asDouble() {
+            if (length == 0) {
+                return 0.0;
+            }
+            if (length == Float.BYTES) {
+                return buffer.getFloat(offset);
+            }
+            assert length == Double.BYTES;
+            return buffer.getDouble(offset);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTupleReader(int numElements, byte[] bytes) {
+        this(numElements, 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTupleReader(int numElements, ByteBuffer buffer) {
+        super(numElements, buffer);
+    }
+
+    /**
+     * Get underlying buffer.
+     *
+     * @return Buffer.
+     */
+    public ByteBuffer getBuffer() {
+        return buffer;
+    }
+
+    /**
+     * Checks whether the given element contains a null value.
+     *
+     * @param index Element index.
+     * @return {@code true} if this element contains a null value, {@code 
false} otherwise.
+     */
+    public boolean hasNullValue(int index) {
+        return seek(index).isNull();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public byte byteValue(int index) {
+        return seek(index).asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Byte byteValueBoxed(int index) {
+        var element = seek(index);

Review Comment:
   We don't use "val" if type is not explicitly mentioned in the statement. So 
please use a proper type for the variable



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleSchema.java:
##########
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+
+/**
+ * Description of a binary tuple.
+ */
+public class BinaryTupleSchema {
+    /** Size of a tuple header, in bytes. */
+    public static int HEADER_SIZE = 1;
+
+    /** Mask for size of entries in variable-length offset table. */
+    public static final int VARSIZE_MASK = 0x011;

Review Comment:
   I may be wrong, but I think you meant 0b11 instead of 0x11



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleReader.java:
##########
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+
+/**
+ * Utility for access to binary tuple elements as typed values.
+ */
+public class BinaryTupleReader extends BinaryTupleParser {
+    /** A helper to locate and handle tuple elements. */
+    private class ElementSink implements BinaryTupleParser.Sink {
+        int offset;
+        int length;
+
+        /** {@inheritDoc} */
+        @Override
+        public void nextElement(int index, int begin, int end) {
+            offset = begin;
+            length = end - begin;
+        }
+
+        boolean isNull() {
+            return offset == 0;
+        }
+
+        byte asByte() {
+            if (length == 0) {
+                return 0;
+            }
+            assert length == Byte.BYTES;
+            return buffer.get(offset);
+        }
+
+        short asShort() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            assert length == Short.BYTES;
+            return buffer.getShort(offset);
+        }
+
+        int asInt() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            assert length == Integer.BYTES;
+            return buffer.getInt(offset);
+        }
+
+        long asLong() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            if (length == Integer.BYTES) {
+                return buffer.getInt(offset);
+            }
+            assert length == Long.BYTES;
+            return buffer.getLong(offset);
+        }
+
+        float asFloat() {
+            if (length == 0) {
+                return 0.0F;
+            }
+            assert length == Float.BYTES;
+            return buffer.getFloat(offset);
+        }
+
+        double asDouble() {
+            if (length == 0) {
+                return 0.0;
+            }
+            if (length == Float.BYTES) {
+                return buffer.getFloat(offset);
+            }
+            assert length == Double.BYTES;
+            return buffer.getDouble(offset);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTupleReader(int numElements, byte[] bytes) {
+        this(numElements, 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTupleReader(int numElements, ByteBuffer buffer) {
+        super(numElements, buffer);
+    }
+
+    /**
+     * Get underlying buffer.
+     *
+     * @return Buffer.
+     */
+    public ByteBuffer getBuffer() {
+        return buffer;
+    }
+
+    /**
+     * Checks whether the given element contains a null value.
+     *
+     * @param index Element index.
+     * @return {@code true} if this element contains a null value, {@code 
false} otherwise.
+     */
+    public boolean hasNullValue(int index) {
+        return seek(index).isNull();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public byte byteValue(int index) {
+        return seek(index).asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Byte byteValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public short shortValue(int index) {
+        return seek(index).asShort();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Short shortValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asShort();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public int intValue(int index) {
+        return seek(index).asInt();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Integer intValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asInt();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public long longValue(int index) {
+        return seek(index).asLong();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Long longValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asLong();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public float floatValue(int index) {
+        return seek(index).asFloat();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Float floatValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asFloat();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public double doubleValue(int index) {
+        return seek(index).asDouble();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Double doubleValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asDouble();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public BigInteger numberValue(int index) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes = new byte[element.length];
+        buffer.duplicate().position(element.offset).limit(element.offset + 
element.length).get(bytes);
+        return new BigInteger(bytes);
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @param scale Decimal scale.
+     * @return Element value.
+     */
+    public BigDecimal decimalValue(int index, int scale) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes = new byte[element.length];
+        buffer.duplicate().position(element.offset).limit(element.offset + 
element.length).get(bytes);
+        return new BigDecimal(new BigInteger(bytes), scale);

Review Comment:
   Here I would ask you to call `numberValue` instead of duplicating its body



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTuple.java:
##########
@@ -0,0 +1,58 @@
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.schema.row.InternalTuple;
+
+/**
+ * Utility for access to binary tuple elements as typed values and with schema 
knowledge that allows to read
+ * elements as objects.
+ */
+public class BinaryTuple extends BinaryTupleReader implements InternalTuple {
+    /** Tuple schema. */
+    private final BinaryTupleSchema schema;
+
+    /**
+     * Constructor.
+     *
+     * @param schema Tuple schema.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTuple(BinaryTupleSchema schema, byte[] bytes) {
+        super(schema.elementCount(), bytes);
+        this.schema = schema;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param schema Tuple schema.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTuple(BinaryTupleSchema schema, ByteBuffer buffer) {
+        super(schema.elementCount(), buffer);
+        this.schema = schema;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int count() {
+        return elementCount();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BigDecimal decimalValue(int index) {
+        return decimalValue(index, schema.element(index).decimalScale);
+    }
+
+    /**
+     * Reads value for specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Object value(int index) {

Review Comment:
   As far as I can tell, this particular method is unused



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleReader.java:
##########
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+
+/**
+ * Utility for access to binary tuple elements as typed values.
+ */
+public class BinaryTupleReader extends BinaryTupleParser {
+    /** A helper to locate and handle tuple elements. */
+    private class ElementSink implements BinaryTupleParser.Sink {
+        int offset;
+        int length;
+
+        /** {@inheritDoc} */
+        @Override
+        public void nextElement(int index, int begin, int end) {
+            offset = begin;
+            length = end - begin;
+        }
+
+        boolean isNull() {
+            return offset == 0;
+        }
+
+        byte asByte() {
+            if (length == 0) {
+                return 0;
+            }
+            assert length == Byte.BYTES;
+            return buffer.get(offset);
+        }
+
+        short asShort() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            assert length == Short.BYTES;
+            return buffer.getShort(offset);
+        }
+
+        int asInt() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            assert length == Integer.BYTES;
+            return buffer.getInt(offset);
+        }
+
+        long asLong() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            if (length == Integer.BYTES) {
+                return buffer.getInt(offset);
+            }
+            assert length == Long.BYTES;
+            return buffer.getLong(offset);
+        }
+
+        float asFloat() {
+            if (length == 0) {
+                return 0.0F;
+            }
+            assert length == Float.BYTES;
+            return buffer.getFloat(offset);
+        }
+
+        double asDouble() {
+            if (length == 0) {
+                return 0.0;
+            }
+            if (length == Float.BYTES) {
+                return buffer.getFloat(offset);
+            }
+            assert length == Double.BYTES;
+            return buffer.getDouble(offset);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTupleReader(int numElements, byte[] bytes) {
+        this(numElements, 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTupleReader(int numElements, ByteBuffer buffer) {
+        super(numElements, buffer);
+    }
+
+    /**
+     * Get underlying buffer.
+     *
+     * @return Buffer.
+     */
+    public ByteBuffer getBuffer() {
+        return buffer;
+    }
+
+    /**
+     * Checks whether the given element contains a null value.
+     *
+     * @param index Element index.
+     * @return {@code true} if this element contains a null value, {@code 
false} otherwise.
+     */
+    public boolean hasNullValue(int index) {
+        return seek(index).isNull();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public byte byteValue(int index) {
+        return seek(index).asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Byte byteValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public short shortValue(int index) {
+        return seek(index).asShort();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Short shortValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asShort();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public int intValue(int index) {
+        return seek(index).asInt();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Integer intValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asInt();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public long longValue(int index) {
+        return seek(index).asLong();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Long longValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asLong();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public float floatValue(int index) {
+        return seek(index).asFloat();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Float floatValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asFloat();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public double doubleValue(int index) {
+        return seek(index).asDouble();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Double doubleValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asDouble();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public BigInteger numberValue(int index) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes = new byte[element.length];
+        buffer.duplicate().position(element.offset).limit(element.offset + 
element.length).get(bytes);
+        return new BigInteger(bytes);
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @param scale Decimal scale.
+     * @return Element value.
+     */
+    public BigDecimal decimalValue(int index, int scale) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes = new byte[element.length];
+        buffer.duplicate().position(element.offset).limit(element.offset + 
element.length).get(bytes);
+        return new BigDecimal(new BigInteger(bytes), scale);
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public String stringValue(int index) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes;
+        int offset;
+        if (buffer.hasArray()) {
+            bytes = buffer.array();
+            offset = element.offset + buffer.arrayOffset();
+        } else {
+            bytes = new byte[element.length];
+            buffer.duplicate().position(element.offset).limit(element.offset + 
element.length).get(bytes);
+            offset = 0;
+        }
+
+        return new String(bytes, offset, element.length, 
StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public byte[] bytesValue(int index) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes = new byte[element.length];

Review Comment:
   Here we can also "cheat" and use `java.util.Arrays#copyOfRange(byte[], int, 
int)`



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleBuilder.java:
##########
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utility to construct a binary tuple.
+ */
+public class BinaryTupleBuilder {
+    /** The buffer size allocated for values when we do not know anything 
better. */
+    private static final int DEFAULT_BUFFER_SIZE = 4000;
+
+    /** Current element. */
+    private int elementIndex = 0;
+
+    /** Number of elements in the tuple. */
+    private final int numElements;
+
+    /** Size of an offset table entry. */
+    private final int entrySize;
+
+    /** Position of the varlen offset table. */
+    private final int entryBase;
+
+    /** Starting position of variable-length values. */
+    private final int valueBase;
+
+    /** Buffer for tuple content. */
+    private ByteBuffer buffer;
+
+    /** Charset encoder for strings. Initialized lazily. */
+    private CharsetEncoder cachedEncoder;
+
+    /** Flag indicating if any NULL values were really put here. */
+    private boolean hasNullValues = false;
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     */
+    private BinaryTupleBuilder(int numElements, boolean allowNulls, int 
totalValueSize) {
+        this.numElements = numElements;
+
+        int base = BinaryTupleSchema.HEADER_SIZE;
+        if (allowNulls) {
+            base += BinaryTupleSchema.nullMapSize(numElements);
+        }
+
+        entryBase = base;
+
+        if (totalValueSize < 0) {
+            entrySize = 4;
+        } else {
+            entrySize = 
BinaryTupleSchema.flagsToEntrySize(BinaryTupleSchema.valueSizeToFlags(totalValueSize));
+        }
+
+        valueBase = base + entrySize * numElements;
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema) {
+        return create(schema.elementCount(), schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls) {
+        return create(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(BinaryTupleSchema schema, int 
totalValueSize) {
+        return create(schema.elementCount(), schema.hasNullableElements(), 
totalValueSize);
+    }
+
+    /**
+     * Creates a builder.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder create(int numElements, boolean allowNulls, int 
totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocate(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema) 
{
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements());
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls) {
+        return createWithDirectBuffer(numElements, allowNulls, -1);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param schema Tuple schema.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(BinaryTupleSchema schema, 
int totalValueSize) {
+        return createWithDirectBuffer(schema.elementCount(), 
schema.hasNullableElements(), totalValueSize);
+    }
+
+    /**
+     * Creates a builder with direct buffer.
+     *
+     * @param numElements Number of tuple elements.
+     * @param allowNulls True if NULL values are possible, false otherwise.
+     * @param totalValueSize Total estimated length of non-NULL values, -1 if 
not known.
+     * @return Tuple builder.
+     */
+    public BinaryTupleBuilder createWithDirectBuffer(int numElements, boolean 
allowNulls, int totalValueSize) {
+        var builder = new BinaryTupleBuilder(numElements, allowNulls, 
totalValueSize);
+        builder.allocateDirect(totalValueSize);
+        return builder;
+    }
+
+    /**
+     * Check if the binary tuple contains a null map.
+     */
+    public boolean hasNullMap() {
+        return entryBase > BinaryTupleSchema.HEADER_SIZE;
+    }
+
+    /**
+     * Append a NULL value for the current element.
+     *
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendNull() {
+        if (!hasNullMap()) {
+            throw new IgniteInternalException("Appending a NULL value in 
binary tuple builder with disabled NULLs");
+        }
+
+        hasNullValues = true;
+
+        int nullIndex = BinaryTupleSchema.HEADER_SIZE + elementIndex / 8;
+        byte nullMask = (byte) (1 << (elementIndex % 8));
+        buffer.put(nullIndex, (byte) (buffer.get(nullIndex) | nullMask));
+
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(byte value) {
+        if (value != 0) {
+            putByte(value);
+        }
+        return proceed();
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendByte(Byte value) {
+        return value == null ? appendNull() : appendByte(value.byteValue());
+    }
+
+    /**
+     * Append a value for the current element.
+     *
+     * @param value Element value.
+     * @return {@code this} for chaining.
+     */
+    public BinaryTupleBuilder appendShort(short value) {
+        if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {

Review Comment:
   It's a nitpicking probably, but I would say that `a <= x && x <= b` looks 
nicer than `x <= b && x >= a`



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleReader.java:
##########
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+
+/**
+ * Utility for access to binary tuple elements as typed values.
+ */
+public class BinaryTupleReader extends BinaryTupleParser {
+    /** A helper to locate and handle tuple elements. */
+    private class ElementSink implements BinaryTupleParser.Sink {
+        int offset;
+        int length;
+
+        /** {@inheritDoc} */
+        @Override
+        public void nextElement(int index, int begin, int end) {
+            offset = begin;
+            length = end - begin;
+        }
+
+        boolean isNull() {
+            return offset == 0;
+        }
+
+        byte asByte() {
+            if (length == 0) {
+                return 0;
+            }
+            assert length == Byte.BYTES;
+            return buffer.get(offset);
+        }
+
+        short asShort() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            assert length == Short.BYTES;
+            return buffer.getShort(offset);
+        }
+
+        int asInt() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            assert length == Integer.BYTES;
+            return buffer.getInt(offset);
+        }
+
+        long asLong() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            if (length == Integer.BYTES) {
+                return buffer.getInt(offset);
+            }
+            assert length == Long.BYTES;
+            return buffer.getLong(offset);
+        }
+
+        float asFloat() {
+            if (length == 0) {
+                return 0.0F;
+            }
+            assert length == Float.BYTES;
+            return buffer.getFloat(offset);
+        }
+
+        double asDouble() {
+            if (length == 0) {
+                return 0.0;
+            }
+            if (length == Float.BYTES) {
+                return buffer.getFloat(offset);
+            }
+            assert length == Double.BYTES;
+            return buffer.getDouble(offset);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTupleReader(int numElements, byte[] bytes) {
+        this(numElements, 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTupleReader(int numElements, ByteBuffer buffer) {
+        super(numElements, buffer);
+    }
+
+    /**
+     * Get underlying buffer.
+     *
+     * @return Buffer.
+     */
+    public ByteBuffer getBuffer() {
+        return buffer;
+    }
+
+    /**
+     * Checks whether the given element contains a null value.
+     *
+     * @param index Element index.
+     * @return {@code true} if this element contains a null value, {@code 
false} otherwise.
+     */
+    public boolean hasNullValue(int index) {
+        return seek(index).isNull();

Review Comment:
   Again, I might be picky, but I don't like the idea of allocating a seek 
object just to check a single bit or read a primitive value, for example. Seems 
like a non-optimal solution



##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/BinaryTupleReader.java:
##########
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.BitSet;
+import java.util.UUID;
+
+/**
+ * Utility for access to binary tuple elements as typed values.
+ */
+public class BinaryTupleReader extends BinaryTupleParser {
+    /** A helper to locate and handle tuple elements. */
+    private class ElementSink implements BinaryTupleParser.Sink {
+        int offset;
+        int length;
+
+        /** {@inheritDoc} */
+        @Override
+        public void nextElement(int index, int begin, int end) {
+            offset = begin;
+            length = end - begin;
+        }
+
+        boolean isNull() {
+            return offset == 0;
+        }
+
+        byte asByte() {
+            if (length == 0) {
+                return 0;
+            }
+            assert length == Byte.BYTES;
+            return buffer.get(offset);
+        }
+
+        short asShort() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            assert length == Short.BYTES;
+            return buffer.getShort(offset);
+        }
+
+        int asInt() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            assert length == Integer.BYTES;
+            return buffer.getInt(offset);
+        }
+
+        long asLong() {
+            if (length == 0) {
+                return 0;
+            }
+            if (length == Byte.BYTES) {
+                return buffer.get(offset);
+            }
+            if (length == Short.BYTES) {
+                return buffer.getShort(offset);
+            }
+            if (length == Integer.BYTES) {
+                return buffer.getInt(offset);
+            }
+            assert length == Long.BYTES;
+            return buffer.getLong(offset);
+        }
+
+        float asFloat() {
+            if (length == 0) {
+                return 0.0F;
+            }
+            assert length == Float.BYTES;
+            return buffer.getFloat(offset);
+        }
+
+        double asDouble() {
+            if (length == 0) {
+                return 0.0;
+            }
+            if (length == Float.BYTES) {
+                return buffer.getFloat(offset);
+            }
+            assert length == Double.BYTES;
+            return buffer.getDouble(offset);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param bytes Binary tuple.
+     */
+    public BinaryTupleReader(int numElements, byte[] bytes) {
+        this(numElements, 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param numElements Number of tuple elements.
+     * @param buffer Buffer with a binary tuple.
+     */
+    public BinaryTupleReader(int numElements, ByteBuffer buffer) {
+        super(numElements, buffer);
+    }
+
+    /**
+     * Get underlying buffer.
+     *
+     * @return Buffer.
+     */
+    public ByteBuffer getBuffer() {
+        return buffer;
+    }
+
+    /**
+     * Checks whether the given element contains a null value.
+     *
+     * @param index Element index.
+     * @return {@code true} if this element contains a null value, {@code 
false} otherwise.
+     */
+    public boolean hasNullValue(int index) {
+        return seek(index).isNull();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public byte byteValue(int index) {
+        return seek(index).asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Byte byteValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asByte();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public short shortValue(int index) {
+        return seek(index).asShort();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Short shortValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asShort();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public int intValue(int index) {
+        return seek(index).asInt();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Integer intValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asInt();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public long longValue(int index) {
+        return seek(index).asLong();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Long longValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asLong();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public float floatValue(int index) {
+        return seek(index).asFloat();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Float floatValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asFloat();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public double doubleValue(int index) {
+        return seek(index).asDouble();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public Double doubleValueBoxed(int index) {
+        var element = seek(index);
+        return element.isNull() ? null : element.asDouble();
+    }
+
+    /**
+     * Reads value of specified element.
+     *
+     * @param index Element index.
+     * @return Element value.
+     */
+    public BigInteger numberValue(int index) {
+        var element = seek(index);
+        if (element.isNull()) {
+            return null;
+        }
+
+        byte[] bytes = new byte[element.length];

Review Comment:
   We should probably add a "TODO" to optimize this code - arrays allocation is 
often not needed, there's a BigInteger constructor that could be used with an 
array and a range of indexes, without explicit copying. Just like you did with 
strings



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to