fix(pgsql): handle nullability/type/default drift on existing columns
Existing databases that already ran an old migration kept stale NOT NULL constraints and mismatched column types/defaults, because the schema writer only emitted idempotent ADD COLUMN IF NOT EXISTS guards and never altered columns that already existed. - Emit guarded ALTER COLUMN ... SET/DROP NOT NULL when a column's nullability differs from the model. - Emit guarded ALTER COLUMN ... TYPE, falling back to renaming the old column and adding a fresh one when the in-place conversion fails. - Emit guarded ALTER COLUMN ... SET/DROP DEFAULT for default drift. - Collapse the previously duplicated plain/guarded templates so WriteSchema (full-schema, live-state-checking) and WriteMigration (diff-based) share the same guarded SQL templates and Go helpers instead of maintaining the logic twice.
This commit is contained in:
@@ -89,27 +89,10 @@ type AddColumnData struct {
|
||||
NotNull bool
|
||||
}
|
||||
|
||||
// AlterColumnTypeData contains data for alter column type template
|
||||
type AlterColumnTypeData struct {
|
||||
SchemaName string
|
||||
TableName string
|
||||
ColumnName string
|
||||
NewType string
|
||||
UsingExpr string
|
||||
}
|
||||
|
||||
// AlterColumnTypeWithFallbackData contains data for the alter column type
|
||||
// template that falls back to renaming the old column and adding a fresh
|
||||
// one when the in-place type conversion is not possible.
|
||||
type AlterColumnTypeWithFallbackData struct {
|
||||
SchemaName string
|
||||
TableName string
|
||||
ColumnName string
|
||||
NewType string
|
||||
UsingExpr string
|
||||
OldColumnName string
|
||||
}
|
||||
|
||||
// AlterColumnTypeWithCheckData contains data for the guarded alter column
|
||||
// type template, which only alters existing columns whose live type
|
||||
// differs from the desired one, and falls back to renaming the old column
|
||||
// and adding a fresh one when the in-place conversion is not possible.
|
||||
type AlterColumnTypeWithCheckData struct {
|
||||
SchemaName string
|
||||
TableName string
|
||||
@@ -119,8 +102,10 @@ type AlterColumnTypeWithCheckData struct {
|
||||
UsingExpr string
|
||||
}
|
||||
|
||||
// AlterColumnDefaultData contains data for alter column default template
|
||||
type AlterColumnDefaultData struct {
|
||||
// AlterColumnDefaultWithCheckData contains data for the guarded alter
|
||||
// column default template, which only alters existing columns whose live
|
||||
// default differs from the desired one.
|
||||
type AlterColumnDefaultWithCheckData struct {
|
||||
SchemaName string
|
||||
TableName string
|
||||
ColumnName string
|
||||
@@ -128,8 +113,10 @@ type AlterColumnDefaultData struct {
|
||||
DefaultValue string
|
||||
}
|
||||
|
||||
// AlterColumnNullabilityData contains data for alter column nullability template
|
||||
type AlterColumnNullabilityData struct {
|
||||
// AlterColumnNullabilityWithCheckData contains data for the guarded alter
|
||||
// column nullability template, which only alters existing columns whose
|
||||
// live NOT NULL state differs from the desired one.
|
||||
type AlterColumnNullabilityWithCheckData struct {
|
||||
SchemaName string
|
||||
TableName string
|
||||
ColumnName string
|
||||
@@ -322,27 +309,9 @@ func (te *TemplateExecutor) ExecuteAddColumn(data AddColumnData) (string, error)
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExecuteAlterColumnType executes the alter column type template
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnType(data AlterColumnTypeData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_type.tmpl", data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute alter_column_type template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExecuteAlterColumnTypeWithFallback executes the alter column type template
|
||||
// that renames the old column and adds a new one when the conversion fails.
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnTypeWithFallback(data AlterColumnTypeWithFallbackData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_type_with_fallback.tmpl", data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute alter_column_type_with_fallback template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExecuteAlterColumnTypeWithCheck executes the guarded alter column type
|
||||
// template shared by the full-schema writer and the diff-based migration
|
||||
// writer.
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnTypeWithCheck(data AlterColumnTypeWithCheckData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_type_with_check.tmpl", data)
|
||||
@@ -352,22 +321,25 @@ func (te *TemplateExecutor) ExecuteAlterColumnTypeWithCheck(data AlterColumnType
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExecuteAlterColumnDefault executes the alter column default template
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnDefault(data AlterColumnDefaultData) (string, error) {
|
||||
// ExecuteAlterColumnDefaultWithCheck executes the guarded alter column
|
||||
// default template shared by the full-schema writer and the diff-based
|
||||
// migration writer.
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnDefaultWithCheck(data AlterColumnDefaultWithCheckData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_default.tmpl", data)
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_default_with_check.tmpl", data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute alter_column_default template: %w", err)
|
||||
return "", fmt.Errorf("failed to execute alter_column_default_with_check template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExecuteAlterColumnNullability executes the alter column nullability template
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnNullability(data AlterColumnNullabilityData) (string, error) {
|
||||
// ExecuteAlterColumnNullabilityWithCheck executes the guarded alter column
|
||||
// nullability template.
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnNullabilityWithCheck(data AlterColumnNullabilityWithCheckData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_nullability.tmpl", data)
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_nullability_with_check.tmpl", data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute alter_column_nullability template: %w", err)
|
||||
return "", fmt.Errorf("failed to execute alter_column_nullability_with_check template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user