{"record":{"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/03d76c7e07908e255ce62d126d07ede3f2365d86/infile.go#L97-L133","documentation":"During LOAD DATA LOCAL INFILE with a 'Reader::<name>' source, the driver calls the handler registered via mysql.RegisterReaderHandler. If that handler returns a nil io.Reader, there is nothing to stream to the server, so the driver errors.","triggerScenarios":"Registering a handler whose function returns nil (e.g. the underlying file/stream could not be opened and the handler swallows the error by returning nil), then executing \"LOAD DATA LOCAL INFILE 'Reader::data'\".","commonSituations":"A lazily-opening handler that returns nil on open failure instead of surfacing the error; a handler wired before its data source is ready.","solutions":["Make the handler return a non-nil io.Reader (or io.ReadCloser) for every call.","If the source may be unavailable, return a reader whose Read returns the underlying error instead of returning nil.","Register the handler only after the data source is ready, or guard the Exec until ready."],"exampleFix":"// before\nmysql.RegisterReaderHandler(\"data\", func() io.Reader {\n    f, _ := os.Open(\"/tmp/data.csv\")\n    if f == nil {\n        return nil // -> \"reader 'data' is <nil>\"\n    }\n    return f\n})\n// after (surface errors via a failing reader)\nmysql.RegisterReaderHandler(\"data\", func() io.Reader {\n    f, err := os.Open(\"/tmp/data.csv\")\n    if err != nil {\n        return errReader{err}\n    }\n    return f\n})\ntype errReader struct{ err error }\nfunc (e errReader) Read(p []byte) (int, error) { return 0, e.err }","handlingStrategy":"validation","validationCode":"// Sanity-check the handler returns non-nil before issuing LOAD DATA.\nif r := readerFactory(); r == nil {\n    return errors.New(\"reader handler returned nil\")\n}","typeGuard":"null","tryCatchPattern":"// LOAD DATA errors come back from db.Exec; handle them there.\nif _, err := db.Exec(\"LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE t\"); err != nil {\n    log.Printf(\"load data failed: %v\", err)\n}","preventionTips":["Never let a RegisterReaderHandler closure return nil.","Return an io.Reader that surfaces open errors on Read.","Register handlers after their source is initialized."],"tags":["go","mysql","infile","load-data"],"backgroundTag":null,"analyzedSha":"03d76c7e07908e255ce62d126d07ede3f2365d86","analyzedAt":"2026-08-07T10:39:17.340Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}