Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 270 additions & 55 deletions go/mysql/conn.go

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions go/mysql/conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func createSocketPair(t *testing.T) (net.Listener, *Conn, *Conn) {
cConn := newConn(clientConn, DefaultFlushDelay, 0)
sConn := newConn(serverConn, DefaultFlushDelay, 0)
sConn.PrepareData = map[uint32]*PrepareData{}
sConn.cursorStates = map[uint32]*CursorState{}

return listener, sConn, cConn
}
Expand Down Expand Up @@ -856,9 +857,9 @@ func TestEmptyQuery(t *testing.T) {
// The queries run will be an empty query; Even with the empty error, the connection should be fine
require.True(t, res, "we should not break the connection in case of no errors")
// Read the result and assert that we indeed see the error for empty query.
data, more, _, err := cConn.ReadQueryResult(100, true)
data, status, _, err := cConn.ReadQueryResult(100, true)
require.EqualError(t, err, "Query was empty (errno 1065) (sqlstate 42000)")
require.False(t, more)
require.False(t, IsMoreResultsExists(status))
require.Nil(t, data)
})
}
Expand Down Expand Up @@ -886,17 +887,17 @@ func TestMultiStatement(t *testing.T) {
// The queries run will be select 1; and select 2; These queries do not return any errors, so the connection should still be open
require.True(t, res, "we should not break the connection in case of no errors")
// Read the result of the query and assert that it is indeed what we want. This will contain the result of the first query.
data, more, _, err := cConn.ReadQueryResult(100, true)
data, status, _, err := cConn.ReadQueryResult(100, true)
require.NoError(t, err)
// Since we executed 2 queries, there should be more results to be read
require.True(t, more)
require.True(t, IsMoreResultsExists(status))
require.True(t, data.Equal(selectRowsResult))

// Read the results for the second query and verify the correctness
data, more, _, err = cConn.ReadQueryResult(100, true)
data, status, _, err = cConn.ReadQueryResult(100, true)
require.NoError(t, err)
// This was the final query run, so we expect that more should be false as there are no more queries.
require.False(t, more)
require.False(t, IsMoreResultsExists(status))
require.True(t, data.Equal(selectRowsResult))

// This time we run two queries fist of which will return an error
Expand All @@ -908,10 +909,10 @@ func TestMultiStatement(t *testing.T) {
require.True(t, res, "we should not break the connection because of execution errors")

// Read the result and assert that we indeed see the error that testRun throws.
data, more, _, err = cConn.ReadQueryResult(100, true)
data, status, _, err = cConn.ReadQueryResult(100, true)
require.EqualError(t, err, "cannot get column number (errno 2027) (sqlstate HY000)")
// In case of errors in a multi-statement, the following statements are not executed, therefore we want that more should be false
require.False(t, more)
require.False(t, IsMoreResultsExists(status))
require.Nil(t, data)
})
}
Expand Down
18 changes: 17 additions & 1 deletion go/mysql/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ const (

// Status flags. They are returned by the server in a few cases.
// Originally found in include/mysql/mysql_com.h
// See http://dev.mysql.com/doc/internals/en/status-flags.html
// See https://dev.mysql.com/doc/dev/mysql-server/latest/mysql__com_8h.html#a1d854e841086925be1883e4d7b4e8cad
// and https://mariadb.com/kb/en/ok_packet/#server-status-flag
const (
// a transaction is active
ServerStatusInTrans uint16 = 0x0001
Expand All @@ -183,6 +184,21 @@ const (
ServerSessionStateChanged uint16 = 0x4000
)

// Helper for checking if SERVER_MORE_RESULTS_EXISTS is set
func IsMoreResultsExists(status uint16) bool {
return status&ServerMoreResultsExists == ServerMoreResultsExists
}

// Cursor Types. They are received on COM_STMT_EXECUTE()
// See https://dev.mysql.com/doc/dev/mysql-server/latest/mysql__com_8h.html#a3e5e9e744ff6f7b989a604fd669977da
// and https://mariadb.com/kb/en/com_stmt_execute/#flag
const (
CursorTypeNoCursor = iota
CursorTypeReadOnly
CursorTypeCursorForUpdate
CursorTypeScrollableCursor
)

// State Change Information
const (
// one or more system variables changed.
Expand Down
32 changes: 16 additions & 16 deletions go/mysql/endtoend/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,29 +161,29 @@ func doTestMultiResult(t *testing.T, disableClientDeprecateEOF bool) {
expectNoError(t, err)
defer conn.Close()

qr, more, err := conn.ExecuteFetchMulti("select 1 from dual; set autocommit=1; select 1 from dual", 10, true)
qr, status, err := conn.ExecuteFetchMulti("select 1 from dual; set autocommit=1; select 1 from dual", 10, true)
expectNoError(t, err)
expectFlag(t, "ExecuteMultiFetch(multi result)", more, true)
expectFlag(t, "ExecuteMultiFetch(multi result)", mysql.IsMoreResultsExists(status), true)
assert.EqualValues(t, 1, len(qr.Rows))

qr, more, _, err = conn.ReadQueryResult(10, true)
qr, status, _, err = conn.ReadQueryResult(10, true)
expectNoError(t, err)
expectFlag(t, "ReadQueryResult(1)", more, true)
expectFlag(t, "ReadQueryResult(1)", mysql.IsMoreResultsExists(status), true)
assert.EqualValues(t, 0, len(qr.Rows))

qr, more, _, err = conn.ReadQueryResult(10, true)
qr, status, _, err = conn.ReadQueryResult(10, true)
expectNoError(t, err)
expectFlag(t, "ReadQueryResult(2)", more, false)
expectFlag(t, "ReadQueryResult(2)", mysql.IsMoreResultsExists(status), false)
assert.EqualValues(t, 1, len(qr.Rows))

qr, more, err = conn.ExecuteFetchMulti("select 1 from dual", 10, true)
qr, status, err = conn.ExecuteFetchMulti("select 1 from dual", 10, true)
expectNoError(t, err)
expectFlag(t, "ExecuteMultiFetch(single result)", more, false)
expectFlag(t, "ExecuteMultiFetch(single result)", mysql.IsMoreResultsExists(status), false)
assert.EqualValues(t, 1, len(qr.Rows))

qr, more, err = conn.ExecuteFetchMulti("set autocommit=1", 10, true)
qr, status, err = conn.ExecuteFetchMulti("set autocommit=1", 10, true)
expectNoError(t, err)
expectFlag(t, "ExecuteMultiFetch(no result)", more, false)
expectFlag(t, "ExecuteMultiFetch(no result)", mysql.IsMoreResultsExists(status), false)
assert.EqualValues(t, 0, len(qr.Rows))

// The ClientDeprecateEOF protocol change has a subtle twist in which an EOF or OK
Expand Down Expand Up @@ -212,19 +212,19 @@ func doTestMultiResult(t *testing.T, disableClientDeprecateEOF bool) {
err = conn.ExecuteFetchMultiDrain("update a set name = concat(name, ', multi drain 1'); select * from a; select count(*) from a")
expectNoError(t, err)
// If the previous command leaves packet in invalid state, this will fail.
qr, more, err = conn.ExecuteFetchMulti("update a set name = concat(name, ', fetch multi'); select * from a; select count(*) from a", 300, true)
qr, status, err = conn.ExecuteFetchMulti("update a set name = concat(name, ', fetch multi'); select * from a; select count(*) from a", 300, true)
expectNoError(t, err)
expectFlag(t, "ExecuteMultiFetch(multi result)", more, true)
expectFlag(t, "ExecuteMultiFetch(multi result)", mysql.IsMoreResultsExists(status), true)
assert.EqualValues(t, 255, qr.RowsAffected)

qr, more, _, err = conn.ReadQueryResult(300, true)
qr, status, _, err = conn.ReadQueryResult(300, true)
expectNoError(t, err)
expectFlag(t, "ReadQueryResult(1)", more, true)
expectFlag(t, "ReadQueryResult(1)", mysql.IsMoreResultsExists(status), true)
assert.EqualValues(t, 255, len(qr.Rows), "ReadQueryResult(1)")

qr, more, _, err = conn.ReadQueryResult(300, true)
qr, status, _, err = conn.ReadQueryResult(300, true)
expectNoError(t, err)
expectFlag(t, "ReadQueryResult(2)", more, false)
expectFlag(t, "ReadQueryResult(2)", mysql.IsMoreResultsExists(status), false)
assert.EqualValues(t, 1, len(qr.Rows), "ReadQueryResult(1)")

// Verify that a ExecuteFetchMultiDrain is happy to operate again after all the above.
Expand Down
1 change: 1 addition & 0 deletions go/mysql/mysql_fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func FuzzHandleNextCommand(data []byte) int {
queryPacket: data,
}, DefaultFlushDelay, 0, false)
sConn.PrepareData = map[uint32]*PrepareData{}
sConn.cursorStates = map[uint32]*CursorState{}

handler := &fuzztestRun{}
_ = sConn.handleNextCommand(handler)
Expand Down
Loading