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

  1. Pass a prepared statement: use db.Preparex(q) to get *sqlx.Stmt, then tx.Stmtx(stmt).
  2. For named statements, use db.PrepareNamed(q) and tx.NamedStmt instead.
  3. 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

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


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