jmoiron/sqlx · error

non-statement type %v passed to Stmtx

Error message

non-statement type %v passed to Stmtx

What it means

StmtxContext accepts only *sql.Stmt, sqlx.Stmt (by value), or *sqlx.Stmt. When the interface{} argument matches none of those types, the library panics with this message naming the actual Go type it received. It is an intentional programmer-error guard, not a runtime/database failure; the panic message says "Stmtmtx" but the throwing method is Tx.StmtxContext.

Source

Thrown at sqlx_context.go:292

// Rebind a query within a Conn's bindvar type.
func (c *Conn) Rebind(query string) string {
	return Rebind(BindType(c.driverName), query)
}

// StmtxContext returns a version of the prepared statement which runs within a
// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt.
func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt {
	var s *sql.Stmt
	switch v := stmt.(type) {
	case Stmt:
		s = v.Stmt
	case *Stmt:
		s = v.Stmt
	case *sql.Stmt:
		s = v
	default:
		panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
	}
	return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper}
}

// NamedStmtContext returns a version of the prepared statement which runs
// within a transaction.
func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt {
	return &NamedStmt{
		QueryString: stmt.QueryString,
		Params:      stmt.Params,
		Stmt:        tx.StmtxContext(ctx, stmt.Stmt),
	}
}

// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
//
// The provided context is used for the preparation of the statement, not for
// the execution of the statement.

View on GitHub (pinned to 41dac167fd)

Solutions

  1. Pass the statement in an accepted form: unwrap it first, e.g. tx.StmtxContext(ctx, named.Stmt) instead of tx.StmtxContext(ctx, named).
  2. If you have a *sql.Stmt or *sqlx.Stmt, pass it directly; if you hold some other wrapper, extract its embedded .Stmt field.
  3. Check for nil-in-interface: a nil *Stmt stored in an interface{} still matches the case, but a nil interface{} or wrong-typed value will panic — assert the concrete type before calling.
  4. Read the panic message's %v output: it prints the actual reflect type received; fix the call site to supply that value as one of the three accepted types.
  5. Wrap the call in a function with recover() if you must accept untrusted input, or pre-validate the type yourself before invoking StmtxContext.

Example fix

// before
named, _ := tx.PrepareNamedContext(ctx, "INSERT INTO t VALUES (:id)")
s := tx.StmtxContext(ctx, named) // panics: non-statement type *sqlx.NamedStmt passed to Stmtx

// after
named, _ := tx.PrepareNamedContext(ctx, "INSERT INTO t VALUES (:id)")
s := tx.StmtxContext(ctx, named.Stmt) // pass the embedded *Stmt
Defensive patterns

Strategy: type-guard

Validate before calling

func isStmtxArg(stmt interface{}) bool {
    switch stmt.(type) {
    case Stmt, *Stmt, *sql.Stmt:
        return true
    }
    return false
}

if !isStmtxArg(stmt) {
    return fmt.Errorf("StmtxContext requires *sql.Stmt or *Stmt, got %T", stmt)
}
s := tx.StmtxContext(ctx, stmt)

Type guard

func asSQLStmt(stmt interface{}) (*sql.Stmt, bool) {
    switch v := stmt.(type) {
    case Stmt:
        return v.Stmt, true
    case *Stmt:
        return v.Stmt, true
    case *sql.Stmt:
        return v, true
    default:
        return nil, false
    }
}

Try / catch

func safeStmtxContext(tx *sqlx.Tx, ctx context.Context, stmt interface{}) (s *sqlx.Stmt, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("StmtxContext failed: %v", r)
        }
    }()
    return tx.StmtxContext(ctx, stmt), nil
}

Prevention

When it happens

Trigger: Calling Tx.StmtxContext(ctx, x) with any value that is not *sql.Stmt or *Stmt/*sqlx.Stmt — e.g. passing a *sqlx.NamedStmt (as NamedStmtContext internally does, so a custom/inlined call chain can propagate it), a *sql.Tx, a *sqlx.Tx, a raw query string, a *StmtContext nil value stored in an interface, or an unrelated wrapper struct.

Common situations: Storing statements in a generic container ([]interface{}, map, context.Value) and losing the concrete type; refactoring from Tx.Stmt to Tx.StmtxContext and passing the wrong handle; confusing NamedStmt (named-parameter wrapper) with the underlying Stmt and passing the whole NamedStmt where its .Stmt field was expected; type mismatches across sqlx versions where a value satisfies no case in the type switch.

Related errors


AI-assisted analysis of jmoiron/sqlx@41dac167fd (2026-09-03). Data as JSON: /api/errors/e788a3be874c899a. Report an issue: GitHub.