Using Grizzle with sqlc
Pairing Grizzle with sqlc creates a unified database workflow where application queries and runtime migrations consume the same SQL schema.
Dual-source problem before Grizzle
Using sqlc typically involves managing two separate representations of the database schema:
- Numbered migration files (
0001_init.up.sql, 0002_add_cols.up.sql).
- An exported schema dump or parsed migration list in
sqlc.yaml.
- Simultaneous branch work causes sequence collisions, drift between the Go models and database tables, and failed CI builds.
Single source of truth workflow
This example keeps one schema.sql next to the sqlc config:
examples/sqlc-workflow/
├── schema.sql <-- Single source of truth for sqlc and Grizzle
├── query.sql <-- Application SQL queries
├── sqlc.yaml <-- Points at schema.sql + query.sql
├── db/ <-- Generated by sqlc (`sqlc generate`)
└── main.go <-- Calls grizzle.Sync() at startup
Configuration (sqlc.yaml)
version: "2"
sql:
- engine: "postgresql"
schema: "schema.sql"
queries: "query.sql"
gen:
go:
package: "db"
out: "db"
sql_package: "database/sql"
Application startup (main.go)
package main
import (
"context"
"database/sql"
_ "embed"
"log"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/muandane/grizzle"
)
//go:embed schema.sql
var schemaSQL string
func main() {
ctx := context.Background()
db, err := sql.Open("pgx", "postgres://localhost:5432/myapp?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := grizzle.Sync(ctx, db, grizzle.Options{
SchemaSQL: schemaSQL,
AllowDrop: false,
}); err != nil {
log.Fatalf("schema sync failed: %v", err)
}
}
Advantages
- Zero schema drift: The code generator (
sqlc) and runtime migrator (Grizzle) consume the same schema.sql.
- Zero sequence collisions: Developers add columns or tables directly to
schema.sql. Git resolves diffs like standard application code.
- Zero build dependencies at boot: No Docker or migration CLI required inside the production image.