Skip to content

Commit 5bce88f

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 5bce88f

1 file changed

Lines changed: 138 additions & 69 deletions

File tree

test/inte/blob_table_inte_test.cpp

Lines changed: 138 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,99 +2500,168 @@ 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 keeps blob-view.resolve.enabled at its default (true) and has no
2522+
// upstream warehouse configured: only a read that dynamically disables resolve can
2523+
// succeed on it.
25122524
arrow::FieldVector fields = {arrow::field("f0", arrow::int32()),
25132525
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");
2526+
std::map<std::string, std::string> source_options = {{Options::MANIFEST_FORMAT, "orc"},
2527+
{Options::FILE_FORMAT, file_format},
2528+
{Options::BUCKET, "-1"},
2529+
{Options::ROW_TRACKING_ENABLED, "true"},
2530+
{Options::DATA_EVOLUTION_ENABLED, "true"},
2531+
{Options::BLOB_VIEW_FIELD, "view"},
2532+
{Options::FILE_SYSTEM, "local"}};
2533+
CreateTable(fields, /*partition_keys=*/{}, source_options);
2534+
std::string source_table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar");
2535+
2536+
// The target table configures the upstream warehouse, so its default read resolves the
2537+
// forwarded references.
2538+
std::map<std::string, std::string> target_options = source_options;
2539+
target_options[Options::BLOB_VIEW_UPSTREAM_WAREHOUSE] = dir_->Str();
2540+
auto schema = arrow::schema(fields);
2541+
::ArrowSchema c_target_schema;
2542+
ASSERT_TRUE(arrow::ExportSchema(*schema, &c_target_schema).ok());
2543+
ASSERT_OK_AND_ASSIGN(auto catalog, Catalog::Create(dir_->Str(), {}));
2544+
ASSERT_OK(catalog->CreateTable(Identifier("foo", "bar_forward"), &c_target_schema,
2545+
/*partition_keys=*/{}, /*primary_keys=*/{}, target_options,
2546+
/*ignore_if_exists=*/false));
2547+
std::string target_table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar_forward");
25242548

2525-
Identifier upstream_identifier("upstream_db", "upstream_table");
2549+
// src array
2550+
Identifier upstream_identifier(upstream_db_name, upstream_table_name);
25262551
arrow::LargeBinaryBuilder view_builder;
2527-
for (int32_t i = 0; i < 4; ++i) {
2528-
if (i == 2) {
2552+
for (int32_t i = 0; i < 8; ++i) {
2553+
if (i < 6) {
2554+
BlobViewStruct view_struct(upstream_identifier, /*field_id=*/6,
2555+
/*row_id=*/static_cast<int64_t>(i));
2556+
auto serialized = view_struct.Serialize(pool_);
2557+
ASSERT_TRUE(view_builder
2558+
.Append(reinterpret_cast<const uint8_t*>(serialized->data()),
2559+
serialized->size())
2560+
.ok());
2561+
} else {
25292562
ASSERT_TRUE(view_builder.AppendNull().ok());
2530-
continue;
25312563
}
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());
25392564
}
25402565
std::shared_ptr<arrow::Array> write_view_array;
25412566
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();
2567+
auto write_f0_array = arrow::ipc::internal::json::ArrayFromJSON(
2568+
arrow::int32(), R"([100,101,102,103,104,105,106,107])")
2569+
.ValueOrDie();
25452570
auto write_struct = std::dynamic_pointer_cast<arrow::StructArray>(
25462571
arrow::StructArray::Make(arrow::ArrayVector({write_f0_array, write_view_array}),
25472572
std::vector<std::string>({"f0", "view"}))
25482573
.ValueOrDie());
25492574

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

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);
2580+
// A default read of the source table attempts resolution and fails on the missing
2581+
// upstream warehouse, so the pass-through below is enabled by the dynamic option alone.
2582+
ASSERT_OK_AND_ASSIGN(auto source_plan, ScanTable(source_table_path));
2583+
ASSERT_NOK_WITH_MSG(
2584+
ReadTable(source_table_path, schema->field_names(), source_plan, /*predicate=*/nullptr),
2585+
"BLOB_VIEW_UPSTREAM_WAREHOUSE");
25642586

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
2587+
// Dynamically disable resolve on the source table and read the raw upstream references.
2588+
ASSERT_OK_AND_ASSIGN(
2589+
auto source_result,
2590+
ReadTable(source_table_path, schema->field_names(), source_plan, /*predicate=*/nullptr,
2591+
{{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}}));
2592+
ASSERT_TRUE(source_result.chunked_array);
2593+
auto source_concat = arrow::Concatenate(source_result.chunked_array->chunks()).ValueOrDie();
2594+
auto source_struct = std::dynamic_pointer_cast<arrow::StructArray>(source_concat);
2595+
ASSERT_EQ(source_struct->length(), 8);
2596+
auto forward_f0_array = source_struct->GetFieldByName("f0");
2597+
ASSERT_TRUE(forward_f0_array);
2598+
auto forward_view_array = source_struct->GetFieldByName("view");
2599+
ASSERT_TRUE(forward_view_array);
2600+
ASSERT_TRUE(forward_view_array->Equals(write_view_array))
2601+
<< "source view:" << forward_view_array->ToString() << std::endl
2602+
<< "written view:" << write_view_array->ToString();
2603+
2604+
// Forward the preserved references into the target blob-view table.
2605+
auto forward_struct = std::dynamic_pointer_cast<arrow::StructArray>(
2606+
arrow::StructArray::Make(arrow::ArrayVector({forward_f0_array, forward_view_array}),
2607+
std::vector<std::string>({"f0", "view"}))
2608+
.ValueOrDie());
2609+
ASSERT_OK_AND_ASSIGN(
2610+
auto forward_commit_msgs,
2611+
WriteArray(target_table_path, {}, schema->field_names(), {forward_struct}));
2612+
ASSERT_OK(Commit(target_table_path, forward_commit_msgs));
2613+
2614+
// The target table stores the original upstream references byte-identically.
2615+
ASSERT_OK_AND_ASSIGN(auto target_plan, ScanTable(target_table_path));
2616+
ASSERT_OK_AND_ASSIGN(
2617+
auto raw_target_result,
2618+
ReadTable(target_table_path, schema->field_names(), target_plan, /*predicate=*/nullptr,
2619+
{{Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}}));
2620+
ASSERT_TRUE(raw_target_result.chunked_array);
2621+
auto raw_target_concat =
2622+
arrow::Concatenate(raw_target_result.chunked_array->chunks()).ValueOrDie();
2623+
auto raw_target_struct = std::dynamic_pointer_cast<arrow::StructArray>(raw_target_concat);
2624+
ASSERT_EQ(raw_target_struct->length(), 8);
2625+
auto raw_target_view_array = raw_target_struct->GetFieldByName("view");
2626+
ASSERT_TRUE(raw_target_view_array);
2627+
ASSERT_TRUE(raw_target_view_array->Equals(write_view_array))
2628+
<< "target view:" << raw_target_view_array->ToString() << std::endl
25742629
<< "written view:" << write_view_array->ToString();
25752630

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");
2631+
// A default read of the target table resolves the forwarded references into the actual
2632+
// blob values stored in the upstream table.
2633+
ASSERT_OK_AND_ASSIGN(auto resolved_result,
2634+
ReadTable(target_table_path, schema->field_names(), target_plan,
2635+
/*predicate=*/nullptr));
2636+
ASSERT_TRUE(resolved_result.chunked_array);
2637+
auto resolved_concat = arrow::Concatenate(resolved_result.chunked_array->chunks()).ValueOrDie();
2638+
auto resolved_struct = std::dynamic_pointer_cast<arrow::StructArray>(resolved_concat);
2639+
ASSERT_EQ(resolved_struct->length(), 8);
2640+
ASSERT_OK_AND_ASSIGN(auto resolved, ConvertDescriptorToRawBlob(resolved_struct, {"view"}));
2641+
2642+
std::string padding_b(2048, 'b');
2643+
std::string padding_d(2048, 'd');
2644+
std::string padding_e(2048, 'e');
2645+
std::string padding_f(2048, 'f');
2646+
// clang-format off
2647+
std::string expected_json = R"([
2648+
[100, null],
2649+
[101, ")" + padding_b + R"("],
2650+
[102, null],
2651+
[103, ")" + padding_d + R"("],
2652+
[104, ")" + padding_e + R"("],
2653+
[105, ")" + padding_f + R"("],
2654+
[106, null],
2655+
[107, null]
2656+
])";
2657+
// clang-format on
2658+
auto expected_struct = std::dynamic_pointer_cast<arrow::StructArray>(
2659+
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), expected_json)
2660+
.ValueOrDie());
2661+
ASSERT_OK_AND_ASSIGN(auto expected_with_rk, PrependRowKindColumn(expected_struct));
2662+
ASSERT_TRUE(resolved->Equals(expected_with_rk))
2663+
<< "resolved:" << resolved->ToString() << std::endl
2664+
<< "expected:" << expected_with_rk->ToString();
25962665
}
25972666

25982667
TEST_P(BlobTableInteTest, TestBlobViewFieldWithUpstreamDescriptorBlob) {

0 commit comments

Comments
 (0)