feat(writer): 🎉 Implement add column statements for schema evolution
All checks were successful
CI / Test (1.24) (push) Successful in -26m24s
CI / Test (1.25) (push) Successful in -26m14s
CI / Lint (push) Successful in -26m30s
CI / Build (push) Successful in -26m41s
Release / Build and Release (push) Successful in -26m29s
Integration Tests / Integration Tests (push) Successful in -26m13s
All checks were successful
CI / Test (1.24) (push) Successful in -26m24s
CI / Test (1.25) (push) Successful in -26m14s
CI / Lint (push) Successful in -26m30s
CI / Build (push) Successful in -26m41s
Release / Build and Release (push) Successful in -26m29s
Integration Tests / Integration Tests (push) Successful in -26m13s
* Add functionality to generate ALTER TABLE ADD COLUMN statements for existing tables. * Introduce tests for generating and writing add column statements. * Enhance schema evolution capabilities when new columns are added.
This commit is contained in:
@@ -168,6 +168,13 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
statements = append(statements, stmts...)
|
||||
}
|
||||
|
||||
// Phase 3.5: Add missing columns (for existing tables)
|
||||
addColStmts, err := w.GenerateAddColumnStatements(schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate add column statements: %w", err)
|
||||
}
|
||||
statements = append(statements, addColStmts...)
|
||||
|
||||
// Phase 4: Primary keys
|
||||
for _, table := range schema.Tables {
|
||||
// First check for explicit PrimaryKeyConstraint
|
||||
@@ -351,6 +358,68 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
return statements, nil
|
||||
}
|
||||
|
||||
// GenerateAddColumnStatements generates ALTER TABLE ADD COLUMN statements for existing tables
|
||||
// This is useful for schema evolution when new columns are added to existing tables
|
||||
func (w *Writer) GenerateAddColumnStatements(schema *models.Schema) ([]string, error) {
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, fmt.Sprintf("-- Add missing columns for schema: %s", schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort columns by sequence or name for consistent output
|
||||
columns := make([]*models.Column, 0, len(table.Columns))
|
||||
for _, col := range table.Columns {
|
||||
columns = append(columns, col)
|
||||
}
|
||||
sort.Slice(columns, func(i, j int) bool {
|
||||
if columns[i].Sequence != columns[j].Sequence {
|
||||
return columns[i].Sequence < columns[j].Sequence
|
||||
}
|
||||
return columns[i].Name < columns[j].Name
|
||||
})
|
||||
|
||||
for _, col := range columns {
|
||||
colDef := w.generateColumnDefinition(col)
|
||||
|
||||
// Generate DO block that checks if column exists before adding
|
||||
stmt := fmt.Sprintf("DO $$\nBEGIN\n"+
|
||||
" IF NOT EXISTS (\n"+
|
||||
" SELECT 1 FROM information_schema.columns\n"+
|
||||
" WHERE table_schema = '%s'\n"+
|
||||
" AND table_name = '%s'\n"+
|
||||
" AND column_name = '%s'\n"+
|
||||
" ) THEN\n"+
|
||||
" ALTER TABLE %s.%s ADD COLUMN %s;\n"+
|
||||
" END IF;\n"+
|
||||
"END;\n$$",
|
||||
schema.Name, table.Name, col.Name,
|
||||
schema.SQLName(), table.SQLName(), colDef)
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
return statements, nil
|
||||
}
|
||||
|
||||
// GenerateAddColumnsForDatabase generates ALTER TABLE ADD COLUMN statements for the entire database
|
||||
func (w *Writer) GenerateAddColumnsForDatabase(db *models.Database) ([]string, error) {
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, "-- Add missing columns to existing tables")
|
||||
statements = append(statements, fmt.Sprintf("-- Database: %s", db.Name))
|
||||
statements = append(statements, "-- Generated by RelSpec")
|
||||
|
||||
for _, schema := range db.Schemas {
|
||||
schemaStatements, err := w.GenerateAddColumnStatements(schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate add column statements for schema %s: %w", schema.Name, err)
|
||||
}
|
||||
statements = append(statements, schemaStatements...)
|
||||
}
|
||||
|
||||
return statements, nil
|
||||
}
|
||||
|
||||
// generateCreateTableStatement generates CREATE TABLE statement
|
||||
func (w *Writer) generateCreateTableStatement(schema *models.Schema, table *models.Table) ([]string, error) {
|
||||
statements := []string{}
|
||||
@@ -373,7 +442,7 @@ func (w *Writer) generateCreateTableStatement(schema *models.Schema, table *mode
|
||||
columnDefs = append(columnDefs, " "+def)
|
||||
}
|
||||
|
||||
stmt := fmt.Sprintf("CREATE TABLE %s.%s (\n%s\n)",
|
||||
stmt := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (\n%s\n)",
|
||||
schema.SQLName(), table.SQLName(), strings.Join(columnDefs, ",\n"))
|
||||
statements = append(statements, stmt)
|
||||
|
||||
@@ -458,6 +527,11 @@ func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Phase 3.5: Add missing columns (priority 120)
|
||||
if err := w.writeAddColumns(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Phase 4: Create primary keys (priority 160)
|
||||
if err := w.writePrimaryKeys(schema); err != nil {
|
||||
return err
|
||||
@@ -499,6 +573,44 @@ func (w *Writer) WriteTable(table *models.Table) error {
|
||||
return w.WriteSchema(schema)
|
||||
}
|
||||
|
||||
// WriteAddColumnStatements writes ALTER TABLE ADD COLUMN statements for a database
|
||||
// This is used for schema evolution/migration when new columns are added
|
||||
func (w *Writer) WriteAddColumnStatements(db *models.Database) error {
|
||||
var writer io.Writer
|
||||
var file *os.File
|
||||
var err error
|
||||
|
||||
// Use existing writer if already set (for testing)
|
||||
if w.writer != nil {
|
||||
writer = w.writer
|
||||
} else if w.options.OutputPath != "" {
|
||||
// Determine output destination
|
||||
file, err = os.Create(w.options.OutputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
writer = file
|
||||
} else {
|
||||
writer = os.Stdout
|
||||
}
|
||||
|
||||
w.writer = writer
|
||||
|
||||
// Generate statements
|
||||
statements, err := w.GenerateAddColumnsForDatabase(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write each statement
|
||||
for _, stmt := range statements {
|
||||
fmt.Fprintf(w.writer, "%s;\n\n", stmt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeCreateSchema generates CREATE SCHEMA statement
|
||||
func (w *Writer) writeCreateSchema(schema *models.Schema) error {
|
||||
if schema.Name == "public" {
|
||||
@@ -564,6 +676,35 @@ func (w *Writer) writeCreateTables(schema *models.Schema) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAddColumns generates ALTER TABLE ADD COLUMN statements for missing columns
|
||||
func (w *Writer) writeAddColumns(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Add missing columns for schema: %s\n", schema.Name)
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort columns by sequence or name for consistent output
|
||||
columns := getSortedColumns(table.Columns)
|
||||
|
||||
for _, col := range columns {
|
||||
colDef := w.generateColumnDefinition(col)
|
||||
|
||||
// Generate DO block that checks if column exists before adding
|
||||
fmt.Fprintf(w.writer, "DO $$\nBEGIN\n")
|
||||
fmt.Fprintf(w.writer, " IF NOT EXISTS (\n")
|
||||
fmt.Fprintf(w.writer, " SELECT 1 FROM information_schema.columns\n")
|
||||
fmt.Fprintf(w.writer, " WHERE table_schema = '%s'\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, " AND table_name = '%s'\n", table.Name)
|
||||
fmt.Fprintf(w.writer, " AND column_name = '%s'\n", col.Name)
|
||||
fmt.Fprintf(w.writer, " ) THEN\n")
|
||||
fmt.Fprintf(w.writer, " ALTER TABLE %s.%s ADD COLUMN %s;\n",
|
||||
schema.SQLName(), table.SQLName(), colDef)
|
||||
fmt.Fprintf(w.writer, " END IF;\n")
|
||||
fmt.Fprintf(w.writer, "END;\n$$;\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writePrimaryKeys generates ALTER TABLE statements for primary keys
|
||||
func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Primary keys for schema: %s\n", schema.Name)
|
||||
|
||||
Reference in New Issue
Block a user