This commit is contained in:
2026-05-31 16:27:32 +02:00
commit 2512546613
6 changed files with 1266 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) nikola@petrovv.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
# go-migrate
A PostgreSQL schema migration tool that generates and applies migrations by comparing your desired schema (SQL files) with the current database state.
## Features
- **Schema comparison**: Compare your desired schema against the live database
- **Migration generation**: Automatically generate SQL migration statements
- **Migration application**: Apply migrations directly to the database
- **Type support**: Handles tables, columns, indexes, foreign keys, enums, and constraints
## Installation
### As a Library
```bash
go get git.petrovv.com/go-migrate
```
### CLI Tool
```bash
go install git.petrovv.com/go-migrate/cmd/go-migrate@latest
```
## Usage
### Library
```go
import "git.petrovv.com/go-migrate"
ctx := context.Background()
db, _ := pgxpool.New(ctx, "postgres://user:pass@localhost:5432/db")
// Generate migrations
migrations, err := migrate.GetMigrations(ctx, db, "schema/")
for _, m := range migrations {
fmt.Println(m)
}
// Or use LoadDesiredSchema and CompareSchemas for more control
desired, _ := migrate.LoadDesiredSchema("schema/")
current, _ := migrate.GetCurrentSchema(ctx, db)
migrations = migrate.CompareSchemas(current, desired)
```
### CLI
```bash
# Generate migration SQL
go-migrate -cmd=generate -dir=schema
# Apply migrations
go-migrate -cmd=apply -dir=schema
```
**Flags:**
- `-dir`: Directory containing schema SQL files (default: `schema`)
- `-cmd`: `generate` or `apply`
## Schema Files
Place your desired schema SQL files in a directory (e.g., `schema/`). Files are loaded alphabetically.
Example `schema/01_users.sql`:
```sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
CREATE INDEX idx_users_name ON users (name);
```
## Environment Variables
For the CLI, database connection can be configured via environment variables or `.env` file:
```
DB_USER=postgres
DB_PASS=password
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
```

105
cmd/go-migrate/main.go Normal file
View File

@@ -0,0 +1,105 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"git.petrovv.com/nikola/go-migrate"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/joho/godotenv"
)
func main() {
// Parse flags
schemaDir := flag.String("dir", "schema", "Directory containing desired schema SQL files")
cmd := flag.String("cmd", "", "Command: generate or apply")
flag.Parse()
if *cmd == "" {
fmt.Println("Usage:")
fmt.Println(" go run main.go -cmd=generate -dir=schema - Compare schema and generate migrations")
fmt.Println(" go run main.go -cmd=apply -dir=schema - Apply pending migrations")
fmt.Println("\nFlags:")
flag.PrintDefaults()
return
}
switch *cmd {
case "generate":
generateMigrations(*schemaDir)
case "apply":
applyMigrations(*schemaDir)
default:
fmt.Println("Unknown command:", *cmd)
fmt.Println("Use 'generate' or 'apply'")
}
}
func generateMigrations(schemaDir string) {
_ = godotenv.Load()
db := getDBConnection()
defer db.Close()
ctx := context.Background()
migrations, err := migrate.GetMigrations(ctx, db, schemaDir)
if err != nil {
log.Fatal(err)
}
if len(migrations) == 0 {
fmt.Println("Database schema is up to date")
return
}
fmt.Println("-- Migration SQL")
for _, m := range migrations {
fmt.Println(m)
}
}
func applyMigrations(schemaDir string) {
_ = godotenv.Load()
db := getDBConnection()
defer db.Close()
ctx := context.Background()
migrations, err := migrate.GetMigrations(ctx, db, schemaDir)
if err != nil {
log.Fatal(err)
}
if len(migrations) == 0 {
fmt.Println("Database schema is up to date")
return
}
fmt.Println("Applying migrations...")
for _, m := range migrations {
fmt.Printf("Executing: %s\n", m)
_, err := db.Exec(ctx, m)
if err != nil {
log.Fatalf("failed to execute %s: %v", m, err)
}
}
fmt.Println("All migrations applied successfully")
}
func getDBConnection() *pgxpool.Pool {
dbUser := os.Getenv("DB_USER")
dbPass := os.Getenv("DB_PASS")
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
dbName := os.Getenv("DB_NAME")
dsn := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", dbUser, dbPass, dbHost, dbPort, dbName)
pool, err := pgxpool.New(context.Background(), dsn)
if err != nil {
log.Fatalf("Unable to connect to database: %v", err)
}
return pool
}

17
go.mod Normal file
View File

@@ -0,0 +1,17 @@
module git.petrovv.com/nikola/go-migrate
go 1.26.2
require (
github.com/jackc/pgx/v5 v5.6.0
github.com/joho/godotenv v1.5.1
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

30
go.sum Normal file
View File

@@ -0,0 +1,30 @@
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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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=

1006
migrate.go Normal file

File diff suppressed because it is too large Load Diff