{"record":{"id":"85ceb9c4b8e9237f","repo":"vitessio/vitess","slug":"queryrowsmap-unexpected-error-v","errorCode":null,"errorMessage":"QueryRowsMap unexpected error: %+v","messagePattern":"QueryRowsMap unexpected error: %\\+v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/external/golib/sqlutils/sqlutils.go","lineNumber":228,"sourceCode":"func ScanRowsToMaps(rows *sql.Rows, on_row func(RowMap) error) error {\n\tcolumns, _ := rows.Columns()\n\terr := ScanRowsToArrays(rows, func(arr []CellData) error {\n\t\tm := rowToMap(arr, columns)\n\t\terr := on_row(m)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n\treturn err\n}\n\n// QueryRowsMap is a convenience function allowing querying a result set while poviding a callback\n// function activated per read row.\nfunc QueryRowsMap(db *sql.DB, query string, on_row func(RowMap) error, args ...any) (err error) {\n\tdefer func() {\n\t\tif derr := recover(); derr != nil {\n\t\t\terr = fmt.Errorf(\"QueryRowsMap unexpected error: %+v\", derr)\n\t\t}\n\t}()\n\n\tvar rows *sql.Rows\n\trows, err = db.Query(query, args...)\n\tif rows != nil {\n\t\tdefer rows.Close()\n\t}\n\tif err != nil && err != sql.ErrNoRows {\n\t\tlog.Error(fmt.Sprint(err))\n\t\treturn err\n\t}\n\terr = ScanRowsToMaps(rows, on_row)\n\treturn\n}\n\n// ExecNoPrepare executes given query using given args on given DB, without using prepared statements.\nfunc ExecNoPrepare(db *sql.DB, query string, args ...any) (res sql.Result, err error) {","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/external/golib/sqlutils/sqlutils.go#L210-L246","documentation":"QueryRowsMap wraps a db.Query loop with a deferred recover. If the on_row callback (or anything else in the function) panics — most commonly a nil pointer dereference or an out-of-range index inside the callback — the panic is converted into this error instead of crashing the process. The %+v captures the recovered panic value, which is usually a runtime error string rather than a real query failure.","triggerScenarios":"Calling sqlutils.QueryRowsMap with an on_row callback that panics (nil map/pointer access, index out of range, calling methods on nil RowMap values); a panic inside db.Query driver code is also caught here.","commonSituations":"Rows with NULL columns accessed without checking row.IsNull / nil; assuming a column exists in the result set; bugs in custom scanning logic inside the callback.","solutions":["Read the recovered panic value in the message to locate the panic (usually 'runtime error: ...'); fix the bug in the on_row callback","Check for NULL columns before use: verify with RowMap.IsNull(col) or check .String/.Int64 nil handling for each column accessed","Validate column names/indices against the actual query result set before accessing them in the callback"],"exampleFix":"// before: panics on NULL/missing column\nerr := sqlutils.QueryRowsMap(db, q, func(r sqlutils.RowMap) error {\n    name := r[\"username\"].String()\n    ...\n})\n// after: check column existence/nullness\nerr := sqlutils.QueryRowsMap(db, q, func(r sqlutils.RowMap) error {\n    var name string\n    if !r.IsNull(\"username\") {\n        name = r[\"username\"].String()\n    }\n    ...\n})","handlingStrategy":"try-catch","validationCode":"func rowMapSafe(r sqlutils.RowMap, col string) (sqlutils.Value, bool) {\n    v, ok := r[col]\n    return v, ok\n}","typeGuard":null,"tryCatchPattern":"// QueryRowsMap already recovers panics into err; handle it at the call site\nif err := sqlutils.QueryRowsMap(db, q, onRow); err != nil {\n    if strings.Contains(err.Error(), \"unexpected error\") {\n        log.Errorf(\"bug in onRow callback: %v\", err)\n    }\n    return err\n}","preventionTips":["Check row.IsNull(col) or presence of the column before accessing RowMap values","Never assume column names exist — derive them from the query or check dynamically","Keep on_row callbacks small and nil-safe"],"tags":["panic","sql","recovered-panic"],"backgroundTag":"recovered-panic-in-callback","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}