diff --git a/include/paimon/defs.h b/include/paimon/defs.h index 709d44904..2c2ef191a 100644 --- a/include/paimon/defs.h +++ b/include/paimon/defs.h @@ -577,12 +577,6 @@ struct PAIMON_EXPORT Options { /// tables at read time. Set to false to preserve serialized BlobViewStruct bytes when /// forwarding blob view values to another blob-view table. Default value is "true". static const char BLOB_VIEW_RESOLVE_ENABLED[]; - /// "blob-view-upstream-warehouse" - Warehouse root the upstream table a blob-view field points - /// at is resolved from. Since the catalog capabilities are partially missing, cpp paimon cannot - /// always obtain it automatically, so it may be configured explicitly. When it is not set, the - /// read falls back to the warehouse the downstream table itself lives in, which resolves a - /// co-located upstream table. No default value. - static const char BLOB_VIEW_UPSTREAM_WAREHOUSE[]; /// "blob-write-null-on-missing-file" - Whether to write NULL for a descriptor BLOB value, /// including an ARRAY element or a MAP<..., BLOB> value, when the referenced file does /// not exist at write time. diff --git a/src/paimon/common/defs.cpp b/src/paimon/common/defs.cpp index d709d6f8d..9ddefb16a 100644 --- a/src/paimon/common/defs.cpp +++ b/src/paimon/common/defs.cpp @@ -154,7 +154,6 @@ const char Options::BLOB_DESCRIPTOR_FIELD[] = "blob-descriptor-field"; const char Options::FALLBACK_BLOB_DESCRIPTOR_FIELD[] = "blob.stored-descriptor-fields"; const char Options::BLOB_VIEW_FIELD[] = "blob-view-field"; const char Options::BLOB_VIEW_RESOLVE_ENABLED[] = "blob-view.resolve.enabled"; -const char Options::BLOB_VIEW_UPSTREAM_WAREHOUSE[] = "blob-view-upstream-warehouse"; const char Options::BLOB_WRITE_NULL_ON_MISSING_FILE[] = "blob-write-null-on-missing-file"; const char Options::BLOB_WRITE_NULL_ON_FETCH_FAILURE[] = "blob-write-null-on-fetch-failure"; const char Options::GLOBAL_INDEX_ENABLED[] = "global-index.enabled"; diff --git a/src/paimon/core/core_options.cpp b/src/paimon/core/core_options.cpp index 666d76850..b02930ff6 100644 --- a/src/paimon/core/core_options.cpp +++ b/src/paimon/core/core_options.cpp @@ -390,7 +390,6 @@ struct CoreOptions::Impl { std::optional field_default_func; std::optional scan_fallback_branch; std::optional data_file_external_paths; - std::optional blob_view_upstream_warehouse; std::optional changelog_file_compression; std::optional global_index_external_path; std::optional scan_tag_name; @@ -582,9 +581,6 @@ struct CoreOptions::Impl { PAIMON_RETURN_NOT_OK(parser.ParseList(Options::BLOB_VIEW_FIELD, Options::FIELDS_SEPARATOR, &blob_view_fields, /*need_trim=*/true)); - // Parse blob-view-upstream-warehouse - warehouse path for configured blob view fields - PAIMON_RETURN_NOT_OK( - parser.Parse(Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, &blob_view_upstream_warehouse)); // Parse blob-view.resolve.enabled - whether to resolve blob view fields at read time PAIMON_RETURN_NOT_OK( parser.Parse(Options::BLOB_VIEW_RESOLVE_ENABLED, &blob_view_resolve_enabled)); @@ -1966,10 +1962,6 @@ const std::vector& CoreOptions::GetBlobViewFields() const { return impl_->blob_view_fields; } -std::optional CoreOptions::GetBlobViewUpstreamWarehouse() const { - return impl_->blob_view_upstream_warehouse; -} - bool CoreOptions::BlobViewResolveEnabled() const { return impl_->blob_view_resolve_enabled; } diff --git a/src/paimon/core/core_options.h b/src/paimon/core/core_options.h index 8bf794265..34aaad53e 100644 --- a/src/paimon/core/core_options.h +++ b/src/paimon/core/core_options.h @@ -304,7 +304,6 @@ class PAIMON_EXPORT CoreOptions { const std::vector& GetBlobFields() const; const std::vector& GetBlobDescriptorFields() const; const std::vector& GetBlobViewFields() const; - std::optional GetBlobViewUpstreamWarehouse() const; bool BlobViewResolveEnabled() const; std::vector GetBlobInlineFields() const; diff --git a/src/paimon/core/core_options_test.cpp b/src/paimon/core/core_options_test.cpp index e3625db80..fa2c77805 100644 --- a/src/paimon/core/core_options_test.cpp +++ b/src/paimon/core/core_options_test.cpp @@ -175,7 +175,6 @@ TEST(CoreOptionsTest, TestDefaultValue) { ASSERT_TRUE(core_options.GetBlobDescriptorFields().empty()); ASSERT_TRUE(core_options.GetBlobViewFields().empty()); ASSERT_TRUE(core_options.GetBlobInlineFields().empty()); - ASSERT_EQ(std::nullopt, core_options.GetBlobViewUpstreamWarehouse()); ASSERT_TRUE(core_options.BlobViewResolveEnabled()); ASSERT_TRUE(core_options.LegacyPartitionNameEnabled()); ASSERT_TRUE(core_options.GlobalIndexEnabled()); @@ -303,7 +302,6 @@ TEST(CoreOptionsTest, TestFromMap) { {Options::BLOB_DESCRIPTOR_FIELD, "blob3,blob4"}, {Options::BLOB_AS_DESCRIPTOR, "true"}, {Options::BLOB_VIEW_FIELD, "blob5"}, - {Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, "FILE:///tmp/blob_view_upstream_warehouse/"}, {Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}, {Options::PARTITION_GENERATE_LEGACY_NAME, "false"}, {Options::GLOBAL_INDEX_ENABLED, "false"}, @@ -476,8 +474,6 @@ TEST(CoreOptionsTest, TestFromMap) { ASSERT_EQ(core_options.GetBlobViewFields(), std::vector({"blob5"})); ASSERT_EQ(core_options.GetBlobInlineFields(), std::vector({"blob3", "blob4", "blob5"})); - ASSERT_EQ(core_options.GetBlobViewUpstreamWarehouse(), - std::optional("FILE:///tmp/blob_view_upstream_warehouse/")); ASSERT_FALSE(core_options.BlobViewResolveEnabled()); ASSERT_FALSE(core_options.LegacyPartitionNameEnabled()); ASSERT_FALSE(core_options.GlobalIndexEnabled()); diff --git a/src/paimon/core/operation/data_evolution_split_read.cpp b/src/paimon/core/operation/data_evolution_split_read.cpp index e3a52c6fe..6d065b46f 100644 --- a/src/paimon/core/operation/data_evolution_split_read.cpp +++ b/src/paimon/core/operation/data_evolution_split_read.cpp @@ -34,7 +34,6 @@ #include "arrow/array/array_binary.h" #include "arrow/array/array_nested.h" #include "arrow/c/bridge.h" -#include "paimon/common/catalog/catalog_context.h" #include "paimon/common/data/blob_defs.h" #include "paimon/common/data/blob_utils.h" #include "paimon/common/data/blob_view_struct.h" @@ -49,7 +48,6 @@ #include "paimon/common/utils/arrow/status_utils.h" #include "paimon/common/utils/checked_cast.h" #include "paimon/common/utils/object_utils.h" -#include "paimon/common/utils/path_util.h" #include "paimon/common/utils/range_helper.h" #include "paimon/common/utils/vector_store_utils.h" #include "paimon/core/core_options.h" @@ -282,33 +280,18 @@ Result> DataEvolutionSplitRead::WrapWithBlobViewRes if (read_blob_view_fields.empty()) { return std::move(inner_reader); } - // The explicit upstream warehouse is optional. Absent it, fall back to the warehouse this - // (downstream) table itself lives in, which is where a co-located upstream table is resolved - // from; a metastore-backed catalog resolves the upstream location by identifier anyway, so the - // root is only a hint there. Stripping the trailing `.db/` (or - // `/
`) components off this table's path yields that warehouse. - std::optional warehouse_path = options_.GetBlobViewUpstreamWarehouse(); - if (!warehouse_path) { - // TODO(yonghao.fyh): refactor warehouse path in the future - warehouse_path = - PathUtil::GetParentDirPath(PathUtil::GetParentDirPath(context_->GetPath())); - } PAIMON_ASSIGN_OR_RAISE(std::unique_ptr pre_reader, CreateBlobViewReader(data_split, read_blob_view_fields, row_ranges)); PAIMON_ASSIGN_OR_RAISE(std::unordered_set blob_view_structs, ExtractBlobViewStructs(pre_reader.get())); - // The dependency table is read through a catalog of its own, told how to resolve a scheme but - // not handed this read's file system: that one authenticates as this table, and the table about - // to be read is a different one whose credentials the metastore issues only for it. - auto catalog_context = std::make_shared( - warehouse_path.value(), options_.ToMap(), context_->GetFileSystemSchemeToIdentifierMap()); // use global thread number uint32_t cpu_count = std::thread::hardware_concurrency(); uint32_t thread_num = cpu_count > 0 ? cpu_count : 1; PAIMON_ASSIGN_OR_RAISE(std::shared_ptr executor, CreateDefaultExecutor(thread_num)); PAIMON_ASSIGN_OR_RAISE( BlobViewResolver resolver, - BlobViewLookup::CreateResolver(blob_view_structs, catalog_context, pool_, executor)); + BlobViewLookup::CreateResolver( + blob_view_structs, context_->CreateDependencyCatalogContext(), pool_, executor)); return std::make_unique(std::move(inner_reader), std::move(read_blob_view_fields), std::move(resolver), arrow_pool_); diff --git a/src/paimon/core/operation/internal_read_context.cpp b/src/paimon/core/operation/internal_read_context.cpp index a89eb0aeb..f36a15d71 100644 --- a/src/paimon/core/operation/internal_read_context.cpp +++ b/src/paimon/core/operation/internal_read_context.cpp @@ -27,6 +27,9 @@ #include "arrow/c/abi.h" #include "arrow/c/bridge.h" #include "fmt/format.h" +#include "paimon/catalog/catalog.h" +#include "paimon/catalog_options.h" +#include "paimon/common/catalog/catalog_context.h" #include "paimon/common/data/variant/variant_access_utils.h" #include "paimon/common/data/variant/variant_type_utils.h" #include "paimon/common/predicate/full_text_match_predicate.h" @@ -36,6 +39,9 @@ #include "paimon/common/utils/arrow/mem_utils.h" #include "paimon/common/utils/arrow/status_utils.h" #include "paimon/common/utils/checked_cast.h" +#include "paimon/common/utils/path_util.h" +#include "paimon/common/utils/string_utils.h" +#include "paimon/common/utils/url_utils.h" #include "paimon/core/options/map_storage_layout.h" #include "paimon/core/schema/arrow_schema_validator.h" #include "paimon/core/utils/nested_projection_utils.h" @@ -43,9 +49,66 @@ #include "paimon/predicate/full_text_search.h" #include "paimon/predicate/predicate_utils.h" #include "paimon/predicate/vector_search.h" +#include "paimon/rest/rest_api.h" #include "paimon/status.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" namespace paimon { +namespace { + +std::string IdentifierToFlatJson(const Identifier& identifier) { + rapidjson::Document document(rapidjson::kObjectType); + auto& allocator = document.GetAllocator(); + document.AddMember(rapidjson::StringRef("database"), + rapidjson::Value(identifier.GetDatabaseName(), allocator), allocator); + // Keep branch and system-table suffixes in the reported object name. + document.AddMember(rapidjson::StringRef("object"), + rapidjson::Value(identifier.GetObjectName(), allocator), allocator); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + return buffer.GetString(); +} + +} // namespace + +std::shared_ptr InternalReadContext::CreateDependencyCatalogContext() const { + const auto& catalog = read_context_->GetCatalog(); + if (!catalog) { + // Preserve path-only filesystem reads using /.db/
. + return std::make_shared( + PathUtil::GetParentDirPath(PathUtil::GetParentDirPath(GetPath())), options_.ToMap(), + GetFileSystemSchemeToIdentifierMap()); + } + + // Preserve the dependency catalog's effective settings: table options, then catalog + // options, then explicit read overrides. The upstream reader loads its own table options; + // ReadContextBuilder must not copy these catalog settings back into that read. + auto dependency_options = options_.ToMap(); + const auto& catalog_options = catalog->GetOptions(); + for (const auto& [key, value] : catalog_options) { + dependency_options[key] = value; + } + const auto& read_options = read_context_->GetOptions(); + for (const auto& [key, value] : read_options) { + dependency_options[key] = value; + } + const std::string read_via_option = + std::string(RestApi::kHeaderOptionPrefix) + RestApi::kReadViaHeader; + auto metastore = dependency_options.find(CatalogOptions::METASTORE); + if (metastore != dependency_options.end() && + StringUtils::ToLowerCase(metastore->second) == "rest" && read_context_->GetIdentifier() && + dependency_options.count(read_via_option) == 0) { + // Only fill in a missing header when the merged options select a REST dependency catalog. + dependency_options[read_via_option] = + UrlUtils::EncodeString(IdentifierToFlatJson(*read_context_->GetIdentifier())); + } + // A REST warehouse identifies the catalog, independent of the table's storage location. + return std::make_shared(catalog->GetRootPath(), dependency_options, + GetFileSystemSchemeToIdentifierMap()); +} Result> InternalReadContext::AlignReadFieldWithTableFieldIds( const std::shared_ptr& read_field, diff --git a/src/paimon/core/operation/internal_read_context.h b/src/paimon/core/operation/internal_read_context.h index d3601fdff..9de415d5f 100644 --- a/src/paimon/core/operation/internal_read_context.h +++ b/src/paimon/core/operation/internal_read_context.h @@ -35,6 +35,7 @@ class Schema; } // namespace arrow namespace paimon { +struct CatalogContext; class Executor; class MemoryPool; class Predicate; @@ -80,6 +81,9 @@ class InternalReadContext { const std::string& GetPath() const { return read_context_->GetPath(); } + + std::shared_ptr CreateDependencyCatalogContext() const; + const std::vector& GetPartitionKeys() const { return table_schema_->PartitionKeys(); } diff --git a/src/paimon/core/operation/internal_read_context_test.cpp b/src/paimon/core/operation/internal_read_context_test.cpp index ca7b79dc2..6e4957f4e 100644 --- a/src/paimon/core/operation/internal_read_context_test.cpp +++ b/src/paimon/core/operation/internal_read_context_test.cpp @@ -29,6 +29,8 @@ #include "arrow/type.h" #include "fmt/format.h" #include "gtest/gtest.h" +#include "paimon/catalog_options.h" +#include "paimon/common/catalog/catalog_context.h" #include "paimon/common/table/special_fields.h" #include "paimon/common/types/data_field.h" #include "paimon/core/schema/schema_manager.h" @@ -40,6 +42,8 @@ #include "paimon/predicate/predicate_builder.h" #include "paimon/predicate/vector_search.h" #include "paimon/status.h" +#include "paimon/testing/mock/mock_catalog.h" +#include "paimon/testing/mock/mock_file_system.h" #include "paimon/testing/utils/testharness.h" namespace paimon::test { @@ -78,25 +82,186 @@ TEST(InternalReadContext, TestReadWithSpecifiedSchema) { ASSERT_TRUE(internal_context->GetReadSchema()->Equals(expected_schema)); } -TEST(InternalReadContext, TestHeaderOptionSurvivesIntoCoreOptions) { - // The `header.` option a read through a REST catalog reports its starting table with has to - // still be among the options a dependency table is read through: `DataEvolutionSplitRead` - // builds that catalog context out of `CoreOptions::ToMap()`, so an option dropped on the way - // here would quietly drop the `X-Paimon-Read-Via` delegation header along with it. +TEST(InternalReadContext, TestDependencyReadMergesTableCatalogAndReadOptions) { std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; - ReadContextBuilder context_builder(path); - ASSERT_OK_AND_ASSIGN(auto read_context, context_builder.Finish()); - SchemaManager schema_manager(std::make_shared(), read_context->GetPath()); + SchemaManager schema_manager(std::make_shared(), path); ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); - - // The merge `TableRead::Create` performs: the table's own options first, then the ones the read - // context carries over them, which is where a header option arrives from. - const std::string read_via = "%7B%22database%22%3A%22db1%22%2C%22object%22%3A%22t1%22%7D"; + auto catalog = std::make_shared(); + catalog->SetTableFileSystem(std::make_shared()); + const std::map catalog_options = { + {CatalogOptions::METASTORE, "rest"}, + {CatalogOptions::URI, "http://catalog"}, + {CatalogOptions::TOKEN, "catalog-token"}, + {CatalogOptions::DATA_TOKEN_ENABLED, "true"}}; + catalog->SetOptions(catalog_options); + const std::map read_options = { + {CatalogOptions::METASTORE, "filesystem"}, + {CatalogOptions::URI, "http://read"}, + {CatalogOptions::TOKEN, "read-token"}, + {Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}}; + ReadContextBuilder builder(path); + ASSERT_OK_AND_ASSIGN( + std::shared_ptr read_context, + builder.WithCatalog(catalog, Identifier("db1", "t1")).SetOptions(read_options).Finish()); std::map options = table_schema->Options(); - options["header.X-Paimon-Read-Via"] = read_via; + options[Options::READ_BATCH_SIZE] = "64"; + options[CatalogOptions::DATA_TOKEN_ENABLED] = "false"; + options["header.X-Paimon-Read-Via"] = "table-view"; + for (const auto& [key, value] : read_options) { + options[key] = value; + } + ASSERT_OK_AND_ASSIGN(auto internal_context, + InternalReadContext::Create(read_context, table_schema, options)); + + auto dependency_context = internal_context->CreateDependencyCatalogContext(); + auto expected_options = options; + expected_options[CatalogOptions::DATA_TOKEN_ENABLED] = "true"; + ASSERT_EQ(dependency_context->root_path, catalog->GetRootPath()); + ASSERT_EQ(dependency_context->options, expected_options); + ASSERT_EQ(dependency_context->options.at("header.X-Paimon-Read-Via"), "table-view"); + ASSERT_EQ(read_context->GetOptions(), read_options); + ASSERT_EQ(catalog->GetOptions(), catalog_options); + ASSERT_EQ(internal_context->GetCoreOptions().ToMap(), options); +} + +TEST(InternalReadContext, TestDependencyReadKeepsTheOutermostView) { + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; + SchemaManager schema_manager(std::make_shared(), path); + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); + auto catalog = std::make_shared(); + catalog->SetTableFileSystem(std::make_shared()); + struct HeaderCase { + const char* catalog_header; + const char* read_header; + const char* expected_header; + }; + const std::vector cases = {{nullptr, nullptr, "table-view"}, + {"catalog-view", nullptr, "catalog-view"}, + {"catalog-view", "explicit-view", "explicit-view"}}; + for (const auto& test_case : cases) { + std::map catalog_options = {{CatalogOptions::METASTORE, "rest"}}; + if (test_case.catalog_header) { + catalog_options["header.X-Paimon-Read-Via"] = test_case.catalog_header; + } + catalog->SetOptions(catalog_options); + std::map read_options; + if (test_case.read_header) { + read_options["header.X-Paimon-Read-Via"] = test_case.read_header; + } + ReadContextBuilder builder(path); + ASSERT_OK_AND_ASSIGN(std::shared_ptr read_context, + builder.WithCatalog(catalog, Identifier("db1", "nested-view")) + .SetOptions(read_options) + .Finish()); + auto options = table_schema->Options(); + options["header.X-Paimon-Read-Via"] = + test_case.read_header ? test_case.read_header : "table-view"; + ASSERT_OK_AND_ASSIGN(auto internal_context, + InternalReadContext::Create(read_context, table_schema, options)); + auto dependency_context = internal_context->CreateDependencyCatalogContext(); + ASSERT_EQ(dependency_context->options.at("header.X-Paimon-Read-Via"), + test_case.expected_header); + ASSERT_EQ(read_context->GetOptions(), read_options); + ASSERT_EQ(catalog->GetOptions(), catalog_options); + ASSERT_EQ(internal_context->GetCoreOptions().ToMap(), options); + } +} + +TEST(InternalReadContext, TestDependencyReadHeaderUsesFinalMetastore) { + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; + SchemaManager schema_manager(std::make_shared(), path); + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); + struct MetastoreCase { + const char* name; + const char* table_metastore; + const char* catalog_metastore; + const char* read_metastore; + const char* expected_metastore; + bool generates_header; + }; + const std::vector cases = { + {"read disables REST", nullptr, "rest", "filesystem", "filesystem", false}, + {"read enables REST", nullptr, "filesystem", "rest", "rest", true}, + {"read enables uppercase REST", nullptr, "hive", "REST", "REST", true}, + {"table enables REST", "rest", nullptr, nullptr, "rest", true}, + {"catalog overrides table REST", "rest", "filesystem", nullptr, "filesystem", false}, + {"missing metastore", nullptr, nullptr, nullptr, nullptr, false}}; + const std::string expected_header = + "%7B%22database%22%3A%22db1%22%2C%22object%22%3A%22t1%22%7D"; + for (const auto& test_case : cases) { + auto catalog = std::make_shared(); + catalog->SetTableFileSystem(std::make_shared()); + std::map catalog_options; + if (test_case.catalog_metastore) { + catalog_options[CatalogOptions::METASTORE] = test_case.catalog_metastore; + } + catalog->SetOptions(catalog_options); + auto options = table_schema->Options(); + if (test_case.table_metastore) { + options[CatalogOptions::METASTORE] = test_case.table_metastore; + } + std::map read_options; + if (test_case.read_metastore) { + read_options[CatalogOptions::METASTORE] = test_case.read_metastore; + options[CatalogOptions::METASTORE] = test_case.read_metastore; + } + ReadContextBuilder builder(path); + ASSERT_OK_AND_ASSIGN(std::shared_ptr read_context, + builder.WithCatalog(catalog, Identifier("db1", "t1")) + .SetOptions(read_options) + .Finish()); + ASSERT_OK_AND_ASSIGN(auto internal_context, + InternalReadContext::Create(read_context, table_schema, options)); + auto dependency_context = internal_context->CreateDependencyCatalogContext(); + if (test_case.expected_metastore) { + ASSERT_EQ(dependency_context->options.at(CatalogOptions::METASTORE), + test_case.expected_metastore); + } else { + ASSERT_EQ(dependency_context->options.count(CatalogOptions::METASTORE), 0u); + } + if (test_case.generates_header) { + ASSERT_EQ(dependency_context->options.at("header.X-Paimon-Read-Via"), expected_header); + } else { + ASSERT_EQ(dependency_context->options.count("header.X-Paimon-Read-Via"), 0u); + } + ASSERT_EQ(read_context->GetOptions(), read_options); + ASSERT_EQ(catalog->GetOptions(), catalog_options); + ASSERT_EQ(internal_context->GetCoreOptions().ToMap(), options); + } +} + +TEST(InternalReadContext, TestDependencyReadHeaderNeedsAnIdentifier) { + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; + SchemaManager schema_manager(std::make_shared(), path); + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); + ReadContextBuilder builder(path); + ASSERT_OK_AND_ASSIGN(auto read_context, + builder.AddOption(CatalogOptions::METASTORE, "REST").Finish()); + ASSERT_FALSE(read_context->GetIdentifier()); + auto options = table_schema->Options(); + options[CatalogOptions::METASTORE] = "REST"; ASSERT_OK_AND_ASSIGN(auto internal_context, InternalReadContext::Create(std::move(read_context), table_schema, options)); - ASSERT_EQ(internal_context->GetCoreOptions().ToMap().at("header.X-Paimon-Read-Via"), read_via); + auto dependency_context = internal_context->CreateDependencyCatalogContext(); + ASSERT_EQ(dependency_context->options.count("header.X-Paimon-Read-Via"), 0u); +} + +TEST(InternalReadContext, TestDependencyReadHeaderReportsTheBranchOfTheStartingTable) { + std::string path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09"; + SchemaManager schema_manager(std::make_shared(), path); + ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0)); + auto catalog = std::make_shared(); + catalog->SetTableFileSystem(std::make_shared()); + catalog->SetOptions({{CatalogOptions::METASTORE, "REST"}}); + ReadContextBuilder builder(path); + ASSERT_OK_AND_ASSIGN(auto read_context, + builder.WithCatalog(catalog, Identifier("db1", "t1", "dev")).Finish()); + ASSERT_OK_AND_ASSIGN(auto internal_context, + InternalReadContext::Create(std::move(read_context), table_schema, + table_schema->Options())); + auto dependency_context = internal_context->CreateDependencyCatalogContext(); + ASSERT_EQ(dependency_context->options.at("header.X-Paimon-Read-Via"), + "%7B%22database%22%3A%22db1%22%2C%22object%22%3A%22t1%24branch_dev%22%7D"); } TEST(InternalReadContext, TestFileSystemSchemeMapReachesTheDependencyRead) { @@ -116,6 +281,8 @@ TEST(InternalReadContext, TestFileSystemSchemeMapReachesTheDependencyRead) { InternalReadContext::Create(std::move(read_context), table_schema, table_schema->Options())); ASSERT_EQ(internal_context->GetFileSystemSchemeToIdentifierMap(), scheme_map); + ASSERT_EQ(internal_context->CreateDependencyCatalogContext()->fs_scheme_to_identifier_map, + scheme_map); } TEST(InternalReadContext, TestReadWithSpecifiedFieldId) { diff --git a/src/paimon/core/operation/read_context.cpp b/src/paimon/core/operation/read_context.cpp index d731c4d3b..bc65b4661 100644 --- a/src/paimon/core/operation/read_context.cpp +++ b/src/paimon/core/operation/read_context.cpp @@ -25,64 +25,18 @@ #include "arrow/c/abi.h" #include "arrow/c/bridge.h" #include "paimon/catalog/catalog.h" -#include "paimon/catalog_options.h" #include "paimon/common/utils/path_util.h" -#include "paimon/common/utils/string_utils.h" -#include "paimon/common/utils/url_utils.h" #include "paimon/core/utils/branch_manager.h" #include "paimon/executor.h" #include "paimon/memory/memory_pool.h" #include "paimon/predicate/full_text_search.h" #include "paimon/predicate/vector_search.h" -#include "paimon/rest/rest_api.h" #include "paimon/status.h" #include "paimon/table/format/format_table.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/writer.h" namespace paimon { class Predicate; -namespace { - -/// True when the catalog-level options - the ones the catalog was created with, not the read's own -/// table options - select the REST protocol, the only kind turning a `header.`-prefixed option into -/// a request header. `Catalog::Create` dispatches on this same `metastore` key, so a catalog that -/// speaks REST is exactly one whose `GetOptions()` names it. The Java counterpart answers this from -/// the catalog loader / `catalogContext.options()` it was built with. -bool IsRestMetastore(const std::map& catalog_options) { - auto metastore = catalog_options.find(CatalogOptions::METASTORE); - if (metastore == catalog_options.end()) { - return false; - } - // Matched leniently in lower case, the way `Catalog::Create` matches it. - const std::string name = StringUtils::ToLowerCase(metastore->second); - return name == "rest"; -} - -/// `{"database":"db1","object":"t1"}`, the flat JSON the metastore parses a header value back into. -/// The field names are the Java `Identifier.FIELD_DATABASE_NAME` and `FIELD_OBJECT_NAME`, and the -/// object name keeps any branch and system-table suffix, as the `getObjectName()` the Java -/// serializer reads does, so a read of a branch reports that branch. -std::string IdentifierToFlatJson(const Identifier& identifier) { - rapidjson::Document document(rapidjson::kObjectType); - auto& allocator = document.GetAllocator(); - document.AddMember(rapidjson::StringRef("database"), - rapidjson::Value(identifier.GetDatabaseName(), allocator), allocator); - // `GetObjectName()` keeps any branch and system-table suffix, matching the Java - // `getObjectName()` this field mirrors. So a branch read reports the branch. - document.AddMember(rapidjson::StringRef("object"), - rapidjson::Value(identifier.GetObjectName(), allocator), allocator); - // Compact, not the pretty form `RapidJsonUtil::ToJson` writes: the value goes into a header. - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - document.Accept(writer); - return buffer.GetString(); -} - -} // namespace - ReadContext::ReadContext( const std::string& path, const std::string& branch, const std::vector& read_field_names, const std::vector& read_field_ids, @@ -474,23 +428,6 @@ Result> ReadContextBuilder::Finish() { return Status::Invalid("row to batch thread number should be greater than 0"); } - const std::string read_via_option = - std::string(RestApi::kHeaderOptionPrefix) + RestApi::kReadViaHeader; - if (impl_->catalog_ != nullptr && impl_->identifier_ && - IsRestMetastore(impl_->catalog_->GetOptions())) { - // A blob-view dependency read (BlobViewLookup) rebuilds its own catalog from a - // CatalogContext whose options come from these read options, so they carry none of the - // catalog-level connection settings. Hand over the ones RestApi::Create needs to come back - // as the same REST catalog - metastore, uri and the auth options - instead of letting it - // silently degrade to a filesystem catalog; a key the caller already set wins. - for (const auto& [key, value] : impl_->catalog_->GetOptions()) { - impl_->options_.emplace(key, value); - } - // Reports the outermost starting table; an existing value, set by an outer read, is kept. - impl_->options_.emplace( - read_via_option, - UrlUtils::EncodeString(IdentifierToFlatJson(impl_->identifier_.value()))); - } auto ctx = std::make_unique( impl_->path_, branch, impl_->read_field_names_, impl_->read_field_ids_, impl_->predicate_, impl_->vector_search_, impl_->full_text_search_, impl_->enable_predicate_filter_, diff --git a/src/paimon/core/operation/read_context_test.cpp b/src/paimon/core/operation/read_context_test.cpp index d9079e702..4fb5e00b4 100644 --- a/src/paimon/core/operation/read_context_test.cpp +++ b/src/paimon/core/operation/read_context_test.cpp @@ -85,91 +85,31 @@ TEST(ReadContextTest, TestWithCatalogNullRejected) { "cannot read through a null catalog"); } -TEST(ReadContextTest, TestReadViaHeaderReportsTheStartingTable) { - // A read through a REST catalog carries the table it started from, so that the metastore can - // authorize a dependency table - the blob table behind a BlobView - through that table's owner. - // The value is the identifier's flat JSON in the form-urlencoded flavor the REST server - // expects, byte for byte what the Java client sends. - const std::string expected = "%7B%22database%22%3A%22db1%22%2C%22object%22%3A%22t1%22%7D"; - for (const std::string metastore : {"rest"}) { - auto catalog = std::make_shared(); - catalog->SetTableFileSystem(std::make_shared()); - catalog->SetOptions({{CatalogOptions::METASTORE, metastore}}); - - ReadContextBuilder builder("table_root_path"); - ASSERT_OK_AND_ASSIGN(auto ctx, - builder.WithCatalog(catalog, Identifier("db1", "t1")).Finish()); - - const std::map& options = ctx->GetOptions(); - ASSERT_EQ(options.count("header.X-Paimon-Read-Via"), 1u) << metastore; - ASSERT_EQ(options.at("header.X-Paimon-Read-Via"), expected) << metastore; - // The catalog-level connection options are handed to the read options so the blob-view - // dependency catalog can be rebuilt as the same REST catalog; here that is the metastore. - ASSERT_EQ(options.count(CatalogOptions::METASTORE), 1u) << metastore; - ASSERT_EQ(options.at(CatalogOptions::METASTORE), metastore) << metastore; - ASSERT_EQ(options.size(), 2u) << metastore; - } -} - -TEST(ReadContextTest, TestReadViaHeaderKeepsTheOutermostView) { - // A view read on behalf of another view arrives with the header already set, and the outermost - // view is the one the metastore hears about: the option in hand wins, as it does in the Java - // `CatalogEnvironment.dependencyReadContext()`. +TEST(ReadContextTest, TestWithCatalogKeepsReadOptionsSeparate) { auto catalog = std::make_shared(); catalog->SetTableFileSystem(std::make_shared()); - catalog->SetOptions({{CatalogOptions::METASTORE, "rest"}}); + catalog->SetOptions({{CatalogOptions::METASTORE, "rest"}, + {CatalogOptions::URI, "http://catalog"}, + {Options::BLOB_VIEW_RESOLVE_ENABLED, "false"}}); ReadContextBuilder builder("table_root_path"); - builder.AddOption("header.X-Paimon-Read-Via", "outer-view"); ASSERT_OK_AND_ASSIGN(auto ctx, builder.WithCatalog(catalog, Identifier("db1", "t1")).Finish()); - ASSERT_EQ(ctx->GetOptions().at("header.X-Paimon-Read-Via"), "outer-view"); -} - -TEST(ReadContextTest, TestReadViaHeaderNeedsARestCatalogAndATable) { - // Only a REST metastore turns a `header.` option into a request header, and only a read naming - // a table has one to report: the two guards the Java counterpart checks before copying the - // options over. A catalog that is not one is told nothing. - for (const std::string metastore : {"filesystem", "hive", "alake"}) { - auto catalog = std::make_shared(); - catalog->SetTableFileSystem(std::make_shared()); - catalog->SetOptions({{CatalogOptions::METASTORE, metastore}}); - - ReadContextBuilder builder("table_root_path"); - ASSERT_OK_AND_ASSIGN(auto ctx, - builder.WithCatalog(catalog, Identifier("db1", "t1")).Finish()); - ASSERT_EQ(ctx->GetOptions().count("header.X-Paimon-Read-Via"), 0u) << metastore; - } + // Finishing a read neither imports catalog defaults nor generates a dependency header. + ASSERT_TRUE(ctx->GetOptions().empty()); - // Nor is one added when the options leave the metastore out, which reads as a filesystem - // catalog, or when the read goes through no catalog and so names no table - however the - // metastore is spelled, since `Catalog::Create` matches it in lower case. - auto catalog = std::make_shared(); - catalog->SetTableFileSystem(std::make_shared()); - ReadContextBuilder no_metastore_builder("table_root_path"); + catalog->SetOptions({{CatalogOptions::METASTORE, "rest"}, + {CatalogOptions::URI, "http://catalog"}, + {"header.X-Paimon-Read-Via", "catalog-view"}}); + ASSERT_OK_AND_ASSIGN(auto nested_ctx, + builder.WithCatalog(catalog, Identifier("db1", "t1")).Finish()); + ASSERT_TRUE(nested_ctx->GetOptions().empty()); + const std::map read_options = { + {Options::BLOB_VIEW_RESOLVE_ENABLED, "true"}, + {"header.X-Paimon-Read-Via", "explicit-view"}}; ASSERT_OK_AND_ASSIGN( - auto no_metastore_ctx, - no_metastore_builder.WithCatalog(catalog, Identifier("db1", "t1")).Finish()); - ASSERT_EQ(no_metastore_ctx->GetOptions().count("header.X-Paimon-Read-Via"), 0u); - - ReadContextBuilder no_catalog_builder("table_root_path"); - no_catalog_builder.AddOption(CatalogOptions::METASTORE, "REST"); - ASSERT_OK_AND_ASSIGN(auto no_catalog_ctx, no_catalog_builder.Finish()); - ASSERT_EQ(no_catalog_ctx->GetOptions().count("header.X-Paimon-Read-Via"), 0u); -} - -TEST(ReadContextTest, TestReadViaHeaderReportsTheBranchOfTheStartingTable) { - // What is reported is the object name, the branch suffix included, because that is what the - // Java `getObjectName()` its serializer reads returns: a read of a branch says so instead of - // naming the data table. The metastore is matched in upper case here as well. - auto catalog = std::make_shared(); - catalog->SetTableFileSystem(std::make_shared()); - catalog->SetOptions({{CatalogOptions::METASTORE, "REST"}}); - - ReadContextBuilder builder("table_root_path"); - ASSERT_OK_AND_ASSIGN(auto ctx, - builder.WithCatalog(catalog, Identifier("db1", "t1", "dev")).Finish()); - ASSERT_EQ(ctx->GetOptions().at("header.X-Paimon-Read-Via"), - "%7B%22database%22%3A%22db1%22%2C%22object%22%3A%22t1%24branch_dev%22%7D"); + auto explicit_ctx, + builder.WithCatalog(catalog, Identifier("db1", "t1")).SetOptions(read_options).Finish()); + ASSERT_EQ(explicit_ctx->GetOptions(), read_options); } TEST(ReadContextTest, TestDefaultValue) { diff --git a/src/paimon/core/table/source/table_read_test.cpp b/src/paimon/core/table/source/table_read_test.cpp index 31e3f74f0..ead31b17a 100644 --- a/src/paimon/core/table/source/table_read_test.cpp +++ b/src/paimon/core/table/source/table_read_test.cpp @@ -26,8 +26,10 @@ #include #include +#include "arrow/type.h" #include "gtest/gtest.h" #include "paimon/catalog/table.h" +#include "paimon/common/catalog/catalog_context.h" #include "paimon/common/data/binary_row.h" #include "paimon/core/core_options.h" #include "paimon/core/operation/abstract_split_read.h" @@ -269,4 +271,37 @@ TEST(TableReadTest, TestMergeOptions) { ASSERT_EQ(expected_options, core_options.ToMap()); } +TEST(TableReadTest, TestDependencyReadWithoutCatalogUsesReadOptions) { + const std::string read_via_option = "header.X-Paimon-Read-Via"; + const std::map table_options = { + {Options::FILE_FORMAT, "orc"}, {Options::BUCKET, "-1"}, {read_via_option, "table-view"}}; + ASSERT_OK_AND_ASSIGN( + std::unique_ptr table_schema, + TableSchema::Create(0, arrow::schema({arrow::field("id", arrow::int32())}), + /*partition_keys=*/{}, /*primary_keys=*/{}, table_options)); + ASSERT_OK_AND_ASSIGN(std::string schema_json, table_schema->ToJsonString()); + + for (bool explicit_header : {false, true}) { + ReadContextBuilder builder("warehouse/db.db/table"); + builder.SetTableSchema(schema_json); + if (explicit_header) { + builder.AddOption(read_via_option, "read-view"); + } + ASSERT_OK_AND_ASSIGN(std::unique_ptr read_context, builder.Finish()); + // Exercise the actual table/read merge before creating the dependency context. + ASSERT_OK_AND_ASSIGN(std::unique_ptr table_read, + TableRead::Create(std::move(read_context))); + auto* append_only_table_read = dynamic_cast(table_read.get()); + ASSERT_NE(append_only_table_read, nullptr); + const auto& internal_context = append_only_table_read->context_; + auto dependency_context = internal_context->CreateDependencyCatalogContext(); + + auto expected_options = table_options; + expected_options[read_via_option] = explicit_header ? "read-view" : "table-view"; + ASSERT_EQ(dependency_context->root_path, "warehouse"); + ASSERT_EQ(dependency_context->options, expected_options); + ASSERT_EQ(internal_context->GetTableSchema()->Options(), table_options); + } +} + } // namespace paimon::test diff --git a/test/inte/blob_table_inte_test.cpp b/test/inte/blob_table_inte_test.cpp index eabb28a73..4cbfda4df 100644 --- a/test/inte/blob_table_inte_test.cpp +++ b/test/inte/blob_table_inte_test.cpp @@ -200,20 +200,15 @@ class BlobTableInteTest : public testing::Test, public ::testing::WithParamInter } /// Creates the (f0 INT, view) blob-view table the dangling-reference tests share: unaware - /// bucket, data evolution on, and `view` resolved against `upstream_warehouse`. Returns the - /// table's fields, which the callers reuse to build their expected arrays. - arrow::FieldVector CreateBlobViewTable(const std::string& upstream_warehouse, - bool deletion_vectors_enabled) const { + /// bucket and data evolution on. Returns the table's fields, which the callers reuse to + /// build their expected arrays. + arrow::FieldVector CreateBlobViewTable(bool deletion_vectors_enabled) const { arrow::FieldVector fields = {arrow::field("f0", arrow::int32()), BlobUtils::ToArrowField("view", true)}; std::map options = { - {Options::FILE_FORMAT, GetParam()}, - {Options::BUCKET, "-1"}, - {Options::ROW_TRACKING_ENABLED, "true"}, - {Options::DATA_EVOLUTION_ENABLED, "true"}, - {Options::BLOB_VIEW_FIELD, "view"}, - {Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, upstream_warehouse}, - {Options::FILE_SYSTEM, "local"}}; + {Options::FILE_FORMAT, GetParam()}, {Options::BUCKET, "-1"}, + {Options::ROW_TRACKING_ENABLED, "true"}, {Options::DATA_EVOLUTION_ENABLED, "true"}, + {Options::BLOB_VIEW_FIELD, "view"}, {Options::FILE_SYSTEM, "local"}}; if (deletion_vectors_enabled) { options.emplace(Options::DELETION_VECTORS_ENABLED, "true"); } @@ -4013,13 +4008,9 @@ TEST_P(BlobTableInteTest, TestBlobViewFieldWithUpstreamTable) { arrow::FieldVector fields = {arrow::field("f0", arrow::int32()), BlobUtils::ToArrowField("view", true)}; std::map options = { - {Options::FILE_FORMAT, file_format}, - {Options::BUCKET, "-1"}, - {Options::ROW_TRACKING_ENABLED, "true"}, - {Options::DATA_EVOLUTION_ENABLED, "true"}, - {Options::BLOB_VIEW_FIELD, "view"}, - {Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, dir_->Str()}, - {Options::FILE_SYSTEM, "local"}}; + {Options::FILE_FORMAT, file_format}, {Options::BUCKET, "-1"}, + {Options::ROW_TRACKING_ENABLED, "true"}, {Options::DATA_EVOLUTION_ENABLED, "true"}, + {Options::BLOB_VIEW_FIELD, "view"}, {Options::FILE_SYSTEM, "local"}}; CreateTable(fields, /*partition_keys=*/{}, options); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); @@ -4177,8 +4168,7 @@ TEST_P(BlobTableInteTest, TestForwardBlobViewReference) { PathUtil::JoinPath(dir_->Str(), upstream_db_name + ".db/" + upstream_table_name); ASSERT_TRUE(TestUtil::CopyDirectory(src_db_path, dst_db_path)); - // The source table configures no explicit upstream warehouse; a default read falls back to the - // warehouse it lives in (dir_), where the copied upstream table sits. + // Both blob-view tables resolve the upstream table from the warehouse they share. arrow::FieldVector fields = {arrow::field("f0", arrow::int32()), BlobUtils::ToArrowField("view", true)}; std::map source_options = { @@ -4188,15 +4178,12 @@ TEST_P(BlobTableInteTest, TestForwardBlobViewReference) { CreateTable(fields, /*partition_keys=*/{}, source_options); std::string source_table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); - // The target table configures the upstream warehouse for resolving forwarded references. - std::map target_options = source_options; - target_options[Options::BLOB_VIEW_UPSTREAM_WAREHOUSE] = dir_->Str(); auto schema = arrow::schema(fields); ::ArrowSchema c_target_schema; ASSERT_TRUE(arrow::ExportSchema(*schema, &c_target_schema).ok()); ASSERT_OK_AND_ASSIGN(auto catalog, Catalog::Create(dir_->Str(), {})); ASSERT_OK(catalog->CreateTable(Identifier("foo", "bar_forward"), &c_target_schema, - /*partition_keys=*/{}, /*primary_keys=*/{}, target_options, + /*partition_keys=*/{}, /*primary_keys=*/{}, source_options, /*ignore_if_exists=*/false)); std::string target_table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar_forward"); @@ -4231,9 +4218,8 @@ TEST_P(BlobTableInteTest, TestForwardBlobViewReference) { WriteArray(source_table_path, {}, schema->field_names(), {write_struct})); ASSERT_OK(Commit(source_table_path, commit_msgs)); - // With no explicit upstream warehouse, a default read now falls back to the source table's own - // warehouse (dir_) and resolves the copied upstream table instead of failing. The pass-through - // read below still uses the dynamic option to preserve the raw BlobViewStruct bytes. + // A default read resolves the copied upstream table in the source table's warehouse. The + // pass-through read below uses the dynamic option to preserve the raw BlobViewStruct bytes. ASSERT_OK_AND_ASSIGN(auto source_plan, ScanTable(source_table_path)); ASSERT_OK_AND_ASSIGN( auto source_default_result, @@ -4370,13 +4356,9 @@ TEST_P(BlobTableInteTest, TestBlobViewFieldWithUpstreamDescriptorBlob) { arrow::FieldVector fields = {arrow::field("f0", arrow::int32()), BlobUtils::ToArrowField("view", true)}; std::map options = { - {Options::FILE_FORMAT, file_format}, - {Options::BUCKET, "-1"}, - {Options::ROW_TRACKING_ENABLED, "true"}, - {Options::DATA_EVOLUTION_ENABLED, "true"}, - {Options::BLOB_VIEW_FIELD, "view"}, - {Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, dir_->Str()}, - {Options::FILE_SYSTEM, "local"}}; + {Options::FILE_FORMAT, file_format}, {Options::BUCKET, "-1"}, + {Options::ROW_TRACKING_ENABLED, "true"}, {Options::DATA_EVOLUTION_ENABLED, "true"}, + {Options::BLOB_VIEW_FIELD, "view"}, {Options::FILE_SYSTEM, "local"}}; CreateTable(fields, /*partition_keys=*/{}, options); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); @@ -4475,13 +4457,9 @@ TEST_P(BlobTableInteTest, TestBlobViewFieldWithMultipleUpstreamTables) { BlobUtils::ToArrowField("view1", true), BlobUtils::ToArrowField("view2", true)}; std::map options = { - {Options::FILE_FORMAT, file_format}, - {Options::BUCKET, "-1"}, - {Options::ROW_TRACKING_ENABLED, "true"}, - {Options::DATA_EVOLUTION_ENABLED, "true"}, - {Options::BLOB_VIEW_FIELD, "view1,view2"}, - {Options::BLOB_VIEW_UPSTREAM_WAREHOUSE, dir_->Str()}, - {Options::FILE_SYSTEM, "local"}}; + {Options::FILE_FORMAT, file_format}, {Options::BUCKET, "-1"}, + {Options::ROW_TRACKING_ENABLED, "true"}, {Options::DATA_EVOLUTION_ENABLED, "true"}, + {Options::BLOB_VIEW_FIELD, "view1,view2"}, {Options::FILE_SYSTEM, "local"}}; CreateTable(fields, /*partition_keys=*/{}, options); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); @@ -4605,9 +4583,7 @@ TEST_P(BlobTableInteTest, TestBlobViewFailsWhenUpstreamTableAbsent) { if (GetParam() == "mosaic") { return; } - auto upstream_dir = UniqueTestDirectory::Create("local"); - arrow::FieldVector fields = - CreateBlobViewTable(upstream_dir->Str(), /*deletion_vectors_enabled=*/false); + arrow::FieldVector fields = CreateBlobViewTable(/*deletion_vectors_enabled=*/false); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); auto schema = arrow::schema(fields); @@ -4623,9 +4599,7 @@ TEST_P(BlobTableInteTest, TestBlobViewSkipsDanglingReferenceOfDeletedRow) { if (GetParam() == "mosaic") { return; } - auto upstream_dir = UniqueTestDirectory::Create("local"); - arrow::FieldVector fields = - CreateBlobViewTable(upstream_dir->Str(), /*deletion_vectors_enabled=*/true); + arrow::FieldVector fields = CreateBlobViewTable(/*deletion_vectors_enabled=*/true); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); auto schema = arrow::schema(fields); @@ -4666,9 +4640,7 @@ TEST_P(BlobTableInteTest, TestBlobViewSkipsDanglingReferenceInEveryRowRangeGroup if (GetParam() == "mosaic") { return; } - auto upstream_dir = UniqueTestDirectory::Create("local"); - arrow::FieldVector fields = - CreateBlobViewTable(upstream_dir->Str(), /*deletion_vectors_enabled=*/true); + arrow::FieldVector fields = CreateBlobViewTable(/*deletion_vectors_enabled=*/true); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); auto schema = arrow::schema(fields); @@ -4710,9 +4682,7 @@ TEST_P(BlobTableInteTest, TestBlobViewPreReadHonorsRowRangesNotPredicate) { if (GetParam() == "mosaic") { return; } - auto upstream_dir = UniqueTestDirectory::Create("local"); - arrow::FieldVector fields = - CreateBlobViewTable(upstream_dir->Str(), /*deletion_vectors_enabled=*/false); + arrow::FieldVector fields = CreateBlobViewTable(/*deletion_vectors_enabled=*/false); std::string table_path = PathUtil::JoinPath(dir_->Str(), "foo.db/bar"); auto schema = arrow::schema(fields);