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
- Pass the statement in an accepted form: unwrap it first, e.g. tx.StmtxContext(ctx, named.Stmt) instead of tx.StmtxContext(ctx, named).
- If you have a *sql.Stmt or *sqlx.Stmt, pass it directly; if you hold some other wrapper, extract its embedded .Stmt field.
- 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.
- 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.
- 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
- Keep prepared statements in their concrete types (*sqlx.Stmt, *sqlx.NamedStmt) instead of interface{} to get compile-time checks.
- Pass named.Stmt, never the whole *NamedStmt, when calling StmtxContext.
- Avoid storing statements in context.Value or map[string]interface{}; retrieve and type-assert immediately.
- Call Tx.NamedStmtContext for *NamedStmt values instead of routing through StmtxContext yourself.
- Add a unit test that covers every statement wrapper type your code passes to StmtxContext, since the failure is a runtime panic, not an error return.
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
- non-statement type %v passed to Stmtx
- empty slice passed to 'in' query
- number of bindVars exceeds arguments
- number of bindVars less than number arguments
- unexpected `:` while reading named param at
AI-assisted analysis of jmoiron/sqlx@41dac167fd (2026-09-03).
Data as JSON: /api/errors/e788a3be874c899a.
Report an issue: GitHub.