Skip to content

Commit 924ca56

Browse files
committed
pw: add db status command
Allow listing all database schema migrations and identify which ones have been applied. Signed-off-by: Robin Jarry <robin@jarry.cc>
1 parent 3bd7b52 commit 924ca56

5 files changed

Lines changed: 68 additions & 6 deletions

File tree

cmd/pw/db/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package db
88
// CLI groups the database management subcommands.
99
type CLI struct {
1010
Sync SyncCmd `cmd:"" help:"Create or update the database schema."`
11+
Status StatusCmd `cmd:"" help:"Show database schema migrations status."`
1112
Export ExportCmd `cmd:"" help:"Export database contents as SQL."`
1213
Import ImportCmd `cmd:"" name:"import" help:"Import SQL data from stdin."`
1314
}

cmd/pw/db/status.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Patchwork - automated patch tracking system
2+
// Copyright (C) The Patchwork Contributors (see CONTRIBUTORS)
3+
//
4+
// SPDX-License-Identifier: GPL-2.0-or-later
5+
6+
package db
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"os"
12+
"text/tabwriter"
13+
14+
"github.com/getpatchwork/patchwork/cmd/pw/pw"
15+
"github.com/getpatchwork/patchwork/pkg/db/migrations"
16+
)
17+
18+
type StatusCmd struct{}
19+
20+
func (c *StatusCmd) Run(ctx context.Context) error {
21+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
22+
fmt.Fprintf(w, "SCHEMA_MIGRATION\tAPPLIED\n")
23+
for _, m := range migrations.ListMigrations(ctx, pw.GetDB(ctx)) {
24+
if m.AppliedAt.IsZero() {
25+
fmt.Fprintf(w, "%s\t%s\n", m.Name, "")
26+
} else {
27+
fmt.Fprintf(w, "%s\t%s\n", m.Name, m.AppliedAt)
28+
}
29+
}
30+
return w.Flush()
31+
}

docs/deployment/management.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ Database
3030
Create or update the database schema. On a fresh database, this creates all
3131
tables. On an existing one, it applies pending migrations.
3232

33+
34+
``pw db status``
35+
~~~~~~~~~~~~~~
36+
37+
.. program:: pw db status
38+
39+
List all database schema migrations and the date at which they were applied.
40+
41+
3342
``pw db export``
3443
~~~~~~~~~~~~~~~~
3544

pkg/db/migrations/migrations.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import (
1111
"path/filepath"
1212
"regexp"
1313
"runtime"
14+
"slices"
1415
"sort"
1516
"strconv"
1617
"time"
1718

1819
"github.com/uptrace/bun"
1920

2021
"github.com/getpatchwork/patchwork/pkg/db"
22+
"github.com/getpatchwork/patchwork/pkg/log"
2123
)
2224

2325
type MigrationFunc func(ctx context.Context, tx bun.Tx) error
@@ -64,7 +66,7 @@ func Register(up, down MigrationFunc) {
6466
})
6567
}
6668

67-
type schemaMigration struct {
69+
type SchemaMigration struct {
6870
bun.BaseModel `bun:"table:schema_migrations"`
6971
Num int `bun:"num,pk"`
7072
Name string `bun:"name,notnull"`
@@ -73,15 +75,15 @@ type schemaMigration struct {
7375

7476
func ensureTable(ctx context.Context, database bun.IDB) error {
7577
_, err := database.NewCreateTable().
76-
Model((*schemaMigration)(nil)).
78+
Model((*SchemaMigration)(nil)).
7779
IfNotExists().
7880
Exec(ctx)
7981
return err
8082
}
8183

8284
func lastApplied(ctx context.Context, database *bun.DB) (int, error) {
8385
var num int
84-
err := database.NewSelect().Model((*schemaMigration)(nil)).
86+
err := database.NewSelect().Model((*SchemaMigration)(nil)).
8587
ColumnExpr("COALESCE(MAX(num), 0)").
8688
Scan(ctx, &num)
8789
return num, err
@@ -96,6 +98,22 @@ func sorted() []Migration {
9698
return out
9799
}
98100

101+
func ListMigrations(ctx context.Context, database *bun.DB) []SchemaMigration {
102+
var out []SchemaMigration
103+
104+
// first get all applied from DB (ignore errors)
105+
database.NewSelect().Model(&out).OrderExpr("num ASC").Scan(ctx)
106+
107+
for _, m := range sorted() {
108+
if !slices.ContainsFunc(out, func(s SchemaMigration) bool { return s.Num == m.Num }) {
109+
// then, fill in unapplied ones (AppliedAt will be zero)
110+
out = append(out, SchemaMigration{Num: m.Num, Name: m.Name})
111+
}
112+
}
113+
114+
return out
115+
}
116+
99117
func RunMigrations(ctx context.Context, database *bun.DB) error {
100118
if !tableExists(ctx, database, "schema_migrations") {
101119
return bootstrap(ctx, database)
@@ -118,6 +136,8 @@ func RunMigrations(ctx context.Context, database *bun.DB) error {
118136
continue
119137
}
120138

139+
log.Noticef("Running database migration %s", m.Name)
140+
121141
tx, err := database.BeginTx(ctx, nil)
122142
if err != nil {
123143
return fmt.Errorf("%s: begin tx: %w", m.Name, err)
@@ -128,7 +148,7 @@ func RunMigrations(ctx context.Context, database *bun.DB) error {
128148
return fmt.Errorf("%s: %w", m.Name, err)
129149
}
130150

131-
if _, err := tx.NewInsert().Model(&schemaMigration{
151+
if _, err := tx.NewInsert().Model(&SchemaMigration{
132152
Num: m.Num,
133153
Name: m.Name,
134154
AppliedAt: time.Now(),
@@ -182,7 +202,7 @@ func Rollback(ctx context.Context, database *bun.DB) error {
182202
return fmt.Errorf("%s: %w", last.Name, err)
183203
}
184204

185-
if _, err := tx.NewDelete().Model((*schemaMigration)(nil)).
205+
if _, err := tx.NewDelete().Model((*SchemaMigration)(nil)).
186206
Where("num = ?", last.Num).Exec(ctx); err != nil {
187207
_ = tx.Rollback()
188208
return fmt.Errorf("%s: remove record: %w", last.Name, err)
@@ -224,7 +244,7 @@ func bootstrap(ctx context.Context, database *bun.DB) error {
224244
}
225245

226246
for _, m := range sorted() {
227-
if _, err := tx.NewInsert().Model(&schemaMigration{
247+
if _, err := tx.NewInsert().Model(&SchemaMigration{
228248
Num: m.Num,
229249
Name: m.Name,
230250
AppliedAt: time.Now(),

pkg/db/schema.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (idx *tableIndex) create(model any, ctx context.Context, database bun.IDB)
6868
tokens = append(tokens, name)
6969
}
7070
name := strings.Join(tokens, "_")
71+
log.Noticef("creating index %q", name)
7172
_, err := c.Index(name).Exec(ctx)
7273
return err
7374
}

0 commit comments

Comments
 (0)