{"record":{"id":"5380c3bef1c7db40","repo":"jmoiron/sqlx","slug":"non-statement-type-v-passed-to-stmtx","errorCode":null,"errorMessage":"non-statement type %v passed to Stmtx","messagePattern":"non-statement type (.+?) passed to Stmtx","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"sqlx.go","lineNumber":484,"sourceCode":"\n// Preparex  a statement within a transaction.\nfunc (tx *Tx) Preparex(query string) (*Stmt, error) {\n\treturn Preparex(tx, query)\n}\n\n// Stmtx returns a version of the prepared statement which runs within a transaction.  Provided\n// stmt can be either *sql.Stmt or *sqlx.Stmt.\nfunc (tx *Tx) Stmtx(stmt interface{}) *Stmt {\n\tvar s *sql.Stmt\n\tswitch v := stmt.(type) {\n\tcase Stmt:\n\t\ts = v.Stmt\n\tcase *Stmt:\n\t\ts = v.Stmt\n\tcase *sql.Stmt:\n\t\ts = v\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"non-statement type %v passed to Stmtx\", reflect.ValueOf(stmt).Type()))\n\t}\n\treturn &Stmt{Stmt: tx.Stmt(s), Mapper: tx.Mapper}\n}\n\n// NamedStmt returns a version of the prepared statement which runs within a transaction.\nfunc (tx *Tx) NamedStmt(stmt *NamedStmt) *NamedStmt {\n\treturn &NamedStmt{\n\t\tQueryString: stmt.QueryString,\n\t\tParams:      stmt.Params,\n\t\tStmt:        tx.Stmtx(stmt.Stmt),\n\t}\n}\n\n// PrepareNamed returns an sqlx.NamedStmt\nfunc (tx *Tx) PrepareNamed(query string) (*NamedStmt, error) {\n\treturn prepareNamed(tx, query)\n}\n","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/jmoiron/sqlx/blob/41dac167fdad5e3fd81d66cafba0951dc6823a30/sqlx.go#L466-L502","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ntx.Stmtx(\"SELECT * FROM users WHERE id = ?\") // panics\n\n// after\nstmt, err := db.Preparex(\"SELECT * FROM users WHERE id = ?\")\nif err != nil { return err }\ntxStmt := tx.Stmtx(stmt)","handlingStrategy":"type-guard","validationCode":"switch s := stmt.(type) {\ncase *sqlx.Stmt, *sqlx.NamedStmt, *sql.Stmt:\n    // ok\ndefault:\n    return fmt.Errorf(\"Stmtmtx needs a prepared statement, got %T\", stmt)\n}","typeGuard":"func isStatement(stmt interface{}) bool {\n    switch stmt.(type) {\n    case *sqlx.Stmt, *sqlx.NamedStmt, *sql.Stmt:\n        return true\n    }\n    return false\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        if strings.Contains(fmt.Sprint(r), \"non-statement type\") {\n            err = fmt.Errorf(\"Stmtmtx misuse: %v\", r)\n        } else {\n            panic(r)\n        }\n    }\n}()\ntxStmt := tx.Stmtx(stmt)","preventionTips":["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)."],"tags":["sqlx","panic","statement","transaction","type-mismatch"],"backgroundTag":"invalid-statement-type","analyzedSha":"41dac167fdad5e3fd81d66cafba0951dc6823a30","analyzedAt":"2026-09-03T06:10:20.382Z","contentChangedAt":"2026-09-03T06:10:20.382Z","schemaVersion":2},"datasetVersion":"2026-09-10T12:17:11.382Z"}