This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 819d5a58 fix: support REST branches with distinct identities (#1109)
819d5a58 is described below
commit 819d5a5865b3169ebdec5aafa4ad6e273d0d8cd6
Author: shyjsarah <[email protected]>
AuthorDate: Mon Oct 12 23:30:37 2026 +0800
fix: support REST branches with distinct identities (#1109)
---
crates/paimon/src/table/mod.rs | 12 ++++++------
crates/paimon/src/table/rest_env.rs | 24 ++++++++++++++++++++++--
crates/paimon/tests/rest_catalog_test.rs | 25 +++++++++++++------------
3 files changed, 41 insertions(+), 20 deletions(-)
diff --git a/crates/paimon/src/table/mod.rs b/crates/paimon/src/table/mod.rs
index d1bb0057..ff1db75b 100644
--- a/crates/paimon/src/table/mod.rs
+++ b/crates/paimon/src/table/mod.rs
@@ -903,12 +903,12 @@ impl Table {
// Catalog branches may have no schema directory in the data store.
// Load their schema, identity and FileIO through the same catalog.
let identifier = env.identifier().with_branch(&branch)?;
- let mut table = env.get_table(&identifier).await?;
- if table.location() != self.location()
- || table
- .rest_env
- .as_ref()
- .is_none_or(|branch_env| branch_env.uuid() != env.uuid())
+ let (current, mut table) = tokio::try_join!(
+ env.current_table_identity_checked(),
+ env.get_table(&identifier)
+ )?;
+ if current.path.as_deref() != Some(self.location())
+ || table.location() != self.location()
{
return Err(crate::Error::DataInvalid {
message: format!(
diff --git a/crates/paimon/src/table/rest_env.rs
b/crates/paimon/src/table/rest_env.rs
index b8cdc314..dfb4b3c7 100644
--- a/crates/paimon/src/table/rest_env.rs
+++ b/crates/paimon/src/table/rest_env.rs
@@ -155,7 +155,7 @@ impl RESTEnv {
schema_id: i64,
fields: &[crate::spec::DataField],
) -> Result<GetTableResponse> {
- let response = self.api.get_table(&self.identifier).await?;
+ let response = self.current_table_identity_checked().await?;
let name = self.identifier.full_name();
let same = |what: &str, loaded: String, now: Option<String>| match now
{
Some(now) if now == loaded => Ok(()),
@@ -168,7 +168,6 @@ impl RESTEnv {
source: None,
}),
};
- same("uuid", self.uuid.clone(), response.id.clone())?;
same(
"schema",
schema_id.to_string(),
@@ -194,6 +193,27 @@ impl RESTEnv {
Ok(response)
}
+ /// Refused unless the identifier still resolves to this catalog entity.
+ pub(crate) async fn current_table_identity_checked(&self) ->
Result<GetTableResponse> {
+ let response = self.api.get_table(&self.identifier).await?;
+ let name = self.identifier.full_name();
+ match response.id.as_deref() {
+ Some(uuid) if uuid == self.uuid => {}
+ uuid => {
+ return Err(crate::Error::DataInvalid {
+ message: format!(
+ "table '{name}' now resolves to uuid {}, not the {}
this handle was loaded \
+ with; re-load the table before reading it",
+ uuid.unwrap_or("nothing the server reports"),
+ self.uuid
+ ),
+ source: None,
+ });
+ }
+ }
+ Ok(response)
+ }
+
/// Get the table identifier.
pub fn identifier(&self) -> &Identifier {
&self.identifier
diff --git a/crates/paimon/tests/rest_catalog_test.rs
b/crates/paimon/tests/rest_catalog_test.rs
index d63a66c4..3d599167 100644
--- a/crates/paimon/tests/rest_catalog_test.rs
+++ b/crates/paimon/tests/rest_catalog_test.rs
@@ -5437,7 +5437,7 @@ async fn test_rest_catalog_manages_policies_end_to_end() {
}
#[tokio::test]
-async fn test_branch_write_copy_loads_catalog_schema_without_schema_files() {
+async fn test_branch_write_copy_loads_catalog_schema_with_distinct_identity() {
let ctx = setup_catalog(vec!["default"]).await;
let path = "memory:/branch-write";
ctx.server
@@ -5452,7 +5452,8 @@ async fn
test_branch_write_copy_loads_catalog_schema_without_schema_files() {
.add_table_with_schema("default", "t$branch_dev",
branch_schema.clone(), path);
ctx.server
.set_table_schema_id("default", "t$branch_dev", branch_schema, 7);
- ctx.server.set_table_uuid("default", "t$branch_dev", "t");
+ ctx.server
+ .set_table_uuid("default", "t$branch_dev", "branch-t");
let main = ctx
.catalog
.get_table(&Identifier::new("default", "t"))
@@ -5490,27 +5491,26 @@ async fn
test_branch_write_copy_loads_catalog_schema_without_schema_files() {
}
#[tokio::test]
-async fn test_branch_write_copy_rejects_replaced_identity_or_location() {
+async fn test_branch_write_copy_rejects_replaced_source_identity_or_location()
{
let ctx = setup_catalog(vec!["default"]).await;
let path = "memory:/branch-identity";
ctx.server
.add_table_with_schema("default", "t", test_schema(), path);
ctx.server
.add_table_with_schema("default", "t$branch_dev", test_schema(), path);
- ctx.server.set_table_uuid("default", "t$branch_dev", "t");
+ ctx.server
+ .set_table_uuid("default", "t$branch_dev", "branch-t");
let main = ctx
.catalog
.get_table(&Identifier::new("default", "t"))
.await
.unwrap();
- main.copy_with_branch("dev").await.unwrap();
+ let branch = main.copy_with_branch("dev").await.unwrap();
ctx.server
- .set_table_uuid("default", "t$branch_dev", "recreated-table");
- let error = main.copy_with_branch("dev").await.unwrap_err();
+ .set_table_uuid("default", "t$branch_dev", "recreated-branch");
+ let error = branch.copy_with_branch("main").await.unwrap_err();
assert!(
- error
- .to_string()
- .contains("no longer belongs to the loaded table"),
+ error.to_string().contains("now resolves to uuid"),
"{error}"
);
ctx.server.add_table_with_schema(
@@ -5519,8 +5519,9 @@ async fn
test_branch_write_copy_rejects_replaced_identity_or_location() {
test_schema(),
"memory:/replacement",
);
- ctx.server.set_table_uuid("default", "t$branch_dev", "t");
- let error = main.copy_with_branch("dev").await.unwrap_err();
+ ctx.server
+ .set_table_uuid("default", "t$branch_dev", "branch-t");
+ let error = branch.copy_with_branch("main").await.unwrap_err();
assert!(
error
.to_string()