Documentation
¶
Overview ¶
Package executor applies migration SQL against the shadow database.
Per docs/DECISIONS.md the executor wraps the full migration in a single transaction by default, respects explicit BEGIN/COMMIT if present in the migration file, and stops on the first SQL error. Lock findings produced before the failure (from M3 onward) are preserved; post-migration plan capture (M4 onward) is skipped on a failed run.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HasExplicitTransaction ¶
HasExplicitTransaction reports whether any statement in stmts is a transaction-control statement (BEGIN, START TRANSACTION, COMMIT, ROLLBACK, or END). When true, the executor MUST NOT wrap the migration in its own transaction — the file is already directing its own transaction boundaries and wrapping would produce nested-transaction errors.
The check uses the first SQL keyword of each statement after leading comments and whitespace have been stripped by SplitStatements. It is intentionally simple: the v1 rule is "any top-level tx-control statement makes the whole migration self-managed".
func SplitStatements ¶
SplitStatements splits a PostgreSQL script into individual statements. It is a small, purpose-built lexer — not a full SQL parser — that correctly handles the constructs that actually appear in real migration files and would break a naive "split on semicolon" approach:
- single-line comments (-- ...)
- block comments (/* ... */, including nested block comments)
- single-quoted strings ('...' with ” as the embedded-quote escape)
- double-quoted identifiers ("..." with "" escape)
- dollar-quoted strings ($$...$$ and $tag$...$tag$, common in functions)
Semicolons inside any of the above are preserved as part of the current statement; only top-level semicolons terminate a statement. Empty statements are discarded. Returned statements have leading and trailing whitespace trimmed but are otherwise unchanged.
Types ¶
type Result ¶
type Result struct {
// Statements is the per-statement log in execution order. On a
// failed run the failing statement is the last entry.
Statements []StatementResult
// ExplicitTx is true when the migration file contained its own
// transaction-control statements (BEGIN/COMMIT/etc.) and the
// executor therefore did NOT wrap the migration in an implicit
// transaction.
ExplicitTx bool
// Failed is true when the executor halted because a statement
// returned an error or a commit/rollback failed. Downstream stages
// use this flag to decide whether post-migration analysis runs.
Failed bool
// FailureIndex is the Index of the statement that caused the run to
// halt. It is -1 when Failed is false, or when the failure happened
// outside of statement execution (e.g. during commit).
FailureIndex int
// FailureErr carries the underlying error that caused the failure.
// It is nil when Failed is false.
FailureErr error
// TotalDuration is the total wall-clock time from the start of
// execution to the end (success or failure). It does not include
// snapshot restore time.
TotalDuration time.Duration
}
Result is the structured outcome of a single migration run.
Milestone 2 is the first milestone to populate a Result. Later milestones read it:
- M3 (lock analyzer) attaches lock findings to whichever statements were executing when the locks were taken, using Statements[i].
- M4 (plan regression analyzer) consults Failed to decide whether post-migration plan capture runs at all. Per DECISIONS.md it is skipped entirely when Failed is true.
- M5 (report generator) surfaces the migration-failure finding in the "Migration Execution" section when Failed is true.
func Run ¶
Run executes the migration script against conn and returns a structured Result describing every statement's outcome.
By default the entire migration is wrapped in a single transaction. If the migration script contains its own BEGIN/COMMIT/ROLLBACK/END or START TRANSACTION statements, Run respects them instead of wrapping — this matches how Flyway and Alembic execute Postgres migrations and is the closed decision in docs/DECISIONS.md.
Run stops at the first statement error and returns a Result with Failed=true and FailureIndex / FailureErr populated. It does not return a Go error for SQL errors — those are product findings. A non-nil Go error return is reserved for unusual conditions like a failure to open the transaction or the context being cancelled before any statement runs.
type StatementResult ¶
type StatementResult struct {
// Index is the 0-based position of the statement in the original
// migration script after splitting.
Index int
// SQL is the trimmed source text of the statement as it was
// executed (without the trailing semicolon).
SQL string
// StartedAt is the wall-clock time immediately before Exec was
// called for this statement. It is captured on every statement —
// successful or failing — so the M3 lock analyzer can attribute
// pg_locks samples to whichever statement window was executing
// when each sample was taken.
StartedAt time.Time
// Duration is the wall-clock time spent in Exec for this statement.
// It is captured even when the statement errored out.
Duration time.Duration
// Err is nil on success and non-nil on failure. The executor stops
// after the first non-nil Err per docs/DECISIONS.md.
Err error
}
StatementResult captures the outcome of a single migration statement. It is the atomic unit of per-statement timing that downstream M3+ lock analysis and M5+ reporting will consume.
func (StatementResult) EndAt ¶
func (s StatementResult) EndAt() time.Time
EndAt reports the wall-clock time at which the statement finished executing. It is derived from StartedAt + Duration and is the right edge of the statement window used for lock-sample attribution.