package diff import ( "encoding/json" "fmt" "io" "strings" "text/template" ) // OutputFormat represents the output format for diff results type OutputFormat string const ( FormatSummary OutputFormat = "summary" FormatJSON OutputFormat = "json" FormatHTML OutputFormat = "html" ) // FormatDiff formats the diff result according to the specified format func FormatDiff(result *DiffResult, format OutputFormat, w io.Writer) error { switch format { case FormatSummary: return formatSummary(result, w) case FormatJSON: return formatJSON(result, w) case FormatHTML: return formatHTML(result, w) default: return fmt.Errorf("unsupported format: %s", format) } } func formatSummary(result *DiffResult, w io.Writer) error { summary := ComputeSummary(result) fmt.Fprintf(w, "\n=== Database Diff Summary ===\n") fmt.Fprintf(w, "Source: %s\n", result.Source) fmt.Fprintf(w, "Target: %s\n\n", result.Target) // Schemas if summary.Schemas.Missing > 0 || summary.Schemas.Extra > 0 || summary.Schemas.Modified > 0 { fmt.Fprintf(w, "Schemas:\n") if summary.Schemas.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Schemas.Missing) } if summary.Schemas.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Schemas.Extra) } if summary.Schemas.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Schemas.Modified) } fmt.Fprintf(w, "\n") } // Tables if summary.Tables.Missing > 0 || summary.Tables.Extra > 0 || summary.Tables.Modified > 0 { fmt.Fprintf(w, "Tables:\n") if summary.Tables.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Tables.Missing) } if summary.Tables.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Tables.Extra) } if summary.Tables.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Tables.Modified) } fmt.Fprintf(w, "\n") } // Columns if summary.Columns.Missing > 0 || summary.Columns.Extra > 0 || summary.Columns.Modified > 0 { fmt.Fprintf(w, "Columns:\n") if summary.Columns.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Columns.Missing) } if summary.Columns.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Columns.Extra) } if summary.Columns.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Columns.Modified) } fmt.Fprintf(w, "\n") } // Indexes if summary.Indexes.Missing > 0 || summary.Indexes.Extra > 0 || summary.Indexes.Modified > 0 { fmt.Fprintf(w, "Indexes:\n") if summary.Indexes.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Indexes.Missing) } if summary.Indexes.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Indexes.Extra) } if summary.Indexes.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Indexes.Modified) } fmt.Fprintf(w, "\n") } // Constraints if summary.Constraints.Missing > 0 || summary.Constraints.Extra > 0 || summary.Constraints.Modified > 0 { fmt.Fprintf(w, "Constraints:\n") if summary.Constraints.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Constraints.Missing) } if summary.Constraints.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Constraints.Extra) } if summary.Constraints.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Constraints.Modified) } fmt.Fprintf(w, "\n") } // Relationships if summary.Relationships.Missing > 0 || summary.Relationships.Extra > 0 || summary.Relationships.Modified > 0 { fmt.Fprintf(w, "Relationships:\n") if summary.Relationships.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Relationships.Missing) } if summary.Relationships.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Relationships.Extra) } if summary.Relationships.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Relationships.Modified) } fmt.Fprintf(w, "\n") } // Views if summary.Views.Missing > 0 || summary.Views.Extra > 0 || summary.Views.Modified > 0 { fmt.Fprintf(w, "Views:\n") if summary.Views.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Views.Missing) } if summary.Views.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Views.Extra) } if summary.Views.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Views.Modified) } fmt.Fprintf(w, "\n") } // Sequences if summary.Sequences.Missing > 0 || summary.Sequences.Extra > 0 || summary.Sequences.Modified > 0 { fmt.Fprintf(w, "Sequences:\n") if summary.Sequences.Missing > 0 { fmt.Fprintf(w, " Missing: %d\n", summary.Sequences.Missing) } if summary.Sequences.Extra > 0 { fmt.Fprintf(w, " Extra: %d\n", summary.Sequences.Extra) } if summary.Sequences.Modified > 0 { fmt.Fprintf(w, " Modified: %d\n", summary.Sequences.Modified) } fmt.Fprintf(w, "\n") } // Check if there are no differences if summary.Schemas.Missing == 0 && summary.Schemas.Extra == 0 && summary.Schemas.Modified == 0 && summary.Tables.Missing == 0 && summary.Tables.Extra == 0 && summary.Tables.Modified == 0 && summary.Columns.Missing == 0 && summary.Columns.Extra == 0 && summary.Columns.Modified == 0 && summary.Indexes.Missing == 0 && summary.Indexes.Extra == 0 && summary.Indexes.Modified == 0 && summary.Constraints.Missing == 0 && summary.Constraints.Extra == 0 && summary.Constraints.Modified == 0 && summary.Relationships.Missing == 0 && summary.Relationships.Extra == 0 && summary.Relationships.Modified == 0 && summary.Views.Missing == 0 && summary.Views.Extra == 0 && summary.Views.Modified == 0 && summary.Sequences.Missing == 0 && summary.Sequences.Extra == 0 && summary.Sequences.Modified == 0 { fmt.Fprintf(w, "No differences found.\n") } return nil } func formatJSON(result *DiffResult, w io.Writer) error { encoder := json.NewEncoder(w) encoder.SetIndent("", " ") return encoder.Encode(result) } func formatHTML(result *DiffResult, w io.Writer) error { tmpl, err := template.New("diff").Funcs(template.FuncMap{ "join": strings.Join, }).Parse(htmlTemplate) if err != nil { return fmt.Errorf("failed to parse template: %w", err) } summary := ComputeSummary(result) data := struct { Result *DiffResult Summary *Summary }{ Result: result, Summary: summary, } return tmpl.Execute(w, data) } const htmlTemplate = ` Database Diff Report

Database Diff Report

Summary

Source: {{.Result.Source}}

Target: {{.Result.Target}}

{{if or .Summary.Schemas.Missing .Summary.Schemas.Extra .Summary.Schemas.Modified}}

Schemas

Missing {{.Summary.Schemas.Missing}}
Extra {{.Summary.Schemas.Extra}}
Modified {{.Summary.Schemas.Modified}}
{{end}} {{if or .Summary.Tables.Missing .Summary.Tables.Extra .Summary.Tables.Modified}}

Tables

Missing {{.Summary.Tables.Missing}}
Extra {{.Summary.Tables.Extra}}
Modified {{.Summary.Tables.Modified}}
{{end}} {{if or .Summary.Columns.Missing .Summary.Columns.Extra .Summary.Columns.Modified}}

Columns

Missing {{.Summary.Columns.Missing}}
Extra {{.Summary.Columns.Extra}}
Modified {{.Summary.Columns.Modified}}
{{end}} {{if or .Summary.Indexes.Missing .Summary.Indexes.Extra .Summary.Indexes.Modified}}

Indexes

Missing {{.Summary.Indexes.Missing}}
Extra {{.Summary.Indexes.Extra}}
Modified {{.Summary.Indexes.Modified}}
{{end}} {{if or .Summary.Constraints.Missing .Summary.Constraints.Extra .Summary.Constraints.Modified}}

Constraints

Missing {{.Summary.Constraints.Missing}}
Extra {{.Summary.Constraints.Extra}}
Modified {{.Summary.Constraints.Modified}}
{{end}} {{if or .Summary.Sequences.Missing .Summary.Sequences.Extra .Summary.Sequences.Modified}}

Sequences

Missing {{.Summary.Sequences.Missing}}
Extra {{.Summary.Sequences.Extra}}
Modified {{.Summary.Sequences.Modified}}
{{end}}
{{if .Result.Schemas}}

Detailed Differences

{{range .Result.Schemas.Missing}}

Schema: {{.Name}} MISSING

{{end}} {{range .Result.Schemas.Extra}}

Schema: {{.Name}} EXTRA

{{end}} {{range .Result.Schemas.Modified}}

Schema: {{.Name}} MODIFIED

{{if .Tables}} {{if .Tables.Missing}}

Missing Tables

{{end}} {{if .Tables.Extra}}

Extra Tables

{{end}} {{if .Tables.Modified}}

Modified Tables

{{range .Tables.Modified}}
Table: {{.Schema}}.{{.Name}}
{{if .Columns}} {{if .Columns.Missing}}
Missing Columns
    {{range .Columns.Missing}}
  • {{.Name}} ({{.Type}})
  • {{end}}
{{end}} {{if .Columns.Extra}}
Extra Columns
    {{range .Columns.Extra}}
  • {{.Name}} ({{.Type}})
  • {{end}}
{{end}} {{if .Columns.Modified}}
Modified Columns
    {{range .Columns.Modified}}
  • {{.Name}}
  • {{end}}
{{end}} {{end}} {{if .Indexes}} {{if .Indexes.Missing}}
Missing Indexes
    {{range .Indexes.Missing}}
  • {{.Name}}
  • {{end}}
{{end}} {{if .Indexes.Extra}}
Extra Indexes
    {{range .Indexes.Extra}}
  • {{.Name}}
  • {{end}}
{{end}} {{end}} {{if .Constraints}} {{if .Constraints.Missing}}
Missing Constraints
    {{range .Constraints.Missing}}
  • {{.Name}} ({{.Type}})
  • {{end}}
{{end}} {{if .Constraints.Extra}}
Extra Constraints
    {{range .Constraints.Extra}}
  • {{.Name}} ({{.Type}})
  • {{end}}
{{end}} {{end}}
{{end}} {{end}} {{end}} {{if .Sequences}} {{if .Sequences.Missing}}

Missing Sequences

{{end}} {{if .Sequences.Extra}}

Extra Sequences

{{end}} {{end}}
{{end}}
{{else}}
✓ No differences found between the databases
{{end}} `