Skip to content

Commit 25cd378

Browse files
feat: refactor migration system with improved execution flow (#15)
This PR refactors the migration system to improve code organization and reliability: - Consolidates Up/Down operations into a single `execute.go` file - Refactors runner logic for better maintainability and testability - Improves error handling and transaction management - Enhances migration file naming and metadata management - Adds proper support for respecting migration count limits - Optimizes database operations and query organization - Implements proper topological sorting with ascending/descending options These changes make the migration system more robust while preserving backward compatibility.
1 parent f4a63fe commit 25cd378

File tree

17 files changed

+422
-498
lines changed

17 files changed

+422
-498
lines changed

cmd/kat/command.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func addExec(c *cli.Context) error {
2929
return migration.Add(c, args[0])
3030
}
3131

32-
func up(c *cli.Context) error {
32+
func upExec(c *cli.Context) error {
3333
cfg, err := config.GetKatConfigFromCtx(c)
3434
if err != nil {
3535
return err
@@ -39,14 +39,14 @@ func up(c *cli.Context) error {
3939
dryRun := c.Bool("dry-run")
4040

4141
if dryRun {
42-
fmt.Fprintf(os.Stdout, "%sDRY RUN: Migrations will not be applied%s\n", output.StyleInfo, output.StyleReset)
42+
fmt.Printf("%sDRY RUN: Migrations will not be applied%s\n", output.StyleInfo, output.StyleReset)
4343
}
4444

4545
// Note: Retry is not used for migrations, only for the ping command
4646
return migration.Up(c, cfg, dryRun)
4747
}
4848

49-
func down(c *cli.Context) error {
49+
func downExec(c *cli.Context) error {
5050
cfg, err := config.GetKatConfigFromCtx(c)
5151
if err != nil {
5252
return err
@@ -56,7 +56,7 @@ func down(c *cli.Context) error {
5656
dryRun := c.Bool("dry-run")
5757

5858
if dryRun {
59-
fmt.Fprintf(os.Stdout, "%sDRY RUN: Migrations will not be rolled back%s\n", output.StyleInfo, output.StyleReset)
59+
fmt.Printf("%sDRY RUN: Migrations will not be rolled back%s\n", output.StyleInfo, output.StyleReset)
6060
}
6161

6262
// Note: Retry is not used for migrations, only for the ping command
@@ -68,7 +68,7 @@ func initialize(c *cli.Context) error {
6868
}
6969

7070
func getVersion(c *cli.Context) error {
71-
fmt.Fprintf(os.Stdout, "%sVersion: %s%s\n", output.StyleInfo, version.Version(), output.StyleReset)
71+
fmt.Printf("%sVersion: %s%s\n", output.StyleInfo, version.Version(), output.StyleReset)
7272
return nil
7373
}
7474

cmd/kat/main.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,26 +192,29 @@ var kat = &cli.App{
192192
Name: "up",
193193
Usage: "Run migrations",
194194
Description: "Apply migrations",
195-
Action: up,
195+
Action: upExec,
196196
Before: config.ParseConfig,
197-
Flags: []cli.Flag{configFlag, dryRunFlag},
197+
Flags: []cli.Flag{
198+
&cli.IntFlag{
199+
Name: "count",
200+
Aliases: []string{"n"},
201+
Usage: "number of migrations to apply (default: 0)",
202+
Value: 0,
203+
}, configFlag, dryRunFlag},
198204
},
199205
{
200206
Name: "down",
201207
Usage: "Rollback migrations",
202208
Description: "Rollback the most recent migration or specify a count with --count flag",
203-
Action: down,
209+
Action: downExec,
204210
Before: config.ParseConfig,
205211
Flags: []cli.Flag{
206212
&cli.IntFlag{
207213
Name: "count",
208214
Aliases: []string{"n"},
209215
Usage: "number of migrations to roll back (default: 1)",
210216
Value: 1,
211-
},
212-
configFlag,
213-
dryRunFlag,
214-
},
217+
}, configFlag, dryRunFlag},
215218
},
216219
{
217220
Name: "ping",
@@ -237,12 +240,12 @@ var kat = &cli.App{
237240

238241
errMsg := err.Error()
239242
if errMsg != "" {
240-
f := fmt.Sprintf("%s%s%s", output.StyleFailure, errMsg, output.StyleReset)
241-
fmt.Fprintln(os.Stderr, f)
243+
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s%s%s", output.StyleFailure, errMsg, output.StyleReset))
242244
}
243245

244246
// Determine exit code
245-
if exitErr, ok := err.(cli.ExitCoder); ok {
247+
var exitErr cli.ExitCoder
248+
if errors.As(err, &exitErr) {
246249
os.Exit(exitErr.ExitCode())
247250
}
248251
os.Exit(1)

doc/migration.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,18 @@ kat up
126126
When you run `kat up`, the following process occurs:
127127

128128
1. Kat scans your migrations directory for all migration folders
129-
2. Kat builds a directed acyclic graph (DAG) based on migration dependencies
130-
3. Kat sorts migrations using topological ordering to respect dependencies
131-
4. Kat connects to your database using your configuration
132-
5. If needed, Kat creates a tracking table (specified by `tablename` in your config)
133-
6. Kat reads the tracking table to determine which migrations have already been applied
134-
7. For each pending migration:
129+
2. Kat computes migration definitions, converting files into executable SQL queries
130+
3. Kat builds a directed acyclic graph (DAG) based on migration dependencies
131+
4. Kat sorts migrations using topological ordering to respect dependencies
132+
5. Kat connects to your database using your configuration
133+
6. If needed, Kat creates a tracking table (specified by `tablename` in your config)
134+
7. Kat reads the tracking table to determine which migrations have already been applied
135+
8. For each pending migration:
135136
- Kat begins a transaction
136-
- Kat executes the SQL in the up.sql file
137+
- Kat executes the SQL queries from the migration Definition
137138
- Kat records the migration in the tracking table
138139
- Kat commits the transaction
139-
8. Kat provides a summary of the applied migrations
140+
9. Kat provides a summary of the applied migrations
140141

141142
### Up Command Options
142143

@@ -180,14 +181,15 @@ kat down
180181

181182
When you run `kat down`, the following process occurs:
182183

183-
1. Kat connects to your database using your configuration
184-
2. Kat reads the tracking table to identify applied migrations
185-
3. By default, Kat selects the most recent migration for rollback
186-
4. Kat begins a transaction
187-
5. Kat executes the SQL in the down.sql file
188-
6. Kat removes the migration record from the tracking table
189-
7. Kat commits the transaction
190-
8. Kat provides a summary of the rolled back migrations
184+
1. Kat scans your migrations directory and computes migration definitions
185+
2. Kat connects to your database using your configuration
186+
3. Kat reads the tracking table to identify applied migrations
187+
4. By default, Kat selects the most recent migration for rollback
188+
5. Kat begins a transaction
189+
6. Kat executes the SQL queries from the migration Definition's DownQuery
190+
7. Kat removes the migration record from the tracking table
191+
8. Kat commits the transaction
192+
9. Kat provides a summary of the rolled back migrations
191193

192194
### Down Command Options
193195

internal/graph/graph.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type Graph struct {
3131
}
3232

3333
func (g *Graph) AddDefinition(def types.Definition) error {
34-
if err := g.graph.AddVertex(def); err != nil {
34+
if err := g.graph.AddVertex(def, graphlib.VertexAttribute("name", def.FileName())); err != nil {
3535
return errors.Wrap(err, "error adding vertex")
3636
}
3737

@@ -80,9 +80,16 @@ func (g *Graph) GetDefinition(timestamp int64) (types.Definition, error) {
8080
return g.graph.Vertex(timestamp)
8181
}
8282

83+
// TopologicalSort returns a valid topological ordering of all the vertices in the graph.
84+
// It uses StableTopologicalSort from the graph library to ensure that elements with
85+
// valid topological ordering are consistently returned in order of their timestamps (i < j),
86+
// making the results deterministic and predictable.
8387
func (g *Graph) TopologicalSort() ([]int64, error) {
84-
return graphlib.TopologicalSort(g.graph)
88+
return graphlib.StableTopologicalSort(g.graph, func(i, j int64) bool {
89+
return i < j
90+
})
8591
}
92+
8693
func (g *Graph) Order() (int, error) {
8794
return g.graph.Order()
8895
}

internal/migration/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func Add(c *cli.Context, name string) error {
3333
)
3434
migrationDirName := fmt.Sprintf("%d_%s", timestamp, sanitizedName)
3535

36-
m := types.Migration{
36+
m := types.TemporaryMigrationInfo{
3737
Up: filepath.Join(cfg.Migration.Directory, migrationDirName, "up.sql"),
3838
Down: filepath.Join(cfg.Migration.Directory, migrationDirName, "down.sql"),
3939
Metadata: filepath.Join(cfg.Migration.Directory, migrationDirName, "metadata.yaml"),

internal/migration/down.go

Lines changed: 0 additions & 168 deletions
This file was deleted.

0 commit comments

Comments
 (0)