{"record":{"id":"e788a3be874c899a","repo":"jmoiron/sqlx","slug":"non-statement-type-v-passed-to-stmtx-e788a3","errorCode":null,"errorMessage":"non-statement type %v passed to Stmtx","messagePattern":"non-statement type (.+?) passed to Stmtx","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sqlx_context.go","lineNumber":292,"sourceCode":"\n// Rebind a query within a Conn's bindvar type.\nfunc (c *Conn) Rebind(query string) string {\n\treturn Rebind(BindType(c.driverName), query)\n}\n\n// StmtxContext returns a version of the prepared statement which runs within a\n// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt.\nfunc (tx *Tx) StmtxContext(ctx context.Context, 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.StmtContext(ctx, s), Mapper: tx.Mapper}\n}\n\n// NamedStmtContext returns a version of the prepared statement which runs\n// within a transaction.\nfunc (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt {\n\treturn &NamedStmt{\n\t\tQueryString: stmt.QueryString,\n\t\tParams:      stmt.Params,\n\t\tStmt:        tx.StmtxContext(ctx, stmt.Stmt),\n\t}\n}\n\n// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.\n//\n// The provided context is used for the preparation of the statement, not for\n// the execution of the statement.","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/jmoiron/sqlx/blob/41dac167fdad5e3fd81d66cafba0951dc6823a30/sqlx_context.go#L274-L310","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nnamed, _ := tx.PrepareNamedContext(ctx, \"INSERT INTO t VALUES (:id)\")\ns := tx.StmtxContext(ctx, named) // panics: non-statement type *sqlx.NamedStmt passed to Stmtx\n\n// after\nnamed, _ := tx.PrepareNamedContext(ctx, \"INSERT INTO t VALUES (:id)\")\ns := tx.StmtxContext(ctx, named.Stmt) // pass the embedded *Stmt","handlingStrategy":"type-guard","validationCode":"func isStmtxArg(stmt interface{}) bool {\n    switch stmt.(type) {\n    case Stmt, *Stmt, *sql.Stmt:\n        return true\n    }\n    return false\n}\n\nif !isStmtxArg(stmt) {\n    return fmt.Errorf(\"StmtxContext requires *sql.Stmt or *Stmt, got %T\", stmt)\n}\ns := tx.StmtxContext(ctx, stmt)","typeGuard":"func asSQLStmt(stmt interface{}) (*sql.Stmt, bool) {\n    switch v := stmt.(type) {\n    case Stmt:\n        return v.Stmt, true\n    case *Stmt:\n        return v.Stmt, true\n    case *sql.Stmt:\n        return v, true\n    default:\n        return nil, false\n    }\n}","tryCatchPattern":"func safeStmtxContext(tx *sqlx.Tx, ctx context.Context, stmt interface{}) (s *sqlx.Stmt, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"StmtxContext failed: %v\", r)\n        }\n    }()\n    return tx.StmtxContext(ctx, stmt), nil\n}","preventionTips":["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."],"tags":["go","sqlx","panic","type-mismatch","transactions","prepared-statements"],"backgroundTag":"panic-type-assertion-failed","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"}