fix(transfer): reseed Oracle identity columns after copying explicit ids

This commit is contained in:
ordinary being
2026-10-13 15:29:47 +08:00
committed by GitHub
parent 773916e18d
commit 2fbeffeb76
+93
View File
@@ -2858,6 +2858,50 @@ fn generate_postgres_sequence_sync_sql(columns: &[db::ColumnInfo], table: &str,
.collect()
}
/// `ALTER TABLE ... MODIFY <column> GENERATED BY DEFAULT AS IDENTITY (START WITH LIMIT VALUE)`
/// statements for Oracle targets.
///
/// Oracle accepts explicit values in a `GENERATED BY DEFAULT AS IDENTITY` column but
/// does not advance its sequence, so the first auto-generated insert after copying
/// explicit ids can collide with a copied value. `START WITH LIMIT VALUE` makes Oracle
/// lock the table and move the sequence past the maximum value in the column.
///
/// The filter mirrors the identity emission in
/// `generate_create_table_ddl_with_column_quoting` (primary key, explicit
/// auto-increment, integer-like mapped type), so the ALTER only targets a column the
/// generated DDL created as an identity column.
fn oracle_identity_reseed_statements(
columns: &[db::ColumnInfo],
table: &str,
schema: &str,
source_schema: &str,
source_db: &DatabaseType,
quote_target_column_names: bool,
) -> Vec<String> {
let full_table = qualified_table(table, schema, &DatabaseType::Oracle, None);
columns
.iter()
.filter(|column| {
column.is_primary_key
&& column_is_auto_increment(column)
&& type_looks_integer(&postgres_column_type_sql(
column,
source_schema,
schema,
source_db,
&DatabaseType::Oracle,
))
})
.map(|column| {
let quoted_column =
transfer_column_identifier(&column.name, &DatabaseType::Oracle, quote_target_column_names);
format!(
"ALTER TABLE {full_table} MODIFY {quoted_column} GENERATED BY DEFAULT AS IDENTITY (START WITH LIMIT VALUE)"
)
})
.collect()
}
/// Explicit identity values do not advance Xugu's backing sequence. Resolve the
/// sequence through SERIAL_ID rather than relying on its generated name, which
/// belongs to SYSDBA even when the table lives in another schema.
@@ -12257,6 +12301,30 @@ where
}
}
// Oracle accepts explicit ids in a `GENERATED BY DEFAULT AS IDENTITY` column but
// does not advance the sequence, so move it past the copied values. Only for
// tables this run created — `START WITH LIMIT VALUE` requires the identity column
// the generated DDL just created, and a preexisting target may not have one.
// OceanBase Oracle and DM8 stay out until their behavior is verified.
if *target_db_type == DatabaseType::Oracle
&& request.create_table
&& !target_table_preexisting
&& total_transferred > 0
{
for statement in oracle_identity_reseed_statements(
&columns,
&target_table,
&request.target_schema,
&request.source_schema,
source_db_type,
request.quote_target_column_names,
) {
execute_on_pool(state, target_pool_key, &statement)
.await
.map_err(|e| format!("Failed to reseed the Oracle identity column for {target_table}: {e}"))?;
}
}
if should_restore_postgres_table_schema {
restore_postgres_table_schema_objects(
state,
@@ -19024,6 +19092,31 @@ PARTITION p_old VALUES LESS THAN (TO_DAYS('2026-01-01')))";
);
}
#[test]
fn oracle_identity_reseed_targets_generated_identity_columns() {
let columns = vec![
db::ColumnInfo {
is_primary_key: true,
extra: Some("auto_increment".to_string()),
..test_column("id", "int")
},
// Not a key on the source: the generated DDL keeps it a plain column.
db::ColumnInfo { extra: Some("auto_increment".to_string()), ..test_column("ticket", "int") },
// Plain integer primary key without an explicit auto-increment stays plain.
db::ColumnInfo { is_primary_key: true, ..test_column("tenant", "int") },
];
let statements =
oracle_identity_reseed_statements(&columns, "users", "SALES", "source", &DatabaseType::Mysql, true);
assert_eq!(
statements,
vec![
"ALTER TABLE \"SALES\".\"users\" MODIFY \"id\" GENERATED BY DEFAULT AS IDENTITY (START WITH LIMIT VALUE)".to_string()
]
);
}
#[test]
fn postgres_sequence_sync_sql_casts_text_and_numeric_columns_to_bigint() {
let sql = generate_postgres_sequence_sync_sql(