mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
feat(pgmigrate/examples): add minimal migration runner example
Demonstrates pgmigrate with embedded SQL migrations: up, down, status, and reset commands. Two migrations (init + create-todos) show the minimum viable pattern for using sqlmigrate with PostgreSQL.
This commit is contained in:
parent
58c6301678
commit
a0e59d7a47
22
database/sqlmigrate/pgmigrate/examples/todos/go.mod
Normal file
22
database/sqlmigrate/pgmigrate/examples/todos/go.mod
Normal file
@ -0,0 +1,22 @@
|
||||
module github.com/therootcompany/golib/database/sqlmigrate/pgmigrate/examples/todos
|
||||
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/jackc/pgx/v5 v5.9.1
|
||||
github.com/therootcompany/golib/database/sqlmigrate v1.0.0
|
||||
github.com/therootcompany/golib/database/sqlmigrate/pgmigrate v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/therootcompany/golib/database/sqlmigrate => ../../..
|
||||
github.com/therootcompany/golib/database/sqlmigrate/pgmigrate => ../..
|
||||
)
|
||||
26
database/sqlmigrate/pgmigrate/examples/todos/go.sum
Normal file
26
database/sqlmigrate/pgmigrate/examples/todos/go.sum
Normal file
@ -0,0 +1,26 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
141
database/sqlmigrate/pgmigrate/examples/todos/main.go
Normal file
141
database/sqlmigrate/pgmigrate/examples/todos/main.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Command todos demonstrates pgmigrate with embedded SQL migrations.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// export PG_URL='postgres://user:pass@localhost:5432/dbname?sslmode=require'
|
||||
// go run . up # apply all pending migrations
|
||||
// go run . up 1 # apply next 1 migration
|
||||
// go run . down # roll back last migration
|
||||
// go run . down 2 # roll back last 2 migrations
|
||||
// go run . status # show applied and pending
|
||||
// go run . reset # roll back all migrations
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
stdfs "io/fs"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/therootcompany/golib/database/sqlmigrate"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate/pgmigrate"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
ctx := context.Background()
|
||||
|
||||
pgURL := os.Getenv("PG_URL")
|
||||
if pgURL == "" {
|
||||
return fmt.Errorf("PG_URL environment variable is required")
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(ctx, pgURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting: %w", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
subFS, err := stdfs.Sub(migrationsFS, "migrations")
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading migrations: %w", err)
|
||||
}
|
||||
migrations, err := sqlmigrate.Collect(subFS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("collecting migrations: %w", err)
|
||||
}
|
||||
|
||||
runner := pgmigrate.New(pool)
|
||||
|
||||
args := os.Args[1:]
|
||||
if len(args) == 0 {
|
||||
args = []string{"status"}
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "up":
|
||||
n := -1
|
||||
if len(args) > 1 {
|
||||
n, err = strconv.Atoi(args[1])
|
||||
if err != nil || n < 1 {
|
||||
return fmt.Errorf("up count must be a positive integer")
|
||||
}
|
||||
}
|
||||
applied, err := sqlmigrate.Up(ctx, runner, migrations, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(applied) == 0 {
|
||||
fmt.Println("Already up-to-date.")
|
||||
} else {
|
||||
for _, name := range applied {
|
||||
fmt.Printf(" applied: %s\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
case "down":
|
||||
n := 1
|
||||
if len(args) > 1 {
|
||||
n, err = strconv.Atoi(args[1])
|
||||
if err != nil || n < 1 {
|
||||
return fmt.Errorf("down count must be a positive integer")
|
||||
}
|
||||
}
|
||||
rolled, err := sqlmigrate.Down(ctx, runner, migrations, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rolled) == 0 {
|
||||
fmt.Println("Nothing to roll back.")
|
||||
} else {
|
||||
for _, name := range rolled {
|
||||
fmt.Printf(" rolled back: %s\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
case "status":
|
||||
status, err := sqlmigrate.GetStatus(ctx, runner, migrations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Applied: %d\n", len(status.Applied))
|
||||
for _, name := range status.Applied {
|
||||
fmt.Printf(" %s\n", name)
|
||||
}
|
||||
fmt.Printf("Pending: %d\n", len(status.Pending))
|
||||
for _, name := range status.Pending {
|
||||
fmt.Printf(" %s\n", name)
|
||||
}
|
||||
|
||||
case "reset":
|
||||
rolled, err := sqlmigrate.Down(ctx, runner, migrations, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rolled) == 0 {
|
||||
fmt.Println("Nothing to reset.")
|
||||
} else {
|
||||
for _, name := range rolled {
|
||||
fmt.Printf(" rolled back: %s\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown command %q (use: up, down, status, reset)", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
DELETE FROM _migrations WHERE id = '00000001';
|
||||
DROP TABLE IF EXISTS _migrations;
|
||||
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS _migrations (
|
||||
id CHAR(8) PRIMARY KEY,
|
||||
name VARCHAR(80) NULL UNIQUE,
|
||||
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO _migrations (name, id) VALUES ('0001_init-migrations', '00000001');
|
||||
@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS todos;
|
||||
DELETE FROM _migrations WHERE id = 'a1b2c3d4';
|
||||
@ -0,0 +1,8 @@
|
||||
CREATE TABLE todos (
|
||||
id CHAR(8) PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
done BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO _migrations (name, id) VALUES ('0002_create-todos', 'a1b2c3d4');
|
||||
Loading…
x
Reference in New Issue
Block a user