This is an automated email from the ASF dual-hosted git repository.
lxy-9602 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 758d225c refactor(blob-view): resolve blob view dependencies through
catalog context (#455)
758d225c is described below
commit 758d225c2a364cd8da976c1a70b7837232e41d77
Author: lszskye <[email protected]>
AuthorDate: Fri Oct 23 13:13:39 2026 +0800
refactor(blob-view): resolve blob view dependencies through catalog context
(#455)
---
include/paimon/defs.h | 6 -
src/paimon/common/defs.cpp | 1 -
src/paimon/core/core_options.cpp | 8 -
src/paimon/core/core_options.h | 1 -
src/paimon/core/core_options_test.cpp | 4 -
.../core/operation/data_evolution_split_read.cpp | 21 +--
.../core/operation/internal_read_context.cpp | 63 +++++++
src/paimon/core/operation/internal_read_context.h | 4 +
.../core/operation/internal_read_context_test.cpp | 195 +++++++++++++++++++--
src/paimon/core/operation/read_context.cpp | 63 -------
src/paimon/core/operation/read_context_test.cpp | 96 ++--------
src/paimon/core/table/source/table_read_test.cpp | 35 ++++
test/inte/blob_table_inte_test.cpp | 76 +++-----
13 files changed, 326 insertions(+), 247 deletions(-)
diff --git a/include/paimon/defs.h b/include/paimon/defs.h
index 709d4490..2c2ef191 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<BLOB> 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 d709d6f8..9ddefb16 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 666d7685..b02930ff 100644
--- a/src/paimon/core/core_options.cpp
+++ b/src/paimon/core/core_options.cpp
@@ -390,7 +390,6 @@ struct CoreOptions::Impl {
std::optional<std::string> field_default_func;
std::optional<std::string> scan_fallback_branch;
std::optional<std::string> data_file_external_paths;
- std::optional<std::string> blob_view_upstream_warehouse;
std::optional<std::string> changelog_file_compression;
std::optional<std::string> global_index_external_path;
std::optional<std::string> scan_tag_name;
@@ -582,9 +581,6 @@ struct CoreOptions::Impl {
PAIMON_RETURN_NOT_OK(parser.ParseList<std::string>(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<bool>(Options::BLOB_VIEW_RESOLVE_ENABLED,
&blob_view_resolve_enabled));
@@ -1966,10 +1962,6 @@ const std::vector<std::string>&
CoreOptions::GetBlobViewFields() const {
return impl_->blob_view_fields;
}
-std::optional<std::string> 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 8bf79426..34aaad53 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<std::string>& GetBlobFields() const;
const std::vector<std::string>& GetBlobDescriptorFields() const;
const std::vector<std::string>& GetBlobViewFields() const;
- std::optional<std::string> GetBlobViewUpstreamWarehouse() const;
bool BlobViewResolveEnabled() const;
std::vector<std::string> GetBlobInlineFields() const;
diff --git a/src/paimon/core/core_options_test.cpp
b/src/paimon/core/core_options_test.cpp
index e3625db8..fa2c7780 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<std::string>({"blob5"}));
ASSERT_EQ(core_options.GetBlobInlineFields(),
std::vector<std::string>({"blob3", "blob4", "blob5"}));
- ASSERT_EQ(core_options.GetBlobViewUpstreamWarehouse(),
-
std::optional<std::string>("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 e3a52c6f..6d065b46 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<std::unique_ptr<BatchReader>>
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
`<database>.db/<table>` (or
- // `<database>/<table>`) components off this table's path yields that
warehouse.
- std::optional<std::string> 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<BatchReader> pre_reader,
CreateBlobViewReader(data_split,
read_blob_view_fields, row_ranges));
PAIMON_ASSIGN_OR_RAISE(std::unordered_set<BlobViewStruct>
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<CatalogContext>(
- 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> 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<BlobViewResolvingBatchReader>(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 a89eb0ae..f36a15d7 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<rapidjson::StringBuffer> writer(buffer);
+ document.Accept(writer);
+ return buffer.GetString();
+}
+
+} // namespace
+
+std::shared_ptr<CatalogContext>
InternalReadContext::CreateDependencyCatalogContext() const {
+ const auto& catalog = read_context_->GetCatalog();
+ if (!catalog) {
+ // Preserve path-only filesystem reads using
<warehouse>/<database>.db/<table>.
+ return std::make_shared<CatalogContext>(
+ 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<CatalogContext>(catalog->GetRootPath(),
dependency_options,
+
GetFileSystemSchemeToIdentifierMap());
+}
Result<std::shared_ptr<arrow::Field>>
InternalReadContext::AlignReadFieldWithTableFieldIds(
const std::shared_ptr<arrow::Field>& read_field,
diff --git a/src/paimon/core/operation/internal_read_context.h
b/src/paimon/core/operation/internal_read_context.h
index d3601fdf..9de415d5 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<CatalogContext> CreateDependencyCatalogContext() const;
+
const std::vector<std::string>& 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 ca7b79dc..6e4957f4 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<LocalFileSystem>(),
read_context->GetPath());
+ SchemaManager schema_manager(std::make_shared<LocalFileSystem>(), 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<MockVersionManagedCatalog>();
+ catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
+ const std::map<std::string, std::string> 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<std::string, std::string> 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<ReadContext> read_context,
+ builder.WithCatalog(catalog, Identifier("db1",
"t1")).SetOptions(read_options).Finish());
std::map<std::string, std::string> 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<LocalFileSystem>(), path);
+ ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0));
+ auto catalog = std::make_shared<MockVersionManagedCatalog>();
+ catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
+ struct HeaderCase {
+ const char* catalog_header;
+ const char* read_header;
+ const char* expected_header;
+ };
+ const std::vector<HeaderCase> cases = {{nullptr, nullptr, "table-view"},
+ {"catalog-view", nullptr,
"catalog-view"},
+ {"catalog-view", "explicit-view",
"explicit-view"}};
+ for (const auto& test_case : cases) {
+ std::map<std::string, std::string> 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<std::string, std::string> 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<ReadContext> 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<LocalFileSystem>(), 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<MetastoreCase> 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<MockVersionManagedCatalog>();
+ catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
+ std::map<std::string, std::string> 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<std::string, std::string> 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<ReadContext> 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<LocalFileSystem>(), 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<LocalFileSystem>(), path);
+ ASSERT_OK_AND_ASSIGN(auto table_schema, schema_manager.ReadSchema(0));
+ auto catalog = std::make_shared<MockVersionManagedCatalog>();
+ catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
+ 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 d731c4d3..bc65b466 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<std::string, std::string>&
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<rapidjson::StringBuffer> writer(buffer);
- document.Accept(writer);
- return buffer.GetString();
-}
-
-} // namespace
-
ReadContext::ReadContext(
const std::string& path, const std::string& branch,
const std::vector<std::string>& read_field_names, const
std::vector<int32_t>& read_field_ids,
@@ -474,23 +428,6 @@ Result<std::unique_ptr<ReadContext>>
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<ReadContext>(
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 d9079e70..4fb5e00b 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<MockVersionManagedCatalog>();
- catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
- 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<std::string, std::string>& 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<MockVersionManagedCatalog>();
catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
- 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<MockVersionManagedCatalog>();
- catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
- 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<MockVersionManagedCatalog>();
- catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
- 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<std::string, std::string> 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<MockVersionManagedCatalog>();
- catalog->SetTableFileSystem(std::make_shared<MockFileSystem>());
- 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 31e3f74f..ead31b17 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 <utility>
#include <vector>
+#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<std::string, std::string> table_options = {
+ {Options::FILE_FORMAT, "orc"}, {Options::BUCKET, "-1"},
{read_via_option, "table-view"}};
+ ASSERT_OK_AND_ASSIGN(
+ std::unique_ptr<TableSchema> 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<ReadContext> read_context,
builder.Finish());
+ // Exercise the actual table/read merge before creating the dependency
context.
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<TableRead> table_read,
+ TableRead::Create(std::move(read_context)));
+ auto* append_only_table_read =
dynamic_cast<AppendOnlyTableRead*>(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 eabb28a7..4cbfda4d 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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);