Skip to content

Commit

Permalink
Add JSON output support to pg-schema-diff plan. (#175)
Browse files Browse the repository at this point in the history
Add JSON output support to pg-schema-diff plan.
  • Loading branch information
tonycosentini committed Sep 21, 2024
1 parent bc41f09 commit f036cb2
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 9 deletions.
59 changes: 54 additions & 5 deletions cmd/pg-schema-diff/plan_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"regexp"
Expand Down Expand Up @@ -55,12 +57,9 @@ func buildPlanCmd() *cobra.Command {
plan, err := generatePlan(context.Background(), logger, connConfig, planConfig)
if err != nil {
return err
} else if len(plan.Statements) == 0 {
fmt.Println("Schema matches expected. No plan generated")
return nil
}
fmt.Printf("\n%s\n", header("Generated plan"))
fmt.Println(planToPrettyS(plan))

fmt.Println(planFlags.outputFormat.convertToOutputString(plan))
return nil
}

Expand All @@ -78,6 +77,11 @@ type (
targetDatabaseDSN string
}

outputFormat struct {
identifier string
convertToOutputString func(diff.Plan) string
}

planFlags struct {
dbSchemaSourceFlags schemaSourceFlags

Expand All @@ -89,6 +93,7 @@ type (
statementTimeoutModifiers []string
lockTimeoutModifiers []string
insertStatements []string
outputFormat outputFormat
}

timeoutModifier struct {
Expand All @@ -115,8 +120,35 @@ type (
}
)

var (
outputFormatPretty outputFormat = outputFormat{identifier: "pretty", convertToOutputString: planToPrettyS}
outputFormatJson outputFormat = outputFormat{identifier: "json", convertToOutputString: planToJsonS}
)

func (e *outputFormat) String() string {
return string(e.identifier)
}

func (e *outputFormat) Set(v string) error {
switch v {
case "pretty":
*e = outputFormatPretty
return nil
case "json":
*e = outputFormatJson
return nil
default:
return errors.New(`must be one of "pretty" or "json"`)
}
}

func (e *outputFormat) Type() string {
return "outputFormat"
}

func createPlanFlags(cmd *cobra.Command) *planFlags {
flags := &planFlags{}
flags.outputFormat = outputFormatPretty

schemaSourceFlagsVar(cmd, &flags.dbSchemaSourceFlags)

Expand All @@ -139,6 +171,8 @@ func createPlanFlags(cmd *cobra.Command) *planFlags {
),
)

cmd.Flags().Var(&flags.outputFormat, "output-format", "Change the output format for what is printed. Defaults to pretty-printed human-readable output. (options: pretty, json)")

return flags
}

Expand Down Expand Up @@ -428,6 +462,13 @@ func applyPlanModifiers(
func planToPrettyS(plan diff.Plan) string {
sb := strings.Builder{}

if len(plan.Statements) == 0 {
sb.WriteString("Schema matches expected. No plan generated")
return sb.String()
}

sb.WriteString(fmt.Sprintf("%s\n", header("Generated plan")))

// We are going to put a statement index before each statement. To do that,
// we need to find how many characters are in the largest index, so we can provide the appropriate amount
// of padding before the statements to align all of them
Expand Down Expand Up @@ -471,3 +512,11 @@ func hazardToPrettyS(hazard diff.MigrationHazard) string {
return hazard.Type
}
}

func planToJsonS(plan diff.Plan) string {
jsonData, err := json.MarshalIndent(plan, "", " ")
if err != nil {
panic(err)
}
return string(jsonData)
}
23 changes: 19 additions & 4 deletions pkg/diff/plan.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package diff

import (
"encoding/json"
"fmt"
"regexp"
"time"
Expand All @@ -25,8 +26,8 @@ const (

// MigrationHazard represents a hazard that a statement poses to a database
type MigrationHazard struct {
Type MigrationHazardType
Message string
Type MigrationHazardType `json:"type"`
Message string `json:"message"`
}

func (p MigrationHazard) String() string {
Expand All @@ -47,18 +48,32 @@ type Statement struct {
Hazards []MigrationHazard
}

func (s Statement) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
DDL string `json:"ddl"`
Timeout int64 `json:"timeout_ms"`
LockTimeout int64 `json:"lock_timeout_ms"`
Hazards []MigrationHazard `json:"hazards"`
}{
DDL: s.DDL,
Timeout: s.Timeout.Milliseconds(),
LockTimeout: s.LockTimeout.Milliseconds(),
Hazards: s.Hazards,
})
}

func (s Statement) ToSQL() string {
return s.DDL + ";"
}

// Plan represents a set of statements to be executed in order to migrate a database from schema A to schema B
type Plan struct {
// Statements is the set of statements to be executed in order to migrate a database from schema A to schema B
Statements []Statement
Statements []Statement `json:"statements"`
// CurrentSchemaHash is the hash of the current schema, schema A. If you serialize this plans somewhere and
// plan on running them later, you should verify that the current schema hash matches the current schema hash.
// To get the current schema hash, you can use schema.GetPublicSchemaHash(ctx, conn)
CurrentSchemaHash string
CurrentSchemaHash string `json:"current_schema_hash"`
}

// ApplyStatementTimeoutModifier applies the given timeout to all statements that match the given regex
Expand Down

0 comments on commit f036cb2

Please sign in to comment.