fix(structure): support postgres-like foreign keys
This commit is contained in:
1 parent
545d203b59
commit
a643cd74dc
5 files changed
+79
-22
No files matched your search
@@ -34,6 +34,7 @@ pub(super) struct TableStructureCapabilities {
|
||||
pub(super) index_filter: bool,
|
||||
pub(super) index_comment: bool,
|
||||
pub(super) alter_primary_key: bool,
|
||||
pub(super) foreign_key: bool,
|
||||
}
|
||||
|
||||
impl Default for TableStructureCapabilities {
|
||||
@@ -54,6 +55,7 @@ impl Default for TableStructureCapabilities {
|
||||
index_filter: false,
|
||||
index_comment: false,
|
||||
alter_primary_key: false,
|
||||
foreign_key: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +84,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
index_type: true,
|
||||
index_comment: true,
|
||||
alter_primary_key: true,
|
||||
foreign_key: true,
|
||||
..base
|
||||
},
|
||||
Some(DatabaseType::Gbase) => TableStructureCapabilities {
|
||||
@@ -115,6 +118,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
index_filter: true,
|
||||
index_comment: true,
|
||||
alter_primary_key: true,
|
||||
foreign_key: true,
|
||||
..base
|
||||
},
|
||||
Some(DatabaseType::Questdb) => TableStructureCapabilities {
|
||||
@@ -133,6 +137,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
index_filter: false,
|
||||
index_comment: false,
|
||||
alter_primary_key: false,
|
||||
foreign_key: false,
|
||||
},
|
||||
Some(DatabaseType::Redshift | DatabaseType::Vertica) => TableStructureCapabilities {
|
||||
dialect: StructureDialect::Postgres,
|
||||
|
||||
@@ -8,18 +8,20 @@ pub(super) fn build_foreign_key_sql(options: &TableStructureSqlOptions, warnings
|
||||
}
|
||||
|
||||
let database_label = database_label(options.database_type);
|
||||
if super::dialect::capabilities_for(options.database_type).dialect != StructureDialect::Mysql {
|
||||
let capabilities = super::dialect::capabilities_for(options.database_type);
|
||||
let dialect = capabilities.dialect;
|
||||
if !capabilities.foreign_key {
|
||||
warnings.push(format!("Editing foreign keys is not supported for {database_label} from this editor."));
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let table = qualified_table(StructureDialect::Mysql, options.schema.as_deref(), &options.table_name);
|
||||
let table = qualified_table(dialect, options.schema.as_deref(), &options.table_name);
|
||||
let mut statements = Vec::new();
|
||||
|
||||
for foreign_key in &options.foreign_keys {
|
||||
if foreign_key.marked_for_drop {
|
||||
if let Some(original) = &foreign_key.original {
|
||||
statements.push(drop_foreign_key_sql(&table, &original.name));
|
||||
statements.push(drop_foreign_key_sql(dialect, &table, &original.name));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -28,10 +30,10 @@ pub(super) fn build_foreign_key_sql(options: &TableStructureSqlOptions, warnings
|
||||
if !has_foreign_key_change(foreign_key, original) {
|
||||
continue;
|
||||
}
|
||||
statements.push(drop_foreign_key_sql(&table, &original.name));
|
||||
statements.push(drop_foreign_key_sql(dialect, &table, &original.name));
|
||||
}
|
||||
|
||||
if let Some(sql) = create_foreign_key_sql(&table, foreign_key, warnings) {
|
||||
if let Some(sql) = create_foreign_key_sql(dialect, &table, foreign_key, warnings) {
|
||||
statements.push(sql);
|
||||
}
|
||||
}
|
||||
@@ -49,11 +51,16 @@ fn has_foreign_key_change(foreign_key: &EditableStructureForeignKey, original: &
|
||||
|| normalize_action(&foreign_key.on_delete) != normalize_action(original.on_delete.as_deref().unwrap_or(""))
|
||||
}
|
||||
|
||||
fn drop_foreign_key_sql(table: &str, name: &str) -> String {
|
||||
format!("ALTER TABLE {table} DROP FOREIGN KEY {};", quote_ident(StructureDialect::Mysql, name))
|
||||
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)),
|
||||
_ => unreachable!("foreign key SQL requested for unsupported dialect"),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_foreign_key_sql(
|
||||
dialect: StructureDialect,
|
||||
table: &str,
|
||||
foreign_key: &EditableStructureForeignKey,
|
||||
warnings: &mut Vec<String>,
|
||||
@@ -72,20 +79,16 @@ fn create_foreign_key_sql(
|
||||
}
|
||||
|
||||
let ref_target = if clean(&foreign_key.ref_schema).is_empty() {
|
||||
quote_ident(StructureDialect::Mysql, &ref_table)
|
||||
quote_ident(dialect, &ref_table)
|
||||
} else {
|
||||
format!(
|
||||
"{}.{}",
|
||||
quote_ident(StructureDialect::Mysql, &foreign_key.ref_schema),
|
||||
quote_ident(StructureDialect::Mysql, &ref_table)
|
||||
)
|
||||
format!("{}.{}", quote_ident(dialect, &foreign_key.ref_schema), quote_ident(dialect, &ref_table))
|
||||
};
|
||||
|
||||
let mut sql = format!(
|
||||
"ALTER TABLE {table} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {ref_target} ({}",
|
||||
quote_ident(StructureDialect::Mysql, &name),
|
||||
columns.iter().map(|column| quote_ident(StructureDialect::Mysql, column)).collect::<Vec<_>>().join(", "),
|
||||
ref_columns.iter().map(|column| quote_ident(StructureDialect::Mysql, column)).collect::<Vec<_>>().join(", ")
|
||||
quote_ident(dialect, &name),
|
||||
columns.iter().map(|column| quote_ident(dialect, column)).collect::<Vec<_>>().join(", "),
|
||||
ref_columns.iter().map(|column| quote_ident(dialect, column)).collect::<Vec<_>>().join(", ")
|
||||
);
|
||||
sql.push(')');
|
||||
|
||||
|
||||
@@ -117,6 +117,45 @@ fn builds_mysql_column_and_index_changes() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_highgo_foreign_key_changes_with_postgres_syntax() {
|
||||
let mut old_fk = foreign_key("orders_user_id_fkey", "user_id", "users", "id");
|
||||
old_fk.marked_for_drop = true;
|
||||
old_fk.original = Some(ForeignKeyInfo {
|
||||
name: "orders_user_id_fkey".to_string(),
|
||||
column: "user_id".to_string(),
|
||||
ref_schema: Some("public".to_string()),
|
||||
ref_table: "users".to_string(),
|
||||
ref_column: "id".to_string(),
|
||||
on_update: None,
|
||||
on_delete: None,
|
||||
});
|
||||
let mut new_fk = foreign_key("orders_account_id_fkey", "account_id", "accounts", "id");
|
||||
new_fk.ref_schema = "crm".to_string();
|
||||
new_fk.on_delete = "CASCADE".to_string();
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Highgo),
|
||||
schema: Some("public".to_string()),
|
||||
table_name: "orders".to_string(),
|
||||
columns: Vec::new(),
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: vec![old_fk, new_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 \"public\".\"orders\" DROP CONSTRAINT \"orders_user_id_fkey\";",
|
||||
"ALTER TABLE \"public\".\"orders\" ADD CONSTRAINT \"orders_account_id_fkey\" FOREIGN KEY (\"account_id\") REFERENCES \"crm\".\"accounts\" (\"id\") ON DELETE CASCADE;",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_informix_column_and_index_changes() {
|
||||
let mut renamed = column("display_name");
|
||||
|
||||
Reference in new issue
Block a user