You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/10/31 12:02:00 UTC

[ignite-3] branch ignite-18017 created (now 9bd50dc7ad)

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

sdanilov pushed a change to branch ignite-18017
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


      at 9bd50dc7ad IGNITE-18017 Add binary tuple tests

This branch includes the following new commits:

     new 9bd50dc7ad IGNITE-18017 Add binary tuple tests

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[ignite-3] 01/01: IGNITE-18017 Add binary tuple tests

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9bd50dc7ad2618197656c91ea8dfebc25093d5e2
Author: Semyon Danilov <sa...@yandex.ru>
AuthorDate: Mon Oct 31 16:01:51 2022 +0400

    IGNITE-18017 Add binary tuple tests
---
 modules/platforms/cpp/ignite/schema/CMakeLists.txt |    1 +
 .../cpp/ignite/schema/binary_tuple_builder.h       |   21 +-
 .../cpp/ignite/schema/binary_tuple_parser.cpp      |    3 +-
 .../platforms/cpp/ignite/schema/tuple_assembler.h  |  125 +++
 modules/platforms/cpp/ignite/schema/tuple_test.cpp | 1132 ++++++++++++++++++++
 5 files changed, 1273 insertions(+), 9 deletions(-)

diff --git a/modules/platforms/cpp/ignite/schema/CMakeLists.txt b/modules/platforms/cpp/ignite/schema/CMakeLists.txt
index 1c1f4fb5a0..18ce66f3ae 100644
--- a/modules/platforms/cpp/ignite/schema/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/schema/CMakeLists.txt
@@ -48,6 +48,7 @@ add_library(${TARGET} STATIC ${SOURCES})
 target_link_libraries(${TARGET} ignite-common)
 
 ignite_test(bignum_test bignum_test.cpp LIBS ${TARGET})
+ignite_test(tuple_test tuple_test.cpp LIBS ${TARGET})
 
 install(TARGETS ${TARGET}
     RUNTIME DESTINATION bin/
diff --git a/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h b/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h
index 0cf0241ac0..abc0c84caa 100644
--- a/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h
+++ b/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h
@@ -205,6 +205,8 @@ public:
      */
     const std::vector<std::byte> &build() {
         assert(element_index == element_count);
+        long needed_size = ptrdiff_t(next_value - binary_tuple.data());
+        binary_tuple.resize(needed_size);
         return binary_tuple;
     }
 
@@ -230,18 +232,21 @@ private:
     /**
      * @brief Checks if a value of a given integer type can be compressed to a smaller integer type.
      *
-     * @tparam T Source integer type.
-     * @tparam U Target integer type.
+     * @tparam SRC Source integer type.
+     * @tparam TGT Target integer type.
      * @param value Source value.
      * @return true If the source value can be compressed.
      * @return false If the source value cannot be compressed.
      */
-    template <typename T, typename U>
-    static bool fits(T value) noexcept {
-        static_assert(std::is_signed_v<T>);
-        static_assert(std::is_signed_v<U>);
-        using V = std::make_unsigned_t<U>;
-        return (std::make_unsigned_t<T>(value) + std::numeric_limits<U>::max() + 1) <= std::numeric_limits<V>::max();
+    template <typename SRC, typename TGT>
+    static bool fits(SRC value) noexcept {
+        static_assert(std::is_signed_v<SRC>);
+        static_assert(std::is_signed_v<TGT>);
+        using U_TGT = std::make_unsigned_t<TGT>;
+
+        // Check if TGT::min <= value <= TGT::max.
+        return std::make_unsigned_t<SRC>(value + std::numeric_limits<TGT>::max() + 1)
+            <= std::numeric_limits<U_TGT>::max();
     }
 
     /**
diff --git a/modules/platforms/cpp/ignite/schema/binary_tuple_parser.cpp b/modules/platforms/cpp/ignite/schema/binary_tuple_parser.cpp
index dede0116b3..408f01f8b6 100644
--- a/modules/platforms/cpp/ignite/schema/binary_tuple_parser.cpp
+++ b/modules/platforms/cpp/ignite/schema/binary_tuple_parser.cpp
@@ -94,7 +94,8 @@ binary_tuple_parser::binary_tuple_parser(IntT num_elements, bytes_view data)
     SizeT table_size = entry_size * element_count;
     next_entry = binary_tuple.data() + binary_tuple_header::SIZE + nullmap_size;
     value_base = next_entry + table_size;
-    if (value_base > &binary_tuple.back()) {
+
+    if (value_base > binary_tuple.data() + binary_tuple.size()) {
         throw std::out_of_range("Too short byte buffer");
     }
 
diff --git a/modules/platforms/cpp/ignite/schema/tuple_assembler.h b/modules/platforms/cpp/ignite/schema/tuple_assembler.h
new file mode 100644
index 0000000000..7fd47f77cd
--- /dev/null
+++ b/modules/platforms/cpp/ignite/schema/tuple_assembler.h
@@ -0,0 +1,125 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#pragma once
+
+#include <ignite/schema/binary_tuple_schema.h>
+#include <ignite/schema/binary_tuple_builder.h>
+
+#include <cstring>
+#include <optional>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+/**
+* This used to be the main tool to construct binary tuples (in IEP-54 format).
+* Now this is just a helper for unit tests.
+*/
+class tuple_assembler {
+private:
+   ignite::binary_tuple_schema schema;
+
+   ignite::binary_tuple_builder builder;
+
+   std::vector<std::vector<std::byte>> buffer;
+
+   ignite::tuple_view bufferTuple;
+
+public:
+   /** Construct a new Tuple Assembler object. */
+   explicit tuple_assembler(ignite::binary_tuple_schema &sch)
+       : schema(std::move(sch))
+       , builder(schema.num_elements()) { }
+
+   /** Start a new tuple. */
+   void start() {
+       buffer.clear();
+       bufferTuple.clear();
+       builder.start();
+   }
+
+   /** Append a null value. */
+   void appendNull() {
+       bufferTuple.emplace_back(std::nullopt);
+       builder.claim(std::nullopt);
+   }
+
+   /** Append a null value. */
+   void append(std::nullopt_t /*null*/) { appendNull(); }
+
+   /** Append next column element. */
+   void append(ignite::bytes_view value) {
+       buffer.emplace_back(value.begin(), value.end());
+       bufferTuple.emplace_back(buffer.back());
+       builder.claim(bufferTuple.back()->size());
+   }
+
+   /** Append next column element. */
+   void append(std::string_view value) {
+       append(ignite::bytes_view {reinterpret_cast<const std::byte *>(value.data()), value.size()});
+   }
+
+   /**
+    * @brief Append next column element.
+    *
+    * @tparam T Type of next column, basic integer of floating point type.
+    * @param x Key or Value for next column.
+    */
+   template <typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
+   void append(T value) {
+       char buf[sizeof value];
+       memcpy(buf, &value, sizeof value);
+       append(ignite::bytes_view {reinterpret_cast<const std::byte *>(buf), sizeof value});
+   }
+
+   /**
+    * @brief Append multiple elements.
+    *
+    * @tparam Ts Types parameter pack.
+    * @param tuple Elements to be appended to column.
+    */
+   template <typename... Ts>
+   void append(std::tuple<Ts...> const &tuple) {
+       std::apply([&](auto &&... args) { ((append(args)), ...); }, tuple);
+   }
+
+   /**
+    * @brief Finalize and return a tuple in IEP-54 binary format.
+    *
+    * @return Byte buffer with tuple in the binary form.
+    */
+   const auto &build() {
+       builder.layout();
+       builder.append(schema, bufferTuple);
+       return builder.build();
+   }
+
+   /**
+    * @brief Assemble and return a tuple in binary format.
+    *
+    * @tparam Ts Types parameter pack.
+    * @param tupleArgs Elements to be appended to column.
+    * @return Byte buffer with tuple in the binary form.
+    */
+   template <typename... Ts>
+   const auto &build(std::tuple<Ts...> const &tuple) {
+       start();
+       append(tuple);
+       return build();
+   }
+};
diff --git a/modules/platforms/cpp/ignite/schema/tuple_test.cpp b/modules/platforms/cpp/ignite/schema/tuple_test.cpp
new file mode 100644
index 0000000000..10dfa44b7d
--- /dev/null
+++ b/modules/platforms/cpp/ignite/schema/tuple_test.cpp
@@ -0,0 +1,1132 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <ignite/schema/column_info.h>
+#include <ignite/schema/binary_tuple_parser.h>
+#include "tuple_assembler.h"
+
+#include <array>
+#include <string>
+
+using namespace std::string_literals;
+using namespace ignite;
+
+template <typename T>
+T read_tuple(bytes_view data);
+
+template<>
+int8_t read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_int8(data);
+}
+
+template<>
+int16_t read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_int16(data);
+}
+
+template<>
+int32_t read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_int32(data);
+}
+
+template<>
+int64_t read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_int64(data);
+}
+
+template<>
+double read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_double(data);
+}
+
+template<>
+float read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_float(data);
+}
+
+template<>
+big_integer read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_number(data);
+}
+
+template<>
+ignite_date read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_date(data);
+}
+
+template<>
+ignite_date_time read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_date_time(data);
+}
+
+template<>
+uuid read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_uuid(data);
+}
+
+template<>
+ignite_time read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_time(data);
+}
+
+template<>
+ignite_timestamp read_tuple(bytes_view data) {
+    return binary_tuple_parser::get_timestamp(data);
+}
+
+template<>
+std::string read_tuple(bytes_view data) {
+    return {reinterpret_cast<const char*>(data.data()), data.size()};
+}
+
+struct SchemaDescriptor {
+
+    std::vector<column_info> columns;
+
+    [[nodiscard]] IntT length() const { return columns.size(); }
+
+    [[nodiscard]] binary_tuple_schema to_tuple_schema() const {
+        return binary_tuple_schema({columns.begin(), columns.end()});
+    }
+
+};
+
+struct SingleValueCheck {
+    explicit SingleValueCheck(binary_tuple_parser &parser)
+        : parser {parser} { }
+
+    binary_tuple_parser &parser;
+
+    template <typename T>
+    void operator()(T value) {
+        EXPECT_EQ(value, read_tuple<T>(parser.get_next().value()));
+    }
+
+    void operator()(std::nullopt_t /*null*/) { EXPECT_FALSE(parser.get_next()); }
+};
+
+template <typename... Ts, int size>
+void checkReaderWriterEquality(const SchemaDescriptor &schema, const std::tuple<Ts...> &values,
+    const int8_t (&data)[size], bool skipAssemblerCheck = false) {
+
+    auto bytes = bytes_view {reinterpret_cast<const std::byte *>(data), size};
+
+    binary_tuple_parser tp(schema.length(), bytes);
+
+    std::apply([&tp](const Ts... args) { ((SingleValueCheck(tp)(args)), ...); }, values);
+
+    EXPECT_EQ(tp.num_elements(), tp.num_parsed_elements());
+
+    if (!skipAssemblerCheck) {
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler ta(sch);
+
+        bytes_view built = ta.build(values);
+
+        EXPECT_THAT(built, testing::ContainerEq(bytes));
+    }
+}
+
+TEST(TuplesTests, FixedKeyFixedValue) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT16, false},
+        {ignite_type::INT16, false},
+    };
+
+    // With value.
+    {
+        const std::tuple<int16_t, int16_t> &values = std::make_tuple<int16_t, int16_t>(33, -71);
+
+        int8_t data[] = {0, 1, 2, 33, -71};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, FixedKeyFixedNullableValue) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT32, false},
+        {ignite_type::INT32, true},
+    };
+
+    // With value.
+    {
+        auto values = std::make_tuple<int32_t, int32_t>(33, -71);
+
+        int8_t data[] = {0, 1, 2, 33, -71};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Null value.
+    {
+        auto values = std::make_tuple(int32_t {77}, std::nullopt);
+
+        int8_t data[] = {4, 2, 1, 1, 77};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+
+TEST(TuplesTests, FixedKeyVarlenValue) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT16, false},
+        {ignite_type::STRING, true},
+    };
+
+    // With value.
+    {
+        auto values = std::make_tuple(int16_t {-33}, std::string {"val"});
+
+        int8_t data[] = {0, 1, 4, -33, 118, 97, 108};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, FixedKeyVarlenNullableValue) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT16, false},
+        {ignite_type::STRING, true},
+    };
+
+    // With value.
+    {
+        auto values = std::make_tuple(int16_t {-33}, std::string {"val"});
+
+        int8_t data[] = {0, 1, 4, -33, 118, 97, 108};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Null value.
+    {
+        auto values = std::make_tuple(int16_t {33}, std::nullopt);
+
+        int8_t data[] = {4, 2, 1, 1, 33};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, VarlenNullableKeyVarlenNullableValue) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+    };
+
+    // With key and value.
+    {
+        auto values = std::make_tuple(std::string {"key"}, std::string {"val"});
+
+        int8_t data[] = {0, 3, 6, 107, 101, 121, 118, 97, 108};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Null key.
+    {
+        auto values = std::make_tuple(std::nullopt, std::string {"val"});
+
+        int8_t data[] = {4, 1, 0, 3, 118, 97, 108};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Null value.
+    {
+        auto values = std::make_tuple(std::string {"key"}, std::nullopt);
+
+        int8_t data[] = {4, 2, 3, 3, 107, 101, 121};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Null both.
+    {
+        auto values = std::make_tuple(std::nullopt, std::nullopt);
+
+        int8_t data[] = {4, 3, 0, 0};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+
+TEST(TuplesTests, FixedAndVarlenKeyFixedAndVarlenValue) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT16, false},
+        {ignite_type::STRING, false},
+        {ignite_type::INT32, true},
+        {ignite_type::STRING, true},
+    };
+
+    // With value.
+    {
+        auto values = std::make_tuple(int16_t {33}, std::string {"keystr"}, int32_t {73}, std::string {"valstr"});
+
+        int8_t data[] = {
+            0, 1, 7, 8, 14, 33, 107, 101, 121, 115, 116, 114, 73, 118, 97, 108, 115, 116, 114};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Null value.
+    {
+        auto values = std::make_tuple(int16_t {33}, std::string {"keystr2"}, std::nullopt, std::nullopt);
+
+        int8_t data[] = {4, 12, 1, 8, 8, 8, 33, 107, 101, 121, 115, 116, 114, 50};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, FixedNullableColumns) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT8, true},
+        {ignite_type::INT16, true},
+        {ignite_type::INT32, true},
+        {ignite_type::INT8, true},
+        {ignite_type::INT16, true},
+        {ignite_type::INT32, true},
+    };
+
+    // Last column null.
+    {
+        auto values =
+            std::make_tuple(int8_t {11}, int16_t {22}, std::nullopt, int8_t {-44}, int16_t {-66}, std::nullopt);
+
+        int8_t data[] = {4, 36, 1, 2, 2, 3, 4, 4, 11, 22, -44, -66};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // First column null.
+    {
+        auto values =
+            std::make_tuple(std::nullopt, int16_t {22}, int32_t {33}, std::nullopt, int16_t {-55}, int32_t {-66});
+
+        int8_t data[] = {4, 9, 0, 1, 2, 2, 3, 4, 22, 33, -55, -66};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Middle column null.
+    {
+        auto values =
+            std::make_tuple(int8_t {11}, std::nullopt, int32_t {33}, int8_t {-44}, std::nullopt, int32_t {-66});
+
+        int8_t data[] = {4, 18, 1, 1, 2, 3, 3, 4, 11, 33, -44, -66};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // All columns null.
+    {
+        auto values =
+            std::make_tuple(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt);
+
+        int8_t data[] = {4, 63, 0, 0, 0, 0, 0, 0};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, VarlenNullableColumns) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+    };
+
+    // Last column null.
+    {
+        auto values = std::make_tuple("abc"s, "ascii"s, std::nullopt, "yz"s, "我愛Java"s, std::nullopt);
+
+        int8_t data[] = {4, 36, 3, 8, 8, 10, 20, 20, 97, 98, 99, 97, 115, 99, 105, 105, 121, 122, -26, -120, -111, -26,
+            -124, -101, 74, 97, 118, 97};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // First column null.
+    {
+        auto values = std::make_tuple(std::nullopt, "ascii"s, "我愛Java"s, std::nullopt, "我愛Java"s, "ascii"s);
+
+        int8_t data[] = {4, 9, 0, 5, 15, 15, 25, 30, 97, 115, 99, 105, 105, -26, -120, -111, -26, -124, -101, 74, 97,
+            118, 97, -26, -120, -111, -26, -124, -101, 74, 97, 118, 97, 97, 115, 99, 105, 105};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Middle column null.
+    {
+        auto values = std::make_tuple("abc"s, std::nullopt, "我愛Java"s, "yz"s, std::nullopt, "ascii"s);
+
+        int8_t data[] = {4, 18, 3, 3, 13, 15, 15, 20, 97, 98, 99, -26, -120, -111, -26, -124, -101, 74, 97, 118, 97,
+            121, 122, 97, 115, 99, 105, 105};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // All columns null.
+    {
+        auto values =
+            std::make_tuple(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt);
+
+        int8_t data[] = {4, 63, 0, 0, 0, 0, 0, 0};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, FixedAndVarlenNullableColumns) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT8, true},
+        {ignite_type::INT16, true},
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+        {ignite_type::INT8, true},
+        {ignite_type::INT16, true},
+        {ignite_type::STRING, true},
+        {ignite_type::STRING, true},
+    };
+
+    // Check null/non-null all fixed/varlen.
+    {
+        auto values = std::make_tuple(
+            int8_t {11}, int16_t {22}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, "yz"s, "ascii"s);
+
+        int8_t data[] = {4, 60, 1, 2, 2, 2, 2, 2, 4, 9, 11, 22, 121, 122, 97, 115, 99, 105, 105};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Check null/non-null single fixed.
+    {
+        auto values =
+            std::make_tuple(std::nullopt, int16_t {22}, "ab"s, "我愛Java"s, int8_t {55}, std::nullopt, "yz"s, "ascii"s);
+
+        int8_t data[] = {4, 33, 0, 1, 3, 13, 14, 14, 16, 21, 22, 97, 98, -26, -120, -111, -26, -124, -101, 74, 97, 118, 97, 55, 121, 122, 97, 115, 99, 105, 105};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Check null/non-null single varlen.
+    {
+        auto values = std::make_tuple(
+            int8_t {11}, int16_t {22}, std::nullopt, "我愛Java"s, int8_t {55}, int16_t {66}, "yz"s, std::nullopt);
+
+        int8_t data[] = {4, -124, 1, 2, 2, 12, 13, 14, 16, 16, 11, 22, -26, -120, -111, -26, -124, -101, 74, 97, 118, 97, 55, 66, 121, 122};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Check null/non-null mixed.
+    {
+        auto values = std::make_tuple(
+            int8_t {11}, std::nullopt, std::nullopt, "我愛Java"s, std::nullopt, int16_t {22}, "yz"s, std::nullopt);
+
+        int8_t data[] = {4, -106, 1, 1, 1, 11, 11, 12, 14, 14, 11, -26, -120, -111, -26, -124, -101, 74, 97, 118, 97, 22, 121, 122};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Check all null/non-null.
+    {
+        auto values = std::make_tuple(
+            int8_t {11}, int16_t {22}, "ab"s, "我愛Java"s, std::nullopt, std::nullopt, std::nullopt, std::nullopt);
+
+        int8_t data[] = {
+            4, -16, 1, 2, 4, 14, 14, 14, 14, 14, 11, 22, 97, 98, -26, -120, -111, -26, -124, -101, 74, 97, 118, 97};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, ZeroLengthVarlen) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT32, false},
+        {ignite_type::BINARY, false},
+    };
+
+    // Single zero-length vector of bytes.
+    {
+        auto values = std::make_tuple(int32_t {0}, ""s);
+
+        int8_t data[] = {0, 0, 0};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Two zero-length vectors of bytes.
+    {
+        schema.columns.emplace_back(column_info {ignite_type::BINARY, false});
+
+        auto values = std::make_tuple(int32_t {0}, ""s, ""s);
+
+        int8_t data[] = {0, 0, 0, 0};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+
+    // Two zero-length vectors of bytes and single integer value.
+    {
+        schema.columns.erase(schema.columns.begin() + 1, schema.columns.end());
+
+        schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+        schema.columns.emplace_back(column_info {ignite_type::BINARY, false});
+        schema.columns.emplace_back(column_info {ignite_type::BINARY, false});
+
+        auto values = std::make_tuple(int32_t {0}, int32_t {123}, ""s, ""s);
+
+        int8_t data[] = {0, 0, 1, 1, 1, 123};
+
+        checkReaderWriterEquality(schema, values, data);
+    }
+}
+
+TEST(TuplesTests, SingleVarlen) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::INT32, false},
+        {ignite_type::BINARY, false},
+    };
+
+    auto values = std::make_tuple(int32_t {0}, "\1\2\3"s);
+
+    int8_t data[] = {0, 0, 3, 1, 2, 3};
+
+    checkReaderWriterEquality(schema, values, data);
+}
+
+TEST(TuplesTests, TinyVarlenFormatOverflowLarge) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+    for (int i = 0; i < 300; i++) {
+        schema.columns.emplace_back(column_info {ignite_type::BINARY, false});
+    }
+
+    // Flags - 1 zero byte
+    // Varlen table - 1 byte for key column + 300 bytes for value columns
+    // Key - 4 zero bytes for int32 zero.
+    std::vector<std::byte> reference(306);
+    std::fill(reference.begin() + 1, reference.end() - 4, std::byte {4});
+
+    binary_tuple_parser tp(schema.length(), {reinterpret_cast<std::byte *>(reference.data()), reference.size()});
+
+    auto first = tp.get_next();
+    EXPECT_TRUE(first.has_value());
+    EXPECT_EQ(0, read_tuple<int32_t>(first.value()));
+
+    for (int i = 1; i < schema.length(); i++) {
+        auto next = tp.get_next();
+        EXPECT_TRUE(next.has_value());
+        EXPECT_EQ(0, next.value().size());
+    }
+}
+
+TEST(TuplesTests, TinyVarlenFormatOverflowMedium) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+    for (int i = 0; i < 300; i++) {
+        schema.columns.emplace_back(column_info {ignite_type::BINARY, false});
+    }
+
+    // Flags - 1 zero byte
+    // Varlen table - 301 bytes
+    // Key - 4 zero bytes for int32 zero.
+    // Varlen values - 3 bytes
+    std::vector<int8_t> reference(309);
+    // Vartable, offsets in medium format - Short per offset.
+    reference[1] = 4;
+    reference[2] = 5;
+    reference[3] = 6;
+    reference[4] = 7;
+
+    // Offsets for zero-length arrays.
+    for (int i = 0; i < 300 - 3; i++) {
+        reference[5 + i] = 7;
+    }
+
+    // Non-empty arrays:
+    reference[306] = 1;
+    reference[307] = 2;
+    reference[308] = 3;
+
+    binary_tuple_parser tp(schema.length(), {reinterpret_cast<std::byte *>(reference.data()), reference.size()});
+
+    auto first = tp.get_next();
+    EXPECT_TRUE(first.has_value());
+    EXPECT_EQ(0, read_tuple<int32_t>(first.value()));
+
+    for (int i = 1; i <= 3; i++) {
+        auto next = tp.get_next();
+        EXPECT_TRUE(next.has_value());
+        EXPECT_EQ(1, next->size());
+        EXPECT_EQ(i, read_tuple<int8_t>(next.value()));
+    }
+    for (IntT i = 4; i < schema.length(); i++) {
+        auto next = tp.get_next();
+        EXPECT_TRUE(next.has_value());
+        EXPECT_EQ(0, next.value().size());
+    }
+}
+
+// Check same binaries that used in testExpectedVarlenTupleBinaries java side test.
+TEST(TuplesTests, ExpectedVarlenTupleBinaries) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    // Value columns are sorted here like they are sorted on java side during schema construction.
+    // Fixed size columns go first, varlen columns last.
+    schema.columns = {
+        {ignite_type::INT32, false},
+        {ignite_type::INT32, false},
+        {ignite_type::INT32, false},
+        {ignite_type::INT32, false},
+        {ignite_type::BINARY, false},
+        {ignite_type::BINARY, false},
+        {ignite_type::STRING, false},
+    };
+
+    // clang-format off
+    std::vector<int8_t> referenceTiny {
+        // Flags.
+        0,
+        // Offsets for values.
+        4, 8, 12, 16, 21, 24, 27,
+        // Key - integer zero.
+        0, 0, 0, 0,
+        // Integer values - 1, 2, 3.
+        1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
+        // Byte[] value.
+        1, 2, 3, 4, 5,
+        // Byte[] value.
+        1, 2, 3,
+        // String "abc".
+        97, 98, 99};
+    // clang-format on
+
+    binary_tuple_parser tpTiny(schema.length(), {reinterpret_cast<std::byte *>(referenceTiny.data()), referenceTiny.size()});
+    tuple_view tiny = tpTiny.parse();
+
+    EXPECT_TRUE(tiny[0].has_value());
+    EXPECT_EQ(0, read_tuple<int32_t>(tiny[0].value()));
+
+    EXPECT_TRUE(tiny[1].has_value());
+    EXPECT_EQ(1, read_tuple<int32_t>(tiny[1].value()));
+
+    EXPECT_TRUE(tiny[2].has_value());
+    EXPECT_EQ(2, read_tuple<int32_t>(tiny[2].value()));
+
+    EXPECT_TRUE(tiny[3].has_value());
+    EXPECT_EQ(3, read_tuple<int32_t>(tiny[3].value()));
+
+    EXPECT_TRUE(tiny[4].has_value());
+    bytes_view varlen = tiny[4].value();
+
+    EXPECT_EQ(5, varlen.size());
+    EXPECT_EQ(1, std::to_integer<int>(varlen[0]));
+    EXPECT_EQ(2, std::to_integer<int>(varlen[1]));
+    EXPECT_EQ(3, std::to_integer<int>(varlen[2]));
+    EXPECT_EQ(4, std::to_integer<int>(varlen[3]));
+    EXPECT_EQ(5, std::to_integer<int>(varlen[4]));
+
+    EXPECT_TRUE(tiny[5].has_value());
+    varlen = tiny[5].value();
+
+    EXPECT_EQ(3, varlen.size());
+    EXPECT_EQ(1, std::to_integer<int>(varlen[0]));
+    EXPECT_EQ(2, std::to_integer<int>(varlen[1]));
+    EXPECT_EQ(3, std::to_integer<int>(varlen[2]));
+
+    EXPECT_TRUE(tiny[6].has_value());
+    varlen = tiny[6].value();
+    std::string str(reinterpret_cast<const char *>(varlen.data()), varlen.size());
+    EXPECT_EQ("abc", str);
+
+    // Make data that doesn't fit to tiny format tuple but fits to medium format tuple.
+    std::vector<int8_t> mediumArray(256 * 2);
+    for (size_t i = 0; i < mediumArray.size(); i++) {
+        mediumArray[i] = (int8_t)((i * 127) % 256);
+    }
+
+    // Construct reference tuple binary independently of tuple assembler.
+    // clang-format off
+    std::vector<int8_t> referenceMedium {
+        // Flags.
+        1,
+        // Offsets for values.
+        4, 0, 8, 0, 12, 0, 16, 0, 21, 0,
+        int8_t(21 + mediumArray.size()), int8_t((21 + mediumArray.size()) >> 8),
+        int8_t(24 + mediumArray.size()), int8_t((24 + mediumArray.size()) >> 8),
+        // Key - integer zero.
+        0, 0, 0, 0,
+        // Integer values - 1, 2, 3.
+        1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
+        // Byte[] value.
+        1, 2, 3, 4, 5,
+        // Byte[] value -- skipped for now, inserted below.
+        // String "abc".
+        97, 98, 99};
+    // clang-format on
+
+    // Copy varlen array that does not fit to tiny format.
+    referenceMedium.insert(referenceMedium.end() - 3, mediumArray.begin(), mediumArray.end());
+
+    binary_tuple_parser tpMedium(schema.length(), {reinterpret_cast<std::byte *>(referenceMedium.data()), referenceMedium.size()});
+    tuple_view medium = tpMedium.parse();
+
+    EXPECT_TRUE(medium[0].has_value());
+    EXPECT_EQ(0, read_tuple<int32_t>(medium[0].value()));
+
+    EXPECT_TRUE(medium[1].has_value());
+    EXPECT_EQ(1, read_tuple<int32_t>(medium[1].value()));
+
+    EXPECT_TRUE(medium[2].has_value());
+    EXPECT_EQ(2, read_tuple<int32_t>(medium[2].value()));
+
+    EXPECT_TRUE(medium[3].has_value());
+    EXPECT_EQ(3, read_tuple<int32_t>(medium[3].value()));
+
+    EXPECT_TRUE(medium[4].has_value());
+    varlen = medium[4].value();
+
+    EXPECT_EQ(5, varlen.size());
+    EXPECT_EQ(1, std::to_integer<int>(varlen[0]));
+    EXPECT_EQ(2, std::to_integer<int>(varlen[1]));
+    EXPECT_EQ(3, std::to_integer<int>(varlen[2]));
+    EXPECT_EQ(4, std::to_integer<int>(varlen[3]));
+    EXPECT_EQ(5, std::to_integer<int>(varlen[4]));
+
+    EXPECT_TRUE(medium[5].has_value());
+    varlen = medium[5].value();
+
+    EXPECT_TRUE(mediumArray
+        == std::vector<int8_t>(reinterpret_cast<const int8_t *>(varlen.data()),
+            reinterpret_cast<const int8_t *>(varlen.data() + varlen.size())));
+
+    EXPECT_TRUE(medium[6].has_value());
+    varlen = medium[6].value();
+    str = std::string(reinterpret_cast<const char *>(varlen.data()), varlen.size());
+    EXPECT_EQ("abc", str);
+
+    // Make data that doesn't fit to medium format tuple but fits to large format tuple.
+    std::vector<int8_t> largeArray(64 * 2 * 1024);
+    for (size_t i = 0; i < largeArray.size(); i++) {
+        largeArray[i] = (int8_t)((i * 127) % 256);
+    }
+
+    // Construct reference tuple binary independently of tuple assembler.
+    // clang-format off
+    std::vector<int8_t> referenceLarge {
+        // Flags.
+        2,
+        // Offsets for values.
+        4, 0, 0, 0, 8, 0, 0, 0, 12, 0, 0, 0, 16, 0, 0, 0, 21, 0, 0, 0,
+        int8_t(21 + largeArray.size()), int8_t((21 + largeArray.size()) >> 8),
+        int8_t((21 + largeArray.size()) >> 16), int8_t((21 + largeArray.size()) >> 24),
+        int8_t(24 + largeArray.size()), int8_t((24 + largeArray.size()) >> 8),
+        int8_t((24 + largeArray.size()) >> 16), int8_t((24 + largeArray.size()) >> 24),
+        // Key - integer zero.
+        0, 0, 0, 0,
+        // Integer values - 1, 2, 3.
+        1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
+        // Byte[] value.
+        1, 2, 3, 4, 5,
+        // Byte[] value -- skipped for now, inserted below.
+        // String "abc".
+        97, 98, 99};
+    // clang-format on
+
+    // Copy varlen array that does not fit to tiny and medium format.
+    referenceLarge.insert(referenceLarge.end() - 3, largeArray.begin(), largeArray.end());
+
+    binary_tuple_parser tpLarge(schema.length(), {reinterpret_cast<std::byte *>(referenceLarge.data()), referenceLarge.size()});
+    tuple_view large = tpLarge.parse();
+
+    EXPECT_TRUE(large[0].has_value());
+    EXPECT_EQ(0, read_tuple<int32_t>(large[0].value()));
+
+    EXPECT_TRUE(large[1].has_value());
+    EXPECT_EQ(1, read_tuple<int32_t>(large[1].value()));
+
+    EXPECT_TRUE(large[2].has_value());
+    EXPECT_EQ(2, read_tuple<int32_t>(large[2].value()));
+
+    EXPECT_TRUE(large[3].has_value());
+    EXPECT_EQ(3, read_tuple<int32_t>(large[3].value()));
+
+    EXPECT_TRUE(large[4].has_value());
+    varlen = large[4].value();
+
+    EXPECT_EQ(5, varlen.size());
+    EXPECT_EQ(1, std::to_integer<int>(varlen[0]));
+    EXPECT_EQ(2, std::to_integer<int>(varlen[1]));
+    EXPECT_EQ(3, std::to_integer<int>(varlen[2]));
+    EXPECT_EQ(4, std::to_integer<int>(varlen[3]));
+    EXPECT_EQ(5, std::to_integer<int>(varlen[4]));
+
+    EXPECT_TRUE(large[5].has_value());
+    varlen = large[5].value();
+
+    EXPECT_TRUE(largeArray
+        == std::vector<int8_t>(reinterpret_cast<const int8_t *>(varlen.data()),
+            reinterpret_cast<const int8_t *>(varlen.data() + varlen.size())));
+
+    EXPECT_TRUE(large[6].has_value());
+    varlen = large[6].value();
+    str = std::string(reinterpret_cast<const char *>(varlen.data()), varlen.size());
+    EXPECT_EQ("abc", str);
+}
+
+TEST(TuplesTests, StringAfterNull) {
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+    schema.columns.emplace_back(column_info {ignite_type::BINARY, true});
+    schema.columns.emplace_back(column_info {ignite_type::STRING, false});
+
+    // 101, null, "Bob"
+    std::vector<std::byte> tuple;
+    for (int i : {4, 2, 1, 1, 4, 101, 66, 111, 98}) {
+        tuple.push_back(static_cast<std::byte>(i));
+    }
+
+    binary_tuple_parser tp(schema.length(), tuple);
+
+    EXPECT_EQ(101, read_tuple<int32_t>(tp.get_next().value()));
+    EXPECT_FALSE(tp.get_next().has_value());
+    EXPECT_EQ("Bob", read_tuple<std::string>(tp.get_next().value()));
+}
+
+TEST(TuplesTests, EmptyValueTupleAssembler) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+
+    binary_tuple_schema sch = schema.to_tuple_schema();
+    tuple_assembler ta(sch);
+
+    auto tuple = ta.build(std::tuple<int32_t>(123));
+
+    binary_tuple_parser tp(schema.length(), tuple);
+
+    auto slice = tp.get_next();
+    ASSERT_TRUE(slice.has_value());
+    EXPECT_EQ(123, read_tuple<int32_t>(slice.value()));
+}
+
+TEST(TuplesTests, TupleWriteRead) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+    schema.columns.emplace_back(column_info {ignite_type::DOUBLE, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT16, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT64, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+
+    for (bool nullable : {false, true}) {
+        for (IntT i = 0; i < schema.length(); i++) {
+            schema.columns[i].nullable = nullable;
+        }
+
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler ta(sch);
+
+        ta.start();
+
+        ta.append<int32_t>(1234);
+        ta.append<double>(5.0);
+        ta.append<int8_t>(6);
+        ta.append<int16_t>(7);
+        if (nullable) {
+            ta.appendNull();
+        } else {
+            ta.append<int64_t>(8);
+        }
+        ta.append<int8_t>(9);
+
+        auto &tuple = ta.build();
+
+        binary_tuple_parser tp(schema.length(), tuple);
+        tuple_view t = tp.parse();
+
+        ASSERT_TRUE(t[0].has_value());
+        EXPECT_EQ(1234, read_tuple<int32_t>(t[0].value()));
+
+        ASSERT_TRUE(t[1].has_value());
+        EXPECT_EQ(5.0, read_tuple<double>(t[1].value()));
+
+        ASSERT_TRUE(t[2].has_value());
+        EXPECT_EQ(6, read_tuple<int8_t>(t[2].value()));
+
+        ASSERT_TRUE(t[3].has_value());
+        EXPECT_EQ(7, read_tuple<int16_t>(t[3].value()));
+
+        if (nullable) {
+            EXPECT_FALSE(t[4].has_value());
+        } else {
+            ASSERT_TRUE(t[4].has_value());
+            EXPECT_EQ(8, read_tuple<int64_t>(t[4].value()));
+        }
+
+        ASSERT_TRUE(t[5].has_value());
+        EXPECT_EQ(9, read_tuple<int8_t>(t[5].value()));
+    }
+}
+
+TEST(TuplesTests, Int8TupleWriteRead) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+    schema.columns.emplace_back(column_info {ignite_type::INT8, false});
+
+    for (bool nullable : {false, true}) {
+        for (IntT i = 0; i < schema.length(); i++) {
+            schema.columns[i].nullable = nullable;
+        }
+
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler ta(sch);
+
+        ta.start();
+
+        ta.append<int8_t>(0);
+        ta.append<int8_t>(1);
+        ta.append<int8_t>(2);
+        ta.append<int8_t>(3);
+        if (nullable) {
+            ta.appendNull();
+        } else {
+            ta.append<int8_t>(4);
+        }
+        ta.append<int8_t>(5);
+
+        auto &tuple = ta.build();
+
+        binary_tuple_parser tp(schema.length(), tuple);
+        tuple_view t = tp.parse();
+
+        ASSERT_TRUE(t[0].has_value());
+        EXPECT_EQ(0, read_tuple<int8_t>(t[0].value()));
+
+        ASSERT_TRUE(t[1].has_value());
+        EXPECT_EQ(1, read_tuple<int8_t>(t[1].value()));
+
+        ASSERT_TRUE(t[2].has_value());
+        EXPECT_EQ(2, read_tuple<int8_t>(t[2].value()));
+
+        ASSERT_TRUE(t[3].has_value());
+        EXPECT_EQ(3, read_tuple<int8_t>(t[3].value()));
+
+        if (nullable) {
+            EXPECT_FALSE(t[4].has_value());
+        } else {
+            ASSERT_TRUE(t[4].has_value());
+            EXPECT_EQ(4, read_tuple<int8_t>(t[4].value()));
+        }
+
+        ASSERT_TRUE(t[5].has_value());
+        EXPECT_EQ(5, read_tuple<int8_t>(t[5].value()));
+    }
+}
+
+TEST(TuplesTests, NullKeyTupleAssembler) { // NOLINT(cert-err58-cpp)
+    GTEST_SKIP() << "Skip, as nullability is not checked during tuple assembly at the moment";
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+
+    {
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler ta(sch);
+
+        ta.start();
+
+        ASSERT_THROW(ta.appendNull(), std::runtime_error);
+    }
+
+    schema.columns[0].nullable = true;
+
+    binary_tuple_schema sch = schema.to_tuple_schema();
+    tuple_assembler ta(sch);
+
+    ta.start();
+
+    ta.appendNull();
+
+    auto &tuple = ta.build(std::make_tuple(std::nullopt));
+
+    binary_tuple_parser tp(schema.length(), tuple);
+
+    ASSERT_FALSE(tp.get_next().has_value());
+}
+
+TEST(TuplesTests, VarlenLargeTest) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns = {
+        {ignite_type::STRING, true}, {ignite_type::STRING, true}, {ignite_type::STRING, true}};
+
+    {
+        auto values = std::make_tuple(std::string(100'000, 'a'), "b"s, std::nullopt);
+
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler tuple_assembler(sch);
+
+        bytes_view tuple = tuple_assembler.build(values);
+
+        binary_tuple_parser tp(schema.length(), tuple);
+
+        EXPECT_EQ(std::get<0>(values), read_tuple<std::string>(tp.get_next().value()));
+        EXPECT_EQ(std::get<1>(values), read_tuple<std::string>(tp.get_next().value()));
+        EXPECT_FALSE(tp.get_next().has_value());
+    }
+    {
+        auto values = std::make_tuple(std::string(100'000, 'a'), "b"s, "c"s);
+
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler tuple_assembler(sch);
+
+        bytes_view tuple = tuple_assembler.build(values);
+
+        binary_tuple_parser tp(schema.length(), tuple);
+
+        EXPECT_EQ(std::get<0>(values), read_tuple<std::string>(tp.get_next().value()));
+        EXPECT_EQ(std::get<1>(values), read_tuple<std::string>(tp.get_next().value()));
+        EXPECT_EQ(std::get<2>(values), read_tuple<std::string>(tp.get_next().value()));
+    }
+    {
+        schema.columns = {
+            {ignite_type::INT8, true}, {ignite_type::STRING, true}, {ignite_type::STRING, true}};
+
+        auto values = std::make_tuple(std::nullopt, std::string(100'000, 'a'), "b"s);
+
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler tuple_assembler(sch);
+
+        bytes_view tuple = tuple_assembler.build(values);
+
+        binary_tuple_parser tp(schema.length(), tuple);
+
+        EXPECT_FALSE(tp.get_next().has_value());
+        EXPECT_EQ(std::get<1>(values), read_tuple<std::string>(tp.get_next().value()));
+        EXPECT_EQ(std::get<2>(values), read_tuple<std::string>(tp.get_next().value()));
+    }
+}
+
+TEST(TuplesTests, VarlenMediumTest) { // NOLINT(cert-err58-cpp)
+    SchemaDescriptor schema;
+
+    schema.columns.emplace_back(column_info {ignite_type::INT32, false});
+    for (int i = 0; i < 300; i++) {
+        schema.columns.emplace_back(column_info {ignite_type::BINARY, false});
+    }
+
+    {
+        binary_tuple_schema sch = schema.to_tuple_schema();
+        tuple_assembler tuple_assembler(sch);
+        tuple_assembler.start();
+        tuple_assembler.append(int32_t {100500});
+
+        // Create a 10-char value.
+        std::string value("0123456789");
+
+        for (int i = 1; i < schema.length(); i++) {
+            tuple_assembler.append(value);
+        }
+
+        bytes_view tuple = tuple_assembler.build();
+
+        // The varlen area will take (10 * 300) = 3000 bytes. So the varlen table entry will take 2 bytes.
+        // The flags field must be equal to log2(2) = 1.
+        EXPECT_EQ(std::byte {1}, tuple[0]);
+
+        binary_tuple_parser tp(schema.length(), tuple);
+
+        EXPECT_EQ(100500, read_tuple<int32_t>(tp.get_next().value()));
+        for (IntT i = 1; i < schema.length(); i++) {
+            EXPECT_EQ(value, read_tuple<std::string>(tp.get_next().value()));
+        }
+    }
+
+    {
+        binary_tuple_schema sch({schema.columns.begin(), schema.columns.end()});
+        tuple_assembler tuple_assembler(sch);
+        tuple_assembler.start();
+        tuple_assembler.append(int32_t {100500});
+
+        // Create a 300-char value.
+        std::string value;
+        for (int i = 0; i < 30; i++) {
+            value += "0123456789";
+        }
+
+        for (int i = 1; i < schema.length(); i++) {
+            tuple_assembler.append(value);
+        }
+
+        bytes_view tuple = tuple_assembler.build();
+
+        // The varlen area will take (300 * 300) = 90000 bytes. The size value does not fit to
+        // one or two bytes. So the varlen table entry will take 4 bytes. The flags field must
+        // be equal to log2(4) = 2.
+        EXPECT_EQ(std::byte {2}, tuple[0]);
+
+        binary_tuple_parser tp(schema.length(), tuple);
+
+        EXPECT_EQ(100500, read_tuple<int32_t>(tp.get_next().value()));
+        for (IntT i = 1; i < schema.length(); i++) {
+            EXPECT_EQ(value, read_tuple<std::string>(tp.get_next().value()));
+        }
+    }
+}