mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-22 17:43:36 +00:00
fix(postgres): cast values during column type changes
This commit is contained in:
@@ -316,11 +316,31 @@ fn build_postgres_like_existing_column_sql(
|
||||
quote_ident(StructureDialect::Postgres, &column.name)
|
||||
));
|
||||
}
|
||||
if column.data_type.trim() != original.data_type.trim() {
|
||||
let type_changed = column.data_type.trim() != original.data_type.trim();
|
||||
if type_changed {
|
||||
let column_name = quote_ident(StructureDialect::Postgres, current_name);
|
||||
let data_type = column_data_type(StructureDialect::Postgres, column);
|
||||
let type_clause = if use_xugu_type_syntax { data_type } else { format!("TYPE {data_type}") };
|
||||
statements.push(format!("ALTER TABLE {table} ALTER COLUMN {column_name} {type_clause};"));
|
||||
if use_xugu_type_syntax {
|
||||
statements.push(format!("ALTER TABLE {table} ALTER COLUMN {column_name} {data_type};"));
|
||||
} else {
|
||||
let current_default = normalize_default(Some(&column.default_value));
|
||||
let mut alterations = Vec::new();
|
||||
if !original_default(column).is_empty() {
|
||||
alterations.push(format!("ALTER COLUMN {column_name} DROP DEFAULT"));
|
||||
}
|
||||
// PostgreSQL assignment casts do not cover many valid explicit conversions. USING keeps
|
||||
// type changes generic for built-ins, arrays, and domains while still rejecting bad data.
|
||||
alterations.push(format!("ALTER COLUMN {column_name} TYPE {data_type} USING {column_name}::{data_type}"));
|
||||
if !current_default.is_empty() {
|
||||
alterations.push(format!(
|
||||
"ALTER COLUMN {column_name} SET DEFAULT {}",
|
||||
format_default_for_sql(StructureDialect::Postgres, &column.data_type, ¤t_default)
|
||||
));
|
||||
}
|
||||
// USING is not applied to defaults. Keeping all three actions in one ALTER TABLE makes
|
||||
// an incompatible restored default fail atomically instead of leaving it dropped.
|
||||
statements.push(format!("ALTER TABLE {table} {};", alterations.join(", ")));
|
||||
}
|
||||
}
|
||||
if column.is_nullable != original.is_nullable {
|
||||
let action = if column.is_nullable { "DROP NOT NULL" } else { "SET NOT NULL" };
|
||||
@@ -329,7 +349,9 @@ fn build_postgres_like_existing_column_sql(
|
||||
quote_ident(StructureDialect::Postgres, current_name)
|
||||
));
|
||||
}
|
||||
if normalize_default(Some(&column.default_value)) != original_default(column) {
|
||||
if (!type_changed || use_xugu_type_syntax)
|
||||
&& normalize_default(Some(&column.default_value)) != original_default(column)
|
||||
{
|
||||
let default_value = normalize_default(Some(&column.default_value));
|
||||
let action = if default_value.is_empty() {
|
||||
"DROP DEFAULT".to_string()
|
||||
|
||||
@@ -193,7 +193,127 @@ fn builds_xugu_type_change_with_native_syntax() {
|
||||
|
||||
assert_eq!(
|
||||
postgres_result.statements,
|
||||
vec!["ALTER TABLE \"public\".\"info_x\" ALTER COLUMN \"code\" TYPE integer;"]
|
||||
vec!["ALTER TABLE \"public\".\"info_x\" ALTER COLUMN \"code\" TYPE integer USING \"code\"::integer;"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_explicit_type_cast_for_renamed_column() {
|
||||
let mut code = column("new code");
|
||||
code.data_type = "bigint".to_string();
|
||||
code.original = Some(ColumnInfo {
|
||||
name: "old code".to_string(),
|
||||
data_type: "character varying(20)".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
schema: Some("public".to_string()),
|
||||
table_name: "items".to_string(),
|
||||
column: code,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec![
|
||||
"ALTER TABLE \"public\".\"items\" RENAME COLUMN \"old code\" TO \"new code\";",
|
||||
"ALTER TABLE \"public\".\"items\" ALTER COLUMN \"new code\" TYPE bigint USING \"new code\"::bigint;",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_atomic_type_change_with_existing_default() {
|
||||
let mut code = column("code");
|
||||
code.data_type = "varchar(20)".to_string();
|
||||
code.default_value = "7".to_string();
|
||||
code.original = Some(ColumnInfo {
|
||||
name: "code".to_string(),
|
||||
data_type: "integer".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: Some("7".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
schema: Some("public".to_string()),
|
||||
table_name: "items".to_string(),
|
||||
column: code,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec!["ALTER TABLE \"public\".\"items\" ALTER COLUMN \"code\" DROP DEFAULT, ALTER COLUMN \"code\" TYPE varchar(20) USING \"code\"::varchar(20), ALTER COLUMN \"code\" SET DEFAULT '7';"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_type_change_that_drops_default() {
|
||||
let mut code = column("code");
|
||||
code.data_type = "bigint".to_string();
|
||||
code.original = Some(ColumnInfo {
|
||||
name: "code".to_string(),
|
||||
data_type: "character varying".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: Some("'7'::character varying".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
schema: None,
|
||||
table_name: "items".to_string(),
|
||||
column: code,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec!["ALTER TABLE \"items\" ALTER COLUMN \"code\" DROP DEFAULT, ALTER COLUMN \"code\" TYPE bigint USING \"code\"::bigint;"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_array_and_domain_type_casts_without_affecting_xugu() {
|
||||
let mut tags = column("tags");
|
||||
tags.data_type = "text[]".to_string();
|
||||
tags.original = Some(ColumnInfo {
|
||||
name: "tags".to_string(),
|
||||
data_type: "varchar(20)[]".to_string(),
|
||||
is_nullable: true,
|
||||
..Default::default()
|
||||
});
|
||||
let postgres = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
schema: Some("catalog".to_string()),
|
||||
table_name: "items".to_string(),
|
||||
column: tags,
|
||||
});
|
||||
assert_eq!(
|
||||
postgres.statements,
|
||||
vec!["ALTER TABLE \"catalog\".\"items\" ALTER COLUMN \"tags\" TYPE text[] USING \"tags\"::text[];"]
|
||||
);
|
||||
|
||||
let mut status = column("status");
|
||||
status.data_type = "catalog.status_domain".to_string();
|
||||
status.original = Some(ColumnInfo {
|
||||
name: "status".to_string(),
|
||||
data_type: "text".to_string(),
|
||||
is_nullable: true,
|
||||
..Default::default()
|
||||
});
|
||||
let postgres = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
schema: Some("catalog".to_string()),
|
||||
table_name: "items".to_string(),
|
||||
column: status,
|
||||
});
|
||||
assert_eq!(
|
||||
postgres.statements,
|
||||
vec!["ALTER TABLE \"catalog\".\"items\" ALTER COLUMN \"status\" TYPE catalog.status_domain USING \"status\"::catalog.status_domain;"]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user