Skip to content

client: add ExecQueryRelayLocalInfile for proxy-friendly LOAD DATA LOCAL INFILE#1164

Merged
lance6716 merged 10 commits into
go-mysql-org:masterfrom
nzin-alayacare:feat/client-exec-query-relay-local-infile
Jul 7, 2026
Merged

client: add ExecQueryRelayLocalInfile for proxy-friendly LOAD DATA LOCAL INFILE#1164
lance6716 merged 10 commits into
go-mysql-org:masterfrom
nzin-alayacare:feat/client-exec-query-relay-local-infile

Conversation

@nzin-alayacare

Copy link
Copy Markdown
Contributor

Summary

Add (*Conn).ExecQueryRelayLocalInfile and the companion helper cleanupLocalInfileAfterRelayError to client/conn.go.

Closes #1161

Motivation

The existing Execute / exec path (and the LOCAL INFILE handling in client/auth.go) reads the file from the local filesystem. In a proxy scenario the file lives on the end-user's machine, not on the proxy. The proxy must relay the 0xfb request and the subsequent file-data packets between the upstream MySQL server and the downstream client.

There is no API today to intercept the 0xfb LOCAL INFILE response and hand off the transfer to a callback. This PR fills that gap.

New API

// ExecQueryRelayLocalInfile sends COM_QUERY and handles the LOCAL INFILE protocol
// when the server responds with a 0xfb packet. relayFile receives the full 0xfb
// request payload; the implementation must forward the request to the application
// client, read file-data packets from that client, and write each to this
// connection via WritePacket (4-byte header + payload) until an empty packet
// signals EOF. The final OK or ERR from the upstream server is read and returned.
//
// If relayFile returns an error the connection is resynchronized before the error
// is returned so the connection can be reused.
func (c *Conn) ExecQueryRelayLocalInfile(
    query string,
    relayFile func(localInfileRequestPayload []byte) error,
) (*mysql.Result, error)

cleanupLocalInfileAfterRelayError is unexported; it sends the empty EOF packet and reads the server's OK/ERR to bring the connection back to a usable state when relayFile fails mid-transfer.

Notes

Made with Cursor

…CAL INFILE

ExecQueryRelayLocalInfile is a COM_QUERY variant that handles the
0xfb LOCAL INFILE response via a caller-supplied callback instead of
reading a local file. The callback receives the full 0xfb request
packet and is responsible for forwarding the request to the application
client, relaying file-data packets to this connection via WritePacket,
and signalling EOF with an empty packet.

A companion helper cleanupLocalInfileAfterRelayError re-synchronizes
the connection if relayFile fails mid-transfer by sending the empty
EOF packet and consuming the server's OK/ERR response.

This is the proxy-friendly counterpart to the local-file reading in
client/auth.go. It enables transparent LOAD DATA LOCAL INFILE relay
in proxy implementations without filesystem access.

Closes go-mysql-org#1161

Co-authored-by: Cursor <cursoragent@cursor.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces cleanupLocalInfileAfterRelayError and ExecQueryRelayLocalInfile to client/conn.go to support relaying LOCAL INFILE requests and handling connection resynchronization on errors. The review feedback correctly identifies several potential runtime panics where the code accesses the first element of a packet slice (resp, data, and data2) without first verifying that the slice is not empty. It is recommended to add length checks before accessing these slices and to consistently trace errors using errors.Trace.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread client/conn.go Outdated
Comment thread client/conn.go
Comment thread client/conn.go Outdated
nzin-alayacare and others added 3 commits July 2, 2026 10:38
Guard against empty packets before indexing response headers and wrap
cleanupLocalInfileAfterRelayError I/O errors with errors.Trace for
consistency with the rest of the client package.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cover direct OK/ERR responses, successful relay with file chunks, and
relay failure with connection resynchronization using net.Pipe.

Co-authored-by: Cursor <cursoragent@cursor.com>
COM_QUERY and LocalInFile_HEADER are already typed as byte, so the
explicit byte() casts are redundant and fail the unconvert linter check.

Co-authored-by: Cursor <cursoragent@cursor.com>
@lance6716

Copy link
Copy Markdown
Collaborator

Oh for previous PR codex found some problems:

[P1] server/resp.go (line 31): when CLIENT_SESSION_TRACK is negotiated but SERVER_SESSION_STATE_CHANGED is not set, writeOK still appends a zero-length session-state block via AppendOKSessionTrackSuffix. In the MySQL OK-packet format, that field is only present when the status bit is set; otherwise the packet should stop after the length-encoded info string. This makes auth OK / normal OK packets malformed for strict clients.

[P2] server/server_conf.go (line 137): the new SetCapability / UnsetCapability API lets callers flip any server capability bit, but some of those bits are not independently configurable. A concrete example is CLIENT_SSL: enabling it on a server without tlsConfig will advertise TLS in the greeting and then fail the SSLRequest path during handshake. The setter should either validate a safe whitelist or tie each flag to the underlying server config.

Can you create a new PR to fix it?

@nzin-alayacare

Copy link
Copy Markdown
Contributor Author

[P1] server/resp.go (line 31): when CLIENT_SESSION_TRACK is negotiated but SERVER_SESSION_STATE_CHANGED is not set, writeOK still appends a zero-length session-state block via AppendOKSessionTrackSuffix. In the MySQL OK-packet format, that field is only present when the status bit is set; otherwise the packet should stop after the length-encoded info string. This makes auth OK / normal OK packets malformed for strict clients.

[P2] server/server_conf.go (line 137): the new SetCapability / UnsetCapability API lets callers flip any server capability bit, but some of those bits are not independently configurable. A concrete example is CLIENT_SSL: enabling it on a server without tlsConfig will advertise TLS in the greeting and then fail the SSLRequest path during handshake. The setter should either validate a safe whitelist or tie each flag to the underlying server config.

sure -> #1167

Comment thread client/conn.go Outdated
Comment thread client/conn.go Outdated
nzin-alayacare and others added 3 commits July 3, 2026 06:24
Make the callback signature func(localInfileRequestPayload []byte, c *Conn)
error so callers can see how to write file data back via c.WritePacket.
Document success vs error-path LOCAL INFILE termination in godoc.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the streaming WritePacket callback with a collect-then-send model:
relayFile returns a reader over the complete file content and the library
sends LOCAL INFILE data packets. On callback error only the empty terminator
is sent (no partial file data upstream).

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread client/conn.go
Comment thread client/conn.go Outdated
Comment thread client/conn.go Outdated
Replace cancelLocalInfileRequest and the split success/error tail paths
with sendLocalInfileContentAndAwaitResult. prepareLocalInfileReader
validates the full reader (io.Seeker discard+seek-back, or io.ReadAll)
before any file data is sent upstream, preventing partial transfers.

Co-authored-by: Cursor <cursoragent@cursor.com>
@nzin-alayacare nzin-alayacare requested a review from lance6716 July 6, 2026 05:45

@lance6716 lance6716 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rest lgtm

Comment thread client/conn.go Outdated
Comment thread client/conn.go
Comment thread client/conn.go
Comment thread client/local_infile_test.go Outdated
Comment thread client/local_infile_test.go Outdated
Reuse a single write buffer, send a defensive terminator on mid-stream
errors, pass filename-only to relayFile, expand godoc Notes, and consolidate
tests into subtests including StreamWriteError.

Co-authored-by: Cursor <cursoragent@cursor.com>
@lance6716 lance6716 merged commit 09059bf into go-mysql-org:master Jul 7, 2026
20 checks passed
dbnski pushed a commit to dbnski/go-mysql that referenced this pull request Jul 10, 2026
…CAL INFILE (go-mysql-org#1164)

* client: add ExecQueryRelayLocalInfile for proxy-friendly LOAD DATA LOCAL INFILE

ExecQueryRelayLocalInfile is a COM_QUERY variant that handles the
0xfb LOCAL INFILE response via a caller-supplied callback instead of
reading a local file. The callback receives the full 0xfb request
packet and is responsible for forwarding the request to the application
client, relaying file-data packets to this connection via WritePacket,
and signalling EOF with an empty packet.

A companion helper cleanupLocalInfileAfterRelayError re-synchronizes
the connection if relayFile fails mid-transfer by sending the empty
EOF packet and consuming the server's OK/ERR response.

This is the proxy-friendly counterpart to the local-file reading in
client/auth.go. It enables transparent LOAD DATA LOCAL INFILE relay
in proxy implementations without filesystem access.

Closes go-mysql-org#1161

Co-authored-by: Cursor <cursoragent@cursor.com>

* client: add empty-packet guards and errors.Trace in LOCAL INFILE relay

Guard against empty packets before indexing response headers and wrap
cleanupLocalInfileAfterRelayError I/O errors with errors.Trace for
consistency with the rest of the client package.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: add unit tests for ExecQueryRelayLocalInfile

Cover direct OK/ERR responses, successful relay with file chunks, and
relay failure with connection resynchronization using net.Pipe.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: remove unnecessary byte() conversions flagged by unconvert linter

COM_QUERY and LocalInFile_HEADER are already typed as byte, so the
explicit byte() casts are redundant and fail the unconvert linter check.

Co-authored-by: Cursor <cursoragent@cursor.com>

* client: pass *Conn to ExecQueryRelayLocalInfile relay callback

Make the callback signature func(localInfileRequestPayload []byte, c *Conn)
error so callers can see how to write file data back via c.WritePacket.
Document success vs error-path LOCAL INFILE termination in godoc.

Co-authored-by: Cursor <cursoragent@cursor.com>

* client: return io.Reader from ExecQueryRelayLocalInfile callback

Replace the streaming WritePacket callback with a collect-then-send model:
relayFile returns a reader over the complete file content and the library
sends LOCAL INFILE data packets. On callback error only the empty terminator
is sent (no partial file data upstream).

Co-authored-by: Cursor <cursoragent@cursor.com>

* client: add usage example to ExecQueryRelayLocalInfile godoc

Co-authored-by: Cursor <cursoragent@cursor.com>

* client: unify LOCAL INFILE send/await and validate reader before send

Replace cancelLocalInfileRequest and the split success/error tail paths
with sendLocalInfileContentAndAwaitResult. prepareLocalInfileReader
validates the full reader (io.Seeker discard+seek-back, or io.ReadAll)
before any file data is sent upstream, preventing partial transfers.

Co-authored-by: Cursor <cursoragent@cursor.com>

* client: address Jul 7 LOCAL INFILE review feedback

Reuse a single write buffer, send a defensive terminator on mid-stream
errors, pass filename-only to relayFile, expand godoc Notes, and consolidate
tests into subtests including StreamWriteError.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: lance6716 <lance6716@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

client: add ExecQueryRelayLocalInfile for proxy-friendly LOAD DATA LOCAL INFILE

2 participants