669 lines
19 KiB
Go
669 lines
19 KiB
Go
package pgsql
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
|
)
|
|
|
|
// MigrationWriter generates differential migration SQL scripts
|
|
type MigrationWriter struct {
|
|
options *writers.WriterOptions
|
|
writer io.Writer
|
|
}
|
|
|
|
// MigrationScript represents a single migration script with priority and sequence
|
|
type MigrationScript struct {
|
|
ObjectName string
|
|
ObjectType string
|
|
Schema string
|
|
Priority int
|
|
Sequence int
|
|
Body string
|
|
}
|
|
|
|
// NewMigrationWriter creates a new migration writer
|
|
func NewMigrationWriter(options *writers.WriterOptions) *MigrationWriter {
|
|
return &MigrationWriter{
|
|
options: options,
|
|
}
|
|
}
|
|
|
|
// WriteMigration generates migration scripts by comparing model (desired) vs current (actual) database
|
|
func (w *MigrationWriter) WriteMigration(model *models.Database, current *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 != "" {
|
|
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 all migration scripts
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Process each schema in the model
|
|
for _, modelSchema := range model.Schemas {
|
|
// Find corresponding schema in current database
|
|
var currentSchema *models.Schema
|
|
for _, cs := range current.Schemas {
|
|
if strings.EqualFold(cs.Name, modelSchema.Name) {
|
|
currentSchema = cs
|
|
break
|
|
}
|
|
}
|
|
|
|
// Generate schema-level scripts
|
|
schemaScripts := w.generateSchemaScripts(modelSchema, currentSchema)
|
|
scripts = append(scripts, schemaScripts...)
|
|
}
|
|
|
|
// Sort scripts by priority and sequence
|
|
sort.Slice(scripts, func(i, j int) bool {
|
|
if scripts[i].Priority != scripts[j].Priority {
|
|
return scripts[i].Priority < scripts[j].Priority
|
|
}
|
|
return scripts[i].Sequence < scripts[j].Sequence
|
|
})
|
|
|
|
// Write header
|
|
fmt.Fprintf(w.writer, "-- PostgreSQL Migration Script\n")
|
|
fmt.Fprintf(w.writer, "-- Generated by RelSpec\n")
|
|
fmt.Fprintf(w.writer, "-- Source: %s -> %s\n\n", current.Name, model.Name)
|
|
|
|
// Write scripts
|
|
for _, script := range scripts {
|
|
fmt.Fprintf(w.writer, "-- Priority: %d | Type: %s | Object: %s\n",
|
|
script.Priority, script.ObjectType, script.ObjectName)
|
|
fmt.Fprintf(w.writer, "%s\n\n", script.Body)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateSchemaScripts generates migration scripts for a schema
|
|
func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Phase 1: Drop constraints and indexes that changed (Priority 11-50)
|
|
if current != nil {
|
|
scripts = append(scripts, w.generateDropScripts(model, current)...)
|
|
}
|
|
|
|
// Phase 2: Rename tables and columns (Priority 60-90)
|
|
if current != nil {
|
|
scripts = append(scripts, w.generateRenameScripts(model, current)...)
|
|
}
|
|
|
|
// Phase 3: Create/Alter tables and columns (Priority 100-145)
|
|
scripts = append(scripts, w.generateTableScripts(model, current)...)
|
|
|
|
// Phase 4: Create indexes (Priority 160-180)
|
|
scripts = append(scripts, w.generateIndexScripts(model, current)...)
|
|
|
|
// Phase 5: Create foreign keys (Priority 195)
|
|
scripts = append(scripts, w.generateForeignKeyScripts(model, current)...)
|
|
|
|
// Phase 6: Add comments (Priority 200+)
|
|
scripts = append(scripts, w.generateCommentScripts(model, current)...)
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateDropScripts generates DROP scripts for removed/changed objects
|
|
func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Build map of model tables for quick lookup
|
|
modelTables := make(map[string]*models.Table)
|
|
for _, table := range model.Tables {
|
|
modelTables[strings.ToLower(table.Name)] = table
|
|
}
|
|
|
|
// Find constraints to drop
|
|
for _, currentTable := range current.Tables {
|
|
modelTable, existsInModel := modelTables[strings.ToLower(currentTable.Name)]
|
|
|
|
if !existsInModel {
|
|
// Table will be dropped, skip individual constraint drops
|
|
continue
|
|
}
|
|
|
|
// Check each constraint in current database
|
|
for constraintName, currentConstraint := range currentTable.Constraints {
|
|
// Check if constraint exists in model
|
|
modelConstraint, existsInModel := modelTable.Constraints[constraintName]
|
|
|
|
shouldDrop := false
|
|
|
|
if !existsInModel {
|
|
shouldDrop = true
|
|
} else if !constraintsEqual(modelConstraint, currentConstraint) {
|
|
// Constraint changed, drop and recreate
|
|
shouldDrop = true
|
|
}
|
|
|
|
if shouldDrop {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", current.Name, currentTable.Name, constraintName),
|
|
ObjectType: "drop constraint",
|
|
Schema: current.Name,
|
|
Priority: 11,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"ALTER TABLE %s.%s DROP CONSTRAINT IF EXISTS %s;",
|
|
current.Name, currentTable.Name, constraintName,
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
|
|
// Check indexes
|
|
for indexName, currentIndex := range currentTable.Indexes {
|
|
modelIndex, existsInModel := modelTable.Indexes[indexName]
|
|
|
|
shouldDrop := false
|
|
|
|
if !existsInModel {
|
|
shouldDrop = true
|
|
} else if !indexesEqual(modelIndex, currentIndex) {
|
|
shouldDrop = true
|
|
}
|
|
|
|
if shouldDrop {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", current.Name, currentTable.Name, indexName),
|
|
ObjectType: "drop index",
|
|
Schema: current.Name,
|
|
Priority: 20,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"DROP INDEX IF EXISTS %s.%s CASCADE;",
|
|
current.Name, indexName,
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
}
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateRenameScripts generates RENAME scripts for renamed objects
|
|
func (w *MigrationWriter) generateRenameScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// For now, we don't attempt to detect renames automatically
|
|
// This would require GUID matching or other heuristics
|
|
// Users would need to handle renames manually or through metadata
|
|
|
|
// Suppress unused parameter warnings
|
|
_ = model
|
|
_ = current
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateTableScripts generates CREATE/ALTER TABLE scripts
|
|
func (w *MigrationWriter) generateTableScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Build map of current tables
|
|
currentTables := make(map[string]*models.Table)
|
|
if current != nil {
|
|
for _, table := range current.Tables {
|
|
currentTables[strings.ToLower(table.Name)] = table
|
|
}
|
|
}
|
|
|
|
// Process each model table
|
|
for _, modelTable := range model.Tables {
|
|
currentTable, exists := currentTables[strings.ToLower(modelTable.Name)]
|
|
|
|
if !exists {
|
|
// Table doesn't exist, create it
|
|
script := w.generateCreateTableScript(model, modelTable)
|
|
scripts = append(scripts, script)
|
|
} else {
|
|
// Table exists, check for column changes
|
|
alterScripts := w.generateAlterTableScripts(model, modelTable, currentTable)
|
|
scripts = append(scripts, alterScripts...)
|
|
}
|
|
}
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateCreateTableScript generates a CREATE TABLE script
|
|
func (w *MigrationWriter) generateCreateTableScript(schema *models.Schema, table *models.Table) MigrationScript {
|
|
var body strings.Builder
|
|
|
|
body.WriteString(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (\n", schema.Name, table.Name))
|
|
|
|
// Get sorted columns
|
|
columns := getSortedColumns(table.Columns)
|
|
columnDefs := make([]string, 0, len(columns))
|
|
|
|
for _, col := range columns {
|
|
colDef := fmt.Sprintf(" %s %s", col.Name, col.Type)
|
|
|
|
// Add default value if present
|
|
if col.Default != nil {
|
|
colDef += fmt.Sprintf(" DEFAULT %v", col.Default)
|
|
}
|
|
|
|
// Add NOT NULL if needed
|
|
if col.NotNull {
|
|
colDef += " NOT NULL"
|
|
}
|
|
|
|
columnDefs = append(columnDefs, colDef)
|
|
}
|
|
|
|
body.WriteString(strings.Join(columnDefs, ",\n"))
|
|
body.WriteString("\n);")
|
|
|
|
return MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s", schema.Name, table.Name),
|
|
ObjectType: "create table",
|
|
Schema: schema.Name,
|
|
Priority: 100,
|
|
Sequence: 0,
|
|
Body: body.String(),
|
|
}
|
|
}
|
|
|
|
// generateAlterTableScripts generates ALTER TABLE scripts for column changes
|
|
func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, modelTable *models.Table, currentTable *models.Table) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Build map of current columns
|
|
currentColumns := make(map[string]*models.Column)
|
|
for name, col := range currentTable.Columns {
|
|
currentColumns[strings.ToLower(name)] = col
|
|
}
|
|
|
|
// Check each model column
|
|
for _, modelCol := range modelTable.Columns {
|
|
currentCol, exists := currentColumns[strings.ToLower(modelCol.Name)]
|
|
|
|
if !exists {
|
|
// Column doesn't exist, add it
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", schema.Name, modelTable.Name, modelCol.Name),
|
|
ObjectType: "create column",
|
|
Schema: schema.Name,
|
|
Priority: 120,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"ALTER TABLE %s.%s\n ADD COLUMN IF NOT EXISTS %s %s%s%s;",
|
|
schema.Name, modelTable.Name, modelCol.Name, modelCol.Type,
|
|
func() string {
|
|
if modelCol.Default != nil {
|
|
return fmt.Sprintf(" DEFAULT %v", modelCol.Default)
|
|
}
|
|
return ""
|
|
}(),
|
|
func() string {
|
|
if modelCol.NotNull {
|
|
return " NOT NULL"
|
|
}
|
|
return ""
|
|
}(),
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
} else if !columnsEqual(modelCol, currentCol) {
|
|
// Column exists but type or properties changed
|
|
if modelCol.Type != currentCol.Type {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", schema.Name, modelTable.Name, modelCol.Name),
|
|
ObjectType: "alter column type",
|
|
Schema: schema.Name,
|
|
Priority: 120,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"ALTER TABLE %s.%s\n ALTER COLUMN %s TYPE %s;",
|
|
schema.Name, modelTable.Name, modelCol.Name, modelCol.Type,
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
|
|
// Check default value changes
|
|
if fmt.Sprintf("%v", modelCol.Default) != fmt.Sprintf("%v", currentCol.Default) {
|
|
if modelCol.Default != nil {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", schema.Name, modelTable.Name, modelCol.Name),
|
|
ObjectType: "alter column default",
|
|
Schema: schema.Name,
|
|
Priority: 145,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"ALTER TABLE %s.%s\n ALTER COLUMN %s SET DEFAULT %v;",
|
|
schema.Name, modelTable.Name, modelCol.Name, modelCol.Default,
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
} else {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", schema.Name, modelTable.Name, modelCol.Name),
|
|
ObjectType: "alter column default",
|
|
Schema: schema.Name,
|
|
Priority: 145,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"ALTER TABLE %s.%s\n ALTER COLUMN %s DROP DEFAULT;",
|
|
schema.Name, modelTable.Name, modelCol.Name,
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateIndexScripts generates CREATE INDEX scripts
|
|
func (w *MigrationWriter) generateIndexScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Build map of current tables
|
|
currentTables := make(map[string]*models.Table)
|
|
if current != nil {
|
|
for _, table := range current.Tables {
|
|
currentTables[strings.ToLower(table.Name)] = table
|
|
}
|
|
}
|
|
|
|
// Process each model table
|
|
for _, modelTable := range model.Tables {
|
|
currentTable := currentTables[strings.ToLower(modelTable.Name)]
|
|
|
|
// Process each index in model
|
|
for indexName, modelIndex := range modelTable.Indexes {
|
|
shouldCreate := true
|
|
|
|
// Check if index exists in current
|
|
if currentTable != nil {
|
|
if currentIndex, exists := currentTable.Indexes[indexName]; exists {
|
|
if indexesEqual(modelIndex, currentIndex) {
|
|
shouldCreate = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if shouldCreate {
|
|
unique := ""
|
|
if modelIndex.Unique {
|
|
unique = "UNIQUE "
|
|
}
|
|
|
|
indexType := "btree"
|
|
if modelIndex.Type != "" {
|
|
indexType = modelIndex.Type
|
|
}
|
|
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, indexName),
|
|
ObjectType: "create index",
|
|
Schema: model.Name,
|
|
Priority: 180,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"CREATE %sINDEX IF NOT EXISTS %s\n ON %s.%s USING %s (%s);",
|
|
unique, indexName, model.Name, modelTable.Name, indexType,
|
|
strings.Join(modelIndex.Columns, ", "),
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
|
|
// Add primary key constraint if it exists
|
|
for constraintName, constraint := range modelTable.Constraints {
|
|
if constraint.Type == models.PrimaryKeyConstraint {
|
|
shouldCreate := true
|
|
|
|
if currentTable != nil {
|
|
if currentConstraint, exists := currentTable.Constraints[constraintName]; exists {
|
|
if constraintsEqual(constraint, currentConstraint) {
|
|
shouldCreate = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if shouldCreate {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, constraintName),
|
|
ObjectType: "create primary key",
|
|
Schema: model.Name,
|
|
Priority: 160,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"DO $$\nBEGIN\n IF NOT EXISTS (\n"+
|
|
" SELECT 1 FROM information_schema.table_constraints\n"+
|
|
" WHERE table_schema = '%s'\n"+
|
|
" AND table_name = '%s'\n"+
|
|
" AND constraint_name = '%s'\n"+
|
|
" ) THEN\n"+
|
|
" ALTER TABLE %s.%s\n"+
|
|
" ADD CONSTRAINT %s PRIMARY KEY (%s);\n"+
|
|
" END IF;\n"+
|
|
"END;\n$$;",
|
|
model.Name, modelTable.Name, constraintName,
|
|
model.Name, modelTable.Name, constraintName,
|
|
strings.Join(constraint.Columns, ", "),
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateForeignKeyScripts generates ADD CONSTRAINT FOREIGN KEY scripts
|
|
func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Build map of current tables
|
|
currentTables := make(map[string]*models.Table)
|
|
if current != nil {
|
|
for _, table := range current.Tables {
|
|
currentTables[strings.ToLower(table.Name)] = table
|
|
}
|
|
}
|
|
|
|
// Process each model table
|
|
for _, modelTable := range model.Tables {
|
|
currentTable := currentTables[strings.ToLower(modelTable.Name)]
|
|
|
|
// Process each constraint
|
|
for constraintName, constraint := range modelTable.Constraints {
|
|
if constraint.Type != models.ForeignKeyConstraint {
|
|
continue
|
|
}
|
|
|
|
shouldCreate := true
|
|
|
|
// Check if constraint exists in current
|
|
if currentTable != nil {
|
|
if currentConstraint, exists := currentTable.Constraints[constraintName]; exists {
|
|
if constraintsEqual(constraint, currentConstraint) {
|
|
shouldCreate = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if shouldCreate {
|
|
onDelete := "NO ACTION"
|
|
if constraint.OnDelete != "" {
|
|
onDelete = strings.ToUpper(constraint.OnDelete)
|
|
}
|
|
|
|
onUpdate := "NO ACTION"
|
|
if constraint.OnUpdate != "" {
|
|
onUpdate = strings.ToUpper(constraint.OnUpdate)
|
|
}
|
|
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, constraintName),
|
|
ObjectType: "create foreign key",
|
|
Schema: model.Name,
|
|
Priority: 195,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"ALTER TABLE %s.%s\n"+
|
|
" DROP CONSTRAINT IF EXISTS %s;\n\n"+
|
|
"ALTER TABLE %s.%s\n"+
|
|
" ADD CONSTRAINT %s\n"+
|
|
" FOREIGN KEY (%s)\n"+
|
|
" REFERENCES %s.%s (%s)\n"+
|
|
" ON DELETE %s\n"+
|
|
" ON UPDATE %s\n"+
|
|
" DEFERRABLE;",
|
|
model.Name, modelTable.Name, constraintName,
|
|
model.Name, modelTable.Name, constraintName,
|
|
strings.Join(constraint.Columns, ", "),
|
|
constraint.ReferencedSchema, constraint.ReferencedTable,
|
|
strings.Join(constraint.ReferencedColumns, ", "),
|
|
onDelete, onUpdate,
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
}
|
|
|
|
return scripts
|
|
}
|
|
|
|
// generateCommentScripts generates COMMENT ON scripts
|
|
func (w *MigrationWriter) generateCommentScripts(model *models.Schema, current *models.Schema) []MigrationScript {
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
// Suppress unused parameter warning (current not used yet, could be used for diffing)
|
|
_ = current
|
|
|
|
// Process each model table
|
|
for _, modelTable := range model.Tables {
|
|
// Table comment
|
|
if modelTable.Description != "" {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s", model.Name, modelTable.Name),
|
|
ObjectType: "comment on table",
|
|
Schema: model.Name,
|
|
Priority: 200,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"COMMENT ON TABLE %s.%s IS '%s';",
|
|
model.Name, modelTable.Name, escapeQuote(modelTable.Description),
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
|
|
// Column comments
|
|
for _, col := range modelTable.Columns {
|
|
if col.Description != "" {
|
|
script := MigrationScript{
|
|
ObjectName: fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, col.Name),
|
|
ObjectType: "comment on column",
|
|
Schema: model.Name,
|
|
Priority: 200,
|
|
Sequence: len(scripts),
|
|
Body: fmt.Sprintf(
|
|
"COMMENT ON COLUMN %s.%s.%s IS '%s';",
|
|
model.Name, modelTable.Name, col.Name, escapeQuote(col.Description),
|
|
),
|
|
}
|
|
scripts = append(scripts, script)
|
|
}
|
|
}
|
|
}
|
|
|
|
return scripts
|
|
}
|
|
|
|
// Comparison helper functions
|
|
|
|
func constraintsEqual(a, b *models.Constraint) bool {
|
|
if a.Type != b.Type {
|
|
return false
|
|
}
|
|
if len(a.Columns) != len(b.Columns) {
|
|
return false
|
|
}
|
|
for i := range a.Columns {
|
|
if !strings.EqualFold(a.Columns[i], b.Columns[i]) {
|
|
return false
|
|
}
|
|
}
|
|
if a.Type == models.ForeignKeyConstraint {
|
|
if a.ReferencedTable != b.ReferencedTable || a.ReferencedSchema != b.ReferencedSchema {
|
|
return false
|
|
}
|
|
if len(a.ReferencedColumns) != len(b.ReferencedColumns) {
|
|
return false
|
|
}
|
|
for i := range a.ReferencedColumns {
|
|
if !strings.EqualFold(a.ReferencedColumns[i], b.ReferencedColumns[i]) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func indexesEqual(a, b *models.Index) bool {
|
|
if a.Unique != b.Unique {
|
|
return false
|
|
}
|
|
if len(a.Columns) != len(b.Columns) {
|
|
return false
|
|
}
|
|
for i := range a.Columns {
|
|
if !strings.EqualFold(a.Columns[i], b.Columns[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func columnsEqual(a, b *models.Column) bool {
|
|
if a.Type != b.Type {
|
|
return false
|
|
}
|
|
if a.NotNull != b.NotNull {
|
|
return false
|
|
}
|
|
if fmt.Sprintf("%v", a.Default) != fmt.Sprintf("%v", b.Default) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|