Skip to content

Commit f036cb2

Browse files
Add JSON output support to pg-schema-diff plan. (#175)
Add JSON output support to pg-schema-diff plan.
1 parent bc41f09 commit f036cb2

File tree

2 files changed

+73
-9
lines changed

2 files changed

+73
-9
lines changed

cmd/pg-schema-diff/plan_cmd.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package main
33
import (
44
"context"
55
"database/sql"
6+
"encoding/json"
7+
"errors"
68
"fmt"
79
"io"
810
"regexp"
@@ -55,12 +57,9 @@ func buildPlanCmd() *cobra.Command {
5557
plan, err := generatePlan(context.Background(), logger, connConfig, planConfig)
5658
if err != nil {
5759
return err
58-
} else if len(plan.Statements) == 0 {
59-
fmt.Println("Schema matches expected. No plan generated")
60-
return nil
6160
}
62-
fmt.Printf("\n%s\n", header("Generated plan"))
63-
fmt.Println(planToPrettyS(plan))
61+
62+
fmt.Println(planFlags.outputFormat.convertToOutputString(plan))
6463
return nil
6564
}
6665

@@ -78,6 +77,11 @@ type (
7877
targetDatabaseDSN string
7978
}
8079

80+
outputFormat struct {
81+
identifier string
82+
convertToOutputString func(diff.Plan) string
83+
}
84+
8185
planFlags struct {
8286
dbSchemaSourceFlags schemaSourceFlags
8387

@@ -89,6 +93,7 @@ type (
8993
statementTimeoutModifiers []string
9094
lockTimeoutModifiers []string
9195
insertStatements []string
96+
outputFormat outputFormat
9297
}
9398

9499
timeoutModifier struct {
@@ -115,8 +120,35 @@ type (
115120
}
116121
)
117122

123+
var (
124+
outputFormatPretty outputFormat = outputFormat{identifier: "pretty", convertToOutputString: planToPrettyS}
125+
outputFormatJson outputFormat = outputFormat{identifier: "json", convertToOutputString: planToJsonS}
126+
)
127+
128+
func (e *outputFormat) String() string {
129+
return string(e.identifier)
130+
}
131+
132+
func (e *outputFormat) Set(v string) error {
133+
switch v {
134+
case "pretty":
135+
*e = outputFormatPretty
136+
return nil
137+
case "json":
138+
*e = outputFormatJson
139+
return nil
140+
default:
141+
return errors.New(`must be one of "pretty" or "json"`)
142+
}
143+
}
144+
145+
func (e *outputFormat) Type() string {
146+
return "outputFormat"
147+
}
148+
118149
func createPlanFlags(cmd *cobra.Command) *planFlags {
119150
flags := &planFlags{}
151+
flags.outputFormat = outputFormatPretty
120152

121153
schemaSourceFlagsVar(cmd, &flags.dbSchemaSourceFlags)
122154

@@ -139,6 +171,8 @@ func createPlanFlags(cmd *cobra.Command) *planFlags {
139171
),
140172
)
141173

174+
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)")
175+
142176
return flags
143177
}
144178

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

465+
if len(plan.Statements) == 0 {
466+
sb.WriteString("Schema matches expected. No plan generated")
467+
return sb.String()
468+
}
469+
470+
sb.WriteString(fmt.Sprintf("%s\n", header("Generated plan")))
471+
431472
// We are going to put a statement index before each statement. To do that,
432473
// we need to find how many characters are in the largest index, so we can provide the appropriate amount
433474
// of padding before the statements to align all of them
@@ -471,3 +512,11 @@ func hazardToPrettyS(hazard diff.MigrationHazard) string {
471512
return hazard.Type
472513
}
473514
}
515+
516+
func planToJsonS(plan diff.Plan) string {
517+
jsonData, err := json.MarshalIndent(plan, "", " ")
518+
if err != nil {
519+
panic(err)
520+
}
521+
return string(jsonData)
522+
}

pkg/diff/plan.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package diff
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"regexp"
67
"time"
@@ -25,8 +26,8 @@ const (
2526

2627
// MigrationHazard represents a hazard that a statement poses to a database
2728
type MigrationHazard struct {
28-
Type MigrationHazardType
29-
Message string
29+
Type MigrationHazardType `json:"type"`
30+
Message string `json:"message"`
3031
}
3132

3233
func (p MigrationHazard) String() string {
@@ -47,18 +48,32 @@ type Statement struct {
4748
Hazards []MigrationHazard
4849
}
4950

51+
func (s Statement) MarshalJSON() ([]byte, error) {
52+
return json.Marshal(&struct {
53+
DDL string `json:"ddl"`
54+
Timeout int64 `json:"timeout_ms"`
55+
LockTimeout int64 `json:"lock_timeout_ms"`
56+
Hazards []MigrationHazard `json:"hazards"`
57+
}{
58+
DDL: s.DDL,
59+
Timeout: s.Timeout.Milliseconds(),
60+
LockTimeout: s.LockTimeout.Milliseconds(),
61+
Hazards: s.Hazards,
62+
})
63+
}
64+
5065
func (s Statement) ToSQL() string {
5166
return s.DDL + ";"
5267
}
5368

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

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

0 commit comments

Comments
 (0)