go-sql-driver/mysql · error
reader ' ' is
Error message
reader '%s' is <nil>
What it means
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.
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.
Example fix
// before
mysql.RegisterReaderHandler("data", func() io.Reader {
f, _ := os.Open("/tmp/data.csv")
if f == nil {
return nil // -> "reader 'data' is <nil>"
}
return f
})
// after (surface errors via a failing reader)
mysql.RegisterReaderHandler("data", func() io.Reader {
f, err := os.Open("/tmp/data.csv")
if err != nil {
return errReader{err}
}
return f
})
type errReader struct{ err error }
func (e errReader) Read(p []byte) (int, error) { return 0, e.err } Defensive patterns
Strategy: validation
Validate before calling
// Sanity-check the handler returns non-nil before issuing LOAD DATA.
if r := readerFactory(); r == nil {
return errors.New("reader handler returned nil")
} Type guard
null
Try / catch
// LOAD DATA errors come back from db.Exec; handle them there.
if _, err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE t"); err != nil {
log.Printf("load data failed: %v", err)
} Prevention
- Never let a RegisterReaderHandler closure return nil.
- Return an io.Reader that surfaces open errors on Read.
- Register handlers after their source is initialized.
When it happens
Trigger: 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'".
Common situations: A lazily-opening handler that returns nil on open failure instead of surfacing the error; a handler wired before its data source is ready.
Related errors
- local file ' ' is not registered
- reader ' ' is not registered
- argument count mismatch
- can't convert %T to time.Time
- cannot convert type: %T
AI-assisted analysis of go-sql-driver/mysql@03d76c7e07 (2026-08-07).
Data as JSON: /api/errors/bc251e1367970b7d.
Report an issue: GitHub.
Appendix: source
Thrown at infile.go:115
var rdr io.Reader
packetSize := min(mc.maxWriteSize, defaultPacketSize)
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
// The server might return an an absolute path. See issue #355.
name = name[idx+8:]
readerRegisterLock.RLock()
handler, inMap := readerRegister[name]
readerRegisterLock.RUnlock()
if inMap {
rdr = handler()
if rdr != nil {
if cl, ok := rdr.(io.Closer); ok {
defer deferredClose(&err, cl)
}
} else {
err = fmt.Errorf("reader '%s' is <nil>", name)
}
} else {
err = fmt.Errorf("reader '%s' is not registered", name)
}
} else { // File
name = strings.Trim(name, `"`)
fileRegisterLock.RLock()
_, exists := fileRegister[name]
fileRegisterLock.RUnlock()
if mc.cfg.AllowAllFiles || exists {
var file *os.File
var fi os.FileInfo
if file, err = os.Open(name); err == nil {
defer deferredClose(&err, file)
// get file size
if fi, err = file.Stat(); err == nil {View on GitHub (pinned to 03d76c7e07)