Skip to content

Commit 38e539d

Browse files
Dump command prototype (#263)
1 parent 5df33b7 commit 38e539d

File tree

4 files changed

+153
-1
lines changed

4 files changed

+153
-1
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,12 @@ echo "CREATE TABLE bar (id varchar(255), message TEXT NOT NULL);" > schema/bar.s
138138
Apply the schema to a fresh database. [The connection string spec can be found here](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING).
139139
Setting the `PGPASSWORD` env var will override any password set in the connection string and is recommended.
140140
```bash
141-
pg-schema-diff apply --from-dsn "postgres://postgres:postgres@localhost:5432/postgres" --to-dir schema
141+
pg-schema-diff apply --from-dsn "postgres://postgres:postgres@localhost:5432/postgres" --to-dir schema
142+
```
143+
144+
Alternatively, if you have an existing database, you can dump its schema to use as a starting point:
145+
```bash
146+
mkdir -p schema && pg-schema-diff dump --dsn "postgres://postgres:postgres@localhost:5432/postgres" > schema/schema.sql
142147
```
143148

144149
## 2. Updating schema

cmd/pg-schema-diff/dump_cmd.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/jackc/pgx/v4"
10+
"github.com/spf13/cobra"
11+
"github.com/stripe/pg-schema-diff/pkg/diff"
12+
"github.com/stripe/pg-schema-diff/pkg/log"
13+
"github.com/stripe/pg-schema-diff/pkg/tempdb"
14+
)
15+
16+
func buildDumpCmd() *cobra.Command {
17+
cmd := &cobra.Command{
18+
Use: "dump",
19+
Short: "Dump the schema of a database as SQL DDL statements (effectively pg_dump)",
20+
}
21+
22+
connFlags := createConnectionFlags(cmd, "", "The database to dump")
23+
24+
var includeSchemas []string
25+
var excludeSchemas []string
26+
cmd.Flags().StringArrayVar(&includeSchemas, "include-schema", nil, "Include the specified schema in the dump")
27+
cmd.Flags().StringArrayVar(&excludeSchemas, "exclude-schema", nil, "Exclude the specified schema from the dump")
28+
29+
cmd.RunE = func(cmd *cobra.Command, args []string) error {
30+
connConfig, err := parseConnectionFlags(connFlags)
31+
if err != nil {
32+
return err
33+
}
34+
35+
cmd.SilenceUsage = true
36+
37+
plan, err := generateDump(cmd.Context(), generateDumpParams{
38+
connConfig: connConfig,
39+
includeSchemas: includeSchemas,
40+
excludeSchemas: excludeSchemas,
41+
})
42+
if err != nil {
43+
return err
44+
}
45+
46+
cmdPrintln(cmd, dumpToSql(plan))
47+
return nil
48+
}
49+
50+
return cmd
51+
}
52+
53+
type generateDumpParams struct {
54+
connConfig *pgx.ConnConfig
55+
includeSchemas []string
56+
excludeSchemas []string
57+
}
58+
59+
func generateDump(ctx context.Context, params generateDumpParams) (diff.Plan, error) {
60+
connPool, err := openDbWithPgxConfig(params.connConfig)
61+
if err != nil {
62+
return diff.Plan{}, fmt.Errorf("opening database connection: %w", err)
63+
}
64+
defer connPool.Close()
65+
connPool.SetMaxOpenConns(defaultMaxConnections)
66+
67+
tempDbFactory, err := tempdb.NewOnInstanceFactory(ctx, func(ctx context.Context, dbName string) (*sql.DB, error) {
68+
cfg := params.connConfig.Copy()
69+
cfg.Database = dbName
70+
return openDbWithPgxConfig(cfg)
71+
}, tempdb.WithRootDatabase(params.connConfig.Database))
72+
if err != nil {
73+
return diff.Plan{}, fmt.Errorf("creating temp db factory: %w", err)
74+
}
75+
defer func() {
76+
if err := tempDbFactory.Close(); err != nil {
77+
log.SimpleLogger().Errorf("error shutting down temp db factory: %v", err)
78+
}
79+
}()
80+
81+
plan, err := diff.Generate(ctx, diff.DDLSchemaSource([]string{}), diff.DBSchemaSource(connPool),
82+
diff.WithTempDbFactory(tempDbFactory),
83+
diff.WithIncludeSchemas(params.includeSchemas...),
84+
diff.WithExcludeSchemas(params.excludeSchemas...),
85+
diff.WithDoNotValidatePlan(),
86+
diff.WithNoConcurrentIndexOps(),
87+
)
88+
if err != nil {
89+
return diff.Plan{}, fmt.Errorf("generating plan: %w", err)
90+
}
91+
return plan, nil
92+
}
93+
94+
// dumpToSql converts the plan to clean SQL without metadata
95+
func dumpToSql(plan diff.Plan) string {
96+
sb := strings.Builder{}
97+
for i, stmt := range plan.Statements {
98+
sb.WriteString(stmt.DDL)
99+
sb.WriteString(";")
100+
if i < len(plan.Statements)-1 {
101+
sb.WriteString("\n\n")
102+
}
103+
}
104+
return sb.String()
105+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
func (suite *cmdTestSuite) TestDumpCmd() {
4+
type testCase struct {
5+
name string
6+
args []string
7+
dynamicArgs []dArgGenerator
8+
9+
// outputContains is a list of substrings that are expected to be contained in the stdout output of the command.
10+
outputContains []string
11+
// expectErrContains is a list of substrings that are expected to be contained in the error returned by
12+
// cmd.RunE. This is DISTINCT from stdErr.
13+
expectErrContains []string
14+
}
15+
16+
for _, tc := range []testCase{
17+
{
18+
name: "dump database with table",
19+
dynamicArgs: []dArgGenerator{
20+
tempDsnDArg(suite.pgEngine, "dsn", []string{
21+
"CREATE TABLE foobar(id INT PRIMARY KEY, name TEXT NOT NULL)",
22+
}),
23+
},
24+
outputContains: []string{
25+
"CREATE TABLE",
26+
"foobar",
27+
"id",
28+
"name",
29+
},
30+
},
31+
} {
32+
suite.Run(tc.name, func() {
33+
suite.runCmdWithAssertions(runCmdWithAssertionsParams{
34+
args: append([]string{"dump"}, tc.args...),
35+
dynamicArgs: tc.dynamicArgs,
36+
outputContains: tc.outputContains,
37+
expectErrContains: tc.expectErrContains,
38+
})
39+
})
40+
}
41+
}

cmd/pg-schema-diff/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ func buildRootCmd() *cobra.Command {
1313
}
1414
rootCmd.AddCommand(buildPlanCmd())
1515
rootCmd.AddCommand(buildApplyCmd())
16+
rootCmd.AddCommand(buildDumpCmd())
1617
rootCmd.AddCommand(buildVersionCmd())
1718
return rootCmd
1819
}

0 commit comments

Comments
 (0)