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