exec

package
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: 30 Imported by: 0

Documentation

Overview

Package exec coordinates statement execution, transactional locking, and safety policy enforcement.

Dependency rule: exec is the execution layer. It coordinates dialects, plans, schemas, and scopes. It has zero knowledge of the public root API.

Index

Constants

View Source
const (
	// DefaultLockTimeout specifies the default timeout for acquiring table/advisory locks.
	DefaultLockTimeout = 5 * time.Second

	// DefaultStatementTimeout specifies the default timeout for executing any single migration DDL statement.
	DefaultStatementTimeout = 5 * time.Minute

	// DefaultMaxRetries specifies the default number of retry attempts upon lock timeout conflicts.
	DefaultMaxRetries = 3
)
View Source
const DefaultDryRunLockTimeout = 2 * time.Second

DefaultDryRunLockTimeout bounds how long dry-run DDL may wait on table locks before failing fast instead of queuing behind application traffic.

Variables

This section is empty.

Functions

func ApplyPostgres

func ApplyPostgres(ctx context.Context, db *sql.DB, p *plan.Plan, cfg PostgresExecConfig) (err error)

ApplyPostgres applies a precomputed plan to PostgreSQL using a dedicated connection, session-level advisory locking, step grouping (NonTx vs Tx), timeouts, and retry logic.

func ApplySQLite

func ApplySQLite(ctx context.Context, db *sql.DB, p *plan.Plan, cfg SQLiteExecConfig) (err error)

ApplySQLite applies a precomputed plan to SQLite in a single transaction with foreign keys handling.

func ApplySessionTimeouts

func ApplySessionTimeouts(ctx context.Context, dbtx dialect.DBTX, lockTimeout, stmtTimeout time.Duration) error

ApplySessionTimeouts sets lock_timeout and statement_timeout on the dedicated session connection.

func ApplyTxTimeouts

func ApplyTxTimeouts(ctx context.Context, dbtx dialect.DBTX, lockTimeout, stmtTimeout time.Duration) error

ApplyTxTimeouts sets lock_timeout and statement_timeout locally for the current transaction.

func BuildKeysetSelectSQL added in v0.2.0

func BuildKeysetSelectSQL(d Dialect, tableRef string, pkCols []string, newCol string, hasCursor bool) string

BuildKeysetSelectSQL builds the keyset SELECT statement for selecting PK batches.

func BuildKeysetUpdateSQL added in v0.2.0

func BuildKeysetUpdateSQL(d Dialect, tableRef string, pkCols []string, oldCol, newCol string, numRows int) string

BuildKeysetUpdateSQL builds the batch UPDATE statement matching the selected PK tuples.

func CollectTablesNeedingStrongLocks added in v0.2.0

func CollectTablesNeedingStrongLocks(primarySchema string, steps []plan.Step) []postgres.SchemaTable

CollectTablesNeedingStrongLocks inspects steps and returns a deduplicated slice of SchemaTable identifiers for all existing tables that will be locked during DDL execution.

func CompileSchemaPostgres

func CompileSchemaPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (*schema.Schema, error)

CompileSchemaPostgres compiles schemaSQL in the shadow schema within a single always-rolled-back transaction and returns the desired schema IR. The shadow schema is created and dropped as part of the transaction, so no durable state is modified.

When cfg.DevDB is non-nil, the shadow compilation transaction runs against cfg.DevDB instead of db. This lets callers compile against an external database when the target database user lacks CREATE SCHEMA privilege.

func CompileSchemaSQLite

func CompileSchemaSQLite(ctx context.Context, schemaSQL string, schemas ...string) (*schema.Schema, error)

CompileSchemaSQLite compiles schemaSQL in an isolated in-memory SQLite shadow database and returns the desired schema IR without touching any persistent database. When schemas lists attached names, empty :memory: databases are ATTACHed so multi-schema DDL compiles without live files.

func ComputeBackoff

func ComputeBackoff(attempt int, randFn func() float64) time.Duration

ComputeBackoff calculates an exponential backoff with jitter for a given retry attempt.

func DiffPostgres

func DiffPostgres(ctx context.Context, dbtx dialect.DBTX, cfg PostgresExecConfig) ([]plan.Step, error)

DiffPostgres computes the diff and renders the sequenced migration steps for PostgreSQL.

func DisableStatementTimeout

func DisableStatementTimeout(ctx context.Context, dbtx dialect.DBTX, stmtTimeout time.Duration) (restore func(), err error)

DisableStatementTimeout disables the statement_timeout on the dedicated session connection so long-running non-transactional DDL (e.g. CREATE INDEX CONCURRENTLY) is never killed mid-execution, which would leave an INVALID index behind.

It returns a restore func that reapplies the given timeout (or RESETs it back to the server default when stmtTimeout <= 0). The restore func is idempotent, uses a detached context so it still runs after cancellation, and must be called before the connection returns to work where the configured timeout should apply again.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reports whether an error is transient and safe to retry while no migration step has committed yet (see SyncPostgres and ApplyPostgres).

Classification is SQLSTATE-based only: errors are inspected through *pgconn.PgError (pgx driver) or the database/sql SQLState() error interface (any other driver, e.g. lib/pq). Message text is never matched, because error text is user-influenced (identifiers, constraint names) and would misclassify ordinary failures as retryable.

func LookupTablePK added in v0.2.0

func LookupTablePK(ctx context.Context, tx *sql.Tx, d Dialect, targetSchema, table string) ([]string, error)

LookupTablePK returns the primary key columns in constraint order and verifies that an index exists on table whose leading columns match the PK order.

func PlanDiffPostgres

func PlanDiffPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (*plan.Plan, error)

PlanDiffPostgres generates the plan for PostgreSQL without applying statements.

func PlanDiffSQLite

func PlanDiffSQLite(ctx context.Context, db *sql.DB, cfg SQLiteExecConfig) (*plan.Plan, error)

PlanDiffSQLite generates the migration plan for SQLite without applying it.

func RunBackfill

func RunBackfill(ctx context.Context, db *sql.DB, targetSchema string, renames map[string]string, steps []plan.Step, hook BackfillFunc, logger *slog.Logger) error

RunBackfill executes the backfill hook outside the DDL lock window in batches.

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 SeedPostgres

func SeedPostgres(ctx context.Context, db *sql.DB, cfg SeedExecConfig, seedSQL string) error

SeedPostgres executes idempotent seed SQL on PostgreSQL. The seed runs in a single transaction under a session-level advisory lock; either the whole seed commits with an 'applied' history record keyed by its seed 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...).

func SeedSQLite

func SeedSQLite(ctx context.Context, db *sql.DB, cfg SeedExecConfig, seedSQL string) error

SeedSQLite executes idempotent seed SQL on SQLite inside a single transaction that either fully commits with an 'applied' history record keyed by its seed hash, or leaves the database untouched. If the same seed hash was already applied and Force is false, the seed is skipped.

func StepNeedsStrongLock added in v0.2.0

func StepNeedsStrongLock(s plan.Step) bool

StepNeedsStrongLock reports whether a plan step acquires a strong lock on an existing table (such as AccessExclusiveLock, ShareRowExclusiveLock, or ShareLock).

func SyncPostgres

func SyncPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (err error)

SyncPostgres synchronizes PostgreSQL with session-level advisory locking, timeouts, and retry logic.

func SyncSQLite

func SyncSQLite(ctx context.Context, db *sql.DB, cfg SQLiteExecConfig) (err error)

SyncSQLite synchronizes SQLite in a single transaction with foreign keys handling.

Types

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.

func NewCopyBackfillHook added in v0.2.0

func NewCopyBackfillHook(d Dialect, batch int) BackfillFunc

NewCopyBackfillHook builds a BackfillFunc that copies values from oldCol to newCol using keyset pagination ordered by primary key. It supports N-column composite primary keys (N >= 1) using SQL row-value comparisons: (pk1, ..., pkN) > ($1, ..., $N).

NULL-free assumption: Primary key columns are guaranteed NOT NULL by relational database constraints (SQL standard, PostgreSQL, and SQLite). Because keyset pagination compares (col1, ..., colN) > (cursor1, ..., cursorN), all operand values are non-null. This eliminates SQL three-valued logic (NULL / unknown) edge cases in row-value comparisons, ensuring strictly two-valued (true or false) evaluations and deterministic progression.

The hook requires and verifies that a database index exists whose leading columns match the primary key columns in exact order before keyset traversal begins.

type Dialect added in v0.2.0

type Dialect string

Dialect identifies the database dialect for backfill operations.

const (
	DialectPostgres Dialect = "postgres"
	DialectSQLite   Dialect = "sqlite"
)

Dialect constants for PostgreSQL and SQLite.

type DryRunResult

type DryRunResult struct {
	// Plan is the verified plan (including skipped NonTx steps).
	Plan *plan.Plan
	// ExecutedSteps counts transactional steps that executed and rolled back cleanly.
	ExecutedSteps int
	// UnverifiedNonTx lists non-transactional steps that could not be run in
	// the rollback sandbox (e.g. CREATE INDEX CONCURRENTLY).
	UnverifiedNonTx []plan.Step
	// VerifiedTx lists transactional steps that executed inside the sandbox.
	VerifiedTx []plan.Step
	// Duration is the total wall time of the verification.
	Duration time.Duration
}

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

func DryRunVerifyPostgres

func DryRunVerifyPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (*DryRunResult, error)

DryRunVerifyPostgres verifies the planned DDL against live data inside an always-rolled-back transaction. Non-transactional steps are skipped and reported in UnverifiedNonTx. Every transaction is unconditionally rolled back, no history is recorded, and hooks do not run unless cfg.ExecuteHooksInDryRun is set.

func DryRunVerifySQLite

func DryRunVerifySQLite(ctx context.Context, db *sql.DB, cfg SQLiteExecConfig) (*DryRunResult, error)

DryRunVerifySQLite verifies the planned DDL against live data inside a SAVEPOINT sandbox that is always rolled back. No history is recorded and hooks do not run unless cfg.ExecuteHooksInDryRun is set.

type HookContext

type HookContext struct {
	// Context is the migration execution context.
	Context context.Context
	// DBTX is the database executor bound to the current step or sync phase.
	DBTX dialect.DBTX
	// Step is the pending (BeforeStep) or just-executed (AfterStep) plan step.
	Step plan.Step
	// Index is the 1-based index of the current step.
	Index int
	// Total is the total number of steps in the plan.
	Total int
	// IsNonTx reports whether the step runs outside a transaction.
	IsNonTx bool
}

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 Metrics added in v0.2.0

type Metrics interface {
	RecordMigrationDuration(ctx context.Context, duration time.Duration, dialect string, status string)
	RecordDriftDetected(ctx context.Context, dialect string, schema string)
	RecordLockWait(ctx context.Context, duration time.Duration, dialect string)
}

Metrics defines the interface for recording Grizzle operational metrics.

type PostgresExecConfig

type PostgresExecConfig struct {
	TargetSchema         string
	TargetSchemas        []string
	ShadowSchema         string
	ShadowSchemas        []string
	SchemaSQL            string
	RolesSQL             string
	CatalogSQL           string
	LockNamespace        string
	LockID               int64
	Filters              scope.Filters
	Policy               plan.DropPolicy
	AcceptHazards        []plan.HazardCode
	AllowResumePartial   bool
	ExpectedHash         string
	NonConcurrentIndexes bool
	LockTimeout          time.Duration
	StatementTimeout     time.Duration
	MaxRetries           int
	RandFloat            func() float64
	Logger               *slog.Logger
	Tracer               Tracer
	Metrics              Metrics
	DryRun               bool
	Backfill             BackfillFunc
	BeforeSync           SyncHook
	AfterSync            SyncHook
	BeforeStep           StepHook
	AfterStep            StepHook
	// 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
	// DryRunLockTimeout bounds lock waits for dry-run verification
	// (defaults to DefaultDryRunLockTimeout of 2s).
	DryRunLockTimeout time.Duration
	// ExecuteHooksInDryRun allows BeforeStep/AfterStep hooks to run inside the
	// dry-run sandbox. Defaults to false to avoid external side effects.
	ExecuteHooksInDryRun bool
	// DevDB is an optional external database connection used exclusively for
	// shadow-schema compilation (the always-rolled-back transaction). When
	// non-nil, shadow transactions open against DevDB instead of the target
	// database. Live catalog introspection always uses the target db passed to
	// the calling function. DevDB is never written to outside of the rolled-back
	// shadow transaction.
	DevDB *sql.DB
	// ShadowDBTX is an optional DBTX used by DiffPostgres to inspect the shadow
	// schema. If nil, dbtx is used for both live and shadow inspection.
	ShadowDBTX dialect.DBTX
}

PostgresExecConfig specifies the execution options for PostgreSQL synchronization.

type SQLiteExecConfig

type SQLiteExecConfig struct {
	SchemaSQL            string
	TargetSchema         string
	TargetSchemas        []string
	SQLiteAttach         map[string]string
	Filters              scope.Filters
	Policy               plan.DropPolicy
	AcceptHazards        []plan.HazardCode
	AllowResumePartial   bool
	ExpectedHash         string
	Logger               *slog.Logger
	Tracer               Tracer
	Metrics              Metrics
	DryRun               bool
	Backfill             BackfillFunc
	BeforeSync           SyncHook
	AfterSync            SyncHook
	BeforeStep           StepHook
	AfterStep            StepHook
	DryRunLockTimeout    time.Duration
	ExecuteHooksInDryRun bool

	RebuildThreshold int
	RebuildBatchSize int
}

SQLiteExecConfig specifies the execution options for SQLite synchronization.

type SeedExecConfig

type SeedExecConfig struct {
	// TargetSchemas lists the PostgreSQL schemas the seed runs against
	// (defaults to ["public"]). Ignored on SQLite.
	TargetSchemas []string

	// LockID is an optional explicit 64-bit integer advisory lock used to
	// serialize concurrent seed runs on PostgreSQL.
	LockID int64

	// LockNamespace is the application namespace string used to derive
	// per-schema advisory locks (defaults to "grizzle").
	LockNamespace string

	// LockTimeout bounds how long the seed waits for the advisory lock
	// (defaults to DefaultLockTimeout). PostgreSQL only.
	LockTimeout time.Duration

	// StatementTimeout bounds the seed execution time. PostgreSQL only.
	StatementTimeout time.Duration

	// Force re-runs the seed even when the same seed hash is already
	// recorded as applied.
	Force bool

	// Logger accepts a structured logger for seed events.
	Logger *slog.Logger
}

SeedExecConfig configures an idempotent seed execution.

type Span

type Span interface {
	End()
	RecordError(err error)
	SetAttribute(key string, value any)
}

Span represents an active trace span recorded by a Tracer.

type StepGroup

type StepGroup struct {
	NonTx bool
	Steps []plan.Step
}

StepGroup partitions contiguous steps into transactional and non-transactional execution batches.

func GroupSteps

func GroupSteps(steps []plan.Step) []StepGroup

GroupSteps partitions migration steps into contiguous batches based on transactional requirement. Non-transactional steps run standalone. VALIDATE CONSTRAINT steps run in separate transactions after ADD ... NOT VALID steps commit, releasing ACCESS EXCLUSIVE table locks.

type StepHook

type StepHook func(ctx HookContext) error

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 func(ctx context.Context, dbtx dialect.DBTX) error

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. Hooks must be idempotent for the same reason as StepHook.

type Tracer

type Tracer interface {
	Start(ctx context.Context, spanName string) (context.Context, Span)
}

Tracer defines the interface for tracing Grizzle lifecycle events.

Jump to

Keyboard shortcuts

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