Skip to content

Commit ac2e446

Browse files
authored
test: add test for test coverage (alibaba#442)
1 parent db56e9f commit ac2e446

13 files changed

Lines changed: 1364 additions & 0 deletions

src/paimon/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,8 @@ if(PAIMON_BUILD_TESTS)
473473
common/data/blob_utils_test.cpp
474474
common/data/variant/generic_variant_test.cpp
475475
common/data/variant/infer_variant_shredding_schema_test.cpp
476+
common/data/variant/variant_access_utils_test.cpp
477+
common/data/variant/variant_shredding_read_plan_factory_test.cpp
476478
common/data/variant/variant_shredding_write_plan_factory_test.cpp
477479
common/data/variant/variant_get_test.cpp
478480
common/data/variant/variant_json_utils_test.cpp

src/paimon/common/compression/block_compression_factory_test.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,35 @@ TEST_P(CompressionFactoryTest, TestCompressInsufficientOutputBuffer) {
115115
INSTANTIATE_TEST_SUITE_P(BlockCompressionTypeGroup, CompressionFactoryTest,
116116
::testing::Values(BlockCompressionType::LZ4, BlockCompressionType::ZSTD));
117117

118+
TEST(CompressionFactoryCreateTest, TestCreateFromCompressOptions) {
119+
ASSERT_OK_AND_ASSIGN(auto none_factory,
120+
BlockCompressionFactory::Create(CompressOptions{"none", 0}));
121+
ASSERT_EQ(BlockCompressionType::NONE, none_factory->GetCompressionType());
122+
123+
// Codec name matching is case-insensitive.
124+
ASSERT_OK_AND_ASSIGN(auto zstd_factory,
125+
BlockCompressionFactory::Create(CompressOptions{"ZSTD", 1}));
126+
ASSERT_EQ(BlockCompressionType::ZSTD, zstd_factory->GetCompressionType());
127+
128+
ASSERT_OK_AND_ASSIGN(auto lz4_factory,
129+
BlockCompressionFactory::Create(CompressOptions{"Lz4", 0}));
130+
ASSERT_EQ(BlockCompressionType::LZ4, lz4_factory->GetCompressionType());
131+
132+
// Unsupported codec name returns an Invalid status.
133+
auto unsupported = BlockCompressionFactory::Create(CompressOptions{"lzo", 0});
134+
ASSERT_NOK(unsupported);
135+
ASSERT_TRUE(unsupported.status().IsInvalid());
136+
}
137+
138+
TEST(CompressionFactoryCreateTest, TestCreateFromCompressionType) {
139+
ASSERT_OK_AND_ASSIGN(auto none_factory,
140+
BlockCompressionFactory::Create(BlockCompressionType::NONE));
141+
ASSERT_EQ(BlockCompressionType::NONE, none_factory->GetCompressionType());
142+
143+
// LZO is declared but not yet implemented, so it hits the default branch.
144+
auto lzo = BlockCompressionFactory::Create(BlockCompressionType::LZO);
145+
ASSERT_NOK(lzo);
146+
ASSERT_TRUE(lzo.status().IsInvalid());
147+
}
148+
118149
} // namespace paimon::test

src/paimon/common/data/variant/generic_variant_test.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
#include "paimon/common/data/variant/generic_variant.h"
1818

19+
#include <functional>
1920
#include <limits>
2021
#include <string>
22+
#include <vector>
2123

2224
#include "gtest/gtest.h"
2325
#include "paimon/common/data/variant/variant_builder.h"
@@ -345,4 +347,75 @@ TEST_F(GenericVariantTest, NonFiniteDoubleToJson) {
345347
ASSERT_EQ(json, "\"Infinity\"");
346348
}
347349

350+
TEST_F(GenericVariantTest, GetTypeInfoReturnsHeaderBits) {
351+
// GetTypeInfo exposes the primitive header's type-info bits; 42 is encoded as an int1.
352+
auto v = FromJson("42");
353+
ASSERT_OK_AND_ASSIGN(int32_t type_info, v->GetTypeInfo());
354+
EXPECT_EQ(type_info, VariantDefs::kInt1);
355+
}
356+
357+
TEST_F(GenericVariantTest, TypedGettersRejectMismatchedTypes) {
358+
// Each accessor validates the value header and fails when the stored type differs.
359+
auto number = FromJson("42"); // primitive int1
360+
auto text = FromJson("\"hi\""); // short string
361+
auto real = FromJson("1.5e0"); // double
362+
363+
// A primitive long is neither boolean/double/decimal/float/binary/string/uuid, nor a
364+
// container.
365+
ASSERT_NOK(number->GetBoolean());
366+
ASSERT_NOK(number->GetDouble());
367+
ASSERT_NOK(number->GetDecimal());
368+
ASSERT_NOK(number->GetFloat());
369+
ASSERT_NOK(number->GetBinary());
370+
ASSERT_NOK(number->GetString());
371+
ASSERT_NOK(number->GetUuid());
372+
ASSERT_NOK(number->ObjectSize());
373+
ASSERT_NOK(number->ArraySize());
374+
// A short string is not a primitive, so long/decimal decoding rejects it early.
375+
ASSERT_NOK(text->GetLong());
376+
ASSERT_NOK(text->GetDecimal());
377+
// A double is a primitive but not an integer-like type.
378+
ASSERT_NOK(real->GetLong());
379+
}
380+
381+
TEST_F(GenericVariantTest, ValueSizeCoversAllPrimitiveWidths) {
382+
// Builds an array whose elements span the primitive width branches of `ValueSize` (int4,
383+
// decimal8, binary, uuid). Copying elements into the array and re-reading each element's
384+
// value both exercise `ValueSize`.
385+
auto build = [this](const std::function<Status(VariantBuilder&)>& append) {
386+
VariantBuilder builder(/*allow_duplicate_keys=*/false);
387+
EXPECT_OK(append(builder));
388+
auto result = builder.Build(pool_);
389+
EXPECT_TRUE(result.ok()) << result.status().ToString();
390+
return result.value();
391+
};
392+
std::shared_ptr<GenericVariant> int4 =
393+
build([](VariantBuilder& b) { return b.AppendLong(100000); }); // needs 4 bytes
394+
std::shared_ptr<GenericVariant> decimal8 =
395+
build([](VariantBuilder& b) { return b.AppendDecimal(VariantDecimal{1234567890, 2}); });
396+
std::shared_ptr<GenericVariant> binary =
397+
build([](VariantBuilder& b) { return b.AppendBinary(std::string_view("abc", 3)); });
398+
std::string uuid_bytes(16, '\x07');
399+
std::shared_ptr<GenericVariant> uuid =
400+
build([&](VariantBuilder& b) { return b.AppendUuid(uuid_bytes); });
401+
402+
VariantBuilder array_builder(/*allow_duplicate_keys=*/false);
403+
int32_t start = array_builder.GetWritePos();
404+
std::vector<int32_t> offsets;
405+
for (const std::shared_ptr<GenericVariant>* element : {&int4, &decimal8, &binary, &uuid}) {
406+
offsets.push_back(array_builder.GetWritePos() - start);
407+
ASSERT_OK(array_builder.AppendVariant(**element));
408+
}
409+
ASSERT_OK(array_builder.FinishWritingArray(start, offsets));
410+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> array_variant, array_builder.Build(pool_));
411+
412+
ASSERT_OK_AND_ASSIGN(int32_t size, array_variant->ArraySize());
413+
ASSERT_EQ(size, 4);
414+
for (int32_t i = 0; i < size; ++i) {
415+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> element,
416+
array_variant->GetElementAtIndex(i));
417+
ASSERT_OK(element->Value());
418+
}
419+
}
420+
348421
} // namespace paimon::test

src/paimon/common/data/variant/infer_variant_shredding_schema_test.cpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "paimon/common/data/variant/infer_variant_shredding_schema.h"
1818

19+
#include <functional>
1920
#include <string>
2021
#include <vector>
2122

@@ -52,6 +53,18 @@ class InferVariantShreddingSchemaTest : public ::testing::Test {
5253
return samples;
5354
}
5455

56+
// Builds a single variant using the direct append API, which can encode types (float, binary,
57+
// uuid, decimals with a specific scale) that JSON parsing never produces.
58+
std::shared_ptr<GenericVariant> BuildVariant(
59+
const std::function<Status(VariantBuilder&)>& append) {
60+
VariantBuilder builder(/*allow_duplicate_keys=*/false);
61+
Status st = append(builder);
62+
EXPECT_TRUE(st.ok()) << st.ToString();
63+
auto result = builder.Build(pool_);
64+
EXPECT_TRUE(result.ok()) << result.status().ToString();
65+
return result.value();
66+
}
67+
5568
protected:
5669
std::shared_ptr<MemoryPool> pool_ = GetDefaultPool();
5770
InferVariantShreddingSchema infer_{/*max_schema_width=*/300, /*max_schema_depth=*/50,
@@ -209,4 +222,149 @@ TEST_F(InferVariantShreddingSchemaTest, TemporalValuesStayUnshredded) {
209222
ASSERT_EQ(ts_inferred, nullptr);
210223
}
211224

225+
TEST_F(InferVariantShreddingSchemaTest, MergeObjectsWithDisjointFields) {
226+
// Objects whose keys interleave exercise both single-side merge branches (field only in the
227+
// first object, and field only in the second).
228+
auto samples = Samples({R"({"a": 1, "c": 3})", R"({"b": 2, "c": 4})"});
229+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred, InferColumn(infer_, samples));
230+
ASSERT_NE(inferred, nullptr);
231+
auto expected =
232+
arrow::struct_({arrow::field("a", arrow::int64()), arrow::field("b", arrow::int64()),
233+
arrow::field("c", arrow::int64())});
234+
ASSERT_TRUE(inferred->Equals(*expected)) << inferred->ToString();
235+
}
236+
237+
TEST_F(InferVariantShreddingSchemaTest, VariantNullSamplesAndFields) {
238+
// A top-level variant null (JSON `null`, not a missing sample) merges away, leaving the other
239+
// sample's inferred type.
240+
auto scalar_and_null = Samples({"1", "null"});
241+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> merged,
242+
InferColumn(infer_, scalar_and_null));
243+
ASSERT_NE(merged, nullptr);
244+
ASSERT_TRUE(merged->Equals(*arrow::int64())) << merged->ToString();
245+
246+
// An object field that is always variant-null becomes an untyped variant leaf.
247+
auto object_with_null = Samples({R"({"a": null, "b": 1})"});
248+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred,
249+
InferColumn(infer_, object_with_null));
250+
ASSERT_NE(inferred, nullptr);
251+
auto expected =
252+
arrow::struct_({arrow::field("a", arrow::null()), arrow::field("b", arrow::int64())});
253+
ASSERT_TRUE(inferred->Equals(*expected)) << inferred->ToString();
254+
}
255+
256+
TEST_F(InferVariantShreddingSchemaTest, ArraysMerge) {
257+
// Two arrays merge element-wise into a single typed element schema.
258+
auto samples = Samples({"[1, 2]", "[3, 4]"});
259+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred, InferColumn(infer_, samples));
260+
ASSERT_NE(inferred, nullptr);
261+
ASSERT_TRUE(inferred->Equals(*arrow::list(arrow::int64()))) << inferred->ToString();
262+
}
263+
264+
TEST_F(InferVariantShreddingSchemaTest, ArrayBeyondDepthLimitStaysVariant) {
265+
InferVariantShreddingSchema shallow_infer{/*max_schema_width=*/300, /*max_schema_depth=*/1,
266+
/*min_field_cardinality_ratio=*/0.1};
267+
auto samples = Samples({R"({"arr": [1, 2]})"});
268+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred,
269+
InferColumn(shallow_infer, samples));
270+
ASSERT_NE(inferred, nullptr);
271+
// Depth 1: the nested array is beyond the limit and stays an untyped variant leaf.
272+
auto expected = arrow::struct_({arrow::field("arr", arrow::null())});
273+
ASSERT_TRUE(inferred->Equals(*expected)) << inferred->ToString();
274+
}
275+
276+
TEST_F(InferVariantShreddingSchemaTest, ScalarLeafTypes) {
277+
// Float and binary leaves shred to their arrow types.
278+
std::shared_ptr<GenericVariant> float_variant =
279+
BuildVariant([](VariantBuilder& b) { return b.AppendFloat(1.5f); });
280+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> float_inferred,
281+
InferColumn(infer_, {float_variant}));
282+
ASSERT_NE(float_inferred, nullptr);
283+
ASSERT_TRUE(float_inferred->Equals(*arrow::float32())) << float_inferred->ToString();
284+
285+
std::shared_ptr<GenericVariant> binary_variant = BuildVariant(
286+
[](VariantBuilder& b) { return b.AppendBinary(std::string_view("\x01\x02", 2)); });
287+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> binary_inferred,
288+
InferColumn(infer_, {binary_variant}));
289+
ASSERT_NE(binary_inferred, nullptr);
290+
ASSERT_TRUE(binary_inferred->Equals(*arrow::binary())) << binary_inferred->ToString();
291+
292+
// A UUID has no shredding type, so the column stays unshredded.
293+
std::string uuid_bytes(16, '\0');
294+
std::shared_ptr<GenericVariant> uuid_variant =
295+
BuildVariant([&](VariantBuilder& b) { return b.AppendUuid(uuid_bytes); });
296+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> uuid_inferred,
297+
InferColumn(infer_, {uuid_variant}));
298+
ASSERT_EQ(uuid_inferred, nullptr);
299+
}
300+
301+
TEST_F(InferVariantShreddingSchemaTest, LargeIntegerAndDecimalMerging) {
302+
// A 19-digit long exceeds decimal(18) precision, so it stays a genuine int64 leaf.
303+
auto big_long = Samples({"1000000000000000000"});
304+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> long_inferred,
305+
InferColumn(infer_, big_long));
306+
ASSERT_NE(long_inferred, nullptr);
307+
ASSERT_TRUE(long_inferred->Equals(*arrow::int64())) << long_inferred->ToString();
308+
309+
// A long (int64) merged with a fractional decimal widens via MergeDecimalWithLong.
310+
auto long_then_decimal = Samples({"1000000000000000000", "1.5"});
311+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ld,
312+
InferColumn(infer_, long_then_decimal));
313+
ASSERT_NE(ld, nullptr);
314+
ASSERT_TRUE(ld->Equals(*arrow::decimal128(38, 1))) << ld->ToString();
315+
316+
// The reversed order (decimal first, then long) hits the mirrored branch.
317+
auto decimal_then_long = Samples({"1.5", "1000000000000000000"});
318+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> dl,
319+
InferColumn(infer_, decimal_then_long));
320+
ASSERT_NE(dl, nullptr);
321+
ASSERT_TRUE(dl->Equals(*arrow::decimal128(38, 1))) << dl->ToString();
322+
}
323+
324+
TEST_F(InferVariantShreddingSchemaTest, LongMergedWithIntegralDecimalStaysLong) {
325+
// A scale-0 decimal that fits in 18 digits merges with a long back to int64.
326+
std::shared_ptr<GenericVariant> integral_decimal =
327+
BuildVariant([](VariantBuilder& b) { return b.AppendDecimal(VariantDecimal{123, 0}); });
328+
std::shared_ptr<GenericVariant> big_long = Samples({"1000000000000000000"})[0];
329+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred,
330+
InferColumn(infer_, {integral_decimal, big_long}));
331+
ASSERT_NE(inferred, nullptr);
332+
ASSERT_TRUE(inferred->Equals(*arrow::int64())) << inferred->ToString();
333+
}
334+
335+
TEST_F(InferVariantShreddingSchemaTest, SmallFractionalDecimalPrecisionAdjusted) {
336+
// 0.0015 has more fractional digits than significant digits; precision widens up to the scale.
337+
auto samples = Samples({"0.0015"});
338+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred, InferColumn(infer_, samples));
339+
ASSERT_NE(inferred, nullptr);
340+
ASSERT_TRUE(inferred->Equals(*arrow::decimal128(18, 4))) << inferred->ToString();
341+
}
342+
343+
TEST_F(InferVariantShreddingSchemaTest, DecimalMergeOverflowFallsToVariant) {
344+
// A 38-digit integral decimal merged with a high-scale decimal would need precision > 38,
345+
// which decimal cannot represent, so the column stays unshredded.
346+
__int128 wide_unscaled = 0;
347+
for (int i = 0; i < 38; ++i) {
348+
wide_unscaled = wide_unscaled * 10 + 1; // 38 ones, no trailing zeros
349+
}
350+
std::shared_ptr<GenericVariant> wide_decimal = BuildVariant(
351+
[&](VariantBuilder& b) { return b.AppendDecimal(VariantDecimal{wide_unscaled, 0}); });
352+
std::shared_ptr<GenericVariant> high_scale_decimal =
353+
BuildVariant([](VariantBuilder& b) { return b.AppendDecimal(VariantDecimal{15, 20}); });
354+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred,
355+
InferColumn(infer_, {wide_decimal, high_scale_decimal}));
356+
ASSERT_EQ(inferred, nullptr);
357+
}
358+
359+
TEST_F(InferVariantShreddingSchemaTest, ObjectWithAllRareFieldsStaysUnshredded) {
360+
InferVariantShreddingSchema strict_infer{/*max_schema_width=*/300, /*max_schema_depth=*/50,
361+
/*min_field_cardinality_ratio=*/0.6};
362+
// Two objects with disjoint single-occurrence keys: with a 0.6 ratio every field is below the
363+
// cardinality threshold, so the object contributes no typed field and the column is dropped.
364+
auto samples = Samples({R"({"a": 1})", R"({"b": 2})"});
365+
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> inferred,
366+
InferColumn(strict_infer, samples));
367+
ASSERT_EQ(inferred, nullptr);
368+
}
369+
212370
} // namespace paimon::test

0 commit comments

Comments
 (0)