Skip to content

Commit b9bdf63

Browse files
authored
Merge branch 'master' into update-example
2 parents e911007 + 9b0f283 commit b9bdf63

File tree

7 files changed

+334
-15
lines changed

7 files changed

+334
-15
lines changed

client/auth.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,18 @@ func (c *Conn) writeAuthHandshake() error {
140140
if !authPluginAllowed(c.authPluginName) {
141141
return fmt.Errorf("unknow auth plugin name '%s'", c.authPluginName)
142142
}
143-
// Adjust client capability flags based on server support
143+
144+
// Set default client capabilities that reflect the abilities of this library
144145
capability := CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION |
145-
CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_PLUGIN_AUTH | c.capability&CLIENT_LONG_FLAG
146+
CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_PLUGIN_AUTH
147+
// Adjust client capability flags based on server support
148+
capability |= c.capability & CLIENT_LONG_FLAG
149+
// Adjust client capability flags on specific client requests
150+
// Only flags that would make any sense setting and aren't handled elsewhere
151+
// in the library are supported here
152+
capability |= c.ccaps&CLIENT_FOUND_ROWS | c.ccaps&CLIENT_IGNORE_SPACE |
153+
c.ccaps&CLIENT_MULTI_STATEMENTS | c.ccaps&CLIENT_MULTI_RESULTS |
154+
c.ccaps&CLIENT_PS_MULTI_RESULTS | c.ccaps&CLIENT_CONNECT_ATTRS
146155

147156
// To enable TLS / SSL
148157
if c.tlsConfig != nil {

client/client_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,41 @@ func (s *clientTestSuite) TestConn_Ping(c *C) {
9494
c.Assert(err, IsNil)
9595
}
9696

97+
func (s *clientTestSuite) TestConn_SetCapability(c *C) {
98+
caps := []uint32{
99+
mysql.CLIENT_LONG_PASSWORD,
100+
mysql.CLIENT_FOUND_ROWS,
101+
mysql.CLIENT_LONG_FLAG,
102+
mysql.CLIENT_CONNECT_WITH_DB,
103+
mysql.CLIENT_NO_SCHEMA,
104+
mysql.CLIENT_COMPRESS,
105+
mysql.CLIENT_ODBC,
106+
mysql.CLIENT_LOCAL_FILES,
107+
mysql.CLIENT_IGNORE_SPACE,
108+
mysql.CLIENT_PROTOCOL_41,
109+
mysql.CLIENT_INTERACTIVE,
110+
mysql.CLIENT_SSL,
111+
mysql.CLIENT_IGNORE_SIGPIPE,
112+
mysql.CLIENT_TRANSACTIONS,
113+
mysql.CLIENT_RESERVED,
114+
mysql.CLIENT_SECURE_CONNECTION,
115+
mysql.CLIENT_MULTI_STATEMENTS,
116+
mysql.CLIENT_MULTI_RESULTS,
117+
mysql.CLIENT_PS_MULTI_RESULTS,
118+
mysql.CLIENT_PLUGIN_AUTH,
119+
mysql.CLIENT_CONNECT_ATTRS,
120+
mysql.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA,
121+
}
122+
123+
for _, cap := range caps {
124+
c.Assert(s.c.ccaps&cap > 0, IsFalse)
125+
s.c.SetCapability(cap)
126+
c.Assert(s.c.ccaps&cap > 0, IsTrue)
127+
s.c.UnsetCapability(cap)
128+
c.Assert(s.c.ccaps&cap > 0, IsFalse)
129+
}
130+
}
131+
97132
// NOTE for MySQL 5.5 and 5.6, server side has to config SSL to pass the TLS test, otherwise, it will throw error that
98133
// MySQL server does not support TLS required by the client. However, for MySQL 5.7 and above, auto generated certificates
99134
// are used by default so that manual config is no longer necessary.

client/conn.go

+13
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ type Conn struct {
2121
tlsConfig *tls.Config
2222
proto string
2323

24+
// server capabilities
2425
capability uint32
26+
// client-set capabilities only
27+
ccaps uint32
2528

2629
status uint16
2730

@@ -120,6 +123,16 @@ func (c *Conn) Ping() error {
120123
return nil
121124
}
122125

126+
// SetCapability enables the use of a specific capability
127+
func (c *Conn) SetCapability(cap uint32) {
128+
c.ccaps |= cap
129+
}
130+
131+
// UnsetCapability disables the use of a specific capability
132+
func (c *Conn) UnsetCapability(cap uint32) {
133+
c.ccaps &= ^cap
134+
}
135+
123136
// UseSSL: use default SSL
124137
// pass to options when connect
125138
func (c *Conn) UseSSL(insecureSkipVerify bool) {

mysql/rowdata.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func (p RowData) ParseBinary(f []*Field, dst []FieldValue) ([]FieldValue, error)
177177
case MYSQL_TYPE_DECIMAL, MYSQL_TYPE_NEWDECIMAL, MYSQL_TYPE_VARCHAR,
178178
MYSQL_TYPE_BIT, MYSQL_TYPE_ENUM, MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB,
179179
MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_BLOB,
180-
MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY:
180+
MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY, MYSQL_TYPE_JSON:
181181
v, isNull, n, err = LengthEncodedString(p[pos:])
182182
pos += n
183183
if err != nil {

server/resp.go

+6-12
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,7 @@ func (c *Conn) writeResultset(r *Resultset) error {
125125
return err
126126
}
127127

128-
for _, v := range r.Fields {
129-
data = data[0:4]
130-
data = append(data, v.Dump()...)
131-
if err := c.WritePacket(data); err != nil {
132-
return err
133-
}
134-
}
135-
136-
if err := c.writeEOF(); err != nil {
128+
if err := c.writeFieldList(r.Fields, data); err != nil {
137129
return err
138130
}
139131

@@ -152,8 +144,10 @@ func (c *Conn) writeResultset(r *Resultset) error {
152144
return nil
153145
}
154146

155-
func (c *Conn) writeFieldList(fs []*Field) error {
156-
data := make([]byte, 4, 1024)
147+
func (c *Conn) writeFieldList(fs []*Field, data []byte) error {
148+
if data == nil {
149+
data = make([]byte, 4, 1024)
150+
}
157151

158152
for _, v := range fs {
159153
data = data[0:4]
@@ -189,7 +183,7 @@ func (c *Conn) writeValue(value interface{}) error {
189183
return c.writeOK(v)
190184
}
191185
case []*Field:
192-
return c.writeFieldList(v)
186+
return c.writeFieldList(v, nil)
193187
case *Stmt:
194188
return c.writePrepare(v)
195189
default:

server/resp_test.go

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package server
2+
3+
import (
4+
"errors"
5+
6+
"github.com/go-mysql-org/go-mysql/mysql"
7+
"github.com/go-mysql-org/go-mysql/packet"
8+
mockconn "github.com/go-mysql-org/go-mysql/test_util/conn"
9+
"github.com/pingcap/check"
10+
)
11+
12+
type respConnTestSuite struct{}
13+
14+
var _ = check.Suite(&respConnTestSuite{})
15+
16+
func (t *respConnTestSuite) TestConnWriteOK(c *check.C) {
17+
clientConn := &mockconn.MockConn{}
18+
conn := &Conn{Conn: packet.NewConn(clientConn)}
19+
20+
result := &mysql.Result{
21+
AffectedRows: 1,
22+
InsertId: 2,
23+
}
24+
25+
// write ok with insertid and affectedrows set
26+
err := conn.writeOK(result)
27+
c.Assert(err, check.IsNil)
28+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{3, 0, 0, 0, mysql.OK_HEADER, 1, 2})
29+
30+
// set capability for CLIENT_PROTOCOL_41
31+
conn.SetCapability(mysql.CLIENT_PROTOCOL_41)
32+
conn.SetStatus(mysql.SERVER_QUERY_WAS_SLOW)
33+
err = conn.writeOK(result)
34+
c.Assert(err, check.IsNil)
35+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{7, 0, 0, 1, mysql.OK_HEADER, 1, 2, 0, 8, 0, 0})
36+
}
37+
38+
func (t *respConnTestSuite) TestConnWriteEOF(c *check.C) {
39+
clientConn := &mockconn.MockConn{}
40+
conn := &Conn{Conn: packet.NewConn(clientConn)}
41+
42+
// write regular EOF
43+
err := conn.writeEOF()
44+
c.Assert(err, check.IsNil)
45+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{1, 0, 0, 0, mysql.EOF_HEADER})
46+
47+
// set capability for CLIENT_PROTOCOL_41
48+
conn.SetCapability(mysql.CLIENT_PROTOCOL_41)
49+
conn.SetStatus(mysql.SERVER_MORE_RESULTS_EXISTS)
50+
err = conn.writeEOF()
51+
c.Assert(err, check.IsNil)
52+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{5, 0, 0, 1, mysql.EOF_HEADER,
53+
0, 0, 8, 0})
54+
}
55+
56+
func (t *respConnTestSuite) TestConnWriteError(c *check.C) {
57+
clientConn := &mockconn.MockConn{}
58+
conn := &Conn{Conn: packet.NewConn(clientConn)}
59+
merr := mysql.NewDefaultError(mysql.ER_YES) // nice and short error message
60+
61+
// write regular Error
62+
err := conn.writeError(merr)
63+
c.Assert(err, check.IsNil)
64+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{6, 0, 0, 0, mysql.ERR_HEADER,
65+
235, 3, 89, 69, 83})
66+
67+
// set capability for CLIENT_PROTOCOL_41
68+
conn.SetCapability(mysql.CLIENT_PROTOCOL_41)
69+
err = conn.writeError(merr)
70+
c.Assert(err, check.IsNil)
71+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{12, 0, 0, 1, mysql.ERR_HEADER,
72+
235, 3, 35, 72, 89, 48, 48, 48, 89, 69, 83})
73+
74+
// unknown error
75+
err = conn.writeError(errors.New("test"))
76+
c.Assert(err, check.IsNil)
77+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{13, 0, 0, 2, mysql.ERR_HEADER,
78+
81, 4, 35, 72, 89, 48, 48, 48, 116, 101, 115, 116})
79+
}
80+
81+
func (t *respConnTestSuite) TestConnWriteAuthSwitchRequest(c *check.C) {
82+
clientConn := &mockconn.MockConn{}
83+
conn := &Conn{Conn: packet.NewConn(clientConn)}
84+
85+
err := conn.writeAuthSwitchRequest("test")
86+
c.Assert(err, check.IsNil)
87+
// first 10 bytes are static, then there is a part random, ending with a \0
88+
c.Assert(clientConn.WriteBuffered[:10], check.BytesEquals, []byte{27, 0, 0, 0, mysql.EOF_HEADER,
89+
116, 101, 115, 116, 0})
90+
c.Assert(clientConn.WriteBuffered[len(clientConn.WriteBuffered)-1:], check.BytesEquals, []byte{0})
91+
}
92+
93+
func (t *respConnTestSuite) TestConnReadAuthSwitchRequestResponse(c *check.C) {
94+
clientConn := &mockconn.MockConn{}
95+
conn := &Conn{Conn: packet.NewConn(clientConn)}
96+
97+
// prepare response for \NUL
98+
clientConn.SetResponse([][]byte{{1, 0, 0, 0, 0}})
99+
data, err := conn.readAuthSwitchRequestResponse()
100+
c.Assert(err, check.IsNil)
101+
c.Assert(data, check.BytesEquals, []byte{})
102+
103+
// prepare response for some auth switch data
104+
clientConn.SetResponse([][]byte{{4, 0, 0, 0, 1, 2, 3, 4}})
105+
conn = &Conn{Conn: packet.NewConn(clientConn)}
106+
107+
data, err = conn.readAuthSwitchRequestResponse()
108+
c.Assert(err, check.IsNil)
109+
c.Assert(data, check.BytesEquals, []byte{1, 2, 3, 4})
110+
}
111+
112+
func (t *respConnTestSuite) TestConnWriteAuthMoreDataPubkey(c *check.C) {
113+
clientConn := &mockconn.MockConn{}
114+
conn := &Conn{
115+
Conn: packet.NewConn(clientConn),
116+
serverConf: &Server{
117+
pubKey: []byte{1, 2, 3, 4},
118+
},
119+
}
120+
121+
err := conn.writeAuthMoreDataPubkey()
122+
c.Assert(err, check.IsNil)
123+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{5, 0, 0, 0, mysql.MORE_DATE_HEADER,
124+
1, 2, 3, 4})
125+
}
126+
127+
func (t *respConnTestSuite) TestConnWriteAuthMoreDataFullAuth(c *check.C) {
128+
clientConn := &mockconn.MockConn{}
129+
conn := &Conn{Conn: packet.NewConn(clientConn)}
130+
131+
err := conn.writeAuthMoreDataFullAuth()
132+
c.Assert(err, check.IsNil)
133+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{2, 0, 0, 0, mysql.MORE_DATE_HEADER,
134+
mysql.CACHE_SHA2_FULL_AUTH})
135+
}
136+
137+
func (t *respConnTestSuite) TestConnWriteAuthMoreDataFastAuth(c *check.C) {
138+
clientConn := &mockconn.MockConn{}
139+
conn := &Conn{Conn: packet.NewConn(clientConn)}
140+
141+
err := conn.writeAuthMoreDataFastAuth()
142+
c.Assert(err, check.IsNil)
143+
c.Assert(clientConn.WriteBuffered, check.BytesEquals, []byte{2, 0, 0, 0, mysql.MORE_DATE_HEADER,
144+
mysql.CACHE_SHA2_FAST_AUTH})
145+
}
146+
147+
func (t *respConnTestSuite) TestConnWriteResultset(c *check.C) {
148+
clientConn := &mockconn.MockConn{MultiWrite: true}
149+
conn := &Conn{Conn: packet.NewConn(clientConn)}
150+
151+
r := mysql.NewResultset(0)
152+
153+
// write minimalistic resultset
154+
err := conn.writeResultset(r)
155+
c.Assert(err, check.IsNil)
156+
// column length 0
157+
c.Assert(clientConn.WriteBuffered[:5], check.BytesEquals, []byte{1, 0, 0, 0, 0})
158+
// no fields and an EOF
159+
c.Assert(clientConn.WriteBuffered[5:10], check.BytesEquals, []byte{1, 0, 0, 1, mysql.EOF_HEADER})
160+
// no rows and another EOF
161+
c.Assert(clientConn.WriteBuffered[10:], check.BytesEquals, []byte{1, 0, 0, 2, mysql.EOF_HEADER})
162+
163+
// reset write buffer and fill up the resultset with (little) data
164+
clientConn.WriteBuffered = []byte{}
165+
r, err = mysql.BuildSimpleTextResultset([]string{"a"}, [][]interface{}{{"b"}})
166+
c.Assert(err, check.IsNil)
167+
err = conn.writeResultset(r)
168+
c.Assert(err, check.IsNil)
169+
// column length 1
170+
c.Assert(clientConn.WriteBuffered[:5], check.BytesEquals, []byte{1, 0, 0, 3, 1})
171+
// fields and EOF
172+
c.Assert(clientConn.WriteBuffered[5:32], check.BytesEquals, []byte{23, 0, 0, 4, 3, 100, 101, 102, 0, 0, 0, 1, 'a', 0, 12, 33, 0, 0, 0, 0, 0, 253, 0, 0, 0, 0, 0})
173+
c.Assert(clientConn.WriteBuffered[32:37], check.BytesEquals, []byte{1, 0, 0, 5, mysql.EOF_HEADER})
174+
// rowdata and EOF
175+
c.Assert(clientConn.WriteBuffered[37:43], check.BytesEquals, []byte{2, 0, 0, 6, 1, 'b'})
176+
c.Assert(clientConn.WriteBuffered[43:], check.BytesEquals, []byte{1, 0, 0, 7, mysql.EOF_HEADER})
177+
}
178+
179+
func (t *respConnTestSuite) TestConnWriteFieldList(c *check.C) {
180+
clientConn := &mockconn.MockConn{MultiWrite: true}
181+
conn := &Conn{Conn: packet.NewConn(clientConn)}
182+
183+
r, err := mysql.BuildSimpleTextResultset([]string{"c"}, [][]interface{}{{"d"}})
184+
c.Assert(err, check.IsNil)
185+
err = conn.writeFieldList(r.Fields, nil)
186+
c.Assert(err, check.IsNil)
187+
188+
// column length 1
189+
c.Assert(clientConn.WriteBuffered[:27], check.BytesEquals, []byte{23, 0, 0, 0, 3, 100, 101, 102, 0, 0, 0, 1, 'c', 0, 12, 33, 0, 0, 0, 0, 0, 253, 0, 0, 0, 0, 0})
190+
c.Assert(clientConn.WriteBuffered[27:], check.BytesEquals, []byte{1, 0, 0, 1, mysql.EOF_HEADER})
191+
}

0 commit comments

Comments
 (0)