jmoiron/sqlx · critical
non-statement type %v passed to Stmtx
Error message
non-statement type %v passed to Stmtx
What it means
Tx.Stmtx prepares a statement within a transaction and accepts only *sqlx.Stmt, *NamedStmt, or *sql.Stmt. Anything else causes a panic (not a returned error) naming the unexpected Go type. It is a programming/type misuse guard.
Source
Thrown at sqlx.go:484
// Preparex a statement within a transaction.
func (tx *Tx) Preparex(query string) (*Stmt, error) {
return Preparex(tx, query)
}
// Stmtx 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) Stmtx(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.Stmt(s), Mapper: tx.Mapper}
}
// NamedStmt returns a version of the prepared statement which runs within a transaction.
func (tx *Tx) NamedStmt(stmt *NamedStmt) *NamedStmt {
return &NamedStmt{
QueryString: stmt.QueryString,
Params: stmt.Params,
Stmt: tx.Stmtx(stmt.Stmt),
}
}
// PrepareNamed returns an sqlx.NamedStmt
func (tx *Tx) PrepareNamed(query string) (*NamedStmt, error) {
return prepareNamed(tx, query)
}
View on GitHub (pinned to 41dac167fd)
Solutions
- Pass a prepared statement: use db.Preparex(q) to get *sqlx.Stmt, then tx.Stmtx(stmt).
- For named statements, use db.PrepareNamed(q) and tx.NamedStmt instead.
- If you only have a query string, prepare it first — Stmtx never accepts strings.
Example fix
// before
tx.Stmtx("SELECT * FROM users WHERE id = ?") // panics
// after
stmt, err := db.Preparex("SELECT * FROM users WHERE id = ?")
if err != nil { return err }
txStmt := tx.Stmtx(stmt) Defensive patterns
Strategy: type-guard
Validate before calling
switch s := stmt.(type) {
case *sqlx.Stmt, *sqlx.NamedStmt, *sql.Stmt:
// ok
default:
return fmt.Errorf("Stmtmtx needs a prepared statement, got %T", stmt)
} Type guard
func isStatement(stmt interface{}) bool {
switch stmt.(type) {
case *sqlx.Stmt, *sqlx.NamedStmt, *sql.Stmt:
return true
}
return false
} Try / catch
defer func() {
if r := recover(); r != nil {
if strings.Contains(fmt.Sprint(r), "non-statement type") {
err = fmt.Errorf("Stmtmtx misuse: %v", r)
} else {
panic(r)
}
}
}()
txStmt := tx.Stmtx(stmt) Prevention
- Only pass results of Preparex/Prepare/PrepareNamed to Stmtx/NamedStmt.
- Never pass raw query strings to Stmtx — it panics rather than errors.
- Wrap Stmtx calls in helpers with typed parameters (*sqlx.Stmt).
When it happens
Trigger: Calling tx.Stmtx(x) where x is a raw string, *sqlx.NamedStmt is fine, but e.g. a string query, a *sql.Tx, or an unrelated type is passed instead of a prepared statement.
Common situations: Confusing Stmtx with NamedExec/PrepareNamed and passing a query string; passing the result of db.Preparex wrongly typed; refactors changing statement types without updating call sites.
Related errors
- non-statement type %v passed to Stmtx
- expected %s but got %s
- structscan expects a struct dest but the provided struct typ
- empty slice passed to 'in' query
- number of bindVars exceeds arguments
AI-assisted analysis of jmoiron/sqlx@41dac167fd (2026-09-03).
Data as JSON: /api/errors/5380c3bef1c7db40.
Report an issue: GitHub.