From 137a1a416176993d30243cb970beb73a392be59c Mon Sep 17 00:00:00 2001 From: songpaopao <41714029+songpaopao@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:18:01 +0800 Subject: [PATCH] fix(transfer): rewrite Oracle-family owner schema in reused DDL --- crates/dbx-core/src/transfer.rs | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/crates/dbx-core/src/transfer.rs b/crates/dbx-core/src/transfer.rs index 65cf6d767e..4c10079b11 100644 --- a/crates/dbx-core/src/transfer.rs +++ b/crates/dbx-core/src/transfer.rs @@ -1385,6 +1385,13 @@ fn is_postgres_compat_transfer(source_db: &DatabaseType, target_db: &DatabaseTyp is_postgres_transfer_dialect(source_db) && is_postgres_transfer_dialect(target_db) } +/// Oracle-family transfer dialects that return `DBMS_METADATA`-style owner-qualified +/// DDL. Dameng is deliberately excluded: its reuse path strips storage clauses as well, +/// so it keeps its own branch above. +fn is_oracle_family_transfer_target(db_type: &DatabaseType) -> bool { + matches!(db_type, DatabaseType::Oracle | DatabaseType::OceanbaseOracle) +} + fn is_postgres_transfer_dialect(db_type: &DatabaseType) -> bool { // KingbaseES supports the PostgreSQL DDL, type, and ON CONFLICT paths used by transfer; // other PG-wire databases stay opt-in until their transfer behavior is verified. @@ -4368,6 +4375,15 @@ fn rewrite_transfer_source_table_ddl( let source_schema = if source_schema.trim().is_empty() { "PUBLIC" } else { source_schema }; let target_schema = if target_schema.trim().is_empty() { "PUBLIC" } else { target_schema }; Some(rewrite_h2_schema_qualifier(sql, source_schema, target_schema)) + } else if is_oracle_family_transfer_target(source_db_type) && is_oracle_family_transfer_target(target_db_type) { + // Oracle-family sources return `DBMS_METADATA`-style DDL whose CREATE TABLE head + // carries the owner schema (`"SALES"."T"`). Unlike the Oracle schema-object path + // this reuse path never rewrote it, so transferring into a differently named + // schema kept the source qualifier and the target rejected it with + // `ORA-00942`/`OBE-00600 ... Unknown database` (or silently created the table in + // the source schema when it existed there). Rewrite only the quoted qualifier so + // string literals and comments keep their original text. + Some(rewrite_double_quoted_schema_qualifier(sql, source_schema, target_schema)) } else if is_mysql_family_target(source_db_type) && is_mysql_family_target(target_db_type) { // The reused SHOW CREATE TABLE DDL carries the source table name; rewrite the // CREATE TABLE header when the transfer renames the table (name case conversion). @@ -13563,6 +13579,39 @@ PARTITION p_old VALUES LESS THAN (TO_DAYS('2026-01-01')))"; ); } + #[test] + fn oracle_transfer_reused_table_ddl_rewrites_schema_qualifier() { + let ddl = concat!( + "CREATE TABLE \"SALES\".\"BASEDATA_T_BANKLOCATIONS\" (\n", + "\"ID\" NUMBER(19, 0) NOT NULL,\n", + "\"NOTE\" VARCHAR2(100) DEFAULT '\"SALES\".literal',\n", + "PRIMARY KEY (\"ID\"));\n", + "COMMENT ON TABLE \"SALES\".\"BASEDATA_T_BANKLOCATIONS\" IS 'keep \"SALES\".comment';\n", + "-- keep \"SALES\".line_comment\n", + "/* keep \"SALES\".block_comment */", + ); + + let rewritten = rewrite_transfer_source_table_ddl( + ddl, + "SALES", + "ANALYTICS", + &DatabaseType::OceanbaseOracle, + &DatabaseType::OceanbaseOracle, + "BASEDATA_T_BANKLOCATIONS", + "BASEDATA_T_BANKLOCATIONS", + ) + .unwrap(); + + assert!(rewritten.contains("CREATE TABLE \"ANALYTICS\".\"BASEDATA_T_BANKLOCATIONS\"")); + assert!(rewritten.contains("COMMENT ON TABLE \"ANALYTICS\".\"BASEDATA_T_BANKLOCATIONS\"")); + assert!(!rewritten.contains("\"SALES\".\"BASEDATA_T_BANKLOCATIONS\"")); + // Literals and comments keep the source qualifier untouched. + assert!(rewritten.contains("'\"SALES\".literal'")); + assert!(rewritten.contains("'keep \"SALES\".comment'")); + assert!(rewritten.contains("-- keep \"SALES\".line_comment")); + assert!(rewritten.contains("/* keep \"SALES\".block_comment */")); + } + #[test] fn hive_create_table_uses_hive_friendly_columns() { let cols = vec![