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
- func ApplyPostgres(ctx context.Context, db *sql.DB, p *plan.Plan, cfg PostgresExecConfig) (err error)
- func ApplySQLite(ctx context.Context, db *sql.DB, p *plan.Plan, cfg SQLiteExecConfig) (err error)
- func ApplySessionTimeouts(ctx context.Context, dbtx dialect.DBTX, lockTimeout, stmtTimeout time.Duration) error
- func ApplyTxTimeouts(ctx context.Context, dbtx dialect.DBTX, lockTimeout, stmtTimeout time.Duration) error
- func BuildKeysetSelectSQL(d Dialect, tableRef string, pkCols []string, newCol string, hasCursor bool) string
- func BuildKeysetUpdateSQL(d Dialect, tableRef string, pkCols []string, oldCol, newCol string, ...) string
- func CollectTablesNeedingStrongLocks(primarySchema string, steps []plan.Step) []postgres.SchemaTable
- func CompileSchemaPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (*schema.Schema, error)
- func CompileSchemaSQLite(ctx context.Context, schemaSQL string, schemas ...string) (*schema.Schema, error)
- func ComputeBackoff(attempt int, randFn func() float64) time.Duration
- func DiffPostgres(ctx context.Context, dbtx dialect.DBTX, cfg PostgresExecConfig) ([]plan.Step, error)
- func DisableStatementTimeout(ctx context.Context, dbtx dialect.DBTX, stmtTimeout time.Duration) (restore func(), err error)
- func IsRetryable(err error) bool
- func LookupTablePK(ctx context.Context, tx *sql.Tx, d Dialect, targetSchema, table string) ([]string, error)
- func PlanDiffPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (*plan.Plan, error)
- func PlanDiffSQLite(ctx context.Context, db *sql.DB, cfg SQLiteExecConfig) (*plan.Plan, error)
- func RunBackfill(ctx context.Context, db *sql.DB, targetSchema string, ...) error
- func SeedHash(seedSQL string) string
- func SeedPostgres(ctx context.Context, db *sql.DB, cfg SeedExecConfig, seedSQL string) error
- func SeedSQLite(ctx context.Context, db *sql.DB, cfg SeedExecConfig, seedSQL string) error
- func StepNeedsStrongLock(s plan.Step) bool
- func SyncPostgres(ctx context.Context, db *sql.DB, cfg PostgresExecConfig) (err error)
- func SyncSQLite(ctx context.Context, db *sql.DB, cfg SQLiteExecConfig) (err error)
- type BackfillFunc
- type Dialect
- type DryRunResult
- type HookContext
- type Metrics
- type PostgresExecConfig
- type SQLiteExecConfig
- type SeedExecConfig
- type Span
- type StepGroup
- type StepHook
- type SyncHook
- type Tracer
Constants ¶
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 )
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 ¶
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 ¶
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 ¶
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 ¶
PlanDiffPostgres generates the plan for PostgreSQL without applying statements.
func PlanDiffSQLite ¶
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 ¶
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 ¶
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 ¶
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
StepNeedsStrongLock reports whether a plan step acquires a strong lock on an existing table (such as AccessExclusiveLock, ShareRowExclusiveLock, or ShareLock).
func SyncPostgres ¶
SyncPostgres synchronizes PostgreSQL with session-level advisory locking, timeouts, and retry logic.
func SyncSQLite ¶
SyncSQLite synchronizes SQLite in a single transaction with foreign keys handling.
Types ¶
type BackfillFunc ¶
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.
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 StepGroup ¶
StepGroup partitions contiguous steps into transactional and non-transactional execution batches.
func GroupSteps ¶
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.