| /* |
| * 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 "paimon/common/data/blob_utils.h" |
| |
| #include "arrow/api.h" |
| #include "arrow/c/bridge.h" |
| #include "gtest/gtest.h" |
| #include "paimon/catalog/identifier.h" |
| #include "paimon/common/data/blob_defs.h" |
| #include "paimon/common/data/blob_descriptor.h" |
| #include "paimon/common/data/blob_view_struct.h" |
| #include "paimon/common/types/data_field.h" |
| #include "paimon/data/blob.h" |
| #include "paimon/memory/memory_pool.h" |
| #include "paimon/testing/utils/testharness.h" |
| |
| namespace paimon::test { |
| |
| class BlobUtilsTest : public ::testing::Test { |
| public: |
| std::shared_ptr<arrow::KeyValueMetadata> CreateBlobMetadata() { |
| std::unordered_map<std::string, std::string> blob_metadata_map = { |
| {BlobDefs::kExtensionTypeKey, BlobDefs::kExtensionTypeValue}}; |
| return std::make_shared<arrow::KeyValueMetadata>(blob_metadata_map); |
| } |
| |
| private: |
| std::shared_ptr<MemoryPool> pool_ = GetDefaultPool(); |
| }; |
| |
| TEST_F(BlobUtilsTest, IsBlobMetadata) { |
| auto correct_metadata = CreateBlobMetadata(); |
| ASSERT_TRUE(BlobUtils::IsBlobMetadata(correct_metadata)); |
| ASSERT_FALSE(BlobUtils::IsBlobMetadata(nullptr)); |
| std::unordered_map<std::string, std::string> wrong_metadata_map = { |
| {BlobDefs::kExtensionTypeKey, "paimon.type.varchar"}}; |
| auto wrong_metadata = std::make_shared<arrow::KeyValueMetadata>(wrong_metadata_map); |
| ASSERT_FALSE(BlobUtils::IsBlobMetadata(wrong_metadata)); |
| std::unordered_map<std::string, std::string> no_extension_metadata_map = { |
| {"other_key", BlobDefs::kExtensionTypeValue}}; |
| auto no_extension_metadata = |
| std::make_shared<arrow::KeyValueMetadata>(no_extension_metadata_map); |
| ASSERT_FALSE(BlobUtils::IsBlobMetadata(no_extension_metadata)); |
| } |
| |
| TEST_F(BlobUtilsTest, IsBlobField) { |
| std::shared_ptr<arrow::Field> blob_field = BlobUtils::ToArrowField("f1", true); |
| ASSERT_TRUE(BlobUtils::IsBlobField(blob_field)); |
| |
| auto int_field = arrow::field("i_int", arrow::int32()); |
| ASSERT_FALSE(BlobUtils::IsBlobField(int_field)); |
| |
| auto binary_field_no_meta = arrow::field("b_no_meta", arrow::large_binary()); |
| ASSERT_FALSE(BlobUtils::IsBlobField(binary_field_no_meta)); |
| |
| auto wrong_meta = std::make_shared<arrow::KeyValueMetadata>( |
| std::unordered_map<std::string, std::string>{{"other_key", "value"}}); |
| auto binary_field_wrong_meta = |
| arrow::field("b_wrong_meta", arrow::large_binary(), false, wrong_meta); |
| ASSERT_FALSE(BlobUtils::IsBlobField(binary_field_wrong_meta)); |
| } |
| |
| TEST_F(BlobUtilsTest, SeparateBlobSchema) { |
| auto int_field = arrow::field("f1_int", arrow::int32()); |
| auto string_field = arrow::field("f2_string", arrow::utf8()); |
| std::shared_ptr<arrow::Field> blob_field_1 = BlobUtils::ToArrowField("f3_blob_1", true); |
| { |
| std::shared_ptr<arrow::Schema> original_schema = |
| arrow::schema({int_field, string_field, blob_field_1}); |
| |
| BlobUtils::SeparatedSchemas schemas = |
| BlobUtils::SeparateBlobSchema(original_schema, /*inline_fields=*/{}); |
| |
| std::shared_ptr<arrow::Schema> expected_main_schema = |
| arrow::schema({int_field, string_field}); |
| ASSERT_TRUE(schemas.main_schema->Equals(*expected_main_schema)); |
| |
| std::shared_ptr<arrow::Schema> expected_blob_schema = arrow::schema({blob_field_1}); |
| ASSERT_TRUE(schemas.blob_schema->Equals(*expected_blob_schema)); |
| } |
| { |
| std::shared_ptr<arrow::Schema> no_blob_schema = arrow::schema({int_field, string_field}); |
| BlobUtils::SeparatedSchemas no_blob_schemas = |
| BlobUtils::SeparateBlobSchema(no_blob_schema, /*inline_fields=*/{}); |
| ASSERT_TRUE(no_blob_schemas.main_schema->Equals(*no_blob_schema)); |
| ASSERT_EQ(no_blob_schemas.blob_schema->num_fields(), 0); |
| } |
| { |
| std::shared_ptr<arrow::Schema> only_blob_schema = arrow::schema({blob_field_1}); |
| BlobUtils::SeparatedSchemas only_blob_schemas = |
| BlobUtils::SeparateBlobSchema(only_blob_schema, /*inline_fields=*/{}); |
| ASSERT_TRUE(only_blob_schemas.blob_schema->Equals(*only_blob_schema)); |
| ASSERT_EQ(only_blob_schemas.main_schema->num_fields(), 0); |
| } |
| { |
| // Inline blob field stays in main_schema instead of going to blob_schema |
| auto blob_field_2 = BlobUtils::ToArrowField("f4_blob_2", false); |
| std::shared_ptr<arrow::Schema> schema = |
| arrow::schema({int_field, blob_field_1, blob_field_2, string_field}); |
| |
| BlobUtils::SeparatedSchemas schemas = |
| BlobUtils::SeparateBlobSchema(schema, /*inline_fields=*/{"f3_blob_1"}); |
| |
| // f3_blob_1 is inline -> stays in main; f4_blob_2 goes to blob |
| std::shared_ptr<arrow::Schema> expected_main = |
| arrow::schema({int_field, blob_field_1, string_field}); |
| ASSERT_TRUE(schemas.main_schema->Equals(*expected_main)); |
| |
| std::shared_ptr<arrow::Schema> expected_blob = arrow::schema({blob_field_2}); |
| ASSERT_TRUE(schemas.blob_schema->Equals(*expected_blob)); |
| } |
| { |
| // All blob fields are inline -> blob_schema is empty |
| std::shared_ptr<arrow::Schema> schema = |
| arrow::schema({int_field, blob_field_1, string_field}); |
| |
| BlobUtils::SeparatedSchemas schemas = |
| BlobUtils::SeparateBlobSchema(schema, /*inline_fields=*/{"f3_blob_1"}); |
| |
| ASSERT_TRUE(schemas.main_schema->Equals(*schema)); |
| ASSERT_EQ(schemas.blob_schema->num_fields(), 0); |
| } |
| } |
| |
| TEST_F(BlobUtilsTest, SeparateBlobArray) { |
| auto int_field = arrow::field("f1_int", arrow::int32()); |
| std::shared_ptr<arrow::Field> blob_field = BlobUtils::ToArrowField("f2_blob", false); |
| auto string_field = arrow::field("f3_string", arrow::utf8()); |
| auto schema = arrow::schema({int_field, blob_field, string_field}); |
| |
| arrow::Int32Builder int_builder; |
| ASSERT_TRUE(int_builder.AppendValues({1, 2, 3}).ok()); |
| auto int_array = int_builder.Finish().ValueOrDie(); |
| |
| arrow::StringBuilder string_builder; |
| ASSERT_TRUE(string_builder.AppendValues({"a", "b", "c"}).ok()); |
| auto string_array = string_builder.Finish().ValueOrDie(); |
| |
| arrow::LargeBinaryBuilder blob_builder; |
| ASSERT_TRUE(blob_builder.Append("1", 1).ok()); |
| ASSERT_TRUE(blob_builder.Append("2", 1).ok()); |
| ASSERT_TRUE(blob_builder.Append("3", 1).ok()); |
| auto blob_array_data = blob_builder.Finish().ValueOrDie(); |
| |
| auto raw_struct_array = |
| arrow::StructArray::Make({int_array, blob_array_data, string_array}, schema->fields()) |
| .ValueOrDie(); |
| |
| std::shared_ptr<arrow::StructArray> struct_array = |
| std::static_pointer_cast<arrow::StructArray>(raw_struct_array); |
| |
| ASSERT_OK_AND_ASSIGN(auto separated, |
| BlobUtils::SeparateBlobArray(struct_array, /*inline_fields=*/{})); |
| |
| std::shared_ptr<arrow::DataType> expected_main_type = arrow::struct_({int_field, string_field}); |
| ASSERT_TRUE(separated.main_array->type()->Equals(*expected_main_type)); |
| ASSERT_EQ(separated.main_array->num_fields(), 2); |
| ASSERT_TRUE(separated.main_array->field(0)->Equals(*int_array)); |
| ASSERT_TRUE(separated.main_array->field(1)->Equals(*string_array)); |
| |
| std::shared_ptr<arrow::DataType> expected_blob_type = arrow::struct_({blob_field}); |
| ASSERT_TRUE(separated.blob_array->type()->Equals(*expected_blob_type)); |
| ASSERT_EQ(separated.blob_array->num_fields(), 1); |
| ASSERT_TRUE(separated.blob_array->field(0)->Equals(*blob_array_data)); |
| |
| // All blob fields are inline -> should return error (no blob field to separate) |
| ASSERT_NOK_WITH_MSG( |
| BlobUtils::SeparateBlobArray(struct_array, /*inline_fields=*/{"f2_blob"}), |
| "SeparateBlobArray expects at least one non-inline blob field, but got none."); |
| |
| // All fields are blob with no inline -> no main array is needed |
| auto all_blob_struct = arrow::StructArray::Make({blob_array_data}, {blob_field}).ValueOrDie(); |
| auto all_blob_sa = std::dynamic_pointer_cast<arrow::StructArray>(all_blob_struct); |
| ASSERT_OK_AND_ASSIGN(auto all_blob_separated, |
| BlobUtils::SeparateBlobArray(all_blob_sa, /*inline_fields=*/{})); |
| ASSERT_EQ(nullptr, all_blob_separated.main_array); |
| ASSERT_TRUE(all_blob_separated.blob_array->Equals(*all_blob_sa)); |
| } |
| |
| TEST_F(BlobUtilsTest, SeparateBlobArrayWithPartialInline) { |
| auto int_field = arrow::field("f1_int", arrow::int32()); |
| std::shared_ptr<arrow::Field> blob_field_1 = BlobUtils::ToArrowField("f2_blob_1", false); |
| std::shared_ptr<arrow::Field> blob_field_2 = BlobUtils::ToArrowField("f3_blob_2", true); |
| auto schema = arrow::schema({int_field, blob_field_1, blob_field_2}); |
| |
| arrow::Int32Builder int_builder; |
| ASSERT_TRUE(int_builder.AppendValues({1, 2}).ok()); |
| auto int_array = int_builder.Finish().ValueOrDie(); |
| |
| arrow::LargeBinaryBuilder blob_builder_1; |
| ASSERT_TRUE(blob_builder_1.Append("a", 1).ok()); |
| ASSERT_TRUE(blob_builder_1.Append("b", 1).ok()); |
| auto blob_array_1 = blob_builder_1.Finish().ValueOrDie(); |
| |
| arrow::LargeBinaryBuilder blob_builder_2; |
| ASSERT_TRUE(blob_builder_2.Append("x", 1).ok()); |
| ASSERT_TRUE(blob_builder_2.AppendNull().ok()); |
| auto blob_array_2 = blob_builder_2.Finish().ValueOrDie(); |
| |
| auto raw_struct_array = |
| arrow::StructArray::Make({int_array, blob_array_1, blob_array_2}, schema->fields()) |
| .ValueOrDie(); |
| auto struct_array = std::static_pointer_cast<arrow::StructArray>(raw_struct_array); |
| |
| // f2_blob_1 is inline, f3_blob_2 goes to blob |
| ASSERT_OK_AND_ASSIGN(auto separated, BlobUtils::SeparateBlobArray( |
| struct_array, /*inline_fields=*/{"f2_blob_1"})); |
| |
| std::shared_ptr<arrow::DataType> expected_main_type = arrow::struct_({int_field, blob_field_1}); |
| ASSERT_TRUE(separated.main_array->type()->Equals(*expected_main_type)); |
| ASSERT_EQ(separated.main_array->num_fields(), 2); |
| ASSERT_TRUE(separated.main_array->field(0)->Equals(*int_array)); |
| ASSERT_TRUE(separated.main_array->field(1)->Equals(*blob_array_1)); |
| |
| std::shared_ptr<arrow::DataType> expected_blob_type = arrow::struct_({blob_field_2}); |
| ASSERT_TRUE(separated.blob_array->type()->Equals(*expected_blob_type)); |
| ASSERT_EQ(separated.blob_array->num_fields(), 1); |
| ASSERT_TRUE(separated.blob_array->field(0)->Equals(*blob_array_2)); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsEmptyFields) { |
| // Empty inline_descriptor_fields -> always OK |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append("random_data").ok()); |
| auto array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({array}, {BlobUtils::ToArrowField("b0")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {}, "blob-descriptor-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsFieldNotPresent) { |
| // Field not in struct_array -> skip, OK |
| arrow::Int32Builder int_builder; |
| ASSERT_TRUE(int_builder.Append(42).ok()); |
| auto int_array = int_builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({int_array}, {arrow::field("f0", arrow::int32())}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| // "b0" does not exist in the struct -> should pass |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {"b0"}, "blob-descriptor-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsWithValidDescriptor) { |
| // Valid BlobDescriptor bytes -> OK |
| ASSERT_OK_AND_ASSIGN(auto descriptor, BlobDescriptor::Create("file:///tmp/test.bin", 0, 100)); |
| auto serialized = descriptor->Serialize(pool_); |
| |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append(serialized->data(), serialized->size()).ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("b0")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {"b0"}, "blob-descriptor-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsWithNullValue) { |
| // Null values in blob column -> skip, OK |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.AppendNull().ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("b0")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {"b0"}, "blob-descriptor-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsWithRawBytes) { |
| // Raw bytes (not a descriptor) -> error |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append("not_a_descriptor_just_raw_data").ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("b0")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_NOK_WITH_MSG(BlobUtils::ValidateBlobInlineFields(sa, {"b0"}, "blob-descriptor-field"), |
| "BLOB inline field b0 require values to be set as corresponding type."); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsMixedValidAndInvalid) { |
| // First row is valid descriptor, second row is raw bytes -> error on row 1 |
| ASSERT_OK_AND_ASSIGN(auto descriptor, BlobDescriptor::Create("file:///tmp/test.bin", 0, 100)); |
| auto serialized = descriptor->Serialize(pool_); |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append(serialized->data(), serialized->size()).ok()); |
| ASSERT_TRUE(builder.Append("raw_bytes_not_descriptor").ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("b0")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_NOK_WITH_MSG(BlobUtils::ValidateBlobInlineFields(sa, {"b0"}, "blob-descriptor-field"), |
| "BLOB inline field b0 require values to be set as corresponding type."); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateInlineBlobDescriptorsMultipleFields) { |
| // Two inline fields: b0 is valid, b1 has raw bytes -> error on b1 |
| ASSERT_OK_AND_ASSIGN(auto descriptor, BlobDescriptor::Create("file:///tmp/test.bin", 0, 100)); |
| auto serialized = descriptor->Serialize(pool_); |
| |
| arrow::LargeBinaryBuilder b0_builder; |
| ASSERT_TRUE(b0_builder.Append(serialized->data(), serialized->size()).ok()); |
| auto b0_array = b0_builder.Finish().ValueOrDie(); |
| |
| arrow::LargeBinaryBuilder b1_builder; |
| ASSERT_TRUE(b1_builder.Append("invalid_raw_data").ok()); |
| auto b1_array = b1_builder.Finish().ValueOrDie(); |
| |
| auto struct_array = |
| arrow::StructArray::Make({b0_array, b1_array}, |
| {BlobUtils::ToArrowField("b0"), BlobUtils::ToArrowField("b1")}) |
| .ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_NOK_WITH_MSG( |
| BlobUtils::ValidateBlobInlineFields(sa, {"b0", "b1"}, "blob-descriptor-field"), |
| "BLOB inline field b1 require values to be set as corresponding type."); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateBlobViewFieldsEmptyFields) { |
| // Empty view_fields -> always OK |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append("random_data").ok()); |
| auto array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({array}, {BlobUtils::ToArrowField("view")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {}, "blob-view-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateBlobViewFieldsFieldNotPresent) { |
| // Field not in struct_array -> skip, OK |
| arrow::Int32Builder int_builder; |
| ASSERT_TRUE(int_builder.Append(42).ok()); |
| auto int_array = int_builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({int_array}, {arrow::field("f0", arrow::int32())}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {"view"}, "blob-view-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateBlobViewFieldsWithValidViewStruct) { |
| // A BlobViewStruct value is accepted for a view field. |
| BlobViewStruct view_struct(Identifier("db", "tbl"), /*field_id=*/2, /*row_id=*/5); |
| auto serialized = view_struct.Serialize(pool_); |
| |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append(serialized->data(), serialized->size()).ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("view")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {"view"}, "blob-view-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateBlobViewFieldsWithNullValue) { |
| // Null values in view column -> skip, OK |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.AppendNull().ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("view")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_OK(BlobUtils::ValidateBlobInlineFields(sa, {"view"}, "blob-view-field")); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateBlobViewFieldsWithRawBytes) { |
| // Raw bytes -> error |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append("raw_bytes_not_view").ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("view")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_NOK_WITH_MSG(BlobUtils::ValidateBlobInlineFields(sa, {"view"}, "blob-view-field"), |
| "BLOB inline field view require values to be set as corresponding type."); |
| } |
| |
| TEST_F(BlobUtilsTest, ValidateBlobViewFieldsRejectsBlobDescriptor) { |
| // A BlobDescriptor value is NOT accepted for a view field. |
| auto pool = GetDefaultPool(); |
| ASSERT_OK_AND_ASSIGN(auto descriptor, BlobDescriptor::Create("file:///tmp/test.bin", 0, 100)); |
| auto serialized = descriptor->Serialize(pool); |
| |
| arrow::LargeBinaryBuilder builder; |
| ASSERT_TRUE(builder.Append(serialized->data(), serialized->size()).ok()); |
| auto blob_array = builder.Finish().ValueOrDie(); |
| auto struct_array = |
| arrow::StructArray::Make({blob_array}, {BlobUtils::ToArrowField("view")}).ValueOrDie(); |
| auto sa = std::dynamic_pointer_cast<arrow::StructArray>(struct_array); |
| ASSERT_NOK_WITH_MSG(BlobUtils::ValidateBlobInlineFields(sa, {"view"}, "blob-view-field"), |
| "BLOB inline field view require values to be set as corresponding type."); |
| } |
| |
| TEST_F(BlobUtilsTest, TestConvertBlobInlineDataFields) { |
| // Schema with a blob field (large_binary with blob metadata) and normal fields. |
| auto blob_field = BlobUtils::ToArrowField("blob_col", /*nullable=*/true); |
| std::vector<DataField> data_fields = {DataField(0, arrow::field("int_col", arrow::int32())), |
| DataField(1, blob_field), |
| DataField(2, arrow::field("str_col", arrow::utf8()))}; |
| |
| // Without inline fields — blob_col stays as large_binary |
| { |
| auto result = BlobUtils::ConvertBlobInlineDataFields(data_fields, {}); |
| ASSERT_EQ(result.size(), 3); |
| ASSERT_EQ(result[1].ArrowField()->type()->id(), arrow::Type::LARGE_BINARY); |
| } |
| |
| // With inline fields — blob_col should be converted from large_binary to binary |
| { |
| auto result = BlobUtils::ConvertBlobInlineDataFields(data_fields, {"blob_col"}); |
| ASSERT_EQ(result.size(), 3); |
| ASSERT_EQ(result[1].ArrowField()->type()->id(), arrow::Type::BINARY); |
| ASSERT_EQ(result[1].Name(), "blob_col"); |
| ASSERT_EQ(result[1].Nullable(), true); |
| // Other fields unchanged |
| ASSERT_EQ(result[0].ArrowField()->type()->id(), arrow::Type::INT32); |
| ASSERT_EQ(result[2].ArrowField()->type()->id(), arrow::Type::STRING); |
| } |
| |
| // Non-matching inline field name — no conversion should happen |
| { |
| auto result = BlobUtils::ConvertBlobInlineDataFields(data_fields, {"non_existent_field"}); |
| ASSERT_EQ(result[1].ArrowField()->type()->id(), arrow::Type::LARGE_BINARY); |
| } |
| } |
| |
| } // namespace paimon::test |