ory/hydra · error
unable to execute statement: %s
Error message
unable to execute statement: %s
What it means
This error wraps a raw SQL failure that occurred while creating or migrating the schema_migration bookkeeping table when the dialect opts into autocommit DDL (noTxDDL, e.g. CockroachDB). In that mode each DDL statement of the workload is executed directly with RawQuery(...).Exec() outside a transaction, and any statement error is wrapped as 'unable to execute statement: %s' with the offending SQL. The wrapped inner error contains the actual database reason (syntax, permissions, object state).
Source
Thrown at oryx/popx/migrator.go:502
var cancel context.CancelFunc
ctx, cancel = context.WithTimeoutCause(ctx, mb.perMigrationTimeout, errors.Errorf("failed to run all SQL migrations: direction=%s timeout=%s", direction, mb.perMigrationTimeout))
defer cancel()
}
return Transaction(ctx, mb.c.WithContext(ctx), func(ctx context.Context, connection *pop.Connection) error {
return errors.WithStack(fn(connection))
})
}
func (mb *MigrationBox) createMigrationStatusTableTransaction(ctx context.Context, transactions ...[]string) error {
for _, statements := range transactions {
// CockroachDB does not support transactional schema changes, so we have to run
// the statements outside of a transaction. The same applies to any dialect
// that opts into autocommit DDL (see noTxDDL).
if mb.noTxDDL() {
for _, statement := range statements {
if err := mb.c.WithContext(ctx).RawQuery(statement).Exec(); err != nil {
return errors.Wrapf(err, "unable to execute statement: %s", statement)
}
}
} else {
if err := mb.isolatedTransaction(ctx, "init", func(conn *pop.Connection) error {
for _, statement := range statements {
if err := conn.WithContext(ctx).RawQuery(statement).Exec(); err != nil {
return errors.Wrapf(err, "unable to execute statement: %s", statement)
}
}
return nil
}); err != nil {
return errors.WithStack(err)
}
}
}
return nil
}View on GitHub (pinned to 4174065ffb)
Solutions
- Read the wrapped inner error to identify the exact SQL statement and database reason.
- Inspect the schema_migration table state; if a previous upgrade half-applied (interim *_transactional or *_pop_legacy tables exist), manually reconcile or drop the leftovers and retry.
- Grant the database user the required DDL privileges (CREATE, ALTER, DROP, INDEX) on the schema.
- Check connectivity and retry CreateSchemaMigrations, which is idempotent once conflicting objects are cleaned up.
Example fix
// before: partial state from failed upgrade DROP TABLE schema_migration_pop_legacy; DROP TABLE schema_migration_transactional; DROP TABLE schema_migration; // after: retry migration init on a clean slate mb.CreateSchemaMigrations(ctx)
Defensive patterns
Strategy: validation
Validate before calling
// validate DDL privileges and table state before initializing
if err := c.RawQuery("SELECT 1 FROM schema_migration LIMIT 1").Exec(); err != nil {
log.Printf("schema_migration state check: %v — cleanup legacy tables before init", err)
} Try / catch
if err := mb.CreateSchemaMigrations(ctx); err != nil {
var wrapped interface{ Cause() error }
if strings.Contains(err.Error(), "unable to execute statement:") {
log.Printf("DDL failed (autocommit dialect): %v", err) // reconcile schema_migration state manually
}
return err
} Prevention
- Grant the app DB user full DDL rights (CREATE/ALTER/DROP) on its schema.
- Never leave partially applied legacy *_pop_legacy / *_transactional tables after a failed upgrade.
- Run migration init against the same dialect you will run migrations with.
- Back up the schema_migration table before version upgrades of the migrator.
When it happens
Trigger: createMigrationStatusTableTransaction, called from createTransactionalMigrationTable or migrateToTransactionalMigrationTable, executes one of the generated DDL statements (CREATE TABLE schema_migration, CREATE INDEX, INSERT, ALTER TABLE ... RENAME, DROP INDEX) via mb.c.WithContext(ctx).RawQuery(statement).Exec() and the database rejects it.
Common situations: The migration table or a legacy _pop_legacy table already exists from a partially failed prior upgrade (e.g. after an interrupted migrateToTransactionalMigrationTable); the database user lacks DDL privileges (CREATE/ALTER/DROP); a left-over index with the same name; connection loss mid-workload leaving inconsistent state on autocommit dialects.
Related errors
- problem with migration
- error processing %s
- error executing %s, sql: %s
- problem checking for migration version %s
- problem inserting migration version %s
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/67d6aa084dbf983d.
Report an issue: GitHub.