feat(oracle): support adding primary keys to existing tables
Closes #6922
This commit is contained in:
1 parent
946677a579
commit
a1e94d8228
8 files changed
+252
-23
No files matched your search
@@ -346,6 +346,32 @@ fn primary_key_change(options: &TableStructureSqlOptions) -> Option<PrimaryKeyCh
|
||||
Some(PrimaryKeyChange { old_ids, new_ids, new_names })
|
||||
}
|
||||
|
||||
fn unsupported_primary_key_change_warning(options: &TableStructureSqlOptions, change: &PrimaryKeyChange<'_>) -> String {
|
||||
let action = if change.old_ids.is_empty() { "Adding" } else { "Changing" };
|
||||
format!("{action} primary keys is not supported for {} from this editor.", database_label(options.database_type))
|
||||
}
|
||||
|
||||
pub(super) fn validate_primary_key_change_scope(options: &TableStructureSqlOptions) -> Vec<String> {
|
||||
if let Some(original) = options
|
||||
.columns
|
||||
.iter()
|
||||
.find(|column| column.marked_for_drop && was_primary_key_column(column))
|
||||
.and_then(|column| column.original.as_ref())
|
||||
{
|
||||
return vec![format!("Primary key column \"{}\" cannot be dropped from this editor.", original.name)];
|
||||
}
|
||||
|
||||
let Some(change) = primary_key_change(options) else { return Vec::new() };
|
||||
let capabilities = capabilities_for(options.database_type);
|
||||
let supported =
|
||||
if change.old_ids.is_empty() { capabilities.add_primary_key } else { capabilities.alter_primary_key };
|
||||
if supported {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![unsupported_primary_key_change_warning(options, &change)]
|
||||
}
|
||||
}
|
||||
|
||||
fn mysql_auto_increment_touches_primary_key(column: &EditableStructureColumn, change: &PrimaryKeyChange<'_>) -> bool {
|
||||
column.extra.as_ref().is_some_and(|extra| extra.auto_increment.unwrap_or(false))
|
||||
&& (change.old_ids.contains(column.id.as_str()) || change.new_ids.contains(column.id.as_str()))
|
||||
@@ -363,11 +389,10 @@ pub(super) fn build_primary_key_sql(
|
||||
// columns is not a PK change. A draft that drops a PK column is also rejected here.
|
||||
let Some(change) = primary_key_change(options) else { return Vec::new() };
|
||||
|
||||
if !capabilities.alter_primary_key {
|
||||
warnings.push(format!(
|
||||
"Changing primary keys is not supported for {} from this editor.",
|
||||
database_label(options.database_type)
|
||||
));
|
||||
let supported =
|
||||
if change.old_ids.is_empty() { capabilities.add_primary_key } else { capabilities.alter_primary_key };
|
||||
if !supported {
|
||||
warnings.push(unsupported_primary_key_change_warning(options, &change));
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ pub(super) struct TableStructureCapabilities {
|
||||
pub(super) index_filter: bool,
|
||||
pub(super) index_comment: bool,
|
||||
pub(super) index_concurrent: bool,
|
||||
pub(super) add_primary_key: bool,
|
||||
pub(super) alter_primary_key: bool,
|
||||
pub(super) foreign_key: bool,
|
||||
}
|
||||
@@ -65,6 +66,7 @@ impl Default for TableStructureCapabilities {
|
||||
index_filter: false,
|
||||
index_comment: false,
|
||||
index_concurrent: false,
|
||||
add_primary_key: false,
|
||||
alter_primary_key: false,
|
||||
foreign_key: false,
|
||||
}
|
||||
@@ -85,6 +87,7 @@ pub(super) fn gaussdb_m_capabilities() -> TableStructureCapabilities {
|
||||
rebuild_index: true,
|
||||
index_type: true,
|
||||
index_comment: true,
|
||||
add_primary_key: true,
|
||||
alter_primary_key: true,
|
||||
..TableStructureCapabilities::default()
|
||||
}
|
||||
@@ -112,6 +115,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
rebuild_index: true,
|
||||
index_type: true,
|
||||
index_comment: true,
|
||||
add_primary_key: true,
|
||||
alter_primary_key: true,
|
||||
foreign_key: true,
|
||||
..base
|
||||
@@ -158,6 +162,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
index_include: true,
|
||||
index_filter: true,
|
||||
index_comment: true,
|
||||
add_primary_key: true,
|
||||
alter_primary_key: true,
|
||||
foreign_key: true,
|
||||
..base
|
||||
@@ -188,6 +193,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
index_filter: false,
|
||||
index_comment: false,
|
||||
index_concurrent: false,
|
||||
add_primary_key: false,
|
||||
alter_primary_key: false,
|
||||
foreign_key: false,
|
||||
},
|
||||
@@ -249,6 +255,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
drop_index: true,
|
||||
rebuild_index: true,
|
||||
index_type: true,
|
||||
add_primary_key: true,
|
||||
alter_primary_key: true,
|
||||
..base
|
||||
},
|
||||
@@ -265,6 +272,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
drop_index: true,
|
||||
rebuild_index: true,
|
||||
index_type: true,
|
||||
add_primary_key: true,
|
||||
alter_primary_key: true,
|
||||
..base
|
||||
},
|
||||
@@ -293,6 +301,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
||||
drop_index: true,
|
||||
rebuild_index: true,
|
||||
index_type: true,
|
||||
add_primary_key: true,
|
||||
foreign_key: true,
|
||||
..base
|
||||
},
|
||||
|
||||
@@ -3522,6 +3522,115 @@ fn dameng_blocks_dropping_former_primary_key_column() {
|
||||
assert!(result.warnings.iter().any(|warning| warning.contains("Primary key column")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_sets_not_null_before_adding_primary_key() {
|
||||
let mut id = existing_pk_column("id", "NUMBER", false, true);
|
||||
id.original.as_mut().unwrap().is_nullable = true;
|
||||
|
||||
let result =
|
||||
build_table_structure_change_sql(structure_change_options(DatabaseType::Oracle, Some("HR"), "users", vec![id]));
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec![
|
||||
"ALTER TABLE \"HR\".\"users\" MODIFY (\"id\" NUMBER NOT NULL);",
|
||||
"ALTER TABLE \"HR\".\"users\" ADD PRIMARY KEY (\"id\");",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_adds_composite_primary_key_in_draft_order() {
|
||||
let mut tenant_id = existing_pk_column("tenant_id", "NUMBER", false, true);
|
||||
tenant_id.id = "tenant_id".to_string();
|
||||
let mut code = existing_pk_column("code", "VARCHAR2(50)", false, true);
|
||||
code.id = "code".to_string();
|
||||
|
||||
let result = build_table_structure_change_sql(structure_change_options(
|
||||
DatabaseType::Oracle,
|
||||
Some("HR"),
|
||||
"users",
|
||||
vec![code, tenant_id],
|
||||
));
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(result.statements, vec!["ALTER TABLE \"HR\".\"users\" ADD PRIMARY KEY (\"code\", \"tenant_id\");"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_adds_new_primary_key_column_before_adding_constraint() {
|
||||
let mut code = column("code");
|
||||
code.data_type = "VARCHAR2(50)".to_string();
|
||||
code.is_nullable = false;
|
||||
code.is_primary_key = true;
|
||||
|
||||
let result = build_table_structure_change_sql(structure_change_options(
|
||||
DatabaseType::Oracle,
|
||||
Some("HR"),
|
||||
"users",
|
||||
vec![code],
|
||||
));
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec![
|
||||
"ALTER TABLE \"HR\".\"users\" ADD (\"code\" VARCHAR2(50));",
|
||||
"ALTER TABLE \"HR\".\"users\" ADD PRIMARY KEY (\"code\");",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_rejects_existing_primary_key_changes_without_partial_sql() {
|
||||
let uncheck =
|
||||
vec![existing_pk_column("id", "NUMBER", true, false), existing_pk_column("name", "VARCHAR2(50)", false, false)];
|
||||
let replacement = vec![
|
||||
existing_pk_column("id", "NUMBER", true, false),
|
||||
existing_pk_column("code", "VARCHAR2(50)", false, true),
|
||||
existing_pk_column("name", "VARCHAR2(50)", false, false),
|
||||
];
|
||||
let second_key = vec![
|
||||
existing_pk_column("id", "NUMBER", true, true),
|
||||
existing_pk_column("code", "VARCHAR2(50)", false, true),
|
||||
existing_pk_column("name", "VARCHAR2(50)", false, false),
|
||||
];
|
||||
|
||||
for (case, columns) in [("uncheck", uncheck), ("replacement", replacement), ("second key", second_key)] {
|
||||
let mut options = structure_change_options(DatabaseType::Oracle, Some("HR"), "users", columns);
|
||||
options.indexes = vec![index("idx_users_name", &["name"])];
|
||||
|
||||
let result = build_table_structure_change_sql(options);
|
||||
|
||||
assert!(result.statements.is_empty(), "{case} must not emit partial SQL: {:?}", result.statements);
|
||||
assert_eq!(result.warnings.len(), 1, "unexpected {case} warnings: {:?}", result.warnings);
|
||||
assert!(
|
||||
result.warnings[0].contains("Changing primary keys"),
|
||||
"unexpected {case} warning: {:?}",
|
||||
result.warnings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_compatible_engines_do_not_inherit_oracle_primary_key_add() {
|
||||
for database_type in [DatabaseType::OceanbaseOracle, DatabaseType::Iris] {
|
||||
let columns = vec![
|
||||
existing_pk_column("id", "NUMBER", false, true),
|
||||
existing_pk_column("name", "VARCHAR2(50)", false, false),
|
||||
];
|
||||
let mut options = structure_change_options(database_type, Some("APP"), "users", columns);
|
||||
options.indexes = vec![index("idx_users_name", &["name"])];
|
||||
|
||||
let result = build_table_structure_change_sql(options);
|
||||
|
||||
assert!(result.statements.is_empty(), "{database_type:?} must not emit partial SQL: {:?}", result.statements);
|
||||
assert_eq!(result.warnings.len(), 1, "unexpected {database_type:?} warnings: {:?}", result.warnings);
|
||||
assert!(result.warnings[0].contains("Adding primary keys"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_uncheck_primary_key_and_drop_column_does_not_emit_drop_column() {
|
||||
// alter_primary_key is false for Oracle: unchecking PK must not unlock DROP COLUMN without a PK drop.
|
||||
|
||||
Reference in new issue
Block a user