go-sql-driver/mysql · error

reader '%s' is <nil>

Error message

reader '%s' is <nil>

What it means

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.

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 c426bd9379)

Solutions

  1. 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.
  2. If the source genuinely has no data, return io.MultiReader() or bytes.NewReader(nil) so the LOAD completes with zero rows.
  3. Move the open() out of the handler so failures surface before LOAD DATA is issued, and the handler only wraps an already-open reader.
  4. Log inside the handler when it would have returned nil, to catch regressions.

Example fix

// before
mysql.RegisterReaderHandler("data", func() io.Reader {
    f, err := os.Open("/tmp/data.csv")
    if err != nil {
        return nil // triggers 'reader data is <nil>'
    }
    return f
})

// after: never return nil; surface the error or return an empty reader
mysql.RegisterReaderHandler("data", func() io.Reader {
    f, err := os.Open("/tmp/data.csv")
    if err != nil {
        return bytes.NewReader(nil) // zero rows instead of a crash
    }
    return f
})
Defensive patterns

Strategy: validation

Validate before calling

// wrap any handler so it can never return nil
func safeReaderHandler(h func() (io.Reader, error)) func() io.Reader {
    return func() io.Reader {
        r, err := h()
        if err != nil || r == nil {
            return bytes.NewReader(nil) // zero-row load instead of '<nil>' error
        }
        return r
    }
}
mysql.RegisterReaderHandler("data", safeReaderHandler(func() (io.Reader, error) {
    return os.Open("/tmp/data.csv")
}))

Type guard

// isValidReaderHandler checks a factory never yields nil
func isValidReaderHandler(h func() io.Reader) bool {
    // best-effort: only call if the handler is side-effect free; otherwise
    // ensure at registration that the contract forbids nil returns
    return h != nil
}

Try / catch

if _, err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE t"); err != nil {
    if strings.Contains(err.Error(), "is <nil>") {
        // fix the handler to return an empty reader and retry
    }
}

Prevention

When it happens

Trigger: 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 ...'.

Common situations: 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.

Related errors


AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04). Data as JSON: /data/errors/bc251e1367970b7d.json. Report an issue: GitHub.