From 97139723c9f73e87394ce82b3769dbe23966e917 Mon Sep 17 00:00:00 2001 From: Hein Date: Fri, 14 Aug 2026 14:10:21 +0200 Subject: [PATCH] feat(migration): add support for altering column nullability * Implement ExecuteAlterColumnNullability function * Create alter_column_nullability template * Add test for altering column nullability behavior --- pkg/writers/pgsql/migration_writer.go | 23 +++++++++++ pkg/writers/pgsql/migration_writer_test.go | 38 +++++++++++++++++++ pkg/writers/pgsql/templates.go | 18 +++++++++ .../templates/alter_column_nullability.tmpl | 7 ++++ 4 files changed, 86 insertions(+) create mode 100644 pkg/writers/pgsql/templates/alter_column_nullability.tmpl diff --git a/pkg/writers/pgsql/migration_writer.go b/pkg/writers/pgsql/migration_writer.go index fef8d95..6640817 100644 --- a/pkg/writers/pgsql/migration_writer.go +++ b/pkg/writers/pgsql/migration_writer.go @@ -497,6 +497,29 @@ func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, model } scripts = append(scripts, script) } + + // Check nullability changes + if modelCol.NotNull != currentCol.NotNull { + sql, err := w.executor.ExecuteAlterColumnNullability(AlterColumnNullabilityData{ + SchemaName: schema.Name, + TableName: modelTable.Name, + ColumnName: modelCol.Name, + NotNull: modelCol.NotNull, + }) + if err != nil { + return nil, err + } + + script := MigrationScript{ + ObjectName: fmt.Sprintf("%s.%s.%s", schema.Name, modelTable.Name, modelCol.Name), + ObjectType: "alter column nullability", + Schema: schema.Name, + Priority: 145, + Sequence: len(scripts), + Body: sql, + } + scripts = append(scripts, script) + } } } diff --git a/pkg/writers/pgsql/migration_writer_test.go b/pkg/writers/pgsql/migration_writer_test.go index f39ed18..9f89978 100644 --- a/pkg/writers/pgsql/migration_writer_test.go +++ b/pkg/writers/pgsql/migration_writer_test.go @@ -136,6 +136,44 @@ func TestWriteMigration_AltersColumnTypeWhenActualTypeDiffers(t *testing.T) { } } +func TestWriteMigration_AltersColumnNullabilityWhenNotNullDiffers(t *testing.T) { + current := models.InitDatabase("testdb") + currentSchema := models.InitSchema("public") + currentTable := models.InitTable("service_instance", "public") + currentType := models.InitColumn("rid_service_instance_type", "service_instance", "public") + currentType.Type = "text" + currentType.NotNull = true + currentTable.Columns["rid_service_instance_type"] = currentType + currentSchema.Tables = append(currentSchema.Tables, currentTable) + current.Schemas = append(current.Schemas, currentSchema) + + model := models.InitDatabase("testdb") + modelSchema := models.InitSchema("public") + modelTable := models.InitTable("service_instance", "public") + modelType := models.InitColumn("rid_service_instance_type", "service_instance", "public") + modelType.Type = "text" + modelType.NotNull = false + modelTable.Columns["rid_service_instance_type"] = modelType + modelSchema.Tables = append(modelSchema.Tables, modelTable) + model.Schemas = append(model.Schemas, modelSchema) + + var buf bytes.Buffer + writer, err := NewMigrationWriter(&writers.WriterOptions{}) + if err != nil { + t.Fatalf("Failed to create writer: %v", err) + } + writer.writer = &buf + + if err := writer.WriteMigration(model, current); err != nil { + t.Fatalf("WriteMigration failed: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "ALTER COLUMN rid_service_instance_type DROP NOT NULL") { + t.Fatalf("expected migration to drop NOT NULL on existing column, got:\n%s", output) + } +} + func TestWriteMigration_UsesStorageTypeForSerialAlterStatements(t *testing.T) { current := models.InitDatabase("testdb") currentSchema := models.InitSchema("public") diff --git a/pkg/writers/pgsql/templates.go b/pkg/writers/pgsql/templates.go index ca6739e..ecb71b5 100644 --- a/pkg/writers/pgsql/templates.go +++ b/pkg/writers/pgsql/templates.go @@ -116,6 +116,14 @@ type AlterColumnDefaultData struct { DefaultValue string } +// AlterColumnNullabilityData contains data for alter column nullability template +type AlterColumnNullabilityData struct { + SchemaName string + TableName string + ColumnName string + NotNull bool +} + // CreatePrimaryKeyData contains data for create primary key template type CreatePrimaryKeyData struct { SchemaName string @@ -331,6 +339,16 @@ func (te *TemplateExecutor) ExecuteAlterColumnDefault(data AlterColumnDefaultDat return buf.String(), nil } +// ExecuteAlterColumnNullability executes the alter column nullability template +func (te *TemplateExecutor) ExecuteAlterColumnNullability(data AlterColumnNullabilityData) (string, error) { + var buf bytes.Buffer + err := te.templates.ExecuteTemplate(&buf, "alter_column_nullability.tmpl", data) + if err != nil { + return "", fmt.Errorf("failed to execute alter_column_nullability template: %w", err) + } + return buf.String(), nil +} + // ExecuteCreatePrimaryKey executes the create primary key template func (te *TemplateExecutor) ExecuteCreatePrimaryKey(data CreatePrimaryKeyData) (string, error) { var buf bytes.Buffer diff --git a/pkg/writers/pgsql/templates/alter_column_nullability.tmpl b/pkg/writers/pgsql/templates/alter_column_nullability.tmpl new file mode 100644 index 0000000..ee81e26 --- /dev/null +++ b/pkg/writers/pgsql/templates/alter_column_nullability.tmpl @@ -0,0 +1,7 @@ +{{- if .NotNull -}} +ALTER TABLE {{qual_table .SchemaName .TableName}} + ALTER COLUMN {{quote_ident .ColumnName}} SET NOT NULL; +{{- else -}} +ALTER TABLE {{qual_table .SchemaName .TableName}} + ALTER COLUMN {{quote_ident .ColumnName}} DROP NOT NULL; +{{- end -}}