mirror of
https://github.com/therootcompany/golib.git
synced 2026-04-24 04:38:02 +00:00
feat(skills): add sqlmigrate skill index and per-database skills
Index skill (use-sqlmigrate) plus focused skills for CLI usage, Go library integration, and per-database conventions (PostgreSQL, MySQL/MariaDB, SQLite, SQL Server).
This commit is contained in:
parent
5b3a4be3c2
commit
0c1eb1f125
117
skills/use-sql-migrate-cli/SKILL.md
Normal file
117
skills/use-sql-migrate-cli/SKILL.md
Normal file
@ -0,0 +1,117 @@
|
||||
---
|
||||
name: use-sql-migrate-cli
|
||||
description: sql-migrate CLI tool for database migrations. Use when initializing migrations, creating migration files, running up/down, checking status, or generating migration scripts. Covers psql, mariadb, mysql, sqlite, sqlcmd.
|
||||
depends: [use-sqlmigrate]
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
webi go
|
||||
go install github.com/therootcompany/golib/cmd/sql-migrate/v2@latest
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### init
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ init --sql-command <psql|mariadb|mysql|sqlite|sqlcmd>
|
||||
```
|
||||
|
||||
Creates: migrations directory, `0001-01-01-001000_init-migrations.{up,down}.sql`,
|
||||
`migrations.log`, `_migrations.sql` query file.
|
||||
|
||||
MUST: Run the generated init script to create the `_migrations` table:
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ up | sh
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ create add-user-tables
|
||||
```
|
||||
|
||||
Generates a canonically-named up/down pair with a random 8-hex-char ID:
|
||||
|
||||
```
|
||||
2026-04-09-001000_add-user-tables.up.sql
|
||||
2026-04-09-001000_add-user-tables.down.sql
|
||||
```
|
||||
|
||||
If files for today already exist, the number increments by 1000.
|
||||
|
||||
### up / down
|
||||
|
||||
```sh
|
||||
# apply ALL pending migrations
|
||||
sql-migrate -d ./sql/migrations/ up | sh
|
||||
|
||||
# apply next 2 pending
|
||||
sql-migrate -d ./sql/migrations/ up 2 | sh
|
||||
|
||||
# roll back 1 (default)
|
||||
sql-migrate -d ./sql/migrations/ down | sh
|
||||
|
||||
# roll back 3
|
||||
sql-migrate -d ./sql/migrations/ down 3 | sh
|
||||
```
|
||||
|
||||
Output is a shell script. Review before piping to `sh`.
|
||||
|
||||
### status
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ status
|
||||
```
|
||||
|
||||
Shows applied (reverse order) and pending migrations. Does not execute anything.
|
||||
|
||||
### sync
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ sync | sh
|
||||
```
|
||||
|
||||
Reloads `migrations.log` from the database. Run after upgrading sql-migrate.
|
||||
|
||||
### list
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ list
|
||||
```
|
||||
|
||||
Lists all up/down migration files found.
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Default | Purpose |
|
||||
|------|---------|---------|
|
||||
| `-d <dir>` | `./sql/migrations/` | Migrations directory |
|
||||
| `--sql-command` | `psql` | SQL command template (init only) |
|
||||
| `--migrations-log` | `../migrations.log` | Log file path relative to migrations dir (init only) |
|
||||
|
||||
## SQL command aliases
|
||||
|
||||
| Alias | Expands to |
|
||||
|-------|-----------|
|
||||
| `psql`, `postgres`, `postgresql`, `pg`, `plpgsql` | `psql "$PG_URL" -v ON_ERROR_STOP=on --no-align --tuples-only --file %s` |
|
||||
| `mariadb` | `mariadb --defaults-extra-file="$MY_CNF" --silent --skip-column-names --raw < %s` |
|
||||
| `mysql`, `my` | `mysql --defaults-extra-file="$MY_CNF" --silent --skip-column-names --raw < %s` |
|
||||
| `sqlite`, `sqlite3`, `lite` | `sqlite3 "$SQLITE_PATH" < %s` |
|
||||
| `sqlcmd`, `mssql`, `sqlserver` | `sqlcmd --exit-on-error --headers -1 --trim-spaces --encrypt-connection strict --input-file %s` |
|
||||
|
||||
Custom commands: pass any string with `%s` as the file placeholder.
|
||||
|
||||
## Configuration
|
||||
|
||||
Stored in the initial migration file as comments:
|
||||
|
||||
```sql
|
||||
-- sql_command: psql "$PG_URL" -v ON_ERROR_STOP=on --no-align --tuples-only --file %s
|
||||
-- migrations_log: ../migrations.log
|
||||
```
|
||||
|
||||
These are read by the CLI on every run. Edit them to change the sql command or log path.
|
||||
151
skills/use-sql-migrate-golang/SKILL.md
Normal file
151
skills/use-sql-migrate-golang/SKILL.md
Normal file
@ -0,0 +1,151 @@
|
||||
---
|
||||
name: use-sql-migrate-golang
|
||||
description: Embed SQL migrations in Go applications using sqlmigrate library. Use when writing Go code that runs migrations on startup, implements auto-migrate, or uses the Migrator interface. Covers pgmigrate, mymigrate, litemigrate, msmigrate.
|
||||
depends: [use-sqlmigrate, go-stack]
|
||||
---
|
||||
|
||||
## Modules
|
||||
|
||||
Each backend is a separate Go module. Import only what you need:
|
||||
|
||||
| Module | Import path |
|
||||
|--------|-------------|
|
||||
| Core | `github.com/therootcompany/golib/database/sqlmigrate` |
|
||||
| PostgreSQL | `github.com/therootcompany/golib/database/sqlmigrate/pgmigrate` |
|
||||
| MySQL/MariaDB | `github.com/therootcompany/golib/database/sqlmigrate/mymigrate` |
|
||||
| SQLite | `github.com/therootcompany/golib/database/sqlmigrate/litemigrate` |
|
||||
| SQL Server | `github.com/therootcompany/golib/database/sqlmigrate/msmigrate` |
|
||||
|
||||
## Core API
|
||||
|
||||
```go
|
||||
// Collect reads .up.sql/.down.sql pairs from an fs.FS
|
||||
scripts, err := sqlmigrate.Collect(migrationsFS, "sql/migrations")
|
||||
|
||||
// Apply all pending migrations
|
||||
applied, err := sqlmigrate.Latest(ctx, runner, scripts)
|
||||
|
||||
// Apply n pending migrations (-1 = all)
|
||||
applied, err := sqlmigrate.Up(ctx, runner, scripts, n)
|
||||
|
||||
// Roll back n migrations (-1 = all, default pattern: 1)
|
||||
rolled, err := sqlmigrate.Down(ctx, runner, scripts, n)
|
||||
|
||||
// Roll back all migrations
|
||||
rolled, err := sqlmigrate.Drop(ctx, runner, scripts)
|
||||
|
||||
// Check status
|
||||
status, err := sqlmigrate.GetStatus(ctx, runner, scripts)
|
||||
// status.Applied, status.Pending
|
||||
```
|
||||
|
||||
## Key types
|
||||
|
||||
```go
|
||||
type Migration struct {
|
||||
ID string // 8-char hex from INSERT statement
|
||||
Name string // e.g. "2026-04-05-001000_create-todos"
|
||||
}
|
||||
|
||||
type Script struct {
|
||||
Migration
|
||||
Up string // .up.sql content
|
||||
Down string // .down.sql content
|
||||
}
|
||||
|
||||
type Migrator interface {
|
||||
ExecUp(ctx context.Context, m Migration, sql string) error
|
||||
ExecDown(ctx context.Context, m Migration, sql string) error
|
||||
Applied(ctx context.Context) ([]Migration, error)
|
||||
}
|
||||
```
|
||||
|
||||
## Embedding migrations
|
||||
|
||||
MUST: Use `embed.FS` to bundle migration files into the binary:
|
||||
|
||||
```go
|
||||
//go:embed sql/migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
```
|
||||
|
||||
## Backend setup pattern
|
||||
|
||||
MUST: Backends take a single connection, not a pool.
|
||||
|
||||
### database/sql backends (MySQL, SQLite, SQL Server)
|
||||
|
||||
```go
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
// ...
|
||||
|
||||
// acquire a dedicated connection for migrations
|
||||
conn, err := db.Conn(ctx)
|
||||
// ...
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
runner := mymigrate.New(conn) // or litemigrate.New(conn), msmigrate.New(conn)
|
||||
```
|
||||
|
||||
### pgx backend (PostgreSQL)
|
||||
|
||||
```go
|
||||
// single connection, not pool
|
||||
conn, err := pgx.Connect(ctx, pgURL)
|
||||
// ...
|
||||
defer func() { _ = conn.Close(ctx) }()
|
||||
|
||||
runner := pgmigrate.New(conn)
|
||||
```
|
||||
|
||||
## Auto-migrate on startup
|
||||
|
||||
Common pattern — run all pending migrations before serving:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// ... open db, get conn ...
|
||||
|
||||
scripts := mustCollectMigrations()
|
||||
runner := litemigrate.New(conn)
|
||||
|
||||
// apply all pending (idempotent)
|
||||
if _, err := sqlmigrate.Latest(ctx, runner, scripts); err != nil {
|
||||
log.Fatalf("auto-migrate: %v", err)
|
||||
}
|
||||
|
||||
// close migration conn, use db/pool for app queries
|
||||
_ = conn.Close()
|
||||
|
||||
// ... start serving ...
|
||||
}
|
||||
```
|
||||
|
||||
## Example app structure
|
||||
|
||||
```
|
||||
my-app/
|
||||
main.go # flag parsing, DB setup, auto-migrate, dispatch
|
||||
demo.go # app-specific CRUD (uses *sql.DB for queries)
|
||||
go.mod
|
||||
sql/
|
||||
migrations/
|
||||
0001-01-01-001000_init-migrations.up.sql
|
||||
0001-01-01-001000_init-migrations.down.sql
|
||||
2026-04-05-001000_create-todos.up.sql
|
||||
2026-04-05-001000_create-todos.down.sql
|
||||
```
|
||||
|
||||
## Migrate subcommand pattern
|
||||
|
||||
Expose `migrate up/down/status/reset` as a subcommand:
|
||||
|
||||
```go
|
||||
case "migrate":
|
||||
err = runMigrate(ctx, runner, migrations, subArgs)
|
||||
case "add":
|
||||
autoMigrate(ctx, runner, migrations)
|
||||
err = runAdd(ctx, db, subArgs)
|
||||
```
|
||||
|
||||
See example apps in `cmd/sql-migrate/examples/` for full implementations.
|
||||
67
skills/use-sql-migrate-mysql/SKILL.md
Normal file
67
skills/use-sql-migrate-mysql/SKILL.md
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
name: use-sql-migrate-mysql
|
||||
description: MySQL and MariaDB migrations with sql-migrate and mymigrate. Use when setting up MySQL/MariaDB migrations, configuring multiStatements, or MY_CNF credentials.
|
||||
depends: [use-sqlmigrate]
|
||||
---
|
||||
|
||||
## CLI setup
|
||||
|
||||
```sh
|
||||
# MariaDB
|
||||
sql-migrate -d ./sql/migrations/ init --sql-command mariadb
|
||||
|
||||
# MySQL
|
||||
sql-migrate -d ./sql/migrations/ init --sql-command mysql
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
```sh
|
||||
# .env
|
||||
MY_URL='user:pass@tcp(localhost:3306)/mydb?multiStatements=true&parseTime=true'
|
||||
MY_CNF='./my.cnf'
|
||||
```
|
||||
|
||||
MUST: Include `multiStatements=true` in the DSN. mymigrate validates this on first exec and returns an error if missing.
|
||||
|
||||
## Credentials file (for CLI)
|
||||
|
||||
The CLI uses `--defaults-extra-file` to avoid passwords in command args:
|
||||
|
||||
```ini
|
||||
# my.cnf
|
||||
[client]
|
||||
host=localhost
|
||||
port=3306
|
||||
database=mydb
|
||||
user=appuser
|
||||
password=secret
|
||||
```
|
||||
|
||||
## Go library
|
||||
|
||||
```go
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate/mymigrate"
|
||||
)
|
||||
|
||||
db, err := sql.Open("mysql", myURL)
|
||||
conn, err := db.Conn(ctx)
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
runner := mymigrate.New(conn)
|
||||
applied, err := sqlmigrate.Latest(ctx, runner, scripts)
|
||||
```
|
||||
|
||||
## SQL dialect notes
|
||||
|
||||
- DDL statements (CREATE/ALTER/DROP) auto-commit in MySQL — partial failures possible on multi-statement down migrations
|
||||
- `INSERT IGNORE` for idempotent seeds (not `ON CONFLICT`)
|
||||
- `NOW()` for current timestamp
|
||||
- String concatenation: `CONCAT(id, CHAR(9), name)` (used by sync query)
|
||||
- `ON UPDATE CURRENT_TIMESTAMP` for auto-updated timestamps
|
||||
- Error 1146 = table doesn't exist (handled automatically by mymigrate)
|
||||
64
skills/use-sql-migrate-postgres/SKILL.md
Normal file
64
skills/use-sql-migrate-postgres/SKILL.md
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
name: use-sql-migrate-postgres
|
||||
description: PostgreSQL migrations with sql-migrate and pgmigrate. Use when setting up PostgreSQL migrations, schema multi-tenancy, or pgx connection for migrations.
|
||||
depends: [use-sqlmigrate]
|
||||
---
|
||||
|
||||
## CLI setup
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ init --sql-command psql
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
```sh
|
||||
# .env
|
||||
PG_URL='postgres://user:pass@localhost:5432/mydb?sslmode=disable'
|
||||
```
|
||||
|
||||
## Go library
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate/pgmigrate"
|
||||
)
|
||||
|
||||
// MUST: use pgx.Connect (single conn), not pgxpool.New
|
||||
conn, err := pgx.Connect(ctx, pgURL)
|
||||
defer func() { _ = conn.Close(ctx) }()
|
||||
|
||||
runner := pgmigrate.New(conn)
|
||||
applied, err := sqlmigrate.Latest(ctx, runner, scripts)
|
||||
```
|
||||
|
||||
## Schema multi-tenancy
|
||||
|
||||
Each PostgreSQL schema gets its own `_migrations` table. Tenants are migrated independently.
|
||||
|
||||
### CLI
|
||||
|
||||
```sh
|
||||
PGOPTIONS="-c search_path=tenant123" sql-migrate -d ./sql/migrations/ up | sh
|
||||
```
|
||||
|
||||
### Go library
|
||||
|
||||
```go
|
||||
conn, err := pgx.Connect(ctx, pgURL)
|
||||
_, err = conn.Exec(ctx, fmt.Sprintf(
|
||||
"SET search_path TO %s",
|
||||
pgx.Identifier{schema}.Sanitize(),
|
||||
))
|
||||
runner := pgmigrate.New(conn)
|
||||
```
|
||||
|
||||
## SQL dialect notes
|
||||
|
||||
- `CREATE TABLE IF NOT EXISTS` works
|
||||
- `ON CONFLICT DO NOTHING` for idempotent seeds
|
||||
- String concatenation: `id || CHR(9) || name` (used by sync query)
|
||||
- Timestamps: `TIMESTAMP DEFAULT CURRENT_TIMESTAMP`
|
||||
- Error code 42P01 = table doesn't exist (handled automatically by pgmigrate)
|
||||
70
skills/use-sql-migrate-sqlite/SKILL.md
Normal file
70
skills/use-sql-migrate-sqlite/SKILL.md
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
name: use-sql-migrate-sqlite
|
||||
description: SQLite migrations with sql-migrate and litemigrate. Use when setting up SQLite migrations, configuring foreign keys, or using modernc.org/sqlite driver.
|
||||
depends: [use-sqlmigrate]
|
||||
---
|
||||
|
||||
## CLI setup
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ init --sql-command sqlite
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
```sh
|
||||
# .env
|
||||
SQLITE_PATH='./app.db'
|
||||
```
|
||||
|
||||
## Go library
|
||||
|
||||
```go
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate/litemigrate"
|
||||
)
|
||||
|
||||
// MUST: enable foreign keys via pragma
|
||||
db, err := sql.Open("sqlite", dbPath+"?_pragma=foreign_keys(1)")
|
||||
conn, err := db.Conn(ctx)
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
runner := litemigrate.New(conn)
|
||||
applied, err := sqlmigrate.Latest(ctx, runner, scripts)
|
||||
```
|
||||
|
||||
MUST: The caller imports the SQLite driver (`modernc.org/sqlite` recommended — pure Go, no CGo).
|
||||
|
||||
## SQL dialect notes
|
||||
|
||||
- `datetime('now')` instead of `CURRENT_TIMESTAMP` for default values in expressions
|
||||
- `TEXT` for timestamp columns (SQLite has no native datetime type)
|
||||
- `INSERT OR IGNORE` for idempotent seeds (not `INSERT IGNORE`)
|
||||
- String concatenation: `id || CHAR(9) || name` (used by sync query)
|
||||
- `ALTER TABLE ... DROP COLUMN` requires SQLite 3.35.0+ (2021-03-12)
|
||||
- "no such table" error string used to detect missing `_migrations` table
|
||||
- Default path: `todos.db` if no env var set
|
||||
|
||||
## sqlc with SQLite
|
||||
|
||||
SQLite `CHAR(n)` columns map to `interface{}` in sqlc. Use column-level overrides:
|
||||
|
||||
```yaml
|
||||
# sqlc.yaml
|
||||
sql:
|
||||
- schema: "sql/migrations/"
|
||||
queries: "sql/queries/"
|
||||
engine: "sqlite"
|
||||
gen:
|
||||
go:
|
||||
out: "internal/tododb"
|
||||
overrides:
|
||||
- column: "todos.id"
|
||||
go_type: "string"
|
||||
- column: "todos.status"
|
||||
go_type: "string"
|
||||
```
|
||||
95
skills/use-sql-migrate-sqlserver/SKILL.md
Normal file
95
skills/use-sql-migrate-sqlserver/SKILL.md
Normal file
@ -0,0 +1,95 @@
|
||||
---
|
||||
name: use-sql-migrate-sqlserver
|
||||
description: SQL Server migrations with sql-migrate and msmigrate. Use when setting up SQL Server migrations, configuring sqlcmd, TDS 8.0 encryption, or SQLCMD environment variables.
|
||||
depends: [use-sqlmigrate]
|
||||
---
|
||||
|
||||
## CLI setup
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ init --sql-command sqlcmd
|
||||
```
|
||||
|
||||
Requires the modern sqlcmd (go-mssqldb), not the legacy ODBC version.
|
||||
Install: `brew install sqlcmd` (macOS), `winget install sqlcmd` (Windows).
|
||||
|
||||
## Environment
|
||||
|
||||
```sh
|
||||
# .env — for Go app (DSN)
|
||||
MS_URL='sqlserver://sa:Password@localhost:1433?database=mydb'
|
||||
|
||||
# .env — for sqlcmd CLI (reads these automatically)
|
||||
SQLCMDSERVER='localhost'
|
||||
SQLCMDDATABASE='mydb'
|
||||
SQLCMDUSER='sa'
|
||||
SQLCMDPASSWORD='secret'
|
||||
```
|
||||
|
||||
SQLCMDSERVER formats:
|
||||
- `localhost` — default instance
|
||||
- `'localhost\SQLEXPRESS'` — named instance (quote the backslash)
|
||||
- `'localhost,1433'` — host and port
|
||||
|
||||
## TDS 8.0 encryption
|
||||
|
||||
Default uses `--encrypt-connection strict` (TLS-first with ALPN `tds/8.0` and SNI).
|
||||
|
||||
For local dev without TLS:
|
||||
|
||||
```sh
|
||||
sql-migrate -d ./sql/migrations/ init \
|
||||
--sql-command 'sqlcmd --exit-on-error --headers -1 --trim-spaces --encrypt-connection disable --input-file %s'
|
||||
```
|
||||
|
||||
## Go library
|
||||
|
||||
```go
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "github.com/microsoft/go-mssqldb"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate"
|
||||
"github.com/therootcompany/golib/database/sqlmigrate/msmigrate"
|
||||
)
|
||||
|
||||
db, err := sql.Open("sqlserver", msURL)
|
||||
conn, err := db.Conn(ctx)
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
runner := msmigrate.New(conn)
|
||||
applied, err := sqlmigrate.Latest(ctx, runner, scripts)
|
||||
```
|
||||
|
||||
## SQL dialect notes
|
||||
|
||||
- `IF OBJECT_ID('table', 'U') IS NULL CREATE TABLE ...` instead of `CREATE TABLE IF NOT EXISTS`
|
||||
- `SYSDATETIME()` instead of `CURRENT_TIMESTAMP` for DATETIME2 defaults
|
||||
- `DATETIME2` for timestamps (not `TIMESTAMP` — that's a row version in SQL Server)
|
||||
- `@p1`, `@p2` for parameterized queries in Go (not `?`)
|
||||
- Dropping columns with defaults requires dropping the default constraint first:
|
||||
|
||||
```sql
|
||||
DECLARE @constraint NVARCHAR(256);
|
||||
SELECT @constraint = name FROM sys.default_constraints
|
||||
WHERE parent_object_id = OBJECT_ID('todos')
|
||||
AND parent_column_id = (SELECT column_id FROM sys.columns
|
||||
WHERE object_id = OBJECT_ID('todos') AND name = 'priority');
|
||||
IF @constraint IS NOT NULL
|
||||
EXEC('ALTER TABLE todos DROP CONSTRAINT ' + @constraint);
|
||||
ALTER TABLE todos DROP COLUMN priority;
|
||||
```
|
||||
|
||||
- `IF NOT EXISTS (SELECT 1 FROM table WHERE ...) INSERT ...` for idempotent seeds
|
||||
- String concatenation: `id + CHAR(9) + name` (used by sync query)
|
||||
- Error 208 = invalid object name (table doesn't exist, handled by msmigrate)
|
||||
|
||||
## SSH tunnel for remote dev
|
||||
|
||||
```sh
|
||||
ssh -o ProxyCommand='sclient --alpn ssh %h' -fnNT \
|
||||
-L 21433:localhost:1433 \
|
||||
tls-<ip>.a.bnna.net
|
||||
```
|
||||
|
||||
Then set `MS_URL='sqlserver://sa:pass@localhost:21433?database=todos'`.
|
||||
45
skills/use-sqlmigrate/SKILL.md
Normal file
45
skills/use-sqlmigrate/SKILL.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
name: use-sqlmigrate
|
||||
description: Database migration tools for Go projects. Use when writing migrations, running sql-migrate CLI, embedding migrations in Go apps, or setting up new database schemas. Covers PostgreSQL, MySQL/MariaDB, SQLite, SQL Server.
|
||||
depends: [go-stack]
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
sqlmigrate is a feature-branch-friendly SQL migration system with two modes:
|
||||
|
||||
1. **CLI** (`sql-migrate`) — generates shell scripts that pipe to `sh`
|
||||
2. **Go library** (`sqlmigrate` + backend) — embed migrations in Go binaries
|
||||
|
||||
Both use the same migration file format and `_migrations` tracking table.
|
||||
|
||||
## Focused skills
|
||||
|
||||
| Skill | When to use |
|
||||
|-------|-------------|
|
||||
| `use-sql-migrate-cli` | CLI tool: init, create, up, down, sync, status |
|
||||
| `use-sql-migrate-golang` | Go library: embed migrations, Migrator interface, auto-migrate on startup |
|
||||
| `use-sql-migrate-postgres` | PostgreSQL: pgx connection, schema multi-tenancy, PGOPTIONS |
|
||||
| `use-sql-migrate-mysql` | MySQL/MariaDB: multiStatements DSN, MY_CNF, mariadb vs mysql |
|
||||
| `use-sql-migrate-sqlite` | SQLite: foreign keys pragma, modernc.org/sqlite driver |
|
||||
| `use-sql-migrate-sqlserver` | SQL Server: sqlcmd, TDS 8.0 encryption, SQLCMD* env vars |
|
||||
|
||||
## Migration file format
|
||||
|
||||
```
|
||||
<yyyy-mm-dd>-<number>_<name>.<up|down>.sql
|
||||
2026-04-05-001000_create-todos.up.sql
|
||||
2026-04-05-001000_create-todos.down.sql
|
||||
```
|
||||
|
||||
- Numbers increment by 1000 (allows inserting between)
|
||||
- Initial migration: `0001-01-01-001000_init-migrations`
|
||||
- Each `.up.sql` MUST end with `INSERT INTO _migrations (name, id) VALUES ('<name>', '<8-hex-id>');`
|
||||
- Each `.down.sql` MUST end with `DELETE FROM _migrations WHERE id = '<8-hex-id>';`
|
||||
|
||||
## Key design decisions
|
||||
|
||||
- **Feature-branch friendly**: no sequential numbering, no conflicts
|
||||
- **ID-based matching**: migrations matched by 8-char hex ID, not name — safe to rename
|
||||
- **Shell-first CLI**: generates reviewable scripts, never executes directly
|
||||
- **Separate Go modules**: each backend is its own module to avoid pulling unnecessary drivers
|
||||
Loading…
x
Reference in New Issue
Block a user