@@ -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
25982663TEST_P (BlobTableInteTest, TestBlobViewFieldWithUpstreamDescriptorBlob) {
0 commit comments