fix(oracle): preserve table metadata in DDL and clones

Closes #5215
This commit is contained in:
t8y2 committed 2026-08-08 14:34:55 +08:00
1 parent 5b85f9eae8
commit 28b3491843
10 files changed
+745 -65

No files matched your search

@@ -245,7 +245,21 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
index_type: true,
..base
},
Some(DatabaseType::Oracle | DatabaseType::OceanbaseOracle | DatabaseType::Yashandb | DatabaseType::Xugu) => {
Some(DatabaseType::Oracle) => TableStructureCapabilities {
dialect: StructureDialect::Oracle,
add_column: true,
drop_column: true,
rename_column: true,
alter_existing_column: true,
comment: true,
create_index: true,
drop_index: true,
rebuild_index: true,
index_type: true,
foreign_key: true,
..base
},
Some(DatabaseType::OceanbaseOracle | DatabaseType::Yashandb | DatabaseType::Xugu) => {
TableStructureCapabilities {
dialect: StructureDialect::Oracle,
add_column: true,
@@ -79,7 +79,9 @@ fn has_foreign_key_change(foreign_key: &EditableStructureForeignKey, original: &
fn drop_foreign_key_sql(dialect: StructureDialect, table: &str, name: &str) -> String {
match dialect {
StructureDialect::Mysql => format!("ALTER TABLE {table} DROP FOREIGN KEY {};", quote_ident(dialect, name)),
StructureDialect::Postgres => format!("ALTER TABLE {table} DROP CONSTRAINT {};", quote_ident(dialect, name)),
StructureDialect::Postgres | StructureDialect::Oracle => {
format!("ALTER TABLE {table} DROP CONSTRAINT {};", quote_ident(dialect, name))
}
_ => unreachable!("foreign key SQL requested for unsupported dialect"),
}
}
@@ -117,11 +119,11 @@ fn create_foreign_key_sql(
);
sql.push(')');
if let Some(action) = action_clause("ON DELETE", &foreign_key.on_delete, warnings) {
if let Some(action) = action_clause(dialect, "ON DELETE", &foreign_key.on_delete, warnings) {
sql.push(' ');
sql.push_str(&action);
}
if let Some(action) = action_clause("ON UPDATE", &foreign_key.on_update, warnings) {
if let Some(action) = action_clause(dialect, "ON UPDATE", &foreign_key.on_update, warnings) {
sql.push(' ');
sql.push_str(&action);
}
@@ -129,11 +131,27 @@ fn create_foreign_key_sql(
Some(sql)
}
fn action_clause(prefix: &str, value: &str, warnings: &mut Vec<String>) -> Option<String> {
fn action_clause(dialect: StructureDialect, prefix: &str, value: &str, warnings: &mut Vec<String>) -> Option<String> {
let action = normalize_action(value);
if action.is_empty() {
return None;
}
if dialect == StructureDialect::Oracle {
if prefix == "ON UPDATE" {
if action != "NO ACTION" {
warnings.push(format!("Oracle does not support {prefix} {action}."));
}
return None;
}
return match action.as_str() {
"CASCADE" | "SET NULL" => Some(format!("{prefix} {action}")),
"NO ACTION" => None,
_ => {
warnings.push(format!("Unsupported Oracle foreign key action \"{}\".", clean(value)));
None
}
};
}
match action.as_str() {
"CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION" => Some(format!("{prefix} {action}")),
_ => {
@@ -3526,6 +3526,71 @@ fn builds_mysql_composite_foreign_key() {
);
}
#[test]
fn builds_oracle_foreign_key_with_supported_actions() {
let mut customer_id = column("CUSTOMER_ID");
customer_id.data_type = "NUMBER(19)".to_string();
let mut customer_fk = foreign_key("ORDERS_COPY_FK1", "CUSTOMER_ID", "CUSTOMERS", "ID");
customer_fk.ref_schema = "CRM".to_string();
customer_fk.on_update = "NO ACTION".to_string();
customer_fk.on_delete = "CASCADE".to_string();
let result = build_create_table_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Oracle),
schema: Some("HR".to_string()),
table_name: "ORDERS_COPY".to_string(),
columns: vec![customer_id],
indexes: Vec::new(),
foreign_keys: vec![customer_fk],
triggers: Vec::new(),
table_comment: None,
original_table_comment: None,
});
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements[1],
"ALTER TABLE \"HR\".\"ORDERS_COPY\" ADD CONSTRAINT \"ORDERS_COPY_FK1\" FOREIGN KEY (\"CUSTOMER_ID\") REFERENCES \"CRM\".\"CUSTOMERS\" (\"ID\") ON DELETE CASCADE;"
);
}
#[test]
fn builds_oracle_foreign_key_replacement() {
let mut customer_fk = foreign_key("ORDERS_FK1", "CUSTOMER_ID", "CUSTOMERS", "ID");
customer_fk.on_delete = "SET NULL".to_string();
customer_fk.original = Some(ForeignKeyInfo {
name: "ORDERS_FK_OLD".to_string(),
column: "CUSTOMER_ID".to_string(),
ref_schema: Some("CRM".to_string()),
ref_table: "CUSTOMERS".to_string(),
ref_column: "ID".to_string(),
on_update: None,
on_delete: Some("NO ACTION".to_string()),
});
customer_fk.ref_schema = "CRM".to_string();
let result = build_table_structure_change_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Oracle),
schema: Some("HR".to_string()),
table_name: "ORDERS".to_string(),
columns: Vec::new(),
indexes: Vec::new(),
foreign_keys: vec![customer_fk],
triggers: Vec::new(),
table_comment: None,
original_table_comment: None,
});
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec![
"ALTER TABLE \"HR\".\"ORDERS\" DROP CONSTRAINT \"ORDERS_FK_OLD\";",
"ALTER TABLE \"HR\".\"ORDERS\" ADD CONSTRAINT \"ORDERS_FK1\" FOREIGN KEY (\"CUSTOMER_ID\") REFERENCES \"CRM\".\"CUSTOMERS\" (\"ID\") ON DELETE SET NULL;",
]
);
}
#[test]
fn builds_mysql_trigger_changes() {
let mut existing = trigger("orders_bu", "BEFORE", "UPDATE", "BEGIN\n SET NEW.updated_at = NOW();\nEND");