mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-22 19:58:51 +00:00
fix(structure): support postgres-like foreign keys
This commit is contained in:
@@ -716,7 +716,7 @@ function canDropIndex(index: EditableStructureIndex): boolean {
|
||||
return !!index.original && !index.isPrimary && structureCapabilities.value.dropIndex;
|
||||
}
|
||||
|
||||
const canEditMysqlForeignKeys = computed(() => structureDialect.value === "mysql");
|
||||
const canEditForeignKeys = computed(() => structureCapabilities.value.foreignKey);
|
||||
const canEditMysqlTriggers = computed(() => structureDialect.value === "mysql");
|
||||
|
||||
function generatedForeignKeyName(column = ""): string {
|
||||
@@ -736,7 +736,7 @@ function generatedForeignKeyName(column = ""): string {
|
||||
}
|
||||
|
||||
function addForeignKey() {
|
||||
if (!canEditMysqlForeignKeys.value) return;
|
||||
if (!canEditForeignKeys.value) return;
|
||||
activeTab.value = "foreignKeys";
|
||||
foreignKeys.value.push({
|
||||
id: `new:${uuid()}`,
|
||||
@@ -761,7 +761,7 @@ function toggleDropForeignKey(foreignKey: EditableStructureForeignKey) {
|
||||
}
|
||||
|
||||
function canEditForeignKeyDraft(foreignKey: EditableStructureForeignKey): boolean {
|
||||
return canEditMysqlForeignKeys.value && !foreignKey.markedForDrop;
|
||||
return canEditForeignKeys.value && !foreignKey.markedForDrop;
|
||||
}
|
||||
|
||||
function addTrigger() {
|
||||
@@ -864,7 +864,7 @@ function addItemForActiveTab(): boolean {
|
||||
addIndex();
|
||||
return true;
|
||||
}
|
||||
if (activeTab.value === "foreignKeys" && canEditMysqlForeignKeys.value) {
|
||||
if (activeTab.value === "foreignKeys" && canEditForeignKeys.value) {
|
||||
addForeignKey();
|
||||
return true;
|
||||
}
|
||||
@@ -1027,7 +1027,7 @@ watch(activeTab, (tab) => {
|
||||
<Plus :class="structureIconClass" />
|
||||
{{ t("structureEditor.addIndex") }}
|
||||
</Button>
|
||||
<Button v-if="activeTab === 'foreignKeys'" size="sm" :class="structureToolbarButtonClass" :disabled="!canEditMysqlForeignKeys" @click="addForeignKey">
|
||||
<Button v-if="activeTab === 'foreignKeys'" size="sm" :class="structureToolbarButtonClass" :disabled="!canEditForeignKeys" @click="addForeignKey">
|
||||
<Plus :class="structureIconClass" />
|
||||
{{ t("structureEditor.addForeignKey") }}
|
||||
</Button>
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface TableStructureCapabilities {
|
||||
indexInclude: boolean;
|
||||
indexFilter: boolean;
|
||||
indexComment: boolean;
|
||||
foreignKey: boolean;
|
||||
}
|
||||
|
||||
const unsupportedCapabilities: TableStructureCapabilities = {
|
||||
@@ -44,6 +45,7 @@ const unsupportedCapabilities: TableStructureCapabilities = {
|
||||
indexInclude: false,
|
||||
indexFilter: false,
|
||||
indexComment: false,
|
||||
foreignKey: false,
|
||||
};
|
||||
|
||||
function capabilities(overrides: Partial<TableStructureCapabilities>): TableStructureCapabilities {
|
||||
@@ -68,6 +70,7 @@ const mysqlCapabilities = capabilities({
|
||||
indexType: true,
|
||||
indexComment: true,
|
||||
alterPrimaryKey: true,
|
||||
foreignKey: true,
|
||||
});
|
||||
|
||||
const gbaseCapabilities = capabilities({
|
||||
@@ -98,6 +101,7 @@ const postgresCapabilities = capabilities({
|
||||
indexFilter: true,
|
||||
indexComment: true,
|
||||
alterPrimaryKey: true,
|
||||
foreignKey: true,
|
||||
});
|
||||
|
||||
const redshiftCapabilities = capabilities({
|
||||
@@ -264,6 +268,12 @@ const questdbCapabilities = capabilities({
|
||||
indexFilter: false,
|
||||
indexComment: false,
|
||||
alterPrimaryKey: false,
|
||||
foreignKey: false,
|
||||
});
|
||||
|
||||
const firebirdCapabilities = capabilities({
|
||||
...postgresCapabilities,
|
||||
foreignKey: false,
|
||||
});
|
||||
|
||||
const capabilityByType: Partial<Record<DatabaseType, TableStructureCapabilities>> = {
|
||||
@@ -284,7 +294,7 @@ const capabilityByType: Partial<Record<DatabaseType, TableStructureCapabilities>
|
||||
highgo: postgresCapabilities,
|
||||
vastbase: postgresCapabilities,
|
||||
kingbase: postgresCapabilities,
|
||||
firebird: postgresCapabilities,
|
||||
firebird: firebirdCapabilities,
|
||||
sqlite: sqliteCapabilities,
|
||||
rqlite: sqliteCapabilities,
|
||||
turso: sqliteCapabilities,
|
||||
|
||||
@@ -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