Skip to content

Commit 2485721

Browse files
SteNicholasclaude
andcommitted
test(blob view): cover forwarding blob view references to another blob-view table
Rework the resolve-disabled integration test into an end-to-end forward scenario: read the source blob-view table with resolve dynamically disabled per read, write the preserved BlobViewStruct bytes into a second blob-view table, verify the target stores the original upstream references, and resolve them to the actual upstream blob values with a default read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 42784bc commit 2485721

1 file changed

Lines changed: 134 additions & 69 deletions

File tree

test/inte/blob_table_inte_test.cpp

Lines changed: 134 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,99 +2500,164 @@ TEST_P(BlobTableInteTest, TestBlobViewFieldWithUpstreamTable) {
25002500
}
25012501
}
25022502

2503-
TEST_P(BlobTableInteTest, TestBlobViewFieldResolveDisabled) {
2503+
TEST_P(BlobTableInteTest, TestForwardBlobViewReference) {
25042504
auto file_format = GetParam();
25052505
if (file_format != "orc" && file_format != "parquet") {
25062506
return;
25072507
}
25082508

2509-
// With blob-view.resolve.enabled=false the read must pass the serialized BlobViewStruct
2510-
// bytes through without touching the upstream side: the referenced upstream table
2511-
// deliberately does not exist and no upstream warehouse is configured.
2509+
// Forward blob view references between two blob-view tables: read the source table with
2510+
// resolve dynamically disabled, write the preserved BlobViewStruct bytes into the target
2511+
// table, then verify the target still stores the original upstream references and a
2512+
// default read resolves them to the actual upstream blob values.
2513+
const std::string upstream_db_name = "append_table_with_multi_blob";
2514+
const std::string upstream_table_name = "append_table_with_multi_blob";
2515+
std::string src_db_path = paimon::test::GetDataDir() + file_format + "/" + upstream_db_name +
2516+
".db/" + upstream_table_name;
2517+
std::string dst_db_path =
2518+
PathUtil::JoinPath(dir_->Str(), upstream_db_name + ".db/" + upstream_table_name);
2519+
ASSERT_TRUE(TestUtil::CopyDirectory(src_db_path, dst_db_path));
2520+
2521+
// The source table has no upstream warehouse configured: only a read with resolve
2522+
// dynamically disabled can succeed on it.
25122523
arrow::FieldVector fields = {arrow::field("f0", arrow::int32()),
25132524
BlobUtils::ToArrowField("view", true)};
2514-
std::map<std::string, std::string> options = {{Options::MANIFEST_FORMAT, "orc"},
2515-
{Options::FILE_FORMAT, file_format},
2516-
{Options::BUCKET, "-1"},
2517-
{Options::ROW_TRACKING_ENABLED, "true"},
2518-
{Options::DATA_EVOLUTION_ENABLED, "true"},
2519-
{Options::BLOB_VIEW_FIELD, "view"},
2520-
{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"},
2521-
{Options::FILE_SYSTEM, "local"}};
2522-
CreateTable(fields, /*partition_keys=*/{}, options);
2523-
std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar");
2525+
std::map<std::string, std::string> source_options = {{Options::MANIFEST_FORMAT, "orc"},
2526+
{Options::FILE_FORMAT, file_format},
2527+
{Options::BUCKET, "-1"},
2528+
{Options::ROW_TRACKING_ENABLED, "true"},
2529+
{Options::DATA_EVOLUTION_ENABLED, "true"},
2530+
{Options::BLOB_VIEW_FIELD, "view"},
2531+
{Options::FILE_SYSTEM, "local"}};
2532+
CreateTable(fields, /*partition_keys=*/{}, source_options);
2533+
std::string source_table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar");
2534+
2535+
// The target table configures the upstream warehouse for resolving forwarded references.
2536+
std::map<std::string, std::string> target_options = source_options;
2537+
target_options[Options::BLOB_VIEW_UPSTREAM_WAREHOUSE] = dir_->Str();
2538+
auto schema = arrow::schema(fields);
2539+
::ArrowSchema c_target_schema;
2540+
ASSERT_TRUE(arrow::ExportSchema(*schema, &c_target_schema).ok());
2541+
ASSERT_OK_AND_ASSIGN(auto catalog, Catalog::Create(dir_->Str(), {}));
2542+
ASSERT_OK(catalog->CreateTable(Identifier("foo", "bar_forward"), &c_target_schema,
2543+
/*partition_keys=*/{}, /*primary_keys=*/{}, target_options,
2544+
/*ignore_if_exists=*/false));
2545+
std::string target_table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar_forward");
25242546

2525-
Identifier upstream_identifier("upstream_db", "upstream_table");
2547+
// src array
2548+
Identifier upstream_identifier(upstream_db_name, upstream_table_name);
25262549
arrow::LargeBinaryBuilder view_builder;
2527-
for (int32_t i = 0; i < 4; ++i) {
2528-
if (i == 2) {
2550+
for (int32_t i = 0; i < 8; ++i) {
2551+
if (i < 6) {
2552+
BlobViewStruct view_struct(upstream_identifier, /*field_id=*/6,
2553+
/*row_id=*/static_cast<int64_t>(i));
2554+
auto serialized = view_struct.Serialize(pool_);
2555+
ASSERT_TRUE(view_builder
2556+
.Append(reinterpret_cast<const uint8_t*>(serialized->data()),
2557+
serialized->size())
2558+
.ok());
2559+
} else {
25292560
ASSERT_TRUE(view_builder.AppendNull().ok());
2530-
continue;
25312561
}
2532-
BlobViewStruct view_struct(upstream_identifier, /*field_id=*/6,
2533-
/*row_id=*/static_cast<int64_t>(i));
2534-
auto serialized = view_struct.Serialize(pool_);
2535-
ASSERT_TRUE(
2536-
view_builder
2537-
.Append(reinterpret_cast<const uint8_t*>(serialized->data()), serialized->size())
2538-
.ok());
25392562
}
25402563
std::shared_ptr<arrow::Array> write_view_array;
25412564
ASSERT_TRUE(view_builder.Finish(&write_view_array).ok());
2542-
auto write_f0_array =
2543-
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), R"([100,101,102,103])")
2544-
.ValueOrDie();
2565+
auto write_f0_array = arrow::ipc::internal::json::ArrayFromJSON(
2566+
arrow::int32(), R"([100,101,102,103,104,105,106,107])")
2567+
.ValueOrDie();
25452568
auto write_struct = std::dynamic_pointer_cast<arrow::StructArray>(
25462569
arrow::StructArray::Make(arrow::ArrayVector({write_f0_array, write_view_array}),
25472570
std::vector<std::string>({"f0", "view"}))
25482571
.ValueOrDie());
25492572

2550-
// write & commit
2551-
auto schema = arrow::schema(fields);
2573+
// write & commit into the source table
25522574
ASSERT_OK_AND_ASSIGN(auto commit_msgs,
2553-
WriteArray(table_path, {}, schema->field_names(), {write_struct}));
2554-
ASSERT_OK(Commit(table_path, commit_msgs));
2575+
WriteArray(source_table_path, {}, schema->field_names(), {write_struct}));
2576+
ASSERT_OK(Commit(source_table_path, commit_msgs));
25552577

2556-
// scan & read
2557-
ASSERT_OK_AND_ASSIGN(auto plan, ScanTable(table_path));
2558-
ASSERT_OK_AND_ASSIGN(auto result,
2559-
ReadTable(table_path, schema->field_names(), plan, /*predicate=*/nullptr));
2560-
ASSERT_TRUE(result.chunked_array);
2561-
auto read_concat = arrow::Concatenate(result.chunked_array->chunks()).ValueOrDie();
2562-
auto read_struct = std::dynamic_pointer_cast<arrow::StructArray>(read_concat);
2563-
ASSERT_EQ(read_struct->length(), 4);
2578+
// A default read fails on the missing upstream warehouse: the pass-through below is
2579+
// enabled by the dynamic option alone.
2580+
ASSERT_OK_AND_ASSIGN(auto source_plan, ScanTable(source_table_path));
2581+
ASSERT_NOK_WITH_MSG(
2582+
ReadTable(source_table_path, schema->field_names(), source_plan, /*predicate=*/nullptr),
2583+
"BLOB_VIEW_UPSTREAM_WAREHOUSE");
25642584

2565-
auto read_f0_array = read_struct->GetFieldByName("f0");
2566-
ASSERT_TRUE(read_f0_array);
2567-
ASSERT_TRUE(read_f0_array->Equals(write_f0_array))
2568-
<< "read f0:" << read_f0_array->ToString() << std::endl
2569-
<< "written f0:" << write_f0_array->ToString();
2570-
auto read_view_array = read_struct->GetFieldByName("view");
2571-
ASSERT_TRUE(read_view_array);
2572-
ASSERT_TRUE(read_view_array->Equals(write_view_array))
2573-
<< "read view:" << read_view_array->ToString() << std::endl
2585+
ASSERT_OK_AND_ASSIGN(
2586+
auto source_result,
2587+
ReadTable(source_table_path, schema->field_names(), source_plan, /*predicate=*/nullptr,
2588+
{{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}}));
2589+
ASSERT_TRUE(source_result.chunked_array);
2590+
auto source_concat = arrow::Concatenate(source_result.chunked_array->chunks()).ValueOrDie();
2591+
auto source_struct = std::dynamic_pointer_cast<arrow::StructArray>(source_concat);
2592+
ASSERT_EQ(source_struct->length(), 8);
2593+
auto forward_f0_array = source_struct->GetFieldByName("f0");
2594+
ASSERT_TRUE(forward_f0_array);
2595+
auto forward_view_array = source_struct->GetFieldByName("view");
2596+
ASSERT_TRUE(forward_view_array);
2597+
ASSERT_TRUE(forward_view_array->Equals(write_view_array))
2598+
<< "source view:" << forward_view_array->ToString() << std::endl
2599+
<< "written view:" << write_view_array->ToString();
2600+
2601+
// Forward the preserved references into the target blob-view table.
2602+
auto forward_struct = std::dynamic_pointer_cast<arrow::StructArray>(
2603+
arrow::StructArray::Make(arrow::ArrayVector({forward_f0_array, forward_view_array}),
2604+
std::vector<std::string>({"f0", "view"}))
2605+
.ValueOrDie());
2606+
ASSERT_OK_AND_ASSIGN(
2607+
auto forward_commit_msgs,
2608+
WriteArray(target_table_path, {}, schema->field_names(), {forward_struct}));
2609+
ASSERT_OK(Commit(target_table_path, forward_commit_msgs));
2610+
2611+
// The target table stores the original upstream references byte-identically.
2612+
ASSERT_OK_AND_ASSIGN(auto target_plan, ScanTable(target_table_path));
2613+
ASSERT_OK_AND_ASSIGN(
2614+
auto raw_target_result,
2615+
ReadTable(target_table_path, schema->field_names(), target_plan, /*predicate=*/nullptr,
2616+
{{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}}));
2617+
ASSERT_TRUE(raw_target_result.chunked_array);
2618+
auto raw_target_concat =
2619+
arrow::Concatenate(raw_target_result.chunked_array->chunks()).ValueOrDie();
2620+
auto raw_target_struct = std::dynamic_pointer_cast<arrow::StructArray>(raw_target_concat);
2621+
ASSERT_EQ(raw_target_struct->length(), 8);
2622+
auto raw_target_view_array = raw_target_struct->GetFieldByName("view");
2623+
ASSERT_TRUE(raw_target_view_array);
2624+
ASSERT_TRUE(raw_target_view_array->Equals(write_view_array))
2625+
<< "target view:" << raw_target_view_array->ToString() << std::endl
25742626
<< "written view:" << write_view_array->ToString();
25752627

2576-
// The pass-through bytes still deserialize to the original view struct, so they can be
2577-
// forwarded to another blob-view table.
2578-
auto typed_view_array = std::dynamic_pointer_cast<arrow::LargeBinaryArray>(read_view_array);
2579-
ASSERT_TRUE(typed_view_array);
2580-
std::string_view first_view = typed_view_array->GetView(0);
2581-
ASSERT_OK_AND_ASSIGN(bool is_view_struct,
2582-
BlobViewStruct::IsBlobViewStruct(first_view.data(), first_view.size()));
2583-
ASSERT_TRUE(is_view_struct);
2584-
ASSERT_OK_AND_ASSIGN(auto view_struct,
2585-
BlobViewStruct::Deserialize(first_view.data(), first_view.size()));
2586-
ASSERT_TRUE(view_struct->GetIdentifier() == upstream_identifier);
2587-
ASSERT_EQ(view_struct->FieldId(), 6);
2588-
ASSERT_EQ(view_struct->RowId(), 0);
2589-
2590-
// Re-enabling resolve makes the same read fail on the missing upstream warehouse.
2591-
// Without this check the assertions above would also pass if resolution were never
2592-
// triggered at all.
2593-
ASSERT_NOK_WITH_MSG(ReadTable(table_path, schema->field_names(), plan, /*predicate=*/nullptr,
2594-
{{Options::BLOB_VIEW_RESOLVE_ENABLED, "true"}}),
2595-
"BLOB_VIEW_UPSTREAM_WAREHOUSE");
2628+
// A default read of the target table resolves to the actual upstream blob values.
2629+
ASSERT_OK_AND_ASSIGN(auto resolved_result,
2630+
ReadTable(target_table_path, schema->field_names(), target_plan,
2631+
/*predicate=*/nullptr));
2632+
ASSERT_TRUE(resolved_result.chunked_array);
2633+
auto resolved_concat = arrow::Concatenate(resolved_result.chunked_array->chunks()).ValueOrDie();
2634+
auto resolved_struct = std::dynamic_pointer_cast<arrow::StructArray>(resolved_concat);
2635+
ASSERT_EQ(resolved_struct->length(), 8);
2636+
ASSERT_OK_AND_ASSIGN(auto resolved, ConvertDescriptorToRawBlob(resolved_struct, {"view"}));
2637+
2638+
std::string padding_b(2048, 'b');
2639+
std::string padding_d(2048, 'd');
2640+
std::string padding_e(2048, 'e');
2641+
std::string padding_f(2048, 'f');
2642+
// clang-format off
2643+
std::string expected_json = R"([
2644+
[100, null],
2645+
[101, ")" + padding_b + R"("],
2646+
[102, null],
2647+
[103, ")" + padding_d + R"("],
2648+
[104, ")" + padding_e + R"("],
2649+
[105, ")" + padding_f + R"("],
2650+
[106, null],
2651+
[107, null]
2652+
])";
2653+
// clang-format on
2654+
auto expected_struct = std::dynamic_pointer_cast<arrow::StructArray>(
2655+
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), expected_json)
2656+
.ValueOrDie());
2657+
ASSERT_OK_AND_ASSIGN(auto expected_with_rk, PrependRowKindColumn(expected_struct));
2658+
ASSERT_TRUE(resolved->Equals(expected_with_rk))
2659+
<< "resolved:" << resolved->ToString() << std::endl
2660+
<< "expected:" << expected_with_rk->ToString();
25962661
}
25972662

25982663
TEST_P(BlobTableInteTest, TestBlobViewFieldWithUpstreamDescriptorBlob) {

0 commit comments

Comments
 (0)