blob: f7da2bbc254dd42af06442f8c6d678f148de3768 [file]
// 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 "vec/columns/column_varbinary.h"
#include <gtest/gtest-message.h>
#include <gtest/gtest-test-part.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
#include "common/exception.h"
#include "runtime/primitive_type.h"
#include "vec/columns/column.h"
#include "vec/columns/column_string.h"
#include "vec/common/assert_cast.h"
#include "vec/common/string_ref.h"
#include "vec/common/string_view.h"
#include "vec/core/types.h"
namespace doris::vectorized {
class ColumnVarbinaryTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
static std::string make_bytes(size_t n, uint8_t seed = 0xAB) {
std::string s;
s.resize(n);
for (size_t i = 0; i < n; ++i) {
s[i] = static_cast<char>(seed + i);
}
if (n >= 3) {
s[n / 3] = '\0';
s[(2 * n) / 3] = '\0';
}
return s;
}
};
TEST_F(ColumnVarbinaryTest, BasicInsertGetPopClear) {
auto col = ColumnVarbinary::create();
EXPECT_EQ(col->get_name(), std::string("ColumnVarbinary"));
EXPECT_EQ(col->size(), 0U);
const size_t inline_len = std::min<size_t>(doris::StringView::kInlineSize, 8);
const std::string small = make_bytes(inline_len, 0x11);
const std::string big = make_bytes(doris::StringView::kInlineSize + 32, 0x22);
size_t before_bytes = col->byte_size();
col->insert_data(small.data(), small.size());
EXPECT_EQ(col->size(), 1U);
auto r0 = col->get_data_at(0);
ASSERT_EQ(r0.size, small.size());
ASSERT_EQ(r0.size, 8U);
ASSERT_EQ(memcmp(r0.data, small.data(), small.size()), 0);
size_t after_small_bytes = col->byte_size();
ASSERT_EQ(after_small_bytes - before_bytes, sizeof(doris::StringView));
ASSERT_EQ(after_small_bytes - before_bytes, 16);
col->insert_default();
EXPECT_EQ(col->size(), 2U);
auto r1 = col->get_data_at(1);
ASSERT_EQ(r1.size, 0U);
col->insert_data(big.data(), big.size());
EXPECT_EQ(col->size(), 3U);
auto r2 = col->get_data_at(2);
ASSERT_EQ(r2.size, big.size());
ASSERT_EQ(r2.size, 44U);
ASSERT_EQ(memcmp(r2.data, big.data(), big.size()), 0);
size_t after_big_bytes = col->byte_size();
// big insert adds one StringView slot + big payload in arena (Arena may add alignment/overhead)
size_t diff = after_big_bytes - after_small_bytes;
std::cout << "after_big_bytes: " << after_big_bytes
<< " after_small_bytes: " << after_small_bytes << " diff: " << diff << std::endl;
ASSERT_GE(diff, sizeof(doris::StringView) + big.size());
// pop_back
col->pop_back(1);
EXPECT_EQ(col->size(), 2U);
auto r_back = col->get_data_at(1);
ASSERT_EQ(r_back.size, 0U);
// clear resets sizes (arena used_size becomes 0)
col->clear();
EXPECT_EQ(col->size(), 0U);
EXPECT_EQ(col->byte_size(), 0U);
}
TEST_F(ColumnVarbinaryTest, InsertFromAndRanges) {
auto src = ColumnVarbinary::create();
std::vector<std::string> vals = {make_bytes(1, 0x01), make_bytes(2, 0x02),
make_bytes(doris::StringView::kInlineSize + 5, 0x03),
make_bytes(0, 0x00), make_bytes(7, 0x05)};
for (auto& v : vals) {
src->insert_data(v.data(), v.size());
}
// insert_from single rows
auto dst1 = ColumnVarbinary::create();
for (size_t i = 0; i < vals.size(); ++i) {
dst1->insert_from(*src, i);
}
ASSERT_EQ(dst1->size(), vals.size());
for (size_t i = 0; i < vals.size(); ++i) {
auto r = dst1->get_data_at(i);
ASSERT_EQ(r.size, vals[i].size());
ASSERT_EQ(memcmp(r.data, vals[i].data(), r.size), 0);
}
// insert_range_from subset
auto dst2 = ColumnVarbinary::create();
dst2->insert_range_from(*src, 1, 3); // expect indices 1,2,3
ASSERT_EQ(dst2->size(), 3U);
for (size_t i = 0; i < 3; ++i) {
auto r = dst2->get_data_at(i);
ASSERT_EQ(r.size, vals[1 + i].size());
ASSERT_EQ(memcmp(r.data, vals[1 + i].data(), r.size), 0);
}
// insert_indices_from with duplicates and reordering
std::vector<uint32_t> indices = {4, 2, 2, 0};
auto dst3 = ColumnVarbinary::create();
dst3->insert_indices_from(*src, indices.data(), indices.data() + indices.size());
ASSERT_EQ(dst3->size(), indices.size());
for (size_t i = 0; i < indices.size(); ++i) {
auto r = dst3->get_data_at(i);
const auto& expect = vals[indices[i]];
ASSERT_EQ(r.size, expect.size());
ASSERT_EQ(memcmp(r.data, expect.data(), r.size), 0);
}
}
TEST_F(ColumnVarbinaryTest, FilterBothModes) {
auto col = ColumnVarbinary::create();
// Mix inline (small) and non-inline (large > kInlineSize) values
std::vector<std::string> vals = {
make_bytes(1, 0x10), // inline
make_bytes(doris::StringView::kInlineSize + 5, 0x91), // non-inline (dropped)
make_bytes(3, 0x12), // inline
make_bytes(doris::StringView::kInlineSize + 7, 0x92), // non-inline
make_bytes(0, 0x00), // empty (dropped)
make_bytes(doris::StringView::kInlineSize + 9, 0x93) // non-inline
};
for (auto& v : vals) {
col->insert_data(v.data(), v.size());
}
IColumn::Filter f = {1, 0, 1, 1, 0, 1};
size_t expected = 4; // number of ones
const auto& ccol = assert_cast<const ColumnVarbinary&>(*col);
ColumnPtr filtered = ccol.filter(f, -1);
const auto& fcol = assert_cast<const ColumnVarbinary&>(*filtered);
ASSERT_EQ(fcol.size(), expected);
std::vector<size_t> kept_idx = {0, 2, 3, 5}; // includes both inline and non-inline
for (size_t i = 0; i < kept_idx.size(); ++i) {
auto r = fcol.get_data_at(i);
const auto& expect = vals[kept_idx[i]];
ASSERT_EQ(r.size, expect.size());
ASSERT_EQ(memcmp(r.data, expect.data(), r.size), 0);
}
auto col_inplace = ColumnVarbinary::create();
for (auto& v : vals) {
col_inplace->insert_data(v.data(), v.size());
}
size_t new_sz = col_inplace->filter(f);
ASSERT_EQ(new_sz, expected);
ASSERT_EQ(col_inplace->size(), expected);
for (size_t i = 0; i < kept_idx.size(); ++i) {
auto r = col_inplace->get_data_at(i);
const auto& expect = vals[kept_idx[i]];
ASSERT_EQ(r.size, expect.size());
ASSERT_EQ(memcmp(r.data, expect.data(), r.size), 0);
}
}
TEST_F(ColumnVarbinaryTest, Permute) {
auto col = ColumnVarbinary::create();
// Include large (non-inline) entries to exercise arena path
std::vector<std::string> vals = {
make_bytes(1, 0x20), // inline
make_bytes(doris::StringView::kInlineSize + 3, 0xA0), // non-inline
make_bytes(3, 0x22), // inline
make_bytes(doris::StringView::kInlineSize + 8, 0xA1) // non-inline
};
for (auto& v : vals) {
col->insert_data(v.data(), v.size());
}
IColumn::Permutation perm = {3, 1, 2, 0};
// limit < size
ColumnPtr p1 = col->permute(perm, 3);
const auto& c1 = assert_cast<const ColumnVarbinary&>(*p1);
ASSERT_EQ(c1.size(), 3U);
for (size_t i = 0; i < 3; ++i) {
auto r = c1.get_data_at(i);
const auto& expect = vals[perm[i]];
ASSERT_EQ(r.size, expect.size());
ASSERT_EQ(memcmp(r.data, expect.data(), r.size), 0);
}
// full size
ColumnPtr p2 = col->permute(perm, vals.size());
const auto& c2 = assert_cast<const ColumnVarbinary&>(*p2);
ASSERT_EQ(c2.size(), vals.size());
for (size_t i = 0; i < vals.size(); ++i) {
auto r = c2.get_data_at(i);
const auto& expect = vals[perm[i]];
ASSERT_EQ(r.size, expect.size());
ASSERT_EQ(memcmp(r.data, expect.data(), r.size), 0);
}
}
TEST_F(ColumnVarbinaryTest, CloneResized) {
auto col = ColumnVarbinary::create();
std::vector<std::string> vals = {make_bytes(1, 0x30), make_bytes(0, 0x00),
make_bytes(doris::StringView::kInlineSize + 1, 0x31)};
for (auto& v : vals) {
col->insert_data(v.data(), v.size());
}
// enlarge
auto c2 = col->clone_resized(5);
const auto& cc2 = assert_cast<const ColumnVarbinary&>(*c2);
ASSERT_EQ(cc2.size(), 5U);
for (size_t i = 0; i < vals.size(); ++i) {
auto r = cc2.get_data_at(i);
ASSERT_EQ(r.size, vals[i].size());
ASSERT_EQ(memcmp(r.data, vals[i].data(), r.size), 0);
}
for (size_t i = vals.size(); i < 5; ++i) {
auto r = cc2.get_data_at(i);
ASSERT_EQ(r.size, 0U); // default rows
}
// shrink
auto c3 = col->clone_resized(2);
const auto& cc3 = assert_cast<const ColumnVarbinary&>(*c3);
ASSERT_EQ(cc3.size(), 2U);
for (size_t i = 0; i < 2; ++i) {
auto r = cc3.get_data_at(i);
ASSERT_EQ(r.size, vals[i].size());
ASSERT_EQ(memcmp(r.data, vals[i].data(), r.size), 0);
}
}
TEST_F(ColumnVarbinaryTest, ReplaceColumnData) {
auto col = ColumnVarbinary::create();
// mix inline and non-inline
std::vector<std::string> vals = {
make_bytes(2, 0x40), // inline
make_bytes(doris::StringView::kInlineSize + 4, 0xB0), // non-inline
make_bytes(4, 0x42) // inline
};
for (auto& v : vals) {
col->insert_data(v.data(), v.size());
}
auto rhs = ColumnVarbinary::create();
std::vector<std::string> rhs_vals = {
make_bytes(doris::StringView::kInlineSize + 7, 0xC0), // non-inline
make_bytes(1, 0x51) // inline
};
for (auto& v : rhs_vals) {
rhs->insert_data(v.data(), v.size());
}
// replace row 0 (inline) with rhs[1] (inline) -> stays inline
col->replace_column_data(*rhs, /*row=*/1, /*self_row=*/0);
auto r0 = col->get_data_at(0);
ASSERT_EQ(r0.size, rhs_vals[1].size());
ASSERT_EQ(memcmp(r0.data, rhs_vals[1].data(), r0.size), 0);
// replace row 2 (inline) with rhs[0] (non-inline)
col->replace_column_data(*rhs, /*row=*/0, /*self_row=*/2);
auto r2 = col->get_data_at(2);
ASSERT_EQ(r2.size, rhs_vals[0].size());
ASSERT_EQ(memcmp(r2.data, rhs_vals[0].data(), r2.size), 0);
}
TEST_F(ColumnVarbinaryTest, SerializeDeserializeRoundtripManual) {
auto col = ColumnVarbinary::create();
std::string v = make_bytes(doris::StringView::kInlineSize + 17, 0x60);
std::vector<char> buf;
auto len = static_cast<uint32_t>(v.size());
buf.resize(sizeof(uint32_t) + v.size());
memcpy(buf.data(), &len, sizeof(uint32_t));
memcpy(buf.data() + sizeof(uint32_t), v.data(), v.size());
const char* p = buf.data();
const char* end = col->deserialize_and_insert_from_arena(p);
ASSERT_EQ(static_cast<size_t>(end - p), sizeof(uint32_t) + v.size());
ASSERT_EQ(col->size(), 1U);
auto r = col->get_data_at(0);
ASSERT_EQ(r.size, v.size());
ASSERT_EQ(memcmp(r.data, v.data(), r.size), 0);
}
TEST_F(ColumnVarbinaryTest, SerializeSizeAtShouldIncludeLengthHeader) {
auto col = ColumnVarbinary::create();
std::string v = make_bytes(9, 0x70);
col->insert_data(v.data(), v.size());
size_t sz = col->serialize_size_at(0);
// Expect payload + 4 bytes length header.
EXPECT_EQ(sz, v.size() + sizeof(uint32_t));
}
TEST_F(ColumnVarbinaryTest, FieldAccessOperatorAndGet) {
auto col = ColumnVarbinary::create();
std::vector<std::string> vals = {
make_bytes(1, 0x11), make_bytes(0, 0x00),
make_bytes(doris::StringView::kInlineSize + 6, 0x12)}; // include non-inline
for (auto& v : vals) {
col->insert_data(v.data(), v.size());
}
for (size_t i = 0; i < vals.size(); ++i) {
// operator[]
Field f = (*col)[i];
auto sv = vectorized::get<const doris::StringView&>(f);
ASSERT_EQ(sv.size(), vals[i].size());
ASSERT_EQ(memcmp(sv.data(), vals[i].data(), sv.size()), 0);
// get(size_t, Field&)
Field f2;
col->get(i, f2);
auto sv2 = vectorized::get<const doris::StringView&>(f2);
ASSERT_EQ(sv2.size(), vals[i].size());
ASSERT_EQ(memcmp(sv2.data(), vals[i].data(), sv2.size()), 0);
}
}
TEST_F(ColumnVarbinaryTest, InsertField) {
auto col = ColumnVarbinary::create();
// prepare inline and non-inline fields
std::string inline_v = make_bytes(2, 0x21);
std::string big_v = make_bytes(doris::StringView::kInlineSize + 10, 0x22);
Field f_inline = Field::create_field<TYPE_VARBINARY>(
doris::StringView(inline_v.data(), static_cast<uint32_t>(inline_v.size())));
Field f_big = Field::create_field<TYPE_VARBINARY>(
doris::StringView(big_v.data(), static_cast<uint32_t>(big_v.size())));
col->insert(f_inline);
col->insert(f_big);
ASSERT_EQ(col->size(), 2U);
auto r0 = col->get_data_at(0);
auto r1 = col->get_data_at(1);
ASSERT_EQ(r0.size, inline_v.size());
ASSERT_EQ(memcmp(r0.data, inline_v.data(), r0.size), 0);
ASSERT_EQ(r1.size, big_v.size());
ASSERT_EQ(memcmp(r1.data, big_v.data(), r1.size), 0);
}
TEST_F(ColumnVarbinaryTest, SerializeValueIntoArenaAndImpl) {
auto col = ColumnVarbinary::create();
std::string small = make_bytes(3, 0x31); // inline
std::string big = make_bytes(doris::StringView::kInlineSize + 12, 0x32); // non-inline
col->insert_data(small.data(), small.size());
col->insert_data(big.data(), big.size());
// serialize_value_into_arena (covers serialize_impl indirectly)
Arena arena;
const char* begin = nullptr;
auto sr_inline = col->serialize_value_into_arena(0, arena, begin);
ASSERT_EQ(sr_inline.size, small.size() + sizeof(uint32_t));
uint32_t len_inline;
memcpy(&len_inline, sr_inline.data, sizeof(uint32_t));
ASSERT_EQ(len_inline, small.size());
ASSERT_EQ(memcmp(sr_inline.data + sizeof(uint32_t), small.data(), small.size()), 0);
auto sr_big = col->serialize_value_into_arena(1, arena, begin);
ASSERT_EQ(sr_big.size, big.size() + sizeof(uint32_t));
uint32_t len_big;
memcpy(&len_big, sr_big.data, sizeof(uint32_t));
ASSERT_EQ(len_big, big.size());
ASSERT_EQ(memcmp(sr_big.data + sizeof(uint32_t), big.data(), big.size()), 0);
// direct serialize_impl
char buf[4096];
size_t written = col->serialize_impl(buf, 1);
ASSERT_EQ(written, big.size() + sizeof(uint32_t));
uint32_t len_big2;
memcpy(&len_big2, buf, sizeof(uint32_t));
ASSERT_EQ(len_big2, big.size());
ASSERT_EQ(memcmp(buf + sizeof(uint32_t), big.data(), big.size()), 0);
}
TEST_F(ColumnVarbinaryTest, AllocatedBytesAndHasEnoughCapacity) {
auto dest = ColumnVarbinary::create();
// Grow dest to obtain some spare capacity
for (int i = 0; i < 64; ++i) {
std::string v = make_bytes((i % 5) + 1, static_cast<uint8_t>(0x40 + i)); // mostly inline
dest->insert_data(v.data(), v.size());
}
// Force some non-inline values to ensure arena usage
for (int i = 0; i < 3; ++i) {
auto big =
make_bytes(doris::StringView::kInlineSize + 20 + i, static_cast<uint8_t>(0x90 + i));
dest->insert_data(big.data(), big.size());
}
// Capture capacity & size
size_t cap = dest->get_data().capacity();
size_t sz = dest->size();
ASSERT_GT(cap, sz);
// Ensure allocated_bytes >= byte_size()
ASSERT_GE(dest->allocated_bytes(), dest->byte_size());
// Create src_small with size less than free slots (cap - sz)
size_t free_slots = cap - sz;
auto src_small = ColumnVarbinary::create();
for (size_t i = 0; i < free_slots - 1; ++i) { // leave at least 1 slot
auto v = make_bytes(1, 0x55);
src_small->insert_data(v.data(), v.size());
}
ASSERT_TRUE(dest->has_enough_capacity(*src_small));
// src_big exactly fills free slots -> expect false (need strictly greater)
auto src_big = ColumnVarbinary::create();
for (size_t i = 0; i < free_slots; ++i) {
auto v = make_bytes(1, 0x66);
src_big->insert_data(v.data(), v.size());
}
ASSERT_FALSE(dest->has_enough_capacity(*src_big));
}
TEST_F(ColumnVarbinaryTest, InsertRangeFromOutOfBoundsThrows) {
auto src = ColumnVarbinary::create();
std::vector<std::string> vals = {make_bytes(2, 0x10), make_bytes(3, 0x20)};
for (auto& v : vals) {
src->insert_data(v.data(), v.size());
}
auto dst = ColumnVarbinary::create();
EXPECT_THROW(dst->insert_range_from(*src, /*start=*/1, /*length=*/5), doris::Exception);
}
TEST_F(ColumnVarbinaryTest, PermuteThrowsOnShortPermutation) {
auto col = ColumnVarbinary::create();
std::vector<std::string> vals = {make_bytes(1, 0x31), make_bytes(1, 0x32),
make_bytes(doris::StringView::kInlineSize + 2, 0x33)};
for (auto& v : vals) {
col->insert_data(v.data(), v.size());
}
IColumn::Permutation perm = {1};
EXPECT_THROW(col->permute(perm, 2), doris::Exception);
}
TEST_F(ColumnVarbinaryTest, ReplaceColumnDataOnNonInlineTarget) {
auto col = ColumnVarbinary::create();
std::string inl = make_bytes(3, 0x41); // inline
std::string big1 = make_bytes(doris::StringView::kInlineSize + 5, 0xB1); // non-inline
std::string big2 = make_bytes(doris::StringView::kInlineSize + 7, 0xB2); // non-inline
col->insert_data(inl.data(), inl.size());
col->insert_data(big1.data(), big1.size()); // row 1: non-inline target
auto rhs = ColumnVarbinary::create();
std::string rhs_inline = make_bytes(2, 0x51); // inline
rhs->insert_data(rhs_inline.data(), rhs_inline.size()); // row 0 inline
rhs->insert_data(big2.data(), big2.size()); // row 1 non-inline
col->replace_column_data(*rhs, /*row=*/0, /*self_row=*/1);
auto r1 = col->get_data_at(1);
ASSERT_EQ(r1.size, rhs_inline.size());
ASSERT_EQ(memcmp(r1.data, rhs_inline.data(), r1.size), 0);
col->replace_column_data(*rhs, /*row=*/1, /*self_row=*/1);
auto r1b = col->get_data_at(1);
ASSERT_EQ(r1b.size, big2.size());
ASSERT_EQ(memcmp(r1b.data, big2.data(), r1b.size), 0);
}
TEST_F(ColumnVarbinaryTest, SerializeSizeAtForNonInline) {
auto col = ColumnVarbinary::create();
std::string small = make_bytes(4, 0x61);
std::string big = make_bytes(doris::StringView::kInlineSize + 9, 0x62);
col->insert_data(small.data(), small.size());
col->insert_data(big.data(), big.size());
EXPECT_EQ(col->serialize_size_at(0), small.size() + sizeof(uint32_t));
EXPECT_EQ(col->serialize_size_at(1), big.size() + sizeof(uint32_t));
}
TEST_F(ColumnVarbinaryTest, CloneResizedZero) {
auto col = ColumnVarbinary::create();
col->insert_data("a", 1);
col->insert_data("", 0);
auto c0 = col->clone_resized(0);
const auto& cc0 = assert_cast<const ColumnVarbinary&>(*c0);
EXPECT_EQ(cc0.size(), 0U);
}
} // namespace doris::vectorized