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:01 UTC
[ignite-3] 01/01: IGNITE-18017 Add binary tuple tests
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()));
+ }
+ }
+}