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

  1. Make the handler return a non-nil io.Reader (or io.ReadCloser) for every call.
  2. If the source may be unavailable, return a reader whose Read returns the underlying error instead of returning nil.
  3. 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

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


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)