fix(dameng): preserve indexes when rebuild fails

Closes #6002
This commit is contained in:
t8y2 committed 2026-08-12 13:48:29 +00:00
1 parent 2517307801
commit 6a88df5500
3 files changed
+204 -9

No files matched your search

@@ -182,6 +182,7 @@ pub fn build_create_table_sql(mut options: TableStructureSqlOptions) -> TableStr
&mut warnings,
options.schema.as_deref(),
&options.table_name,
false,
));
}
@@ -47,13 +47,17 @@ pub(super) fn build_index_sql(options: &TableStructureSqlOptions, warnings: &mut
warnings.push(format!("Primary index \"{}\" cannot be edited from this editor.", original.name));
continue;
}
statements.push(build_drop_index_sql(
options.database_type,
dialect,
&table,
options.schema.as_deref(),
&original.name,
));
let or_replace =
options.database_type == Some(DatabaseType::Dameng) && clean(&index.name) == clean(&original.name);
if !or_replace {
statements.push(build_drop_index_sql(
options.database_type,
dialect,
&table,
options.schema.as_deref(),
&original.name,
));
}
statements.extend(build_create_index_statements(
dialect,
&table,
@@ -61,6 +65,7 @@ pub(super) fn build_index_sql(options: &TableStructureSqlOptions, warnings: &mut
warnings,
options.schema.as_deref(),
&options.table_name,
or_replace,
));
continue;
}
@@ -76,6 +81,7 @@ pub(super) fn build_index_sql(options: &TableStructureSqlOptions, warnings: &mut
warnings,
options.schema.as_deref(),
&options.table_name,
false,
));
}
@@ -161,6 +167,7 @@ pub(super) fn build_create_index_statements(
warnings: &mut Vec<String>,
schema: Option<&str>,
table_name: &str,
or_replace: bool,
) -> Vec<String> {
let capabilities = capabilities_for(database_type_for_dialect(dialect));
let name = clean(&index.name);
@@ -171,6 +178,7 @@ pub(super) fn build_create_index_statements(
}
let unique = if index.is_unique { "UNIQUE " } else { "" };
let replace = if or_replace { "OR REPLACE " } else { "" };
let cols = columns
.iter()
.map(|column| {
@@ -236,11 +244,11 @@ pub(super) fn build_create_index_statements(
if dialect == StructureDialect::Sqlite { quote_ident(dialect, table_name) } else { table.to_string() };
let create_sql = if dialect == StructureDialect::Postgres {
format!(
"CREATE {unique}{type_prefix}INDEX {index_name} ON {create_table}{using_clause} ({cols}){include_clause}{where_clause};"
"CREATE {replace}{unique}{type_prefix}INDEX {index_name} ON {create_table}{using_clause} ({cols}){include_clause}{where_clause};"
)
} else {
format!(
"CREATE {unique}{type_prefix}INDEX {index_name}{using_clause} ON {create_table} ({cols}){include_clause}{where_clause}{comment_clause};"
"CREATE {replace}{unique}{type_prefix}INDEX {index_name}{using_clause} ON {create_table} ({cols}){include_clause}{where_clause}{comment_clause};"
)
};
let mut statements = vec![create_sql];
@@ -78,6 +78,40 @@ fn index(name: &str, columns: &[&str]) -> EditableStructureIndex {
}
}
fn existing_index(name: &str, columns: &[&str], is_unique: bool) -> EditableStructureIndex {
let mut index = index(name, columns);
index.is_unique = is_unique;
index.original = Some(IndexInfo {
name: name.to_string(),
columns: columns.iter().map(|column| column.to_string()).collect(),
is_unique,
is_primary: false,
filter: None,
index_type: None,
included_columns: None,
comment: None,
});
index
}
fn index_change_options(
database_type: DatabaseType,
schema: Option<&str>,
index: EditableStructureIndex,
) -> TableStructureSqlOptions {
TableStructureSqlOptions {
database_type: Some(database_type),
schema: schema.map(str::to_string),
table_name: "USERS".to_string(),
columns: Vec::new(),
indexes: vec![index],
foreign_keys: Vec::new(),
triggers: Vec::new(),
table_comment: None,
original_table_comment: None,
}
}
fn foreign_key(name: &str, column: &str, ref_table: &str, ref_column: &str) -> EditableStructureForeignKey {
EditableStructureForeignKey {
id: name.to_string(),
@@ -163,6 +197,158 @@ fn builds_mysql_column_and_index_changes() {
);
}
#[test]
fn dameng_replaces_same_name_index_before_validating_uniqueness() {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
changed.name = " IDX_USERS_EMAIL ".to_string();
changed.is_unique = true;
let mut options = index_change_options(DatabaseType::Dameng, Some("SYSDBA"), changed);
options.table_name = "DBX_6002_USERS".to_string();
let result = build_table_structure_change_sql(options);
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec!["CREATE OR REPLACE UNIQUE INDEX \"IDX_USERS_EMAIL\" ON \"SYSDBA\".\"DBX_6002_USERS\" (\"EMAIL\");"]
);
}
#[test]
fn dameng_replaces_same_name_unique_index_with_normal_index() {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], true);
changed.is_unique = false;
let result = build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), changed));
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec!["CREATE OR REPLACE INDEX \"IDX_USERS_EMAIL\" ON \"APP\".\"USERS\" (\"EMAIL\");"]
);
}
#[test]
fn dameng_replaces_same_name_index_when_columns_change() {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
changed.columns = vec!["LOGIN".to_string(), "EMAIL".to_string()];
let result = build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), changed));
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec!["CREATE OR REPLACE INDEX \"IDX_USERS_EMAIL\" ON \"APP\".\"USERS\" (\"LOGIN\", \"EMAIL\");"]
);
}
#[test]
fn dameng_replaces_same_name_index_when_type_changes_to_bitmap() {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
changed.index_type = "bitmap".to_string();
let result = build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), changed));
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec!["CREATE OR REPLACE BITMAP INDEX \"IDX_USERS_EMAIL\" ON \"APP\".\"USERS\" (\"EMAIL\");"]
);
}
#[test]
fn dameng_replaces_same_name_index_without_unsupported_comment_clause() {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
changed.comment = "New comment".to_string();
changed.original.as_mut().unwrap().comment = Some("Old comment".to_string());
let result = build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), changed));
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec!["CREATE OR REPLACE INDEX \"IDX_USERS_EMAIL\" ON \"APP\".\"USERS\" (\"EMAIL\");"]
);
}
#[test]
fn dameng_renamed_index_keeps_drop_then_create_path() {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
changed.name = "IDX_USERS_LOGIN".to_string();
let result = build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), changed));
assert_eq!(result.warnings, Vec::<String>::new());
assert_eq!(
result.statements,
vec![
"DROP INDEX \"APP\".\"IDX_USERS_EMAIL\";",
"CREATE INDEX \"IDX_USERS_LOGIN\" ON \"APP\".\"USERS\" (\"EMAIL\");",
]
);
}
#[test]
fn dameng_primary_and_unchanged_indexes_keep_existing_behavior() {
let unchanged = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
let unchanged_result =
build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), unchanged));
assert_eq!(unchanged_result.warnings, Vec::<String>::new());
assert!(unchanged_result.statements.is_empty());
let mut primary = existing_index("PK_USERS", &["ID"], true);
primary.columns.push("TENANT_ID".to_string());
primary.original.as_mut().unwrap().is_primary = true;
let primary_result =
build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), primary));
assert!(primary_result.statements.is_empty());
assert_eq!(primary_result.warnings, vec!["Primary index \"PK_USERS\" cannot be edited from this editor."]);
}
#[test]
fn dameng_new_and_dropped_indexes_do_not_use_or_replace() {
let new_index = index("IDX_USERS_LOGIN", &["LOGIN"]);
let new_result =
build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), new_index));
assert_eq!(new_result.warnings, Vec::<String>::new());
assert_eq!(new_result.statements, vec!["CREATE INDEX \"IDX_USERS_LOGIN\" ON \"APP\".\"USERS\" (\"LOGIN\");"]);
let mut dropped = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
dropped.marked_for_drop = true;
let drop_result =
build_table_structure_change_sql(index_change_options(DatabaseType::Dameng, Some("APP"), dropped));
assert_eq!(drop_result.warnings, Vec::<String>::new());
assert_eq!(drop_result.statements, vec!["DROP INDEX \"APP\".\"IDX_USERS_EMAIL\";"]);
}
#[test]
fn non_dameng_index_rebuilds_do_not_use_or_replace() {
for database_type in [
DatabaseType::Mysql,
DatabaseType::Postgres,
DatabaseType::Sqlite,
DatabaseType::SqlServer,
DatabaseType::Oracle,
DatabaseType::Oscar,
DatabaseType::H2,
DatabaseType::Informix,
DatabaseType::Iris,
] {
let mut changed = existing_index("IDX_USERS_EMAIL", &["EMAIL"], false);
changed.is_unique = true;
let result = build_table_structure_change_sql(index_change_options(database_type, None, changed));
assert_eq!(result.warnings, Vec::<String>::new(), "database type: {database_type:?}");
assert_eq!(result.statements.len(), 2, "database type: {database_type:?}");
assert!(result.statements[0].starts_with("DROP INDEX "), "database type: {database_type:?}");
assert!(result.statements[1].starts_with("CREATE UNIQUE INDEX "), "database type: {database_type:?}");
assert!(
result.statements.iter().all(|statement| !statement.contains("OR REPLACE")),
"database type: {database_type:?}"
);
}
}
#[test]
fn builds_xugu_type_change_with_native_syntax() {
let mut code = column("code");