fix(transfer): preserve NUMBER(p,s) precision for oracle-family targets

This commit is contained in:
zipg authored and GitHub committed 2026-09-21 01:07:08 +08:00
1 parent 21833a9c8e
commit 29470b8b3f
5 files changed
+64 -6

No files matched your search

+64 -6
View File
@@ -3354,13 +3354,22 @@ pub fn map_column_type(source_type: &str, source_db: &DatabaseType, target_db: &
},
"decimal" | "numeric" | "number" => {
if t.contains('(') {
let params = &t[t.find('(').unwrap()..];
match target_db {
DatabaseType::Mysql | DatabaseType::SqlServer | DatabaseType::Oracle | DatabaseType::H2 => {
format!("DECIMAL{}", &t[t.find('(').unwrap()..])
}
target_db if is_postgres_transfer_dialect(target_db) => {
format!("DECIMAL{}", &t[t.find('(').unwrap()..])
}
// Oracle-family targets accept DECIMAL(p, s) as a synonym for
// NUMBER(p, s). OceanBase's Oracle mode and Dameng used to fall into
// the bare-NUMERIC fallback below, which is NUMBER(38, 0): transferring
// Oracle NUMBER(14, 2) silently dropped every decimal (#9667).
DatabaseType::Mysql
| DatabaseType::SqlServer
| DatabaseType::Oracle
| DatabaseType::OceanbaseOracle
| DatabaseType::Dameng
| DatabaseType::H2 => format!("DECIMAL{params}"),
target_db if is_postgres_transfer_dialect(target_db) => format!("DECIMAL{params}"),
// Every other target keeps the historical bare spelling: its decimal
// semantics are not the Oracle NUMBER synonym, so passing (p, s) through
// needs per-engine verification and stays out of scope here.
_ => "NUMERIC".into(),
}
} else {
@@ -16333,6 +16342,55 @@ SELECT 1 FROM dual"#
assert_eq!(map_column_type("NVARCHAR2(50 CHAR)", &DatabaseType::Oracle, &DatabaseType::Mysql), "TEXT");
}
#[test]
fn map_column_type_keeps_number_precision_for_oracle_family_targets() {
// Issue #9667: transferring an Oracle table to OceanBase(Oracle mode) generated a
// bare `NUMERIC` for every NUMBER(p, s) column. In Oracle-compatible semantics that
// is NUMBER(38, 0), so the target silently dropped every decimal (only the integer
// part survived). Oracle-family targets must keep precision and scale.
for target in [DatabaseType::OceanbaseOracle, DatabaseType::Dameng] {
assert_eq!(map_column_type("NUMBER(14,2)", &DatabaseType::Oracle, &target), "DECIMAL(14,2)");
assert_eq!(map_column_type("NUMBER(14,10)", &DatabaseType::Oracle, &target), "DECIMAL(14,10)");
assert_eq!(map_column_type("NUMBER(15,0)", &DatabaseType::Oracle, &target), "DECIMAL(15,0)");
assert_eq!(map_column_type("NUMBER(12,4)", &DatabaseType::Oracle, &target), "DECIMAL(12,4)");
assert_eq!(map_column_type("NUMBER(10,-2)", &DatabaseType::Oracle, &target), "DECIMAL(10,-2)");
// Spacing inside the parameter list is preserved verbatim, exactly like the
// existing Mysql/H2/Oracle branches.
assert_eq!(map_column_type("NUMBER(14, 2)", &DatabaseType::Oracle, &target), "DECIMAL(14, 2)");
}
// A source type without parameters keeps the historical bare spelling: it carries
// no precision to lose.
assert_eq!(map_column_type("NUMBER", &DatabaseType::Oracle, &DatabaseType::OceanbaseOracle), "NUMERIC");
assert_eq!(map_column_type("NUMBER", &DatabaseType::Oracle, &DatabaseType::Dameng), "NUMERIC");
}
#[test]
fn transfer_create_table_oracle_to_oceanbase_oracle_keeps_number_scale() {
let columns = vec![
db::ColumnInfo { is_primary_key: true, ..test_column("ID", "NUMBER(10)") },
test_column("SUMZEROTAXPREMIUM", "NUMBER(14,2)"),
test_column("XBCOMPANYRATE", "NUMBER(14,10)"),
test_column("PLAINNUMBER", "NUMBER"),
];
let ddl = generate_create_table_ddl(
&columns,
"TXF_PREC",
"DBX_TEST",
"DBX_TEST",
&DatabaseType::OceanbaseOracle,
&DatabaseType::Oracle,
None,
None,
);
assert!(ddl.contains("\"SUMZEROTAXPREMIUM\" DECIMAL(14,2)"), "{ddl}");
assert!(ddl.contains("\"XBCOMPANYRATE\" DECIMAL(14,10)"), "{ddl}");
assert!(ddl.contains("\"ID\" DECIMAL(10)"), "{ddl}");
assert!(ddl.contains("\"PLAINNUMBER\" NUMERIC"), "{ddl}");
}
#[test]
fn map_column_type_oracle_plain_varchar2_to_mysql() {
// Plain VARCHAR2 without a length unit must keep working unchanged.