grizzle

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

Grizzle

CI Go Reference License

Declarative schema migrations for Go.

Grizzle compares your live database against a plain schema.sql file and applies the required DDL on boot. You define the desired schema, and Grizzle calculates the minimal safe migration plan, eliminating numbered migration files (001_init.sql) and external migration binaries in production containers.

  • Safe by default: Destructive drops are blocked unless explicitly allowed.
  • Production-safe DDL: Builds PostgreSQL indexes with CONCURRENTLY and validates foreign keys in separate stages.
  • Cluster coordination: Uses session-level PostgreSQL advisory locks so multiple replicas boot safely without race conditions.
  • Engine support: PostgreSQL (14 to 18) and SQLite (3.35+, pure-Go or CGo).
  • Tooling compatibility: Shares the same schema.sql file with code generators like sqlc.

Quickstart

1. Define your desired schema
-- schema.sql
CREATE TABLE users (
    id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email      VARCHAR(255) NOT NULL UNIQUE,
    full_name  TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_created_at ON users (created_at);

Grizzle supports both SERIAL and GENERATED ALWAYS AS IDENTITY columns (recommended; see docs/SAFETY.md).

2. Run sync on boot

Embed schema.sql and call grizzle.Sync during application startup:

package main

import (
	"context"
	"database/sql"
	_ "embed"
	"log"
	"time"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/muandane/grizzle"
)

//go:embed schema.sql
var schemaSQL string

func main() {
	db, err := sql.Open("pgx", "postgres://postgres:secret@localhost:5432/myapp?sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Bring the database in line with schema.sql
	err = grizzle.Sync(context.Background(), db, grizzle.Options{
		SchemaSQL:        schemaSQL,
		AllowDrop:        false, // Fails if the diff requires dropping tables or columns
		LockTimeout:      5 * time.Second,
		StatementTimeout: 2 * time.Minute,
	})
	if err != nil {
		log.Fatalf("migration failed: %v", err)
	}
}
Staged planning and approval workflows

For CI pipelines or approval gates where changes must be reviewed before execution:

// 1. Generate the migration plan without applying changes
plan, err := grizzle.PlanDiff(ctx, db, opts)
if err != nil {
    log.Fatal(err)
}

// 2. Apply the plan safely; aborts if the live database changed since planning
err = grizzle.Apply(ctx, db, plan, grizzle.ApplyOpts{
    ExpectedHash: plan.Hash(),
})

CLI

Installation
go install github.com/muandane/grizzle/cmd/grizzle@latest

Pre-compiled binaries for Linux, macOS, and Windows are available on GitHub Releases.

Usage

Use the CLI for dry runs, CI drift detection, or offline linting:

# Preview changes and generate a migration plan
grizzle plan --dsn "$DATABASE_URL" --schema schema.sql --out plan.json

# Use an external dev database if the target database user cannot run CREATE SCHEMA
grizzle plan --dsn "$DATABASE_URL" --dev-url "$DEV_DATABASE_URL" --schema schema.sql

# Apply a generated plan
grizzle apply --dsn "$DATABASE_URL" --plan plan.json

# Inspect migration status and history
grizzle status --dsn "$DATABASE_URL" --schema schema.sql
grizzle history --dsn "$DATABASE_URL"

# Dump the current database schema and baseline it
grizzle dump --dsn "$DATABASE_URL" --out schema.sql
grizzle baseline --dsn "$DATABASE_URL" --schema schema.sql

# Mark an interrupted migration as resolved
grizzle repair --dsn "$DATABASE_URL" --note "manually resolved"

# Lint schema.sql offline
grizzle lint --schema schema.sql

# Run strict linting with GitHub Actions annotations in CI
grizzle lint --schema schema.sql --profile strict --format github --fail-on-warning

# Export plans to Goose or golang-migrate SQL files
grizzle export --plan plan.json --format goose --out ./migrations
grizzle export --plan plan.json --format migrate --out ./migrations
Exit codes
Code Meaning
0 Success / database is in sync
1 Connection or execution error (or drift detected in status)
2 Blocked by an unaccepted hazard (such as an unexpected DROP)
3 Plan hash mismatch (database drifted after plan was generated)
4 Schema drift detected (grizzle check)
GitHub Actions Integration

Use the official Grizzle GitHub Action to lint schemas, detect drift, and preview plans in CI workflows:

name: Schema CI
on: [pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Lint declarative schema
        uses: muandane/grizzle@v0.2.0
        with:
          command: lint
          schema: schema.sql
          strict: true
          format: github

  drift-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Check for schema drift
        uses: muandane/grizzle@v0.2.0
        with:
          command: check
          schema: schema.sql
          dsn: ${{ secrets.STAGING_DATABASE_URL }}

Supported engines

  • PostgreSQL (14 to 18): Tables, partitioned tables, concurrent indexes, foreign keys, check constraints, enums, domains, composite types, routines, triggers, views, row-level security, sequences, extensions, and logical replication.
  • SQLite (3.35+): Tables, columns, indexes, foreign keys, check constraints, and generated columns. Unsupported in-place alters run through an automatic 12-step table rebuild. Compatible with modernc.org/sqlite (pure Go) and mattn/go-sqlite3 (CGo).

For the full feature matrix, safety guards, and catalog details, see docs/FEATURES.md and docs/SPEC.md.


Operational notes

  • Connection pooling (PgBouncer): Grizzle acquires session-level PostgreSQL advisory locks to coordinate migrations across application replicas. Connect directly to PostgreSQL or use Session Pooling. Do not run migrations through Transaction Pooling.
  • Destructive changes: Dropping tables or columns requires setting AllowDrop: true (or --allow-drop in the CLI). See docs/SAFETY.md for hazard codes and approval gates.

Examples


Documentation

  • FEATURES: supported feature matrix and safety guards for PostgreSQL and SQLite
  • SPEC: SQL constructs, catalog introspection, and engine specification
  • SAFETY: hazard classification, safety rules, and drop policies
  • LINTER: schema review rules and CI linting
  • ARCHITECTURE: internal planner and execution pipeline
  • CONTRIBUTING: development setup and contribution guidelines
  • CHANGELOG: release notes and version history

Current tag: v0.2.0.


License

Apache 2.0. See LICENSE.

Documentation

Overview

Package grizzle provides declarative, in-process database schema automigration for Go.

Grizzle synchronizes PostgreSQL and SQLite database schemas directly from standard schema.sql DDL definitions on application startup, eliminating sequence file collisions, manual migration versioning, and external CLI/Docker runtime dependencies.

Core Concepts

  • Declarative Schema: Write standard SQL DDL (schema.sql) as the single source of truth.
  • In-Process Execution: Runs natively within your Go binary without needing external CLI binaries.
  • Shadow Sandbox: Replays and validates desired schema state inside a temporary shadow schema or SQLite in-memory sandbox.
  • Deterministic Diff: Pure schema comparison detecting missing tables, columns, indexes, constraints, and types.
  • Safe Execution: Dedicated advisory locking, multi-node contention retry, hazard gating, and transactional execution.
  • Zero-Downtime Operations: Safe non-transactional enum expansions (PostgreSQL ALTER TYPE ... ADD VALUE) and concurrent index generation.

Subpackages & Modules

  • griztest: Testing primitives (MustSync, MustPlan, Reset) for effortless test database setup and teardown.
  • otelgrizzle: Optional OpenTelemetry tracing integration (github.com/muandane/grizzle/otelgrizzle).

Basic Usage

db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
if err != nil {
    log.Fatal(err)
}
defer db.Close()

err = grizzle.Sync(ctx, db, grizzle.Options{
    SchemaSQL: schemaSQL,
})
if err != nil {
    log.Fatalf("migration failed: %v", err)
}

Index

Examples

Constants

View Source
const (
	// PartitionStrategyRange indicates range-based partitioning.
	PartitionStrategyRange = schema.PartitionStrategyRange
	// PartitionStrategyList indicates list-based partitioning.
	PartitionStrategyList = schema.PartitionStrategyList
	// PartitionStrategyHash indicates hash-based partitioning.
	PartitionStrategyHash = schema.PartitionStrategyHash
)

PartitionStrategy constants

View Source
const (
	ChangeCreateEnum          = plan.ChangeCreateEnum
	ChangeAlterEnum           = plan.ChangeAlterEnum
	ChangeRenameEnumValue     = plan.ChangeRenameEnumValue
	ChangeDropEnumValue       = plan.ChangeDropEnumValue
	ChangeCreateTable         = plan.ChangeCreateTable
	ChangeDropTable           = plan.ChangeDropTable
	ChangeAddColumn           = plan.ChangeAddColumn
	ChangeDropColumn          = plan.ChangeDropColumn
	ChangeAlterColumn         = plan.ChangeAlterColumn
	ChangeCreateIndex         = plan.ChangeCreateIndex
	ChangeDropIndex           = plan.ChangeDropIndex
	ChangeAddFK               = plan.ChangeAddFK
	ChangeDropFK              = plan.ChangeDropFK
	ChangeAddCheck            = plan.ChangeAddCheck
	ChangeDropCheck           = plan.ChangeDropCheck
	ChangeValidateConstraint  = plan.ChangeValidateConstraint
	ChangeRenameColumn        = plan.ChangeRenameColumn
	ChangeAttachPartition     = plan.ChangeAttachPartition
	ChangeDetachPartition     = plan.ChangeDetachPartition
	ChangeCreateExtension     = plan.ChangeCreateExtension
	ChangeDropExtension       = plan.ChangeDropExtension
	ChangeEnableRLS           = plan.ChangeEnableRLS
	ChangeDisableRLS          = plan.ChangeDisableRLS
	ChangeForceRLS            = plan.ChangeForceRLS
	ChangeNoForceRLS          = plan.ChangeNoForceRLS
	ChangeCreatePolicy        = plan.ChangeCreatePolicy
	ChangeAlterPolicy         = plan.ChangeAlterPolicy
	ChangeDropPolicy          = plan.ChangeDropPolicy
	ChangeCreateFunction      = plan.ChangeCreateFunction
	ChangeDropFunction        = plan.ChangeDropFunction
	ChangeCreateTrigger       = plan.ChangeCreateTrigger
	ChangeDropTrigger         = plan.ChangeDropTrigger
	ChangeCreateView          = plan.ChangeCreateView
	ChangeDropView            = plan.ChangeDropView
	ChangeRefreshMatView      = plan.ChangeRefreshMatView
	ChangeCreateRangeType     = plan.ChangeCreateRangeType
	ChangeDropRangeType       = plan.ChangeDropRangeType
	ChangeDropRangeRetype     = plan.ChangeDropRangeRetype
	ChangeCreateCompositeType = plan.ChangeCreateCompositeType
	ChangeAlterCompositeType  = plan.ChangeAlterCompositeType
	ChangeDropCompositeType   = plan.ChangeDropCompositeType
	ChangeDropCompositeRetype = plan.ChangeDropCompositeRetype
	ChangeCreateSequence      = plan.ChangeCreateSequence
	ChangeAlterSequence       = plan.ChangeAlterSequence
	ChangeDropSequence        = plan.ChangeDropSequence
)

ChangeType constants

View Source
const (
	HazardDropTable              = plan.HazardDropTable
	HazardDropColumn             = plan.HazardDropColumn
	HazardDropEnumValue          = plan.HazardDropEnumValue
	HazardTypeNarrow             = plan.HazardTypeNarrow
	HazardNotNullNoDefault       = plan.HazardNotNullNoDefault
	HazardIndexBuild             = plan.HazardIndexBuild
	HazardDropIndex              = plan.HazardDropIndex
	HazardDropFK                 = plan.HazardDropFK
	HazardDropCheck              = plan.HazardDropCheck
	HazardCheckValidateScan      = plan.HazardCheckValidateScan
	HazardRenameAmbiguous        = plan.HazardRenameAmbiguous
	HazardUnmanagedDependency    = plan.HazardUnmanagedDependency
	HazardGeneratedRewrite       = plan.HazardGeneratedRewrite
	HazardPartitionAttachScan    = plan.HazardPartitionAttachScan
	HazardPartitionPendingDetach = plan.HazardPartitionPendingDetach
	HazardExtensionPrivilege     = plan.HazardExtensionPrivilege
	HazardDropExtension          = plan.HazardDropExtension
	HazardRLSEnable              = plan.HazardRLSEnable
	HazardDropPolicy             = plan.HazardDropPolicy
	HazardDropFunction           = plan.HazardDropFunction
	HazardSecurityDefiner        = plan.HazardSecurityDefiner
	HazardDropTrigger            = plan.HazardDropTrigger
	HazardDropView               = plan.HazardDropView
	HazardDropDomain             = plan.HazardDropDomain
	HazardDropRangeType          = plan.HazardDropRangeType
	HazardDropCompositeType      = plan.HazardDropCompositeType
	HazardDropSequence           = plan.HazardDropSequence
)

HazardCode constants

View Source
const (
	// UnmanagedView indicates a standard SQL VIEW.
	UnmanagedView = schema.UnmanagedView
	// UnmanagedMaterialized indicates a MATERIALIZED VIEW.
	UnmanagedMaterialized = schema.UnmanagedMaterialized
	// UnmanagedTrigger indicates a database trigger attached to a table.
	UnmanagedTrigger = schema.UnmanagedTrigger
	// UnmanagedFunction indicates a stored procedure or function.
	UnmanagedFunction = schema.UnmanagedFunction
	// UnmanagedSequence indicates an unmanaged database sequence.
	UnmanagedSequence = schema.UnmanagedSequence
	// UnmanagedEnum indicates an unmanaged custom enum type.
	UnmanagedEnum = schema.UnmanagedEnum
	// UnmanagedDomain indicates an unmanaged domain type.
	UnmanagedDomain = schema.UnmanagedDomain
)
View Source
const (
	HazardLevelCritical = plan.HazardLevelCritical
	HazardLevelWarning  = plan.HazardLevelWarning
	HazardLevelNotice   = plan.HazardLevelNotice
)

HazardLevel constants

View Source
const (
	// ExportFormatSQL targets raw SQL files with transaction markers.
	ExportFormatSQL = export.FormatSQL
	// ExportFormatGoose targets Goose-compatible migration files.
	ExportFormatGoose = export.FormatGoose
	// ExportFormatAtlas targets Atlas-compatible migration files.
	ExportFormatAtlas = export.FormatAtlas
	// ExportFormatMigrate targets golang-migrate compatible (.up.sql / .down.sql) migration files.
	ExportFormatMigrate = export.FormatMigrate
	// ExportFormatGolangMigrate is an alias for ExportFormatMigrate.
	ExportFormatGolangMigrate = export.FormatGolangMigrate
)
View Source
const (
	// LintSeverityError marks a structural anti-pattern that must be fixed.
	LintSeverityError = lint.SeverityError
	// LintSeverityWarning marks a recommendation that may be ignored deliberately.
	LintSeverityWarning = lint.SeverityWarning
	// LintSeverityInfo marks a stylistic suggestion with no correctness impact.
	LintSeverityInfo = lint.SeverityInfo
)

Variables

View Source
var (
	// ErrEmptySchema is returned when the provided SchemaSQL string is empty or contains only whitespace.
	ErrEmptySchema = plan.ErrEmptySchema

	// ErrDestructiveBlocked is returned when a destructive operation (such as DROP TABLE or DROP COLUMN)
	// is rejected by the configured drop safety policy.
	ErrDestructiveBlocked = plan.ErrDestructiveBlocked

	// ErrLockAcquisition is returned when acquiring the database advisory lock fails or times out.
	ErrLockAcquisition = plan.ErrLockAcquisition

	// ErrCompilationFailed is returned when the user's schema.sql fails to compile/execute inside the shadow schema.
	ErrCompilationFailed = plan.ErrCompilationFailed

	// ErrExecutionFailed is returned when applying the planned DDL migration steps to the target schema fails.
	ErrExecutionFailed = plan.ErrExecutionFailed

	// ErrInspectionFailed is returned when inspecting the schema metadata fails.
	ErrInspectionFailed = plan.ErrInspectionFailed

	// ErrUnsupportedDialect is returned when an unrecognized database driver or dialect is provided.
	ErrUnsupportedDialect = plan.ErrUnsupportedDialect

	// ErrHazardBlocked is returned when a migration plan contains critical hazards not explicitly accepted in AcceptHazards.
	ErrHazardBlocked = plan.ErrHazardBlocked

	// ErrPlanDrift is returned when the approved plan hash differs from the recomputed plan hash after lock acquisition.
	ErrPlanDrift = plan.ErrPlanDrift

	// ErrDrift is returned by Check when the live database schema differs from the desired schema.
	ErrDrift = plan.ErrDrift

	// ErrLockTimeout is returned when an advisory lock cannot be acquired within the configured lock timeout.
	ErrLockTimeout = plan.ErrLockTimeout

	// ErrLockContention is returned when conflicting locks are held on target tables during pre-flight check.
	ErrLockContention = plan.ErrLockContention

	// ErrInvalidOptions is returned when provided Options fail validation.
	ErrInvalidOptions = plan.ErrInvalidOptions

	// ErrNoMatchingSchemas is returned when TargetSchemaPattern matches zero schemas.
	ErrNoMatchingSchemas = plan.ErrNoMatchingSchemas

	// ErrStrictScope is returned when StrictScope is enabled but no IncludeTables are specified.
	ErrStrictScope = plan.ErrStrictScope

	// ErrPartitionConversion is returned when attempting to convert a regular table to a partitioned table or vice versa in-place.
	ErrPartitionConversion = plan.ErrPartitionConversion

	// ErrUnsupportedMultiSchema is returned when CompileSchema cannot compile
	// multiple PostgreSQL schemas in one shadow (SQLite multi-schema uses ATTACH).
	ErrUnsupportedMultiSchema = plan.ErrUnsupportedMultiSchema

	// ErrPartitionKeyNotInUnique is returned when a primary key or unique constraint on a partitioned table does not include all partition key columns.
	ErrPartitionKeyNotInUnique = plan.ErrPartitionKeyNotInUnique

	// ErrAfterSyncFailed is returned when the AfterSync hook fails after the migration steps have already committed.
	ErrAfterSyncFailed = plan.ErrAfterSyncFailed

	// ErrSeedFailed is returned when seed SQL execution fails; the seed
	// transaction is rolled back and the schema is left intact.
	ErrSeedFailed = plan.ErrSeedFailed

	// ErrHistoryRecord is returned when the migration succeeded but the
	// grizzle_history record could not be written (non-fatal; detect with
	// errors.Is).
	ErrHistoryRecord = plan.ErrHistoryRecord

	// ErrPriorFailureBlocked is returned by Sync when the latest recorded migration
	// in grizzle_history is in a 'partial' or 'failed' state and AllowResumePartial is false.
	ErrPriorFailureBlocked = plan.ErrPriorFailureBlocked

	// ErrNoFailedMigration is returned by Repair when no partial or failed migration was found to resolve.
	ErrNoFailedMigration = plan.ErrNoFailedMigration

	// ErrBaselineDrift is returned by Baseline when the live database differs from desired schema and Force is false.
	ErrBaselineDrift = plan.ErrBaselineDrift

	// ErrTransactionPooling is returned when transaction-mode pooling (e.g., PgBouncer)
	// prevents session-level advisory lock acquisition.
	ErrTransactionPooling = plan.ErrTransactionPooling
)

Functions

func Apply

func Apply(ctx context.Context, db *sql.DB, p *Plan, opts ApplyOpts) error

Apply applies an approved migration plan to the database. If ExpectedHash is provided and the plan recomputed post-lock differs, Apply aborts with ErrPlanDrift.

func BuildIdempotentInsert

func BuildIdempotentInsert(table string, columns []string, conflictTarget string, updateColumns ...string) string

BuildIdempotentInsert builds a canonical idempotent INSERT statement skeleton with conflict handling for seed data.

Formats: INSERT INTO "table" ("col1", "col2") VALUES ... ON CONFLICT ...

  • If conflictTarget == "": appends ON CONFLICT DO NOTHING;
  • If conflictTarget != "" and len(updateColumns) == 0: appends ON CONFLICT (<conflictTarget>) DO NOTHING;
  • If conflictTarget != "" and len(updateColumns) > 0: appends ON CONFLICT (<conflictTarget>) DO UPDATE SET <col> = EXCLUDED.<col>, ...;

Standard Idempotent Seed Patterns:

  1. Unconditional ignore (blank conflictTarget): Useful when unique constraints or primary keys should silently prevent duplicates without needing specific target specification. Example: BuildIdempotentInsert("roles", []string{"name"}, "") // Output: INSERT INTO "roles" ("name") VALUES ... ON CONFLICT DO NOTHING;

  2. Targeted ignore (conflictTarget specified, no updateColumns): Ignores conflict on a specific key or unique constraint. Example: BuildIdempotentInsert("users", []string{"id", "email"}, "id") // Output: INSERT INTO "users" ("id", "email") VALUES ... ON CONFLICT (id) DO NOTHING;

  3. Upsert / update on conflict (conflictTarget and updateColumns specified): Updates specified mutable columns from EXCLUDED values on conflict. Example: BuildIdempotentInsert("users", []string{"id", "name", "email"}, "id", "name", "email") // Output: INSERT INTO "users" ("id", "name", "email") VALUES ... ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email;

func Check

func Check(ctx context.Context, db *sql.DB, opts Options) error

Check inspects the live database and returns ErrDrift (wrapped in DriftError) if the schema differs from the desired schema specified in opts.SchemaSQL. It is strictly read-only, never modifies the database, and never auto-fixes drift.

func Dump

func Dump(ctx context.Context, db *sql.DB, opts DumpOptions) (string, error)

Dump reverse-engineers the live database schema into canonical DDL SQL.

func GenerateLockID

func GenerateLockID(dbName, schemaName string) int64

GenerateLockID produces a deterministic 64-bit integer lock ID from db and schema names.

func IsReady added in v0.2.0

func IsReady(ctx context.Context, db *sql.DB, opts ...Option) (bool, error)

IsReady checks whether the database is ready for application traffic by inspecting the latest migration state in grizzle_history. It reads migration state only with zero side effects (no DDL, no table creation) and without acquiring any advisory locks.

It returns:

  • (true, nil) if the latest migration record has status "applied" or "baseline".
  • (false, nil) if grizzle_history does not exist, is empty, or the latest record has a non-ready status ("partial", "failed", "in_progress").
  • (false, err) if database connectivity or the query fails.

func LintFormatGitHub

func LintFormatGitHub(w io.Writer, diags []LintDiagnostic) error

LintFormatGitHub renders diagnostics as GitHub Actions annotations.

func LintFormatJSON

func LintFormatJSON(w io.Writer, diags []LintDiagnostic) error

LintFormatJSON renders diagnostics as a JSON array.

func LintFormatText

func LintFormatText(w io.Writer, diags []LintDiagnostic) error

LintFormatText renders diagnostics as human-readable text.

func LintHasErrors

func LintHasErrors(diags []LintDiagnostic) bool

LintHasErrors reports whether any diagnostic has severity ERROR.

func Seed

func Seed(ctx context.Context, db *sql.DB, seedSQL string, opts ...Option) error

Seed executes idempotent seed SQL against the database. The seed runs in a single transaction: either the whole script commits with an 'applied' grizzle_history record keyed by the seed's content hash, or nothing persists. If the same seed hash was already applied and force is false, the seed is skipped. Forced re-runs execute the script again, so seed scripts should tolerate re-execution (IF NOT EXISTS, ON CONFLICT...).

Dialect and target schema are taken from opts (auto-detected when unset).

func SeedHash

func SeedHash(seedSQL string) string

SeedHash returns the deterministic sha256 identity of a seed script. Leading and trailing whitespace is ignored so reformatted-but-identical seeds do not re-run.

func Sync

func Sync(ctx context.Context, db *sql.DB, opts Options) error

Sync synchronizes the target database schema to match the desired state in opts.SchemaSQL.

Example
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	"github.com/muandane/grizzle"
	_ "modernc.org/sqlite"
)

func main() {
	db, err := sql.Open("sqlite", ":memory:")
	if err != nil {
		log.Fatalf("failed to open database: %v", err)
	}
	defer func() { _ = db.Close() }()

	schema := `
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    email TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL
);`

	err = grizzle.Sync(context.Background(), db, grizzle.Options{
		Dialect:   grizzle.DialectSQLite,
		SchemaSQL: schema,
	})
	if err != nil {
		log.Fatalf("sync failed: %v", err)
	}

	var count int
	_ = db.QueryRow("SELECT count(*) FROM users;").Scan(&count)
	fmt.Printf("Users table exists with row count: %d\n", count)

}
Output:
Users table exists with row count: 0

Types

type ApplyOpts

type ApplyOpts struct {
	// ExpectedHash is the approved plan hash. If non-empty, Apply recomputes the plan after
	// acquiring the lock and verifies the recomputed hash matches ExpectedHash.
	// If it does not match, Apply aborts with ErrPlanDrift.
	ExpectedHash string

	// AcceptHazards specifies explicitly accepted critical hazards.
	AcceptHazards []HazardCode

	// Backfill hook function run outside the DDL lock window in batches during staged expand migration.
	Backfill BackfillFunc

	// BeforeSync runs once before any migration steps or locks are executed.
	BeforeSync SyncHook

	// AfterSync runs once after all migration steps and history recording succeed.
	AfterSync SyncHook

	// BeforeStep executes immediately prior to executing each plan step.
	BeforeStep StepHook

	// AfterStep executes immediately following the successful execution of each plan step.
	AfterStep StepHook

	// SQLiteRebuildThreshold defines the row count threshold above which SQLite table rebuilds
	// chunk data copying by keyset. Defaults to 100000.
	SQLiteRebuildThreshold int

	// SQLiteRebuildBatchSize defines the chunk size when copying data in batches during SQLite table rebuilds.
	// Defaults to 10000.
	SQLiteRebuildBatchSize int

	// SQLiteAttach maps ATTACH DATABASE schema names to filesystem paths for
	// multi-schema SQLite Apply. Not carried in the plan artifact.
	SQLiteAttach map[string]string

	// LockNamespace specifies the application namespace string used for PostgreSQL advisory locking (defaults to "grizzle").
	LockNamespace string

	// DevDatabaseURL optionally specifies an external database connection URL
	// used for shadow schema compilation during plan verification.
	DevDatabaseURL string

	// FailOnContention gates DDL execution on PostgreSQL: if concurrent sessions hold conflicting
	// locks on target tables, retry with backoff up to LockTimeout; if contention persists, abort
	// with ErrLockContention instead of proceeding. When false (default), lock contention is advisory
	// and logs a warning before proceeding.
	FailOnContention bool

	// Logger receives structured migration logs during Apply (defaults to no logging).
	// The plan artifact cannot carry a logger, so it is supplied here.
	Logger *slog.Logger

	// Tracer specifies an optional tracer for observing plan application.
	Tracer Tracer

	// Metrics specifies an optional metrics recorder for observing plan application.
	Metrics Metrics
}

ApplyOpts configures verification and execution parameters when applying an approved Plan.

type BackfillFunc

type BackfillFunc func(ctx context.Context, tx *sql.Tx, table, oldCol, newCol string) error

BackfillFunc defines the hook function signature for batch backfilling columns outside the DDL lock window.

type BaselineOptions

type BaselineOptions struct {
	TargetSchema   string
	Note           string
	Force          bool // If true, record baseline even if unapplied changes exist
	DevDatabaseURL string
}

BaselineOptions configures baselining an existing database.

type ChangeType

type ChangeType = plan.ChangeType

ChangeType describes the classification of a schema change.

type CheckConstraintIR

type CheckConstraintIR = schema.CheckConstraint

CheckConstraintIR represents the intermediate representation of a CHECK constraint.

type ColumnIR

type ColumnIR = schema.Column

ColumnIR represents the intermediate representation of a database column.

type CompositeAttributeIR

type CompositeAttributeIR = schema.CompositeAttribute

CompositeAttributeIR represents the intermediate representation of an attribute in a composite type.

type CompositeTypeIR

type CompositeTypeIR = schema.CompositeType

CompositeTypeIR represents the intermediate representation of a composite type.

type DependencyRef

type DependencyRef = schema.DependencyRef

DependencyRef represents a reference to a table or column that an unmanaged object depends on.

type DestructiveViolationError

type DestructiveViolationError = plan.DestructiveViolationError

DestructiveViolationError reports all destructive steps that were rejected by the active safety policy.

type Dialect

type Dialect string

Dialect specifies the target SQL database dialect.

const (
	// DialectAuto instructs Grizzle to auto-detect the engine from the driver type.
	DialectAuto Dialect = ""
	// DialectPostgres specifies PostgreSQL (14+; CI-tested through 18).
	DialectPostgres Dialect = "postgres"
	// DialectSQLite specifies SQLite (3.35+).
	DialectSQLite Dialect = "sqlite"
)

type DriftError

type DriftError = plan.DriftError

DriftError reports unapplied differences between the live schema and the desired schema.

type DropPolicy

type DropPolicy = plan.DropPolicy

DropPolicy defines the safety behavior for destructive drops.

type DryRunResult

type DryRunResult = exec.DryRunResult

DryRunResult reports the outcome of a live dry-run verification.

func DryRunVerify

func DryRunVerify(ctx context.Context, db *sql.DB, opts Options) (*DryRunResult, error)

DryRunVerify executes a live dry-run rollback against the target database: the planned DDL runs against real data inside transactions that are unconditionally rolled back, so constraints, casts, and check expressions are verified without persisting any change. Non-transactional steps (CREATE INDEX CONCURRENTLY, ALTER TYPE ... ADD VALUE, DETACH PARTITION CONCURRENTLY) are skipped and reported in DryRunResult.UnverifiedNonTx. No records are written to grizzle_history, and hooks do not run unless opts.ExecuteHooksInDryRun is set.

func DryRunVerifyPlan

func DryRunVerifyPlan(ctx context.Context, db *sql.DB, p *Plan, opts ApplyOpts) (*DryRunResult, error)

DryRunVerifyPlan performs a live dry-run of an approved plan artifact. It verifies the artifact hash against ExpectedHash, honors every plan-carried intent field (target schemas, scope, policy, execution semantics), and checks that a re-diff of the live database against the artifact's SchemaSQL still produces the same plan hash — so a tampered or drifted artifact cannot silently verify a different migration. Runtime-only options (logger, tracer, hazards, lock namespace) come from opts.

type DumpOptions

type DumpOptions struct {
	TargetSchema string
}

DumpOptions configures schema reverse-engineering / dumping.

type EnumIR

type EnumIR = schema.Enum

EnumIR represents the intermediate representation of an enum type.

type ExportArtifact

type ExportArtifact = export.Artifact

ExportArtifact represents a generated migration file ready to be saved.

func Export

func Export(p *Plan, format ExportFormat, version string, timestamp ...time.Time) ([]ExportArtifact, error)

Export serializes the given plan into migration files for the requested tool format.

type ExportFormat

type ExportFormat = export.Format

ExportFormat defines supported target formats for migration export.

type ExtensionIR

type ExtensionIR = schema.Extension

ExtensionIR represents the intermediate representation of a PostgreSQL extension.

type ForeignKeyIR

type ForeignKeyIR = schema.ForeignKey

ForeignKeyIR represents the intermediate representation of a foreign key relationship.

type GeneratedColumn

type GeneratedColumn = schema.GeneratedColumn

GeneratedColumn describes a computed or generated column.

type Hazard

type Hazard = plan.Hazard

Hazard describes an operation that could cause data loss or service disruption.

type HazardCode

type HazardCode = plan.HazardCode

HazardCode identifies the specific type of migration hazard detected.

type HazardError

type HazardError = plan.HazardError

HazardError reports all critical hazards that were not explicitly accepted.

type HazardLevel

type HazardLevel = plan.HazardLevel

HazardLevel indicates the severity of potential data loss or downtime.

type HistoryEntry

type HistoryEntry struct {
	ID         int64     `json:"id"`
	PlanHash   string    `json:"plan_hash"`
	Status     string    `json:"status"` // applied, partial, failed, baseline, resolved
	FailedStep int       `json:"failed_step,omitempty"`
	Error      string    `json:"error,omitempty"`
	AppliedAt  time.Time `json:"applied_at"`
	DurationMs int64     `json:"duration_ms"`
	AppliedBy  string    `json:"applied_by"`
	StepsJSON  string    `json:"steps,omitempty"`
	SeedHash   string    `json:"seed_hash,omitempty"`
}

HistoryEntry represents a single migration history record from grizzle_history.

func Baseline

func Baseline(ctx context.Context, db *sql.DB, schemaSQL string, opts BaselineOptions) (*HistoryEntry, error)

Baseline marks an existing database as synchronized with the desired schema, inserting a baseline record into grizzle_history without executing DDL. If the database differs from desired schema and opts.Force is false, Baseline returns ErrBaselineDrift.

func ListHistory

func ListHistory(ctx context.Context, db *sql.DB, opts HistoryOptions) ([]HistoryEntry, error)

ListHistory returns migration history entries recorded in grizzle_history.

func Repair

func Repair(ctx context.Context, db *sql.DB, opts RepairOptions) (*HistoryEntry, error)

Repair resolves the latest partial or failed migration in grizzle_history, marking it as 'resolved' with an optional operator note. It returns ErrNoFailedMigration if there are no partial or failed migrations.

type HistoryOptions

type HistoryOptions struct {
	TargetSchema string
	Limit        int
	Descending   bool
}

HistoryOptions configures retrieval of history records.

type HookContext

type HookContext = exec.HookContext

HookContext provides invocation context and database access for a step hook. DBTX is bound to the executor of the pending step: a *sql.Tx for transactional groups or a *sql.Conn for non-transactional steps.

type IndexIR

type IndexIR = schema.Index

IndexIR represents the intermediate representation of a database index.

type LintDiagnostic

type LintDiagnostic = lint.Diagnostic

LintDiagnostic is a single static lint finding against a schema element.

func LintSchema

func LintSchema(s *SchemaIR, rules ...LintRule) []LintDiagnostic

LintSchema runs rules (defaulting to DefaultLintRules) against the compiled schema IR and returns deterministically sorted diagnostics.

func LintSeed

func LintSeed(seedSQL string) []LintDiagnostic

LintSeed inspects seed SQL statements for idempotency and safety issues (such as bare INSERTs without conflict guards, TRUNCATE, or unconditional DELETE) and returns deterministically sorted diagnostics.

type LintRule

type LintRule = lint.Rule

LintRule is a pure check executed against the desired schema IR.

func DefaultLintRules

func DefaultLintRules() []LintRule

DefaultLintRules returns the built-in lint rule set (L001..L013).

func FilterLintRules added in v0.2.0

func FilterLintRules(rules []LintRule, disabledIDs []string) []LintRule

FilterLintRules excludes rules whose IDs match any entry in disabledIDs.

func LintRulesForProfile added in v0.2.0

func LintRulesForProfile(profile string) ([]LintRule, error)

LintRulesForProfile returns the lint rules corresponding to a profile name ("standard", "strict", "all"). An empty name defaults to "standard".

type LintSeverity

type LintSeverity = lint.Severity

LintSeverity indicates how strongly a lint diagnostic should block a release.

type LockConflict added in v0.2.0

type LockConflict = plan.LockConflict

LockConflict describes a concurrent database session holding a lock on a target table.

type LockContentionError added in v0.2.0

type LockContentionError = plan.LockContentionError

LockContentionError contains details about conflicting lock holders detected during pre-flight check.

type Metrics added in v0.2.0

type Metrics = exec.Metrics

Metrics defines the interface for recording Grizzle operational metrics.

type Option

type Option func(*Options)

Option represents a functional option for configuring Options.

func WithAcceptHazards

func WithAcceptHazards(hazards ...plan.HazardCode) Option

WithAcceptHazards configures explicitly accepted critical hazard codes.

func WithAfterStep

func WithAfterStep(fn StepHook) Option

WithAfterStep registers a hook that runs immediately after each successful plan step.

func WithAfterSync

func WithAfterSync(fn SyncHook) Option

WithAfterSync registers a hook that runs once after all migration steps and history recording succeed.

func WithAllowDrop

func WithAllowDrop(allow bool) Option

WithAllowDrop sets the general drop permission.

func WithAllowResumePartial

func WithAllowResumePartial(allow bool) Option

WithAllowResumePartial allows Sync to resume after a prior failed or partial migration.

func WithBackfill

func WithBackfill(fn BackfillFunc) Option

WithBackfill configures the batch backfill hook function for staged expand migrations. The CLI also installs a hook via --backfill / --backfill-file; a library Backfill wins when both are configured by the caller.

func WithBeforeStep

func WithBeforeStep(fn StepHook) Option

WithBeforeStep registers a hook that runs immediately prior to each plan step.

func WithBeforeSync

func WithBeforeSync(fn SyncHook) Option

WithBeforeSync registers a hook that runs once before any migration steps or locks are executed.

func WithDevDatabaseURL added in v0.2.0

func WithDevDatabaseURL(url string) Option

WithDevDatabaseURL sets the external database URL used for shadow-schema compilation. When non-empty, the always-rolled-back shadow transaction runs against this database instead of the target database.

Use when the target database user lacks CREATE SCHEMA privilege. Without this option a privilege failure during shadow setup returns a clear error naming --dev-url / Options.DevDatabaseURL as the fix.

PostgreSQL only; ignored on SQLite.

func WithDialect

func WithDialect(d Dialect) Option

WithDialect sets the database dialect.

func WithDryRun

func WithDryRun() Option

WithDryRun enables dry-run mode: the planned SQL is validated against the live database without executing it.

func WithDryRunLockTimeout

func WithDryRunLockTimeout(d time.Duration) Option

WithDryRunLockTimeout sets the lock wait bound for live dry-run verification.

func WithExcludeTables

func WithExcludeTables(tables ...string) Option

WithExcludeTables sets excluded tables and glob patterns.

func WithExecuteHooksInDryRun

func WithExecuteHooksInDryRun() Option

WithExecuteHooksInDryRun allows BeforeStep/AfterStep hooks to run during live dry-run verification.

func WithExpandContract

func WithExpandContract(expand bool) Option

WithExpandContract enables or disables staged expand-and-contract zero-downtime migrations.

func WithExpectedHash

func WithExpectedHash(hash string) Option

WithExpectedHash specifies the approved plan hash to verify post-lock during Sync.

func WithFailOnContention added in v0.2.0

func WithFailOnContention(fail bool) Option

WithFailOnContention configures whether lock contention pre-flight gates migration execution. When true, conflicting locks on target tables trigger backoff retries up to LockTimeout, and abort with ErrLockContention if contention does not clear.

func WithIncludeTables

func WithIncludeTables(tables ...string) Option

WithIncludeTables sets included tables.

func WithLockNamespace

func WithLockNamespace(ns string) Option

WithLockNamespace sets the application namespace string for PostgreSQL advisory locks.

func WithLockTimeout

func WithLockTimeout(d time.Duration) Option

WithLockTimeout sets the maximum duration to wait for acquiring locks.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the structured logger.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the maximum retry attempts upon lock timeout conflict (SQLSTATE 55P03).

func WithMetrics added in v0.2.0

func WithMetrics(m Metrics) Option

WithMetrics sets the metrics recorder.

func WithNonConcurrentIndexes

func WithNonConcurrentIndexes(disabled bool) Option

WithNonConcurrentIndexes controls whether PostgreSQL index creation should run inside the transaction.

func WithRandFloat

func WithRandFloat(fn func() float64) Option

WithRandFloat sets a custom random float function for deterministic backoff jitter in tests.

func WithRenames

func WithRenames(renames map[string]string) Option

WithRenames sets the explicit column rename mapping. Keys are `table.old` or `old`; values are the new column name.

func WithSQLiteAttach

func WithSQLiteAttach(attach map[string]string) Option

WithSQLiteAttach sets the SQLite ATTACH DATABASE map (schema name → path).

func WithSQLiteRebuildBatching

func WithSQLiteRebuildBatching(threshold, batchSize int) Option

WithSQLiteRebuildBatching sets the threshold and batch size for chunked keyset copying during SQLite rebuilds.

func WithSeedForce

func WithSeedForce(force bool) Option

WithSeedForce re-runs the seed even when the same seed hash was already applied.

func WithSeedSQL

func WithSeedSQL(seedSQL string) Option

WithSeedSQL attaches idempotent seed SQL executed after a successful sync.

func WithStatementTimeout

func WithStatementTimeout(d time.Duration) Option

WithStatementTimeout sets the maximum duration for any single migration DDL statement.

func WithStrictScope

func WithStrictScope(strict bool) Option

WithStrictScope enables strict scoping mode requiring non-empty IncludeTables.

func WithTargetSchema

func WithTargetSchema(schema string) Option

WithTargetSchema sets the target schema. Deprecated: Use WithTargetSchemas for multi-schema support.

func WithTargetSchemaPattern added in v0.2.0

func WithTargetSchemaPattern(pattern string) Option

WithTargetSchemaPattern sets a LIKE-style pattern to discover target PostgreSQL schemas dynamically from pg_namespace (e.g. "tenant_%").

func WithTargetSchemas

func WithTargetSchemas(schemas ...string) Option

WithTargetSchemas sets the target schemas to manage.

func WithTracer

func WithTracer(t Tracer) Option

WithTracer sets the tracer.

type Options

type Options struct {
	// Dialect explicitly defines the database engine (DialectPostgres or DialectSQLite).
	Dialect Dialect

	// SchemaSQL contains the complete desired state. PostgreSQL role and
	// catalog statements may share this file; they bypass shadow compilation
	// and are merged with the optional side-channel files.
	SchemaSQL string

	// TargetSchema is the schema to manage (defaults to "public" for Postgres, "main" for SQLite).
	// Deprecated: Use TargetSchemas for multi-schema support.
	TargetSchema string

	// TargetSchemas specifies the database schemas to manage (defaults to [TargetSchema] or ["public"] for Postgres).
	// For SQLite, entries other than "main" are ATTACH DATABASE schema names listed in SQLiteAttach.
	TargetSchemas []string

	// TargetSchemaPattern specifies a LIKE-style pattern to discover target PostgreSQL schemas
	// dynamically from pg_namespace (e.g. "tenant_%"). Discovered schemas are sorted deterministically,
	// exclude system schemas, and are populated into TargetSchemas.
	TargetSchemaPattern string

	// SQLiteAttach maps ATTACH DATABASE schema names to filesystem paths.
	// Every TargetSchemas entry other than "main" must appear here; "main" is
	// the primary database already open and must not be listed. Unknown keys,
	// empty paths, and duplicate names are rejected. PostgreSQL ignores this field.
	SQLiteAttach map[string]string

	// ShadowSchema is the temporary schema name used for validation (defaults to "_grizzle_shadow").
	ShadowSchema string

	// AllowDrop permits all destructive operations when set to true.
	// Defaults to false for zero data loss.
	AllowDrop bool

	// AllowRevoke permits privilege revocation when a grant in RolesSQL is
	// removed (access loss). Has no granular override; REVOKE_PRIVILEGE
	// (CRITICAL) still requires AcceptHazards.
	AllowRevoke bool

	// AllowDropRole permits dropping a managed role that left RolesSQL.
	// Has no granular override; DROP_ROLE (CRITICAL) still requires
	// AcceptHazards.
	AllowDropRole bool

	// AllowDropPublication permits dropping a managed publication that
	// left CatalogSQL. Has no granular override; DROP_PUBLICATION
	// (CRITICAL) still requires AcceptHazards.
	AllowDropPublication bool

	// AllowDropEventTrigger permits dropping a managed event trigger that
	// left CatalogSQL. Has no granular override; DROP_EVENT_TRIGGER
	// (CRITICAL) still requires AcceptHazards.
	AllowDropEventTrigger bool

	// AllowDropSubscription permits dropping a managed subscription that
	// left CatalogSQL. Has no granular override; DROP_SUBSCRIPTION
	// (CRITICAL) still requires AcceptHazards. Remote slots are kept
	// (DISABLE + SET slot_name = NONE before DROP).
	AllowDropSubscription bool

	// AllowDropReplicationSlot permits dropping a standalone logical
	// replication slot via explicit SELECT pg_drop_replication_slot.
	// Live-only slots are never swept. DROP_REPLICATION_SLOT (CRITICAL)
	// still requires AcceptHazards. Active slots (active_pid IS NOT NULL)
	// are refused.
	AllowDropReplicationSlot bool

	// Granular drop overrides (nil inherits from AllowDrop):
	AllowDropTable         *bool
	AllowDropColumn        *bool
	AllowDropIndex         *bool
	AllowDropFK            *bool
	AllowDropCheck         *bool
	AllowDropExtension     *bool
	AllowDropFunction      *bool
	AllowDropPolicy        *bool
	AllowDropTrigger       *bool
	AllowDropView          *bool
	AllowDropDomain        *bool
	AllowDropType          *bool
	AllowDropRangeType     *bool
	AllowDropCompositeType *bool
	AllowDropSequence      *bool

	// ExcludeTables defines table names or glob patterns (e.g. "spatial_ref_sys", "asynq_*")
	// that Grizzle will never manage, alter, or drop.
	ExcludeTables []string

	// IncludeTables limits Grizzle's management scope to only the specified tables or patterns.
	IncludeTables []string

	// StrictScope requires IncludeTables to be non-empty. When true and IncludeTables is empty,
	// operations fail immediately with ErrStrictScope.
	// Strongly recommended for production environments to avoid accidental alterations or drops
	// of untracked tables.
	StrictScope bool

	// AcceptHazards lists critical hazard codes that are explicitly approved to execute.
	// Critical hazards not present in this list will cause Apply to fail with ErrHazardBlocked.
	AcceptHazards []plan.HazardCode

	// NonConcurrentIndexes opts out of emitting CONCURRENTLY for PostgreSQL index creation/drops.
	// When true, index operations are created inside the transaction.
	NonConcurrentIndexes bool

	// LockNamespace specifies the application namespace string used for PostgreSQL advisory locking (defaults to "grizzle").
	LockNamespace string

	// LockID is an optional explicit 64-bit integer used for the PostgreSQL advisory lock (pg_advisory_xact_lock).
	// If 0, Grizzle derives 2-int per-schema advisory locks using (hash32(LockNamespace), hash32(schema)).
	LockID int64

	// LockTimeout specifies the maximum time to wait when acquiring locks (defaults to 5s).
	LockTimeout time.Duration

	// StatementTimeout specifies the maximum execution time for any single DDL statement (defaults to 5m).
	StatementTimeout time.Duration

	// MaxRetries specifies the number of retry attempts upon lock timeout (SQLSTATE 55P03, defaults to 3).
	MaxRetries int

	// RandFloat provides an optional random source func returning in [0.0, 1.0) for deterministic jitter in tests.
	RandFloat func() float64

	// Renames maps old column names to new column names (e.g. "users.old_col": "new_col" or "old_col": "new_col")
	// to explicitly disambiguate column renames instead of treating them as DROP + ADD.
	// Keys are `table.old` or `old`; values are the new column name.
	Renames map[string]string

	// ExpandContract enables staged expand-and-contract zero-downtime migrations (ZDM).
	// In expand mode, renamed or modified columns are added alongside existing columns,
	// delaying destructive drops to a later, separately approved plan.
	ExpandContract bool

	// Backfill hook function run outside the DDL lock window in batches during staged expand migration.
	// The CLI installs a hook via --backfill / --backfill-file when --expand-contract is set; a library
	// Backfill still wins when both are configured by the caller.
	Backfill BackfillFunc

	// BeforeSync runs once before any migration steps or locks are executed.
	// If it returns an error, the migration aborts before any DDL runs and no
	// history record is written.
	BeforeSync SyncHook

	// AfterSync runs once after all migration steps and history recording succeed.
	// If it returns an error, the DDL has already committed; the error is
	// wrapped with ErrAfterSyncFailed.
	AfterSync SyncHook

	// BeforeStep executes immediately prior to executing each plan step.
	// If it returns an error, the pending step is not executed; in a
	// transactional group the transaction is rolled back and history records
	// the failure with error "before_step hook: ...".
	BeforeStep StepHook

	// AfterStep executes immediately following the successful execution of each
	// plan step. If it returns an error in a transactional group, the
	// transaction is rolled back (reverting the step); in a non-transactional
	// group the step has already committed and history records "partial".
	AfterStep StepHook

	// DryRun returns the planned SQL statements without executing them on the live database.
	DryRun bool

	// DryRunLockTimeout bounds how long live dry-run verification waits on
	// table locks before failing fast (defaults to 2s).
	DryRunLockTimeout time.Duration

	// ExecuteHooksInDryRun allows BeforeStep/AfterStep hooks to run during
	// live dry-run verification. Defaults to false to avoid accidental
	// external side effects (webhooks, message publishing, etc.).
	ExecuteHooksInDryRun bool

	// SeedSQL contains idempotent data-seed SQL executed after a successful
	// sync (Sync DDL → AfterSync → Seed). The seed runs in a single
	// transaction and is skipped when the same seed (by content hash) was
	// already applied, unless SeedForce is set.
	SeedSQL string

	// SeedForce re-runs the seed even when the same seed hash was already
	// applied.
	SeedForce bool

	// RolesSQL optionally supplies desired roles and privilege grants
	// alongside SchemaSQL. Duplicate role/grant identities in this explicit
	// side-channel override entries extracted from SchemaSQL. Role statements
	// are never shadow-compiled, and schema DDL is not valid here. Managed
	// attributes include LOGIN/NOLOGIN, PASSWORD, VALID UNTIL, CONNECTION
	// LIMIT, INHERIT, CREATEDB, CREATEROLE, and ALTER ROLE SET/RESET.
	// SUPERUSER/REPLICATION/BYPASSRLS are refused. Passwords are compared by
	// server-side hash equality and never written into Step.SQL or plan JSON.
	// PostgreSQL only — ignored on SQLite.
	RolesSQL string

	// CatalogSQL optionally supplies desired cluster-catalog objects alongside
	// SchemaSQL. Duplicate publication/event-trigger names in this explicit
	// side-channel override entries extracted from SchemaSQL. Catalog
	// statements are never shadow-compiled or run inside the shadow
	// transaction, and schema DDL is not valid here. Drops are narrow — only
	// marker-stamped (grizzle-managed) objects are considered. Event triggers
	// reference functions that must exist in SchemaSQL or as live objects.
	// PostgreSQL only — rejected on SQLite.
	CatalogSQL string

	// SQLiteRebuildThreshold defines the row count threshold above which SQLite table rebuilds
	// chunk data copying by keyset to prevent journal memory exhaustion.
	// Defaults to 100000. Set to 0 to disable batching.
	SQLiteRebuildThreshold int

	// SQLiteRebuildBatchSize defines the chunk size when copying data in batches during SQLite table rebuilds.
	// Defaults to 10000.
	SQLiteRebuildBatchSize int

	// AllowResumePartial permits Sync to resume execution after a prior run
	// ended in "partial" or "failed" status. Defaults to false (fail closed).
	AllowResumePartial bool

	// ExpectedHash specifies an approved plan hash. When non-empty, Sync
	// verifies the post-lock live plan hash against this value and aborts
	// with ErrPlanDrift if they differ.
	ExpectedHash string

	// DevDatabaseURL is an optional PostgreSQL connection string used
	// exclusively for shadow-schema compilation (the always-rolled-back
	// transaction that validates SchemaSQL). When non-empty the shadow
	// transaction runs against this external database and never touches the
	// target database. Live catalog introspection always uses the target DB.
	//
	// Use DevDatabaseURL when the target database user lacks CREATE SCHEMA
	// privilege. Without it, a privilege failure during shadow setup is
	// returned as an error that names this field and --dev-url as the fix.
	//
	// PostgreSQL only; ignored on SQLite.
	DevDatabaseURL string

	// FailOnContention gates DDL execution on PostgreSQL: if concurrent sessions hold conflicting
	// locks on target tables, retry with backoff up to LockTimeout; if contention persists, abort
	// with ErrLockContention instead of proceeding. When false (default), lock contention is advisory
	// and logs a warning before proceeding.
	//
	// NOTE: Pre-flight lock inspection is inherently racy: concurrent transactions may acquire
	// locks between the pre-flight check and subsequent DDL execution. Always configure LockTimeout
	// alongside FailOnContention to bound wait duration if a race occurs.
	FailOnContention bool

	// Logger accepts a structured logger (*slog.Logger) for migration events.
	Logger *slog.Logger

	// Tracer specifies an optional tracer (OpenTelemetry or custom) for observing migrations.
	Tracer Tracer

	// Metrics specifies an optional metrics recorder (OpenTelemetry or custom) for observing migrations.
	Metrics Metrics
}

Options configures the schema synchronization process.

func Development

func Development(schemaSQL string, opts ...Option) Options

Development returns an Options preset optimized for local development and CI testing: permits destructive operations, executes index creation synchronously (NonConcurrentIndexes=true), and allows partial migration resumption.

func Production

func Production(schemaSQL string, opts ...Option) Options

Production returns an Options preset configured with strict safety defaults: zero-data-loss policy (AllowDrop=false, AllowRevoke=false), online index builds (NonConcurrentIndexes=false), and fail-closed prior-failure gating.

func (*Options) Validate

func (o *Options) Validate() error

Validate is the canonical pure validation entry point for Options. It checks SchemaSQL, strict scope, dialect value, target/shadow identifiers, timeout and retry bounds, SQLite rebuild parameters, and multi-schema SQLite rejection. Dialect-specific checks are skipped while Dialect is DialectAuto (prepareOptions detects the dialect then re-validates). Defaults are applied by prepareOptions after Validate succeeds — Validate never mutates Options.

type PartitionKey

type PartitionKey = schema.PartitionKey

PartitionKey defines how a partitioned table is divided.

type PartitionOf

type PartitionOf = schema.PartitionOf

PartitionOf defines the relationship of a partition table to its parent partitioned table.

type PartitionStrategy

type PartitionStrategy = schema.PartitionStrategy

PartitionStrategy represents the partitioning method used by a partitioned table.

type Plan

type Plan = plan.Plan

Plan is the complete migration plan containing sequenced steps, policy, and hashes.

func ParsePlanJSON

func ParsePlanJSON(data []byte) (*Plan, string, error)

ParsePlanJSON parses a serialized plan JSON string or bytes into a *Plan, returning the recorded plan hash.

func PlanDiff

func PlanDiff(ctx context.Context, db *sql.DB, opts Options) (*Plan, error)

PlanDiff inspects the live database and computes the planned migration steps without applying them.

Example
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	"github.com/muandane/grizzle"
	_ "modernc.org/sqlite"
)

func main() {
	db, err := sql.Open("sqlite", ":memory:")
	if err != nil {
		log.Fatalf("failed to open database: %v", err)
	}
	defer func() { _ = db.Close() }()

	schema := `
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    sku TEXT NOT NULL UNIQUE,
    price INTEGER NOT NULL DEFAULT 0
);`

	plan, err := grizzle.PlanDiff(context.Background(), db, grizzle.Options{
		Dialect:   grizzle.DialectSQLite,
		SchemaSQL: schema,
	})
	if err != nil {
		log.Fatalf("plan diff failed: %v", err)
	}

	fmt.Printf("Plan steps to execute: %d\n", len(plan.Steps))

}
Output:
Plan steps to execute: 1

type PlanDocument

type PlanDocument = plan.Document

PlanDocument represents a complete, serialized migration plan artifact including hash, hazards, scope, and options digest.

type PolicyIR

type PolicyIR = schema.Policy

PolicyIR represents the intermediate representation of a row-level security policy.

type PrimaryKeyIR

type PrimaryKeyIR = schema.PrimaryKey

PrimaryKeyIR represents the intermediate representation of a table primary key.

type RangeTypeIR

type RangeTypeIR = schema.RangeType

RangeTypeIR represents the intermediate representation of a range type.

type RepairOptions

type RepairOptions struct {
	TargetSchema string
	Note         string
}

RepairOptions configures repairing failed/partial migration history.

type RoutineIR

type RoutineIR = schema.Routine

RoutineIR represents the intermediate representation of a function or procedure.

type SchemaIR

type SchemaIR = schema.Schema

SchemaIR represents the database-agnostic schema intermediate representation.

func CompileSchema

func CompileSchema(ctx context.Context, db *sql.DB, opts Options) (*SchemaIR, error)

CompileSchema compiles opts.SchemaSQL into the desired SchemaIR without diffing or executing anything. For PostgreSQL the compilation happens in the shadow schema inside an always-rolled-back transaction (requires a connection to the target database); for SQLite it happens in an in-memory database, making it fully offline.

type ScopeDocument

type ScopeDocument = plan.ScopeDocument

ScopeDocument represents the scope section of a plan document.

type SequenceIR

type SequenceIR = schema.Sequence

SequenceIR represents the intermediate representation of a database sequence generator.

type Span

type Span = exec.Span

Span represents an active trace span recorded by a Tracer.

type StatusReport

type StatusReport struct {
	Dialect      string        `json:"dialect"`
	TargetSchema string        `json:"target_schema,omitempty"`
	InSync       bool          `json:"in_sync"`
	PendingSteps int           `json:"pending_steps"`
	AppliedCount int           `json:"applied_count"`
	PartialCount int           `json:"partial_count"`
	FailedCount  int           `json:"failed_count"`
	Latest       *HistoryEntry `json:"latest,omitempty"`
}

StatusReport details the current synchronization status between desired schema and the live database.

func Status

func Status(ctx context.Context, db *sql.DB, opts Options) (*StatusReport, error)

Status reports the synchronization state and history summary.

type Step

type Step = plan.Step

Step represents a single atomic migration step to execute.

type StepHook

type StepHook = exec.StepHook

StepHook executes custom imperative code immediately before or after each plan step. Hooks must be idempotent: if a later step fails and the migration is retried or resumed after partial execution, hooks may be invoked again.

type SyncHook

type SyncHook = exec.SyncHook

SyncHook executes custom imperative code once before or after the entire synchronization. It receives a dedicated connection (not a transaction) because the execution may contain non-transactional statements.

type TableIR

type TableIR = schema.Table

TableIR represents the intermediate representation of a database table.

type Tracer

type Tracer = exec.Tracer

Tracer defines the interface for tracing Grizzle lifecycle events.

type TriggerIR

type TriggerIR = schema.Trigger

TriggerIR represents the intermediate representation of a trigger.

type UnmanagedKind

type UnmanagedKind = schema.UnmanagedKind

UnmanagedKind represents the category of an unmanaged database object.

type UnmanagedObject

type UnmanagedObject = schema.UnmanagedObject

UnmanagedObject represents an unmanaged database object detected during introspection.

type ViewIR

type ViewIR = schema.View

ViewIR represents the intermediate representation of a view or materialized view.

Directories

Path Synopsis
cmd
grizzle command
Package main implements the grizzle CLI.
Package main implements the grizzle CLI.
examples
expand-contract command
Package main demonstrates zero-downtime expand/contract migrations with Grizzle: a column rename executed in two separately-approved stages with a backfill between them.
Package main demonstrates zero-downtime expand/contract migrations with Grizzle: a column rename executed in two separately-approved stages with a backfill between them.
full-catalog command
Package main demonstrates Grizzle's full managed surface: extensions, domains, tables with RLS + policies, COMMENT ON, functions, procedures, triggers, and (materialized) views — all declared in one schema.sql and synced in-process on boot.
Package main demonstrates Grizzle's full managed surface: extensions, domains, tables with RLS + policies, COMMENT ON, functions, procedures, triggers, and (materialized) views — all declared in one schema.sql and synced in-process on boot.
postgres-stdlib command
roles-grants command
Package main demonstrates RolesSQL side-channel privilege sync: schema objects live in schema.sql; CREATE ROLE / GRANT statements live in roles.sql.
Package main demonstrates RolesSQL side-channel privilege sync: schema objects live in schema.sql; CREATE ROLE / GRANT statements live in roles.sql.
sqlc-workflow command
sqlc-workflow/db
Package db provides type-safe database queries.
Package db provides type-safe database queries.
sqlite-embedded command
Package griztest provides testing utilities and ergonomic primitives for testing applications powered by Grizzle schema migrations.
Package griztest provides testing utilities and ergonomic primitives for testing applications powered by Grizzle schema migrations.
internal
config
Package config manages grizzle.yaml and grizzle.json configuration files.
Package config manages grizzle.yaml and grizzle.json configuration files.
dialect
Package dialect defines the interface for database-specific introspection, DDL rendering, and transactional locking behaviors.
Package dialect defines the interface for database-specific introspection, DDL rendering, and transactional locking behaviors.
dialect/postgres
Package postgres implements PostgreSQL-specific introspection, DDL rendering, advisory locking, and shadow schema compilation.
Package postgres implements PostgreSQL-specific introspection, DDL rendering, advisory locking, and shadow schema compilation.
dialect/sqlite
Package sqlite implements SQLite-specific schema introspection, in-memory shadow validation, and the 12-step table rebuild engine.
Package sqlite implements SQLite-specific schema introspection, in-memory shadow validation, and the 12-step table rebuild engine.
diff
Package diff performs pure relational comparison between live and desired database schemas.
Package diff performs pure relational comparison between live and desired database schemas.
exec
Package exec coordinates statement execution, transactional locking, and safety policy enforcement.
Package exec coordinates statement execution, transactional locking, and safety policy enforcement.
history
Package history manages migration history records and schema drift tracking.
Package history manages migration history records and schema drift tracking.
lint
Package lint provides static, database-free linting of a desired schema IR.
Package lint provides static, database-free linting of a desired schema IR.
plan
Package plan models migration steps, execution plans, safety policies, and hazard detection.
Package plan models migration steps, execution plans, safety policies, and hazard detection.
schema
Package schema defines the pure, engine-agnostic relational schema representation (Table, Column, Index, Constraint, Schema) and normalization logic.
Package schema defines the pure, engine-agnostic relational schema representation (Table, Column, Index, Constraint, Schema) and normalization logic.
scope
Package scope handles table filtering and unmanaged-table boundaries (PostGIS, background queues, and user-specified include/exclude patterns).
Package scope handles table filtering and unmanaged-table boundaries (PostGIS, background queues, and user-specified include/exclude patterns).
testutil
Package testutil provides shared test infrastructure for integration tests.
Package testutil provides shared test infrastructure for integration tests.
otelgrizzle module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL