{"id":"bc251e1367970b7d","repo":"go-sql-driver/mysql","slug":"reader-s-is-nil","errorCode":null,"errorMessage":"reader '%s' is <nil>","messagePattern":"reader '(.+?)' is <nil>","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infile.go","lineNumber":115,"sourceCode":"\tvar rdr io.Reader\n\tpacketSize := min(mc.maxWriteSize, defaultPacketSize)\n\n\tif idx := strings.Index(name, \"Reader::\"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader\n\t\t// The server might return an an absolute path. See issue #355.\n\t\tname = name[idx+8:]\n\n\t\treaderRegisterLock.RLock()\n\t\thandler, inMap := readerRegister[name]\n\t\treaderRegisterLock.RUnlock()\n\n\t\tif inMap {\n\t\t\trdr = handler()\n\t\t\tif rdr != nil {\n\t\t\t\tif cl, ok := rdr.(io.Closer); ok {\n\t\t\t\t\tdefer deferredClose(&err, cl)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terr = fmt.Errorf(\"reader '%s' is <nil>\", name)\n\t\t\t}\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"reader '%s' is not registered\", name)\n\t\t}\n\t} else { // File\n\t\tname = strings.Trim(name, `\"`)\n\t\tfileRegisterLock.RLock()\n\t\t_, exists := fileRegister[name]\n\t\tfileRegisterLock.RUnlock()\n\t\tif mc.cfg.AllowAllFiles || exists {\n\t\t\tvar file *os.File\n\t\t\tvar fi os.FileInfo\n\n\t\t\tif file, err = os.Open(name); err == nil {\n\t\t\t\tdefer deferredClose(&err, file)\n\n\t\t\t\t// get file size\n\t\t\t\tif fi, err = file.Stat(); err == nil {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/go-sql-driver/mysql/blob/c426bd93799de0f0e094c8f0582872c529d0ed0a/infile.go#L97-L133","documentation":"Thrown by handleInFileRequest (infile.go:115) during a 'LOAD DATA LOCAL INFILE Reader::<name>' operation when the registered handler function for <name> exists but returned nil instead of an io.Reader. The driver cannot read from a nil reader, so the LOAD DATA fails. The %s is the reader name from the SQL.","triggerScenarios":"Registering a reader via mysql.RegisterReaderHandler(\"data\", func() io.Reader { ... }) whose factory returns nil (e.g. the underlying file/resource could not be opened), then executing 'LOAD DATA LOCAL INFILE Reader::data INTO TABLE ...'.","commonSituations":"The handler tries to open a file/stream at call time and returns nil on error instead of a sentinel reader; lazy initialization that hasn't completed; a handler that returns nil for 'no data today' as a convention; refactoring that broke the handler's contract.","solutions":["Make the registered handler never return nil — return a real io.Reader (e.g. an empty bytes.Reader if there's no data) or return a reader that surfaces the underlying open error.","If the source genuinely has no data, return io.MultiReader() or bytes.NewReader(nil) so the LOAD completes with zero rows.","Move the open() out of the handler so failures surface before LOAD DATA is issued, and the handler only wraps an already-open reader.","Log inside the handler when it would have returned nil, to catch regressions."],"exampleFix":"// before\nmysql.RegisterReaderHandler(\"data\", func() io.Reader {\n    f, err := os.Open(\"/tmp/data.csv\")\n    if err != nil {\n        return nil // triggers 'reader data is <nil>'\n    }\n    return f\n})\n\n// after: never return nil; surface the error or return an empty reader\nmysql.RegisterReaderHandler(\"data\", func() io.Reader {\n    f, err := os.Open(\"/tmp/data.csv\")\n    if err != nil {\n        return bytes.NewReader(nil) // zero rows instead of a crash\n    }\n    return f\n})","handlingStrategy":"validation","validationCode":"// wrap any handler so it can never return nil\nfunc safeReaderHandler(h func() (io.Reader, error)) func() io.Reader {\n    return func() io.Reader {\n        r, err := h()\n        if err != nil || r == nil {\n            return bytes.NewReader(nil) // zero-row load instead of '<nil>' error\n        }\n        return r\n    }\n}\nmysql.RegisterReaderHandler(\"data\", safeReaderHandler(func() (io.Reader, error) {\n    return os.Open(\"/tmp/data.csv\")\n}))","typeGuard":"// isValidReaderHandler checks a factory never yields nil\nfunc isValidReaderHandler(h func() io.Reader) bool {\n    // best-effort: only call if the handler is side-effect free; otherwise\n    // ensure at registration that the contract forbids nil returns\n    return h != nil\n}","tryCatchPattern":"if _, err := db.Exec(\"LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE t\"); err != nil {\n    if strings.Contains(err.Error(), \"is <nil>\") {\n        // fix the handler to return an empty reader and retry\n    }\n}","preventionTips":["Never let a registered handler return nil; return an empty reader for the no-data case.","Open resources before registering the handler so factory failures surface earlier.","Add a unit test asserting each handler returns a non-nil reader."],"tags":["load-data","infile","reader-handler","api-misuse"],"analyzedSha":"c426bd93799de0f0e094c8f0582872c529d0ed0a","analyzedAt":"2026-08-04T21:52:59.219Z","schemaVersion":2}