@@ -2500,6 +2500,101 @@ TEST_P(BlobTableInteTest, TestBlobViewFieldWithUpstreamTable) {
25002500 }
25012501}
25022502
2503+ TEST_P (BlobTableInteTest, TestBlobViewFieldResolveDisabled) {
2504+ auto file_format = GetParam ();
2505+ if (file_format != " orc" && file_format != " parquet" ) {
2506+ return ;
2507+ }
2508+
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.
2512+ arrow::FieldVector fields = {arrow::field (" f0" , arrow::int32 ()),
2513+ 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" );
2524+
2525+ Identifier upstream_identifier (" upstream_db" , " upstream_table" );
2526+ arrow::LargeBinaryBuilder view_builder;
2527+ for (int32_t i = 0 ; i < 4 ; ++i) {
2528+ if (i == 2 ) {
2529+ ASSERT_TRUE (view_builder.AppendNull ().ok ());
2530+ continue ;
2531+ }
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 ());
2539+ }
2540+ std::shared_ptr<arrow::Array> write_view_array;
2541+ 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 ();
2545+ auto write_struct = std::dynamic_pointer_cast<arrow::StructArray>(
2546+ arrow::StructArray::Make (arrow::ArrayVector ({write_f0_array, write_view_array}),
2547+ std::vector<std::string>({" f0" , " view" }))
2548+ .ValueOrDie ());
2549+
2550+ // write & commit
2551+ auto schema = arrow::schema (fields);
2552+ ASSERT_OK_AND_ASSIGN (auto commit_msgs,
2553+ WriteArray (table_path, {}, schema->field_names (), {write_struct}));
2554+ ASSERT_OK (Commit (table_path, commit_msgs));
2555+
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 );
2564+
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
2574+ << " written view:" << write_view_array->ToString ();
2575+
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" );
2596+ }
2597+
25032598TEST_P (BlobTableInteTest, TestBlobViewFieldWithUpstreamDescriptorBlob) {
25042599 auto file_format = GetParam ();
25052600 if (GetParam () == " lance" ) {
0 commit comments