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

  1. Read the wrapped inner error to identify the exact SQL statement and database reason.
  2. 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.
  3. Grant the database user the required DDL privileges (CREATE, ALTER, DROP, INDEX) on the schema.
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/67d6aa084dbf983d. Report an issue: GitHub.