diff --git a/TODO.MD b/TODO.MD index 6ac97bca3..1a4d09dc1 100644 --- a/TODO.MD +++ b/TODO.MD @@ -45,6 +45,7 @@ 1. gredis增加cluster支持; 1. gset.Add/Remove/Contains方法增加批量操作支持; 1. gmlock增加手动清理机制:当内存锁不再使用时,由调用端决定是否清理内存锁; +1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进; # DONE 1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换; diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index ad76190ac..3d6d855ce 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -20,7 +20,6 @@ import ( "github.com/gogf/gf/g/container/gvar" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/util/grand" - _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" "time" ) diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index a1fd04b1e..9532d093a 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -15,7 +15,6 @@ import ( "github.com/gogf/gf/g/text/gregex" "github.com/gogf/gf/g/text/gstr" "github.com/gogf/gf/g/util/gconv" - _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" "reflect" "strings" ) diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go index d87a85fd6..f039a64ae 100644 --- a/g/database/gdb/gdb_model.go +++ b/g/database/gdb/gdb_model.go @@ -13,7 +13,6 @@ import ( "errors" "fmt" "github.com/gogf/gf/g/util/gconv" - _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" "reflect" ) diff --git a/g/database/gdb/gdb_mysql.go b/g/database/gdb/gdb_mysql.go index 66b24b64c..56addd197 100644 --- a/g/database/gdb/gdb_mysql.go +++ b/g/database/gdb/gdb_mysql.go @@ -8,8 +8,9 @@ package gdb import ( - "fmt" - "database/sql" + "database/sql" + "fmt" + _ "github.com/gogf/gf/third/github.com/gf-third/mysql" ) // 数据库链接对象 @@ -26,7 +27,7 @@ func (db *dbMysql) Open (config *ConfigNode) (*sql.DB, error) { source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true", config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset) } - if db, err := sql.Open("mysql", source); err == nil { + if db, err := sql.Open("gf-mysql", source); err == nil { return db, nil } else { return nil, err diff --git a/g/database/gdb/gdb_transaction.go b/g/database/gdb/gdb_transaction.go index b90e664e4..7e7668bb6 100644 --- a/g/database/gdb/gdb_transaction.go +++ b/g/database/gdb/gdb_transaction.go @@ -10,7 +10,6 @@ import ( "database/sql" "fmt" "github.com/gogf/gf/g/text/gregex" - _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" "reflect" ) diff --git a/g/encoding/gbinary/gbinary.go b/g/encoding/gbinary/gbinary.go index a7ad3c301..ba44b73a8 100644 --- a/g/encoding/gbinary/gbinary.go +++ b/g/encoding/gbinary/gbinary.go @@ -5,6 +5,8 @@ // You can obtain one at https://github.com/gogf/gf. // Package gbinary provides useful API for handling binary/bytes data. +// +// 注意gbinary模块统一使用LittleEndian进行编码。 package gbinary import ( diff --git a/g/g_object.go b/g/g_object.go index 8bb8feaed..5e09f327d 100644 --- a/g/g_object.go +++ b/g/g_object.go @@ -38,8 +38,8 @@ func View(name...string) *gview.View { } // Config returns an instance of config object with specified name. -func Config(file...string) *gcfg.Config { - return gins.Config(file...) +func Config(name...string) *gcfg.Config { + return gins.Config(name...) } // Database returns an instance of database ORM object with specified configuration group name. diff --git a/g/net/ghttp/ghttp_client_request.go b/g/net/ghttp/ghttp_client_request.go index 85df3bada..e907e8297 100644 --- a/g/net/ghttp/ghttp_client_request.go +++ b/g/net/ghttp/ghttp_client_request.go @@ -10,7 +10,8 @@ package ghttp import ( "bytes" - "encoding/json" + "crypto/tls" + "encoding/json" "errors" "fmt" "github.com/gogf/gf/g/os/gfile" @@ -40,11 +41,16 @@ func NewClient() *Client { return &Client{ Client : http.Client { Transport: &http.Transport { + // 默认不校验HTTPS证书有效性 + TLSClientConfig : &tls.Config{ + InsecureSkipVerify: true, + }, + // 默认关闭KeepAlive功能 DisableKeepAlives: true, }, }, - header : make(map[string]string), - cookies : make(map[string]string), + header : make(map[string]string), + cookies: make(map[string]string), } } diff --git a/g/net/ghttp/ghttp_request.go b/g/net/ghttp/ghttp_request.go index 7acd1f821..9f8c314dc 100644 --- a/g/net/ghttp/ghttp_request.go +++ b/g/net/ghttp/ghttp_request.go @@ -7,7 +7,8 @@ package ghttp import ( - "github.com/gogf/gf/g/container/gvar" + "fmt" + "github.com/gogf/gf/g/container/gvar" "github.com/gogf/gf/g/encoding/gjson" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/text/gregex" @@ -217,7 +218,16 @@ func (r *Request) GetClientIp() string { return r.clientIp } -// 获得来源URL地址 +// 获得当前请求URL地址 +func (r *Request) GetUrl() string { + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + return fmt.Sprintf(`%s://%s%s`, scheme, r.Host, r.URL.String()) +} + +// 获得请求来源URL地址 func (r *Request) GetReferer() string { return r.Header.Get("Referer") } diff --git a/g/net/ghttp/ghttp_server_handler.go b/g/net/ghttp/ghttp_server_handler.go index 7b8021ea4..f1522c216 100644 --- a/g/net/ghttp/ghttp_server_handler.go +++ b/g/net/ghttp/ghttp_server_handler.go @@ -3,7 +3,6 @@ // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// 请求处理. package ghttp @@ -58,6 +57,10 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { if !request.IsExited() { s.callHookHandler(HOOK_BEFORE_OUTPUT, request) } + // 如果没有产生异常状态,那么设置返回状态为200 + if request.Response.Status == 0 { + request.Response.Status = http.StatusOK + } // error log if e := recover(); e != nil { request.Response.WriteStatus(http.StatusInternalServerError) diff --git a/g/net/ghttp/ghttp_server_log.go b/g/net/ghttp/ghttp_server_log.go index 845b7dda3..3801abf4f 100644 --- a/g/net/ghttp/ghttp_server_log.go +++ b/g/net/ghttp/ghttp_server_log.go @@ -22,9 +22,13 @@ func (s *Server) handleAccessLog(r *Request) { v(r) return } - content := fmt.Sprintf(`%d "%s %s %s %s"`, + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + content := fmt.Sprintf(`%d "%s %s %s %s %s"`, r.Response.Status, - r.Method, r.Host, r.URL.String(), r.Proto, + r.Method, scheme, r.Host, r.URL.String(), r.Proto, ) content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000) content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent()) @@ -45,7 +49,11 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) { } // 错误日志信息 - content := fmt.Sprintf(`%v, "%s %s %s %s"`, error, r.Method, r.Host, r.URL.String(), r.Proto) + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + content := fmt.Sprintf(`%v, "%s %s %s %s %s"`, error, r.Method, scheme, r.Host, r.URL.String(), r.Proto) if r.LeaveTime > r.EnterTime { content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000) } else { diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index be618496b..a608c848c 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -18,13 +18,15 @@ import ( type Conn struct { conn net.Conn // 底层tcp对象 reader *bufio.Reader // 当前链接的缓冲读取对象 + buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理) recvDeadline time.Time // 读取超时时间 sendDeadline time.Time // 写入超时时间 recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔 } const ( - gRECV_ALL_WAIT_TIMEOUT = time.Millisecond // 读取全部缓冲数据时,没有缓冲数据时的等待间隔 + // 读取全部缓冲数据时,没有缓冲数据时的等待间隔 + gRECV_ALL_WAIT_TIMEOUT = time.Millisecond ) // 创建TCP链接 @@ -103,7 +105,7 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { // 缓冲区数据写入等待处理。 // 如果已经读取到数据(这点很关键,表明缓冲区已经有数据,剩下的操作就是将所有数据读取完毕), // 那么可以设置读取全部缓冲数据的超时时间;如果没有接收到任何数据,那么将会进入读取阻塞(或者自定义的超时阻塞); - // 仅对读取全部缓冲数据操作有效 + // 仅对读取全部缓冲区数据操作有效 if length <= 0 && index > 0 { bufferWait = true c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) @@ -117,9 +119,14 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { break } } else { - // 如果长度超过了自定义的读取缓冲区,那么自动增长 if index >= gDEFAULT_READ_BUFFER_SIZE { + // 如果长度超过了自定义的读取缓冲区,那么自动增长 buffer = append(buffer, make([]byte, gDEFAULT_READ_BUFFER_SIZE)...) + } else { + // 如果第一次读取的数据并未达到缓冲变量长度,那么直接返回 + if !bufferWait { + break + } } } } @@ -234,8 +241,8 @@ func (c *Conn) SetSendDeadline(t time.Time) error { // 读取全部缓冲区数据时,读取完毕后的写入等待间隔,如果超过该等待时间后仍无可读数据,那么读取操作返回。 // 该时间间隔不能设置得太大,会影响Recv读取时长(默认为1毫秒)。 -func (c *Conn) SetRecvBufferWait(d time.Duration) { - c.recvBufferWait = d +func (c *Conn) SetRecvBufferWait(bufferWaitDuration time.Duration) { + c.recvBufferWait = bufferWaitDuration } func (c *Conn) LocalAddr() net.Addr { diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go new file mode 100644 index 000000000..cd0a8ee53 --- /dev/null +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -0,0 +1,115 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtcp + +import ( + "encoding/binary" + "errors" + "fmt" + "time" +) + +const ( + // 允许最大的简单协议包大小(byte), 15MB + PKG_MAX_SIZE = 0xFFFFFF + // 消息包头大小: "总长度"3字节+"校验码"4字节 + PKG_HEADER_SIZE = 7 +) + +// 根据简单协议发送数据包。 +// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。 +// 注意: +// 1. "总长度"包含自身3字节及"校验码"4字节。 +// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。 +func (c *Conn) SendPkg(data []byte, retry...Retry) error { + length := uint32(len(data)) + if length > PKG_MAX_SIZE - PKG_HEADER_SIZE { + return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE)) + } + buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data)) + copy(buffer[PKG_HEADER_SIZE + 1 : ], data) + binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length) + binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data)) + //fmt.Println("SendPkg:", buffer[1:]) + return c.Send(buffer[1:], retry...) +} + +// 简单协议: 带超时时间的数据发送 +func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { + c.SetSendDeadline(time.Now().Add(timeout)) + defer c.SetSendDeadline(time.Time{}) + return c.SendPkg(data, retry...) +} + +// 简单协议: 发送数据并等待接收返回数据 +func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { + if err := c.SendPkg(data, retry...); err == nil { + return c.RecvPkg(retry...) + } else { + return nil, err + } +} + +// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间) +func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { + if err := c.SendPkg(data, retry...); err == nil { + return c.RecvPkgWithTimeout(timeout, retry...) + } else { + return nil, err + } +} + +// 简单协议: 获取一个数据包。 +func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { + var temp []byte + var length uint32 + for { + // 先根据对象的缓冲区数据进行计算 + for { + if len(c.buffer) >= PKG_HEADER_SIZE { + // 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 + length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}) + // 解析的大小是否符合规范 + if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE { + c.buffer = c.buffer[1:] + continue + } + // 不满足包大小,需要继续读取 + if uint32(len(c.buffer)) < length { + break + } + // 数据校验 + if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) { + c.buffer = c.buffer[1:] + continue + } + result = c.buffer[PKG_HEADER_SIZE : length] + c.buffer = c.buffer[length: ] + return + } else { + break + } + } + // 读取系统socket缓冲区的完整数据 + temp, err = c.Recv(-1, retry...) + if err != nil { + break + } + if len(temp) > 0 { + c.buffer = append(c.buffer, temp...) + } + //fmt.Println("RecvPkg:", c.buffer) + } + return +} + +// 简单协议: 带超时时间的消息包获取 +func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { + c.SetRecvDeadline(time.Now().Add(timeout)) + defer c.SetRecvDeadline(time.Time{}) + return c.RecvPkg(retry...) +} \ No newline at end of file diff --git a/g/net/gtcp/gtcp_func.go b/g/net/gtcp/gtcp_func.go index dbd3bdc14..e4aacd6ef 100644 --- a/g/net/gtcp/gtcp_func.go +++ b/g/net/gtcp/gtcp_func.go @@ -51,12 +51,12 @@ func Send(addr string, data []byte, retry...Retry) error { // (面向短链接)发送数据并等待接收返回数据 func SendRecv(addr string, data []byte, receive int, retry...Retry) ([]byte, error) { - conn, err := NewConn(addr) - if err != nil { - return nil, err - } - defer conn.Close() - return conn.SendRecv(data, receive, retry...) + conn, err := NewConn(addr) + if err != nil { + return nil, err + } + defer conn.Close() + return conn.SendRecv(data, receive, retry...) } // (面向短链接)带超时时间的数据发送 diff --git a/g/net/gtcp/gtcp_func_pkg.go b/g/net/gtcp/gtcp_func_pkg.go new file mode 100644 index 000000000..164d513cb --- /dev/null +++ b/g/net/gtcp/gtcp_func_pkg.go @@ -0,0 +1,49 @@ +// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtcp + +import "time" + +// 简单协议: (面向短链接)发送消息包 +func SendPkg(addr string, data []byte, retry...Retry) error { + conn, err := NewConn(addr) + if err != nil { + return err + } + defer conn.Close() + return conn.SendPkg(data, retry...) +} + +// 简单协议: (面向短链接)发送数据并等待接收返回数据 +func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) { + conn, err := NewConn(addr) + if err != nil { + return nil, err + } + defer conn.Close() + return conn.SendRecvPkg(data, retry...) +} + +// 简单协议: (面向短链接)带超时时间的数据发送 +func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error { + conn, err := NewConn(addr) + if err != nil { + return err + } + defer conn.Close() + return conn.SendPkgWithTimeout(data, timeout, retry...) +} + +// 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间) +func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { + conn, err := NewConn(addr) + if err != nil { + return nil, err + } + defer conn.Close() + return conn.SendRecvPkgWithTimeout(data, timeout, retry...) +} \ No newline at end of file diff --git a/g/net/gtcp/gtcp_pool_pkg.go b/g/net/gtcp/gtcp_pool_pkg.go new file mode 100644 index 000000000..f22b60da6 --- /dev/null +++ b/g/net/gtcp/gtcp_pool_pkg.go @@ -0,0 +1,72 @@ +// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtcp + +import ( + "time" +) + +// 简单协议: (方法覆盖)发送数据 +func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) { + if err = c.Conn.SendPkg(data, retry...); err != nil && c.status == gCONN_STATUS_UNKNOWN { + if v, e := c.pool.NewFunc(); e == nil { + c.Conn = v.(*PoolConn).Conn + err = c.Conn.SendPkg(data, retry...) + } else { + err = e + } + } + if err != nil { + c.status = gCONN_STATUS_ERROR + } else { + c.status = gCONN_STATUS_ACTIVE + } + return err +} + +// 简单协议: (方法覆盖)接收数据 +func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) { + data, err := c.Conn.RecvPkg(retry...) + if err != nil { + c.status = gCONN_STATUS_ERROR + } else { + c.status = gCONN_STATUS_ACTIVE + } + return data, err +} + +// 简单协议: (方法覆盖)带超时时间的数据获取 +func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { + c.SetRecvDeadline(time.Now().Add(timeout)) + defer c.SetRecvDeadline(time.Time{}) + return c.RecvPkg(retry...) +} + +// 简单协议: (方法覆盖)带超时时间的数据发送 +func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { + c.SetSendDeadline(time.Now().Add(timeout)) + defer c.SetSendDeadline(time.Time{}) + return c.SendPkg(data, retry...) +} + +// 简单协议: (方法覆盖)发送数据并等待接收返回数据 +func (c *PoolConn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { + if err := c.SendPkg(data, retry...); err == nil { + return c.RecvPkg(retry...) + } else { + return nil, err + } +} + +// 简单协议: (方法覆盖)发送数据并等待接收返回数据(带返回超时等待时间) +func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { + if err := c.SendPkg(data, retry...); err == nil { + return c.RecvPkgWithTimeout(timeout, retry...) + } else { + return nil, err + } +} \ No newline at end of file diff --git a/g/net/gudp/gudp_conn.go b/g/net/gudp/gudp_conn.go index e1c751bc7..975ef8c37 100644 --- a/g/net/gudp/gudp_conn.go +++ b/g/net/gudp/gudp_conn.go @@ -14,11 +14,12 @@ import ( // 封装的链接对象 type Conn struct { - conn *net.UDPConn // 底层链接对象 - raddr *net.UDPAddr // 远程地址 - recvDeadline time.Time // 读取超时时间 - sendDeadline time.Time // 写入超时时间 - recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔 + conn *net.UDPConn // 底层链接对象 + raddr *net.UDPAddr // 远程地址 + buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理) + recvDeadline time.Time // 读取超时时间 + sendDeadline time.Time // 写入超时时间 + recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔 } const ( @@ -119,9 +120,14 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { break } } else { - // 如果长度超过了自定义的读取缓冲区,那么自动增长 if index >= gDEFAULT_READ_BUFFER_SIZE { + // 如果长度超过了自定义的读取缓冲区,那么自动增长 buffer = append(buffer, make([]byte, gDEFAULT_READ_BUFFER_SIZE)...) + } else { + // 如果第一次读取的数据并未达到缓冲变量长度,那么直接返回 + if !bufferWait { + break + } } } } diff --git a/g/net/gudp/gudp_conn_pkg.go b/g/net/gudp/gudp_conn_pkg.go new file mode 100644 index 000000000..4b65be743 --- /dev/null +++ b/g/net/gudp/gudp_conn_pkg.go @@ -0,0 +1,115 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gudp + +import ( + "encoding/binary" + "errors" + "fmt" + "time" +) + +const ( + // 允许最大的简单协议包大小(byte), 15MB + PKG_MAX_SIZE = 0xFFFFFF + // 消息包头大小: "总长度"3字节+"校验码"4字节 + PKG_HEADER_SIZE = 7 +) + +// 根据简单协议发送数据包。 +// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。 +// 注意: +// 1. "总长度"包含自身3字节及"校验码"4字节。 +// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。 +func (c *Conn) SendPkg(data []byte, retry...Retry) error { + length := uint32(len(data)) + if length > PKG_MAX_SIZE - PKG_HEADER_SIZE { + return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE)) + } + buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data)) + copy(buffer[PKG_HEADER_SIZE + 1 : ], data) + binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length) + binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data)) + //fmt.Println("SendPkg:", buffer[1:]) + return c.Send(buffer[1:], retry...) +} + +// 简单协议: 带超时时间的数据发送 +func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { + c.SetSendDeadline(time.Now().Add(timeout)) + defer c.SetSendDeadline(time.Time{}) + return c.SendPkg(data, retry...) +} + +// 简单协议: 发送数据并等待接收返回数据 +func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { + if err := c.SendPkg(data, retry...); err == nil { + return c.RecvPkg(retry...) + } else { + return nil, err + } +} + +// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间) +func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { + if err := c.SendPkg(data, retry...); err == nil { + return c.RecvPkgWithTimeout(timeout, retry...) + } else { + return nil, err + } +} + +// 简单协议: 获取一个数据包。 +func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { + var temp []byte + var length uint32 + for { + // 先根据对象的缓冲区数据进行计算 + for { + if len(c.buffer) >= PKG_HEADER_SIZE { + // 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 + length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}) + // 解析的大小是否符合规范 + if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE { + c.buffer = c.buffer[1:] + continue + } + // 不满足包大小,需要继续读取 + if uint32(len(c.buffer)) < length { + break + } + // 数据校验 + if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) { + c.buffer = c.buffer[1:] + continue + } + result = c.buffer[PKG_HEADER_SIZE : length] + c.buffer = c.buffer[length: ] + return + } else { + break + } + } + // 读取系统socket缓冲区的完整数据 + temp, err = c.Recv(-1, retry...) + if err != nil { + break + } + if len(temp) > 0 { + c.buffer = append(c.buffer, temp...) + } + //fmt.Println("RecvPkg:", c.buffer) + } + return +} + +// 简单协议: 带超时时间的消息包获取 +func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { + c.SetRecvDeadline(time.Now().Add(timeout)) + defer c.SetRecvDeadline(time.Time{}) + return c.RecvPkg(retry...) +} \ No newline at end of file diff --git a/g/net/gudp/gudp_func.go b/g/net/gudp/gudp_func.go index 3d964a528..859832637 100644 --- a/g/net/gudp/gudp_func.go +++ b/g/net/gudp/gudp_func.go @@ -10,6 +10,15 @@ import ( "net" ) +// 常见的二进制数据校验方式,生成校验结果 +func Checksum(buffer []byte) uint32 { + var checksum uint32 + for _, b := range buffer { + checksum += uint32(b) + } + return checksum +} + // 创建标准库UDP链接操作对象 func NewNetConn(raddr string, laddr...string) (*net.UDPConn, error) { var err error diff --git a/g/net/gudp/gudp_func_pkg.go b/g/net/gudp/gudp_func_pkg.go new file mode 100644 index 000000000..f0796064a --- /dev/null +++ b/g/net/gudp/gudp_func_pkg.go @@ -0,0 +1,49 @@ +// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gudp + +import "time" + +// 简单协议: (面向短链接)发送消息包 +func SendPkg(addr string, data []byte, retry...Retry) error { + conn, err := NewConn(addr) + if err != nil { + return err + } + defer conn.Close() + return conn.SendPkg(data, retry...) +} + +// 简单协议: (面向短链接)发送数据并等待接收返回数据 +func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) { + conn, err := NewConn(addr) + if err != nil { + return nil, err + } + defer conn.Close() + return conn.SendRecvPkg(data, retry...) +} + +// 简单协议: (面向短链接)带超时时间的数据发送 +func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error { + conn, err := NewConn(addr) + if err != nil { + return err + } + defer conn.Close() + return conn.SendPkgWithTimeout(data, timeout, retry...) +} + +// 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间) +func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { + conn, err := NewConn(addr) + if err != nil { + return nil, err + } + defer conn.Close() + return conn.SendRecvPkgWithTimeout(data, timeout, retry...) +} \ No newline at end of file diff --git a/g/os/gcron/gcron.go b/g/os/gcron/gcron.go index 03c07f410..4369b2245 100644 --- a/g/os/gcron/gcron.go +++ b/g/os/gcron/gcron.go @@ -5,8 +5,6 @@ // You can obtain one at https://github.com/gogf/gf. // Package gcron implements a cron pattern parser and job runner. -// -// 定时任务. package gcron import ( @@ -25,96 +23,108 @@ const ( ) var ( - // 默认的cron管理对象 + // Default cron object. defaultCron = New() ) -// 设置日志输出路径 +// SetLogPath sets the logging folder path for default cron object. func SetLogPath(path string) { defaultCron.SetLogPath(path) } -// 获取设置的日志输出路径 +// GetLogPath returns the logging folder path of default cron object. func GetLogPath() string { return defaultCron.GetLogPath() } -// 设置日志输出等级。 +// SetLogLevel sets the logging level for default cron object. func SetLogLevel(level int) { defaultCron.SetLogLevel(level) } -// 获取日志输出等级。 +// GetLogLevel returns the logging level for default cron object. func GetLogLevel() int { return defaultCron.GetLogLevel() } -// 添加定时任务,可以给定名字,以便于后续执行删除 +// Add adds a timed task to default cron object. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func Add(pattern string, job func(), name ... string) (*Entry, error) { return defaultCron.Add(pattern, job, name...) } -// 添加单例运行定时任务 +// AddSingleton adds a singleton timed task, to default cron object. +// A singleton timed task is that can only be running one single instance at the same time. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func AddSingleton(pattern string, job func(), name ... string) (*Entry, error) { return defaultCron.AddSingleton(pattern, job, name...) } -// 添加只运行一次的定时任务 +// AddOnce adds a timed task which can be run only once, to default cron object. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func AddOnce(pattern string, job func(), name ... string) (*Entry, error) { return defaultCron.AddOnce(pattern, job, name...) } -// 添加运行指定次数的定时任务 +// AddTimes adds a timed task which can be run specified times, to default cron object. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func AddTimes(pattern string, times int, job func(), name ... string) (*Entry, error) { return defaultCron.AddTimes(pattern, times, job, name...) } -// 延迟添加定时任务 +// DelayAdd adds a timed task to default cron object after time. func DelayAdd(delay time.Duration, pattern string, job func(), name ... string) { defaultCron.DelayAdd(delay, pattern, job, name...) } -// 延迟添加单例定时任务,delay参数单位为秒 +// DelayAddSingleton adds a singleton timed task after time to default cron object. func DelayAddSingleton(delay time.Duration, pattern string, job func(), name ... string) { defaultCron.DelayAddSingleton(delay, pattern, job, name...) } -// 延迟添加只运行一次的定时任务,delay参数单位为秒 +// DelayAddOnce adds a timed task after time to default cron object. +// This timed task can be run only once. func DelayAddOnce(delay time.Duration, pattern string, job func(), name ... string) { defaultCron.DelayAddOnce(delay, pattern, job, name...) } -// 延迟添加运行指定次数的定时任务,delay参数单位为秒 +// DelayAddTimes adds a timed task after time to default cron object. +// This timed task can be run specified times. func DelayAddTimes(delay time.Duration, pattern string, times int, job func(), name ... string) { defaultCron.DelayAddTimes(delay, pattern, times, job, name...) } -// 检索指定名称的定时任务 +// Search returns a scheduled task with the specified . +// It returns nil if no found. func Search(name string) *Entry { return defaultCron.Search(name) } -// 根据指定名称删除定时任务 +// Remove deletes scheduled task which named . func Remove(name string) { defaultCron.Remove(name) } -// 获取所有已注册的定时任务数量 +// Size returns the size of the timed tasks of default cron. func Size() int { return defaultCron.Size() } -// 获取所有已注册的定时任务项 +// Entries return all timed tasks as slice. func Entries() []*Entry { return defaultCron.Entries() } -// 启动指定的定时任务 +// Start starts running the specified timed task named . func Start(name string) { defaultCron.Start(name) } -// 停止指定的定时任务 +// Stop stops running the specified timed task named . func Stop(name string) { defaultCron.Stop(name) } diff --git a/g/os/gcron/gcron_cron.go b/g/os/gcron/gcron_cron.go index bb7eb2a06..b1a21aa88 100644 --- a/g/os/gcron/gcron_cron.go +++ b/g/os/gcron/gcron_cron.go @@ -17,16 +17,15 @@ import ( "time" ) -// 定时任务管理对象 type Cron struct { - idGen *gtype.Int64 // 用于唯一名称生成 - status *gtype.Int // 定时任务状态(0: 未执行; 1: 运行中; 2: 已停止; -1:删除关闭) - entries *gmap.StringInterfaceMap // 所有的定时任务项 - logPath *gtype.String // 日志文件输出目录 - logLevel *gtype.Int // 日志输出等级 + idGen *gtype.Int64 // Used for unique name generation. + status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed) + entries *gmap.StringInterfaceMap // All timed task entries. + logPath *gtype.String // Logging path(folder). + logLevel *gtype.Int // Logging level. } -// 创建自定义的定时任务管理对象 +// New returns a new Cron object with default settings. func New() *Cron { return &Cron { idGen : gtype.NewInt64(), @@ -37,37 +36,42 @@ func New() *Cron { } } -// 设置日志输出路径 +// SetLogPath sets the logging folder path. func (c *Cron) SetLogPath(path string) { c.logPath.Set(path) } -// 获取设置的日志输出路径 +// GetLogPath return the logging folder path. func (c *Cron) GetLogPath() string { return c.logPath.Val() } -// 设置日志输出等级。 +// SetLogLevel sets the logging level. func (c *Cron) SetLogLevel(level int) { c.logLevel.Set(level) } -// 获取日志输出等级。 +// GetLogLevel returns the logging level. func (c *Cron) GetLogLevel() int { return c.logLevel.Val() } -// 添加定时任务 +// Add adds a timed task. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func (c *Cron) Add(pattern string, job func(), name ... string) (*Entry, error) { if len(name) > 0 { if c.Search(name[0]) != nil { return nil, errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0])) } } - return c.addEntry(pattern, job, false, gDEFAULT_TIMES, name...) + return c.addEntry(pattern, job, false, name...) } -// 添加单例运行定时任务 +// AddSingleton adds a singleton timed task. +// A singleton timed task is that can only be running one single instance at the same time. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func (c *Cron) AddSingleton(pattern string, job func(), name ... string) (*Entry, error) { if entry, err := c.Add(pattern, job, name ...); err != nil { return nil, err @@ -77,7 +81,9 @@ func (c *Cron) AddSingleton(pattern string, job func(), name ... string) (*Entry } } -// 添加只运行一次的定时任务 +// AddOnce adds a timed task which can be run only once. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func (c *Cron) AddOnce(pattern string, job func(), name ... string) (*Entry, error) { if entry, err := c.Add(pattern, job, name ...); err != nil { return nil, err @@ -87,7 +93,9 @@ func (c *Cron) AddOnce(pattern string, job func(), name ... string) (*Entry, err } } -// 添加运行指定次数的定时任务 +// AddTimes adds a timed task which can be run specified times. +// A unique can be bound with the timed task. +// It returns and error if the is already used. func (c *Cron) AddTimes(pattern string, times int, job func(), name ... string) (*Entry, error) { if entry, err := c.Add(pattern, job, name ...); err != nil { return nil, err @@ -97,7 +105,7 @@ func (c *Cron) AddTimes(pattern string, times int, job func(), name ... string) } } -// 延迟添加定时任务 +// DelayAdd adds a timed task after time. func (c *Cron) DelayAdd(delay time.Duration, pattern string, job func(), name ... string) { gtimer.AddOnce(delay, func() { if _, err := c.Add(pattern, job, name ...); err != nil { @@ -106,7 +114,7 @@ func (c *Cron) DelayAdd(delay time.Duration, pattern string, job func(), name .. }) } -// 延迟添加单例定时任务 +// DelayAddSingleton adds a singleton timed task after time. func (c *Cron) DelayAddSingleton(delay time.Duration, pattern string, job func(), name ... string) { gtimer.AddOnce(delay, func() { if _, err := c.AddSingleton(pattern, job, name ...); err != nil { @@ -115,7 +123,8 @@ func (c *Cron) DelayAddSingleton(delay time.Duration, pattern string, job func() }) } -// 延迟添加运行指定次数的定时任务 +// DelayAddOnce adds a timed task after time. +// This timed task can be run only once. func (c *Cron) DelayAddOnce(delay time.Duration, pattern string, job func(), name ... string) { gtimer.AddOnce(delay, func() { if _, err := c.AddOnce(pattern, job, name ...); err != nil { @@ -124,7 +133,8 @@ func (c *Cron) DelayAddOnce(delay time.Duration, pattern string, job func(), nam }) } -// 延迟添加只运行一次的定时任务 +// DelayAddTimes adds a timed task after time. +// This timed task can be run specified times. func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job func(), name ... string) { gtimer.AddOnce(delay, func() { if _, err := c.AddTimes(pattern, times, job, name ...); err != nil { @@ -133,7 +143,8 @@ func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job }) } -// 检索指定名称的定时任务 +// Search returns a scheduled task with the specified . +// It returns nil if no found. func (c *Cron) Search(name string) *Entry { if v := c.entries.Get(name); v != nil { return v.(*Entry) @@ -141,7 +152,7 @@ func (c *Cron) Search(name string) *Entry { return nil } -// 开启定时任务执行(可以指定特定名称的一个或若干个定时任务) +// Start starts running the specified timed task named . func (c *Cron) Start(name...string) { if len(name) > 0 { for _, v := range name { @@ -154,7 +165,7 @@ func (c *Cron) Start(name...string) { } } -// 停止定时任务执行(可以指定特定名称的一个或若干个定时任务) +// Stop stops running the specified timed task named . func (c *Cron) Stop(name...string) { if len(name) > 0 { for _, v := range name { @@ -167,24 +178,24 @@ func (c *Cron) Stop(name...string) { } } -// 根据指定名称删除定时任务。 +// Remove deletes scheduled task which named . func (c *Cron) Remove(name string) { if v := c.entries.Get(name); v != nil { v.(*Entry).Close() } } -// 关闭定时任务 +// Close stops and closes current cron. func (c *Cron) Close() { c.status.Set(STATUS_CLOSED) } -// 获取所有已注册的定时任务数量 +// Size returns the size of the timed tasks. func (c *Cron) Size() int { return c.entries.Size() } -// 获取所有已注册的定时任务项(按照注册时间从小到大进行排序) +// Entries return all timed tasks as slice(order by registered time asc). func (c *Cron) Entries() []*Entry { array := garray.NewSortedArraySize(c.entries.Size(), func(v1, v2 interface{}) int { entry1 := v1.(*Entry) diff --git a/g/os/gcron/gcron_entry.go b/g/os/gcron/gcron_entry.go index b4802eba0..ecc880089 100644 --- a/g/os/gcron/gcron_entry.go +++ b/g/os/gcron/gcron_entry.go @@ -7,12 +7,13 @@ package gcron import ( - "github.com/gogf/gf/g/os/glog" - "github.com/gogf/gf/g/os/gtimer" - "github.com/gogf/gf/g/util/gconv" - "reflect" - "runtime" - "time" + "github.com/gogf/gf/g/container/gtype" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gtimer" + "github.com/gogf/gf/g/util/gconv" + "reflect" + "runtime" + "time" ) // Timed task entry. @@ -21,6 +22,7 @@ type Entry struct { entry *gtimer.Entry // Associated gtimer.Entry. schedule *cronSchedule // Timed schedule object. jobName string // Callback function name(address info). + times *gtype.Int // Running times limit. Name string // Entry name. Job func() `json:"-"` // Callback function. Time time.Time // Registered time. @@ -29,17 +31,18 @@ type Entry struct { // addEntry creates and returns a new Entry object. // Param is the callback function for timed task execution. // Param specifies whether timed task executing in singleton mode. -// Param limits the times for timed task executing. // Param names this entry for manual control. -func (c *Cron) addEntry(pattern string, job func(), singleton bool, times int, name ... string) (*Entry, error) { +func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ... string) (*Entry, error) { schedule, err := newSchedule(pattern) if err != nil { return nil, err } + // No limit for , for gtimer checking scheduling every second. entry := &Entry { cron : c, schedule : schedule, jobName : runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(), + times : gtype.NewInt(gDEFAULT_TIMES), Job : job, Time : time.Now(), } @@ -48,82 +51,98 @@ func (c *Cron) addEntry(pattern string, job func(), singleton bool, times int, n } else { entry.Name = "gcron-" + gconv.String(c.idGen.Add(1)) } - entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, times, gtimer.STATUS_STOPPED) - entry.entry.Start() + // When you add a scheduled task, you cannot allow it to run. + // It cannot start running when added to gtimer. + // It should start running after the entry is added to the entries map, + // to avoid the task from running during adding where the entries + // does not have the entry information, which might cause panic. + entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, -1, gtimer.STATUS_STOPPED) c.entries.Set(entry.Name, entry) + entry.entry.Start() return entry, nil } -// 是否单例运行 +// IsSingleton return whether this entry is a singleton timed task. func (entry *Entry) IsSingleton() bool { return entry.entry.IsSingleton() } -// 设置单例运行 +// SetSingleton sets the entry running in singleton mode. func (entry *Entry) SetSingleton(enabled bool) { entry.entry.SetSingleton(true) } -// 设置任务的运行次数 +// SetTimes sets the times which the entry can run. func (entry *Entry) SetTimes(times int) { - entry.entry.SetTimes(times) + entry.times.Set(times) } -// 定时任务状态 +// Status returns the status of entry. func (entry *Entry) Status() int { return entry.entry.Status() } -// 设置定时任务状态, 返回设置之前的状态 +// SetStatus sets the status of the entry. func (entry *Entry) SetStatus(status int) int { return entry.entry.SetStatus(status) } -// 启动定时任务 +// Start starts running the entry. func (entry *Entry) Start() { entry.entry.Start() } -// 停止定时任务 +// Stop stops running the entry. func (entry *Entry) Stop() { entry.entry.Stop() } -// 关闭定时任务 +// Close stops and removes the entry from cron. func (entry *Entry) Close() { entry.cron.entries.Remove(entry.Name) entry.entry.Close() } -// 定时任务检查执行 +// Timed task check execution. +// The running times limits feature is implemented by gcron.Entry and cannot be implemented by gtimer.Entry. +// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second. func (entry *Entry) check() { if entry.schedule.meet(time.Now()) { path := entry.cron.GetLogPath() level := entry.cron.GetLogLevel() - // 检查定时任务对象状态(非任务状态) switch entry.cron.status.Val() { case STATUS_STOPPED: return case STATUS_CLOSED: - entry.cron.Remove(entry.Name) - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s remove", entry.Name, entry.schedule.pattern, entry.jobName) - gtimer.Exit() + glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName) + entry.Close() case STATUS_READY: fallthrough case STATUS_RUNNING: + // Running times check. + times := entry.times.Add(-1) + if times <= 0 { + if entry.entry.SetStatus(STATUS_CLOSED) == STATUS_CLOSED || times < 0 { + return + } + } + if times < 2000000000 && times > 1000000000 { + entry.times.Set(gDEFAULT_TIMES) + } glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName) defer func() { - if entry.entry.Status() == STATUS_CLOSED { - entry.cron.Remove(entry.Name) - } if err := recover(); err != nil { glog.Path(path).Level(level).Errorfln("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err) } else { glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) } + if entry.entry.Status() == STATUS_CLOSED { + entry.Close() + } }() entry.Job() + } } } diff --git a/g/os/gcron/gcron_unit_1_test.go b/g/os/gcron/gcron_unit_1_test.go index 4fb93494d..9a378ca47 100644 --- a/g/os/gcron/gcron_unit_1_test.go +++ b/g/os/gcron/gcron_unit_1_test.go @@ -122,7 +122,7 @@ func TestCron_AddSingleton(t *testing.T) { } -func TestCron_AddOnce(t *testing.T) { +func TestCron_AddOnce1(t *testing.T) { gtest.Case(t, func() { cron := gcron.New() array := garray.New() @@ -139,6 +139,20 @@ func TestCron_AddOnce(t *testing.T) { }) } +func TestCron_AddOnce2(t *testing.T) { + gtest.Case(t, func() { + cron := gcron.New() + array := garray.New() + cron.AddOnce("@every 2s", func() { + array.Append(1) + }) + gtest.Assert(cron.Size(), 1) + time.Sleep(2500*time.Millisecond) + gtest.Assert(array.Len(), 1) + gtest.Assert(cron.Size(), 0) + }) +} + func TestCron_AddTimes(t *testing.T) { gtest.Case(t, func() { cron := gcron.New() diff --git a/g/os/gtime/gtime_format.go b/g/os/gtime/gtime_format.go index 79e8dc807..f0ef77a16 100644 --- a/g/os/gtime/gtime_format.go +++ b/g/os/gtime/gtime_format.go @@ -93,19 +93,17 @@ func formatToStdLayout(format string) string { if f, ok := formats[format[i]]; ok { // 有几个转换的符号需要特殊处理 switch format[i] { - case 'j': - b.WriteString("02") - case 'G': - b.WriteString("15") - case 'u': - if i > 0 && format[i-1] == '.' { - b.WriteString("000") - } else { - b.WriteString(".000") - } + case 'j': b.WriteString("02") + case 'G': b.WriteString("15") + case 'u': + if i > 0 && format[i-1] == '.' { + b.WriteString("000") + } else { + b.WriteString(".000") + } - default: - b.WriteString(f) + default: + b.WriteString(f) } } else { b.WriteByte(format[i]) @@ -130,47 +128,37 @@ func (t *Time) Format(format string) string { buffer := bytes.NewBuffer(nil) for i := 0; i < len(runes); { switch runes[i] { - case '\\': - if i < len(runes)-1 { - buffer.WriteRune(runes[i+1]) - i += 2 - continue - } else { - return buffer.String() - } - case 'W': - buffer.WriteString(strconv.Itoa(t.WeeksOfYear())) - case 'z': - buffer.WriteString(strconv.Itoa(t.DayOfYear())) - case 't': - buffer.WriteString(strconv.Itoa(t.DaysInMonth())) - default: - if runes[i] > 255 { - buffer.WriteRune(runes[i]) - break - } - if f, ok := formats[byte(runes[i])]; ok { - result := t.Time.Format(f) - // 有几个转换的符号需要特殊处理 - switch runes[i] { - case 'j': - buffer.WriteString(gstr.ReplaceByArray(result, []string{"=j=0", "", "=j=", ""})) - case 'G': - buffer.WriteString(gstr.ReplaceByArray(result, []string{"=G=0", "", "=G=", ""})) - case 'u': - buffer.WriteString(strings.Replace(result, "=u=.", "", -1)) - case 'w': - buffer.WriteString(weekMap[result]) - case 'N': - buffer.WriteString(strings.Replace(weekMap[result], "0", "7", -1)) - case 'S': - buffer.WriteString(formatMonthDaySuffixMap(result)) - default: - buffer.WriteString(result) + case '\\': + if i < len(runes)-1 { + buffer.WriteRune(runes[i+1]) + i += 2 + continue + } else { + return buffer.String() + } + case 'W': buffer.WriteString(strconv.Itoa(t.WeeksOfYear())) + case 'z': buffer.WriteString(strconv.Itoa(t.DayOfYear())) + case 't': buffer.WriteString(strconv.Itoa(t.DaysInMonth())) + default: + if runes[i] > 255 { + buffer.WriteRune(runes[i]) + break + } + if f, ok := formats[byte(runes[i])]; ok { + result := t.Time.Format(f) + // 有几个转换的符号需要特殊处理 + switch runes[i] { + case 'j': buffer.WriteString(gstr.ReplaceByArray(result, []string{"=j=0", "", "=j=", ""})) + case 'G': buffer.WriteString(gstr.ReplaceByArray(result, []string{"=G=0", "", "=G=", ""})) + case 'u': buffer.WriteString(strings.Replace(result, "=u=.", "", -1)) + case 'w': buffer.WriteString(weekMap[result]) + case 'N': buffer.WriteString(strings.Replace(weekMap[result], "0", "7", -1)) + case 'S': buffer.WriteString(formatMonthDaySuffixMap(result)) + default: buffer.WriteString(result) + } + } else { + buffer.WriteRune(runes[i]) } - } else { - buffer.WriteRune(runes[i]) - } } i++ } @@ -180,19 +168,15 @@ func (t *Time) Format(format string) string { // 每月天数后面的英文后缀,2 个字符st nd,rd 或者 th func formatMonthDaySuffixMap(day string) string { switch day { - case "01": - return "st" - case "02": - return "nd" - case "03": - return "rd" - default: - return "th" + case "01": return "st" + case "02": return "nd" + case "03": return "rd" + default: return "th" } } // 返回是否是润年 -func (t *Time)IsLeapYear() bool { +func (t *Time) IsLeapYear() bool { year := t.Year() if (year%4 == 0 && year%100 != 0) || year%400 == 0 { return true @@ -201,9 +185,9 @@ func (t *Time)IsLeapYear() bool { } // 返回一个时间点在当年中是第几天 0到365 有润年情况 -func (t *Time)DayOfYear() int { +func (t *Time) DayOfYear() int { month := int(t.Month()) - day := t.Day() + day := t.Day() // 判断是否润年 if t.IsLeapYear() { @@ -216,13 +200,12 @@ func (t *Time)DayOfYear() int { } // 一个时间点所在的月最长有多少天 28至31 -func (t *Time)DaysInMonth() int { - month := int(t.Month()) - switch month { - case 1, 3, 5, 7, 8, 10, 12: - return 31 - case 4, 6, 9, 11: - return 30 +func (t *Time) DaysInMonth() int { + switch t.Month() { + case 1, 3, 5, 7, 8, 10, 12: + return 31 + case 4, 6, 9, 11: + return 30 } // 只剩下第二月份,润年29天 @@ -233,9 +216,9 @@ func (t *Time)DaysInMonth() int { } // 获取时间点在本年内是第多少周 -func (t *Time)WeeksOfYear() int { - _, nums := t.ISOWeek() - return nums +func (t *Time) WeeksOfYear() int { + _, week := t.ISOWeek() + return week } // 格式化使用标准库格式 diff --git a/g/os/gtimer/gtimer.go b/g/os/gtimer/gtimer.go index 7c5418062..9454a4c48 100644 --- a/g/os/gtimer/gtimer.go +++ b/g/os/gtimer/gtimer.go @@ -99,7 +99,7 @@ func DelayAddTimes(delay time.Duration, interval time.Duration, times int, job J defaultTimer.DelayAddTimes(delay, interval, times, job) } -// 在Job方法中调用,停止当前运行的任务。 +// 在Job方法中调用,停止并删除当前运行的任务。 func Exit() { panic(gPANIC_EXIT) } diff --git a/g/os/gtimer/gtimer_entry.go b/g/os/gtimer/gtimer_entry.go index 0b6c9ba62..4174805c2 100644 --- a/g/os/gtimer/gtimer_entry.go +++ b/g/os/gtimer/gtimer_entry.go @@ -29,7 +29,11 @@ type Entry struct { type JobFunc = func() // 创建定时任务。 +// 如果times参数<=0,表示不限制运行次数。 func (w *wheel) addEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { + if times <= 0 { + times = gDEFAULT_TIMES + } ms := interval.Nanoseconds()/1e6 num := ms/w.intervalMs if num == 0 { @@ -172,7 +176,6 @@ func (entry *Entry) check(nowTicks int64, nowMs int64) (runnable, addable bool) } // 是否不限制运行次数 if times < 2000000000 && times > 1000000000 { - times = gDEFAULT_TIMES entry.times.Set(gDEFAULT_TIMES) } return true, true diff --git a/geg/database/orm/mysql/gdb_value.go b/geg/database/orm/mysql/gdb_value.go index 8cdc29c3e..e53c42376 100644 --- a/geg/database/orm/mysql/gdb_value.go +++ b/geg/database/orm/mysql/gdb_value.go @@ -10,7 +10,10 @@ func main() { // 开启调试模式,以便于记录所有执行的SQL db.SetDebug(true) - r, _ := db.Table("test").Where("id IN (?)", []interface{}{1, 2}).All() + r, e := db.Table("test").Where("id IN (?)", []interface{}{1, 2}).All() + if e != nil { + panic(e) + } if r != nil { fmt.Println(r.ToList()) } diff --git a/geg/net/ghttp/client/get.go b/geg/net/ghttp/client/get.go index 1cbdba695..5aaf4efdf 100644 --- a/geg/net/ghttp/client/get.go +++ b/geg/net/ghttp/client/get.go @@ -1,12 +1,18 @@ package main import ( + "crypto/tls" "fmt" "github.com/gogf/gf/g/net/ghttp" + "net/http" ) func main() { c := ghttp.NewClient() - r, _ := c.Get("http://baidu.com") + c.Transport = &http.Transport{ + TLSClientConfig : &tls.Config{ InsecureSkipVerify: true}, + } + r, e := c.Clone().Get("https://127.0.0.1:8199") + fmt.Println(e) fmt.Println(r.StatusCode) } diff --git a/geg/net/ghttp/server/https/https.go b/geg/net/ghttp/server/https/https.go index 40b9f367b..60fcddcb5 100644 --- a/geg/net/ghttp/server/https/https.go +++ b/geg/net/ghttp/server/https/https.go @@ -10,6 +10,7 @@ func main() { r.Response.Writeln("来自于HTTPS的:哈喽世界!") }) s.EnableHTTPS("./server.crt", "./server.key") + s.SetAccessLogEnabled(true) s.SetPort(8199) s.Run() } diff --git a/geg/net/gtcp/pkg_operations/common/funcs/funcs.go b/geg/net/gtcp/pkg_operations/common/funcs/funcs.go new file mode 100644 index 000000000..bc76ea693 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/common/funcs/funcs.go @@ -0,0 +1,38 @@ +package funcs + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/common/types" +) + +// 自定义格式发送消息包 +func SendPkg(conn *gtcp.Conn, act string, data...string) error { + s := "" + if len(data) > 0 { + s = data[0] + } + msg, err := json.Marshal(types.Msg{ + Act : act, + Data : s, + }) + if err != nil { + panic(err) + } + return conn.SendPkg(msg) +} + +// 自定义格式接收消息包 +func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) { + if data, err := conn.RecvPkg(); err != nil { + return nil, err + } else { + msg = &types.Msg{} + err = json.Unmarshal(data, msg) + if err != nil { + return nil, fmt.Errorf("invalid package structure: %s", err.Error()) + } + return msg, err + } +} \ No newline at end of file diff --git a/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go b/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go new file mode 100644 index 000000000..e4d9ed49c --- /dev/null +++ b/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go @@ -0,0 +1,60 @@ +package main + +import ( + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gtimer" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/common/funcs" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/common/types" + "time" +) + +func main() { + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + // 心跳消息 + gtimer.SetInterval(time.Second, func() { + if err := funcs.SendPkg(conn, "heartbeat"); err != nil { + panic(err) + } + }) + // 测试消息, 3秒后向服务端发送hello消息 + gtimer.SetTimeout(3*time.Second, func() { + if err := funcs.SendPkg(conn, "hello", "My name's John!"); err != nil { + panic(err) + } + }) + for { + msg, err := funcs.RecvPkg(conn) + if err != nil { + if err.Error() == "EOF" { + glog.Println("server closed") + } + break + } + switch msg.Act { + case "hello": onServerHello(conn, msg) + case "doexit": onServerDoExit(conn, msg) + case "heartbeat": onServerHeartBeat(conn, msg) + default: + glog.Errorfln("invalid message: %v", msg) + break + } + } +} + +func onServerHello(conn *gtcp.Conn, msg *types.Msg) { + glog.Printfln("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) +} + +func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) { + glog.Printfln("heartbeat from [%s]", conn.RemoteAddr().String()) +} + +func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) { + glog.Printfln("exit command from [%s]", conn.RemoteAddr().String()) + conn.Close() +} \ No newline at end of file diff --git a/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go b/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go new file mode 100644 index 000000000..51bffe544 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go @@ -0,0 +1,45 @@ +package main + +import ( + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gtimer" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/common/funcs" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/common/types" + "time" +) + +func main() { + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + // 测试消息, 10秒后让客户端主动退出 + gtimer.SetTimeout(10*time.Second, func() { + funcs.SendPkg(conn, "doexit") + }) + for { + msg, err := funcs.RecvPkg(conn) + if err != nil { + if err.Error() == "EOF" { + glog.Println("client closed") + } + break + } + switch msg.Act { + case "hello": onClientHello(conn, msg) + case "heartbeat": onClientHeartBeat(conn, msg) + default: + glog.Errorfln("invalid message: %v", msg) + break + } + } + }).Run() +} + +func onClientHello(conn *gtcp.Conn, msg *types.Msg) { + glog.Printfln("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + funcs.SendPkg(conn, msg.Act, "Nice to meet you!") +} + +func onClientHeartBeat(conn *gtcp.Conn, msg *types.Msg) { + glog.Printfln("heartbeat from [%s]", conn.RemoteAddr().String()) +} diff --git a/geg/net/gtcp/pkg_operations/common/types/types.go b/geg/net/gtcp/pkg_operations/common/types/types.go new file mode 100644 index 000000000..f2f712fe4 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/common/types/types.go @@ -0,0 +1,6 @@ +package types + +type Msg struct { + Act string // 操作 + Data string // 数据 +} diff --git a/geg/net/gtcp/pkg_operations/gtcp_server_client1.go b/geg/net/gtcp/pkg_operations/gtcp_server_client1.go new file mode 100644 index 000000000..867a7e404 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/gtcp_server_client1.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "time" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + fmt.Println(err) + break + } + fmt.Println("receive:", data) + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10000; i++ { + if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(1*time.Second) + } +} diff --git a/geg/net/gtcp/pkg_operations/gtcp_server_client2.go b/geg/net/gtcp/pkg_operations/gtcp_server_client2.go new file mode 100644 index 000000000..519cd0203 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/gtcp_server_client2.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "time" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + fmt.Println(err) + break + } + fmt.Println("RecvPkg:", string(data)) + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10000; i++ { + if err := conn.SendPkg(nil); err != nil { + glog.Error(err) + } + time.Sleep(1*time.Second) + } +} diff --git a/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_client.go b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_client.go new file mode 100644 index 000000000..85579a94f --- /dev/null +++ b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_client.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gtime" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/monitor/types" +) + +func main() { + // 数据上报客户端 + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + // 使用JSON格式化数据字段 + info, err := json.Marshal(types.NodeInfo{ + Cpu : float32(66.66), + Host : "localhost", + Ip : g.Map { + "etho" : "192.168.1.100", + "eth1" : "114.114.10.11", + }, + MemUsed : 15560320, + MemTotal : 16333788, + Time : int(gtime.Second()), + }) + if err != nil { + panic(err) + } + // 使用 SendRecvPkg 发送消息包并接受返回 + if result, err := conn.SendRecvPkg(info); err != nil { + if err.Error() == "EOF" { + glog.Println("server closed") + } + } else { + glog.Println(string(result)) + } +} diff --git a/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go new file mode 100644 index 000000000..e91f1b34b --- /dev/null +++ b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go @@ -0,0 +1,31 @@ +package main + +import ( + "encoding/json" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/geg/net/gtcp/pkg_operations/monitor/types" +) + +func main() { + // 服务端,接收客户端数据并格式化为指定数据结构,打印 + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + if err.Error() == "EOF" { + glog.Println("client closed") + } + break + } + info := &types.NodeInfo{} + if err := json.Unmarshal(data, info); err != nil { + glog.Errorfln("invalid package structure: %s", err.Error()) + } else { + glog.Println(info) + conn.SendPkg([]byte("ok")) + } + } + }).Run() +} diff --git a/geg/net/gtcp/pkg_operations/monitor/types/types.go b/geg/net/gtcp/pkg_operations/monitor/types/types.go new file mode 100644 index 000000000..680157d03 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/monitor/types/types.go @@ -0,0 +1,12 @@ +package types + +import "github.com/gogf/gf/g" + +type NodeInfo struct { + Cpu float32 // CPU百分比(%) + Host string // 主机名称 + Ip g.Map // IP地址信息(可能多个) + MemUsed int // 内存使用(byte) + MemTotal int // 内存总量(byte) + Time int // 上报时间(时间戳) +} diff --git a/geg/os/gcron/gcron2.go b/geg/os/gcron/gcron2.go index 7390efd68..931317e40 100644 --- a/geg/os/gcron/gcron2.go +++ b/geg/os/gcron/gcron2.go @@ -1,20 +1,19 @@ package main import ( - "github.com/gogf/gf/g" "github.com/gogf/gf/g/os/gcron" + "github.com/gogf/gf/g/os/glog" "time" ) func test() { - + glog.Println(111) } func main() { - _, err := gcron.Add("*/10 * * * * ?", test) + _, err := gcron.AddOnce("@every 2s", test) if err != nil { panic(err) } - g.Dump(gcron.Entries()) time.Sleep(10 * time.Second) } diff --git a/geg/other/test.go b/geg/other/test.go index 18343ac6c..8cb13c441 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -5,7 +5,5 @@ import ( ) func main() { - array := make([]interface{}, 0, 10) - array[8] = 1 - fmt.Println(array) + fmt.Println("10" > "4") } \ No newline at end of file diff --git a/third/github.com/go-sql-driver/mysql/AUTHORS b/third/github.com/gf-third/mysql/AUTHORS similarity index 94% rename from third/github.com/go-sql-driver/mysql/AUTHORS rename to third/github.com/gf-third/mysql/AUTHORS index 5ce4f7eca..cdf7ae3b8 100644 --- a/third/github.com/go-sql-driver/mysql/AUTHORS +++ b/third/github.com/gf-third/mysql/AUTHORS @@ -27,6 +27,7 @@ Daniël van Eeden Dave Protasowski DisposaBoy Egor Smolyakov +Erwan Martin Evan Shaw Frederick Mayle Gustavo Kristic @@ -34,6 +35,7 @@ Hajime Nakagami Hanno Braun Henri Yandell Hirotaka Yamamoto +Huyiguang ICHINOSE Shogo Ilia Cimpoes INADA Naoki @@ -41,6 +43,7 @@ Jacek Szwec James Harr Jeff Hodges Jeffrey Charles +Jerome Meyer Jian Zhen Joshua Prunier Julien Lefevre @@ -70,11 +73,13 @@ Richard Wilkes Robert Russell Runrioter Wung Shuode Li +Simon J Mudd Soroush Pour Stan Putrya Stanley Gunawan Steven Hartland Thomas Wodarek +Tim Ruffles Tom Jenkinson Xiangyu Hu Xiaobing Jiang @@ -85,6 +90,7 @@ Zhenye Xie Barracuda Networks, Inc. Counting Ltd. +GitHub Inc. Google Inc. InfoSum Ltd. Keybase Inc. diff --git a/third/github.com/go-sql-driver/mysql/CHANGELOG.md b/third/github.com/gf-third/mysql/CHANGELOG.md similarity index 100% rename from third/github.com/go-sql-driver/mysql/CHANGELOG.md rename to third/github.com/gf-third/mysql/CHANGELOG.md diff --git a/third/github.com/go-sql-driver/mysql/CONTRIBUTING.md b/third/github.com/gf-third/mysql/CONTRIBUTING.md similarity index 100% rename from third/github.com/go-sql-driver/mysql/CONTRIBUTING.md rename to third/github.com/gf-third/mysql/CONTRIBUTING.md diff --git a/third/github.com/go-sql-driver/mysql/LICENSE b/third/github.com/gf-third/mysql/LICENSE similarity index 100% rename from third/github.com/go-sql-driver/mysql/LICENSE rename to third/github.com/gf-third/mysql/LICENSE diff --git a/third/github.com/go-sql-driver/mysql/README.md b/third/github.com/gf-third/mysql/README.md similarity index 93% rename from third/github.com/go-sql-driver/mysql/README.md rename to third/github.com/gf-third/mysql/README.md index 129c8b3c7..c6adf1d63 100644 --- a/third/github.com/go-sql-driver/mysql/README.md +++ b/third/github.com/gf-third/mysql/README.md @@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac * Optional placeholder interpolation ## Requirements - * Go 1.8 or higher. We aim to support the 3 latest versions of Go. + * Go 1.9 or higher. We aim to support the 3 latest versions of Go. * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) --------------------------------------- @@ -58,7 +58,7 @@ _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`: ```go import "database/sql" -import _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" +import _ "github.com/go-sql-driver/mysql" db, err := sql.Open("mysql", "user:password@/dbname") ``` @@ -171,13 +171,18 @@ Unless you need the fallback behavior, please use `collation` instead. ``` Type: string Valid Values: -Default: utf8_general_ci +Default: utf8mb4_general_ci ``` Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail. A list of valid charsets for a server is retrievable with `SHOW COLLATION`. +The default collation (`utf8mb4_general_ci`) is supported from MySQL 5.5. You should use an older collation (e.g. `utf8_general_ci`) for older MySQL. + +Collations for charset "ucs2", "utf16", "utf16le", and "utf32" can not be used ([ref](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset)). + + ##### `clientFoundRows` ``` @@ -332,7 +337,7 @@ Valid Values: true, false, skip-verify, preferred, Default: false ``` -`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use `preferred` to use TLS only when advertised by the server, this is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). +`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). ##### `writeTimeout` @@ -431,7 +436,7 @@ See [context support in the database/sql package](https://golang.org/doc/go1.8#d ### `LOAD DATA LOCAL INFILE` support For this feature you need direct access to the package. Therefore you must change the import path (no `_`): ```go -import "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" +import "github.com/go-sql-driver/mysql" ``` Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)). @@ -444,7 +449,7 @@ See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/my ### `time.Time` support The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program. -However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter. +However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical equivalent in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter. **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes). diff --git a/third/github.com/go-sql-driver/mysql/appengine.go b/third/github.com/gf-third/mysql/appengine.go similarity index 66% rename from third/github.com/go-sql-driver/mysql/appengine.go rename to third/github.com/gf-third/mysql/appengine.go index be41f2ee6..914e6623b 100644 --- a/third/github.com/go-sql-driver/mysql/appengine.go +++ b/third/github.com/gf-third/mysql/appengine.go @@ -11,9 +11,15 @@ package mysql import ( + "context" + "net" + "google.golang.org/appengine/cloudsql" ) func init() { - RegisterDial("cloudsql", cloudsql.Dial) + RegisterDialContext("cloudsql", func(_ context.Context, instance string) (net.Conn, error) { + // XXX: the cloudsql driver still does not export a Context-aware dialer. + return cloudsql.Dial(instance) + }) } diff --git a/third/github.com/go-sql-driver/mysql/auth.go b/third/github.com/gf-third/mysql/auth.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/auth.go rename to third/github.com/gf-third/mysql/auth.go diff --git a/third/github.com/go-sql-driver/mysql/auth_test.go b/third/github.com/gf-third/mysql/auth_test.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/auth_test.go rename to third/github.com/gf-third/mysql/auth_test.go diff --git a/third/github.com/go-sql-driver/mysql/benchmark_test.go b/third/github.com/gf-third/mysql/benchmark_test.go similarity index 83% rename from third/github.com/go-sql-driver/mysql/benchmark_test.go rename to third/github.com/gf-third/mysql/benchmark_test.go index 5828d40f9..3e25a3bf2 100644 --- a/third/github.com/go-sql-driver/mysql/benchmark_test.go +++ b/third/github.com/gf-third/mysql/benchmark_test.go @@ -317,3 +317,57 @@ func BenchmarkExecContext(b *testing.B) { }) } } + +// BenchmarkQueryRawBytes benchmarks fetching 100 blobs using sql.RawBytes. +// "size=" means size of each blobs. +func BenchmarkQueryRawBytes(b *testing.B) { + var sizes []int = []int{100, 1000, 2000, 4000, 8000, 12000, 16000, 32000, 64000, 256000} + db := initDB(b, + "DROP TABLE IF EXISTS bench_rawbytes", + "CREATE TABLE bench_rawbytes (id INT PRIMARY KEY, val LONGBLOB)", + ) + defer db.Close() + + blob := make([]byte, sizes[len(sizes)-1]) + for i := range blob { + blob[i] = 42 + } + for i := 0; i < 100; i++ { + _, err := db.Exec("INSERT INTO bench_rawbytes VALUES (?, ?)", i, blob) + if err != nil { + b.Fatal(err) + } + } + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%v", s), func(b *testing.B) { + db.SetMaxIdleConns(0) + db.SetMaxIdleConns(1) + b.ReportAllocs() + b.ResetTimer() + + for j := 0; j < b.N; j++ { + rows, err := db.Query("SELECT LEFT(val, ?) as v FROM bench_rawbytes", s) + if err != nil { + b.Fatal(err) + } + nrows := 0 + for rows.Next() { + var buf sql.RawBytes + err := rows.Scan(&buf) + if err != nil { + b.Fatal(err) + } + if len(buf) != s { + b.Fatalf("size mismatch: expected %v, got %v", s, len(buf)) + } + nrows++ + } + rows.Close() + if nrows != 100 { + b.Fatalf("numbers of rows mismatch: expected %v, got %v", 100, nrows) + } + } + }) + } +} diff --git a/third/github.com/go-sql-driver/mysql/buffer.go b/third/github.com/gf-third/mysql/buffer.go similarity index 70% rename from third/github.com/go-sql-driver/mysql/buffer.go rename to third/github.com/gf-third/mysql/buffer.go index 19486bd6f..0774c5c8c 100644 --- a/third/github.com/go-sql-driver/mysql/buffer.go +++ b/third/github.com/gf-third/mysql/buffer.go @@ -15,47 +15,69 @@ import ( ) const defaultBufSize = 4096 +const maxCachedBufSize = 256 * 1024 // A buffer which is used for both reading and writing. // This is possible since communication on each connection is synchronous. // In other words, we can't write and read simultaneously on the same connection. // The buffer is similar to bufio.Reader / Writer but zero-copy-ish // Also highly optimized for this particular use case. +// This buffer is backed by two byte slices in a double-buffering scheme type buffer struct { buf []byte // buf is a byte buffer who's length and capacity are equal. nc net.Conn idx int length int timeout time.Duration + dbuf [2][]byte // dbuf is an array with the two byte slices that back this buffer + flipcnt uint // flipccnt is the current buffer counter for double-buffering } // newBuffer allocates and returns a new buffer. func newBuffer(nc net.Conn) buffer { + fg := make([]byte, defaultBufSize) return buffer{ - buf: make([]byte, defaultBufSize), - nc: nc, + buf: fg, + nc: nc, + dbuf: [2][]byte{fg, nil}, } } +// flip replaces the active buffer with the background buffer +// this is a delayed flip that simply increases the buffer counter; +// the actual flip will be performed the next time we call `buffer.fill` +func (b *buffer) flip() { + b.flipcnt += 1 +} + // fill reads into the buffer until at least _need_ bytes are in it func (b *buffer) fill(need int) error { n := b.length + // fill data into its double-buffering target: if we've called + // flip on this buffer, we'll be copying to the background buffer, + // and then filling it with network data; otherwise we'll just move + // the contents of the current buffer to the front before filling it + dest := b.dbuf[b.flipcnt&1] - // move existing data to the beginning - if n > 0 && b.idx > 0 { - copy(b.buf[0:n], b.buf[b.idx:]) - } - - // grow buffer if necessary - // TODO: let the buffer shrink again at some point - // Maybe keep the org buf slice and swap back? - if need > len(b.buf) { + // grow buffer if necessary to fit the whole packet. + if need > len(dest) { // Round up to the next multiple of the default size - newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) - copy(newBuf, b.buf) - b.buf = newBuf + dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) + + // if the allocated buffer is not too large, move it to backing storage + // to prevent extra allocations on applications that perform large reads + if len(dest) <= maxCachedBufSize { + b.dbuf[b.flipcnt&1] = dest + } } + // if we're filling the fg buffer, move the existing data to the start of it. + // if we're filling the bg buffer, copy over the data + if n > 0 { + copy(dest[:n], b.buf[b.idx:]) + } + + b.buf = dest b.idx = 0 for { diff --git a/third/github.com/gf-third/mysql/collations.go b/third/github.com/gf-third/mysql/collations.go new file mode 100644 index 000000000..8d2b55676 --- /dev/null +++ b/third/github.com/gf-third/mysql/collations.go @@ -0,0 +1,265 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +package mysql + +const defaultCollation = "utf8mb4_general_ci" +const binaryCollation = "binary" + +// A list of available collations mapped to the internal ID. +// To update this map use the following MySQL query: +// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID +// +// Handshake packet have only 1 byte for collation_id. So we can't use collations with ID > 255. +// +// ucs2, utf16, and utf32 can't be used for connection charset. +// https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset +// They are commented out to reduce this map. +var collations = map[string]byte{ + "big5_chinese_ci": 1, + "latin2_czech_cs": 2, + "dec8_swedish_ci": 3, + "cp850_general_ci": 4, + "latin1_german1_ci": 5, + "hp8_english_ci": 6, + "koi8r_general_ci": 7, + "latin1_swedish_ci": 8, + "latin2_general_ci": 9, + "swe7_swedish_ci": 10, + "ascii_general_ci": 11, + "ujis_japanese_ci": 12, + "sjis_japanese_ci": 13, + "cp1251_bulgarian_ci": 14, + "latin1_danish_ci": 15, + "hebrew_general_ci": 16, + "tis620_thai_ci": 18, + "euckr_korean_ci": 19, + "latin7_estonian_cs": 20, + "latin2_hungarian_ci": 21, + "koi8u_general_ci": 22, + "cp1251_ukrainian_ci": 23, + "gb2312_chinese_ci": 24, + "greek_general_ci": 25, + "cp1250_general_ci": 26, + "latin2_croatian_ci": 27, + "gbk_chinese_ci": 28, + "cp1257_lithuanian_ci": 29, + "latin5_turkish_ci": 30, + "latin1_german2_ci": 31, + "armscii8_general_ci": 32, + "utf8_general_ci": 33, + "cp1250_czech_cs": 34, + //"ucs2_general_ci": 35, + "cp866_general_ci": 36, + "keybcs2_general_ci": 37, + "macce_general_ci": 38, + "macroman_general_ci": 39, + "cp852_general_ci": 40, + "latin7_general_ci": 41, + "latin7_general_cs": 42, + "macce_bin": 43, + "cp1250_croatian_ci": 44, + "utf8mb4_general_ci": 45, + "utf8mb4_bin": 46, + "latin1_bin": 47, + "latin1_general_ci": 48, + "latin1_general_cs": 49, + "cp1251_bin": 50, + "cp1251_general_ci": 51, + "cp1251_general_cs": 52, + "macroman_bin": 53, + //"utf16_general_ci": 54, + //"utf16_bin": 55, + //"utf16le_general_ci": 56, + "cp1256_general_ci": 57, + "cp1257_bin": 58, + "cp1257_general_ci": 59, + //"utf32_general_ci": 60, + //"utf32_bin": 61, + //"utf16le_bin": 62, + "binary": 63, + "armscii8_bin": 64, + "ascii_bin": 65, + "cp1250_bin": 66, + "cp1256_bin": 67, + "cp866_bin": 68, + "dec8_bin": 69, + "greek_bin": 70, + "hebrew_bin": 71, + "hp8_bin": 72, + "keybcs2_bin": 73, + "koi8r_bin": 74, + "koi8u_bin": 75, + "utf8_tolower_ci": 76, + "latin2_bin": 77, + "latin5_bin": 78, + "latin7_bin": 79, + "cp850_bin": 80, + "cp852_bin": 81, + "swe7_bin": 82, + "utf8_bin": 83, + "big5_bin": 84, + "euckr_bin": 85, + "gb2312_bin": 86, + "gbk_bin": 87, + "sjis_bin": 88, + "tis620_bin": 89, + //"ucs2_bin": 90, + "ujis_bin": 91, + "geostd8_general_ci": 92, + "geostd8_bin": 93, + "latin1_spanish_ci": 94, + "cp932_japanese_ci": 95, + "cp932_bin": 96, + "eucjpms_japanese_ci": 97, + "eucjpms_bin": 98, + "cp1250_polish_ci": 99, + //"utf16_unicode_ci": 101, + //"utf16_icelandic_ci": 102, + //"utf16_latvian_ci": 103, + //"utf16_romanian_ci": 104, + //"utf16_slovenian_ci": 105, + //"utf16_polish_ci": 106, + //"utf16_estonian_ci": 107, + //"utf16_spanish_ci": 108, + //"utf16_swedish_ci": 109, + //"utf16_turkish_ci": 110, + //"utf16_czech_ci": 111, + //"utf16_danish_ci": 112, + //"utf16_lithuanian_ci": 113, + //"utf16_slovak_ci": 114, + //"utf16_spanish2_ci": 115, + //"utf16_roman_ci": 116, + //"utf16_persian_ci": 117, + //"utf16_esperanto_ci": 118, + //"utf16_hungarian_ci": 119, + //"utf16_sinhala_ci": 120, + //"utf16_german2_ci": 121, + //"utf16_croatian_ci": 122, + //"utf16_unicode_520_ci": 123, + //"utf16_vietnamese_ci": 124, + //"ucs2_unicode_ci": 128, + //"ucs2_icelandic_ci": 129, + //"ucs2_latvian_ci": 130, + //"ucs2_romanian_ci": 131, + //"ucs2_slovenian_ci": 132, + //"ucs2_polish_ci": 133, + //"ucs2_estonian_ci": 134, + //"ucs2_spanish_ci": 135, + //"ucs2_swedish_ci": 136, + //"ucs2_turkish_ci": 137, + //"ucs2_czech_ci": 138, + //"ucs2_danish_ci": 139, + //"ucs2_lithuanian_ci": 140, + //"ucs2_slovak_ci": 141, + //"ucs2_spanish2_ci": 142, + //"ucs2_roman_ci": 143, + //"ucs2_persian_ci": 144, + //"ucs2_esperanto_ci": 145, + //"ucs2_hungarian_ci": 146, + //"ucs2_sinhala_ci": 147, + //"ucs2_german2_ci": 148, + //"ucs2_croatian_ci": 149, + //"ucs2_unicode_520_ci": 150, + //"ucs2_vietnamese_ci": 151, + //"ucs2_general_mysql500_ci": 159, + //"utf32_unicode_ci": 160, + //"utf32_icelandic_ci": 161, + //"utf32_latvian_ci": 162, + //"utf32_romanian_ci": 163, + //"utf32_slovenian_ci": 164, + //"utf32_polish_ci": 165, + //"utf32_estonian_ci": 166, + //"utf32_spanish_ci": 167, + //"utf32_swedish_ci": 168, + //"utf32_turkish_ci": 169, + //"utf32_czech_ci": 170, + //"utf32_danish_ci": 171, + //"utf32_lithuanian_ci": 172, + //"utf32_slovak_ci": 173, + //"utf32_spanish2_ci": 174, + //"utf32_roman_ci": 175, + //"utf32_persian_ci": 176, + //"utf32_esperanto_ci": 177, + //"utf32_hungarian_ci": 178, + //"utf32_sinhala_ci": 179, + //"utf32_german2_ci": 180, + //"utf32_croatian_ci": 181, + //"utf32_unicode_520_ci": 182, + //"utf32_vietnamese_ci": 183, + "utf8_unicode_ci": 192, + "utf8_icelandic_ci": 193, + "utf8_latvian_ci": 194, + "utf8_romanian_ci": 195, + "utf8_slovenian_ci": 196, + "utf8_polish_ci": 197, + "utf8_estonian_ci": 198, + "utf8_spanish_ci": 199, + "utf8_swedish_ci": 200, + "utf8_turkish_ci": 201, + "utf8_czech_ci": 202, + "utf8_danish_ci": 203, + "utf8_lithuanian_ci": 204, + "utf8_slovak_ci": 205, + "utf8_spanish2_ci": 206, + "utf8_roman_ci": 207, + "utf8_persian_ci": 208, + "utf8_esperanto_ci": 209, + "utf8_hungarian_ci": 210, + "utf8_sinhala_ci": 211, + "utf8_german2_ci": 212, + "utf8_croatian_ci": 213, + "utf8_unicode_520_ci": 214, + "utf8_vietnamese_ci": 215, + "utf8_general_mysql500_ci": 223, + "utf8mb4_unicode_ci": 224, + "utf8mb4_icelandic_ci": 225, + "utf8mb4_latvian_ci": 226, + "utf8mb4_romanian_ci": 227, + "utf8mb4_slovenian_ci": 228, + "utf8mb4_polish_ci": 229, + "utf8mb4_estonian_ci": 230, + "utf8mb4_spanish_ci": 231, + "utf8mb4_swedish_ci": 232, + "utf8mb4_turkish_ci": 233, + "utf8mb4_czech_ci": 234, + "utf8mb4_danish_ci": 235, + "utf8mb4_lithuanian_ci": 236, + "utf8mb4_slovak_ci": 237, + "utf8mb4_spanish2_ci": 238, + "utf8mb4_roman_ci": 239, + "utf8mb4_persian_ci": 240, + "utf8mb4_esperanto_ci": 241, + "utf8mb4_hungarian_ci": 242, + "utf8mb4_sinhala_ci": 243, + "utf8mb4_german2_ci": 244, + "utf8mb4_croatian_ci": 245, + "utf8mb4_unicode_520_ci": 246, + "utf8mb4_vietnamese_ci": 247, + "gb18030_chinese_ci": 248, + "gb18030_bin": 249, + "gb18030_unicode_520_ci": 250, + "utf8mb4_0900_ai_ci": 255, +} + +// A blacklist of collations which is unsafe to interpolate parameters. +// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes. +var unsafeCollations = map[string]bool{ + "big5_chinese_ci": true, + "sjis_japanese_ci": true, + "gbk_chinese_ci": true, + "big5_bin": true, + "gb2312_bin": true, + "gbk_bin": true, + "sjis_bin": true, + "cp932_japanese_ci": true, + "cp932_bin": true, + "gb18030_chinese_ci": true, + "gb18030_bin": true, + "gb18030_unicode_520_ci": true, +} diff --git a/third/github.com/gf-third/mysql/conncheck.go b/third/github.com/gf-third/mysql/conncheck.go new file mode 100644 index 000000000..cc47aa559 --- /dev/null +++ b/third/github.com/gf-third/mysql/conncheck.go @@ -0,0 +1,53 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build !windows,!appengine + +package mysql + +import ( + "errors" + "io" + "net" + "syscall" +) + +var errUnexpectedRead = errors.New("unexpected read from socket") + +func connCheck(c net.Conn) error { + var ( + n int + err error + buff [1]byte + ) + + sconn, ok := c.(syscall.Conn) + if !ok { + return nil + } + rc, err := sconn.SyscallConn() + if err != nil { + return err + } + rerr := rc.Read(func(fd uintptr) bool { + n, err = syscall.Read(int(fd), buff[:]) + return true + }) + switch { + case rerr != nil: + return rerr + case n == 0 && err == nil: + return io.EOF + case n > 0: + return errUnexpectedRead + case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK: + return nil + default: + return err + } +} diff --git a/third/github.com/gf-third/mysql/conncheck_dummy.go b/third/github.com/gf-third/mysql/conncheck_dummy.go new file mode 100644 index 000000000..fd01f64c9 --- /dev/null +++ b/third/github.com/gf-third/mysql/conncheck_dummy.go @@ -0,0 +1,17 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build windows appengine + +package mysql + +import "net" + +func connCheck(c net.Conn) error { + return nil +} diff --git a/third/github.com/gf-third/mysql/conncheck_test.go b/third/github.com/gf-third/mysql/conncheck_test.go new file mode 100644 index 000000000..b7234b0f5 --- /dev/null +++ b/third/github.com/gf-third/mysql/conncheck_test.go @@ -0,0 +1,38 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build go1.10,!windows + +package mysql + +import ( + "testing" + "time" +) + +func TestStaleConnectionChecks(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + dbt.mustExec("SET @@SESSION.wait_timeout = 2") + + if err := dbt.db.Ping(); err != nil { + dbt.Fatal(err) + } + + // wait for MySQL to close our connection + time.Sleep(3 * time.Second) + + tx, err := dbt.db.Begin() + if err != nil { + dbt.Fatal(err) + } + + if err := tx.Rollback(); err != nil { + dbt.Fatal(err) + } + }) +} diff --git a/third/github.com/go-sql-driver/mysql/connection.go b/third/github.com/gf-third/mysql/connection.go similarity index 97% rename from third/github.com/go-sql-driver/mysql/connection.go rename to third/github.com/gf-third/mysql/connection.go index fc4ec7597..565a5480a 100644 --- a/third/github.com/go-sql-driver/mysql/connection.go +++ b/third/github.com/gf-third/mysql/connection.go @@ -22,6 +22,7 @@ import ( type mysqlConn struct { buf buffer netConn net.Conn + rawConn net.Conn // underlying connection when netConn is TLS connection. affectedRows uint64 insertId uint64 cfg *Config @@ -32,6 +33,7 @@ type mysqlConn struct { status statusFlag sequence uint8 parseTime bool + reset bool // set when the Go SQL package calls ResetSession // for context support (Go 1.8+) watching bool @@ -211,6 +213,9 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin switch v := arg.(type) { case int64: buf = strconv.AppendInt(buf, v, 10) + case uint64: + // Handle uint64 explicitly because our custom ConvertValue emits unsigned values + buf = strconv.AppendUint(buf, v, 10) case float64: buf = strconv.AppendFloat(buf, v, 'g', -1, 64) case bool: @@ -639,5 +644,6 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error { if mc.closed.IsSet() { return driver.ErrBadConn } + mc.reset = true return nil } diff --git a/third/github.com/go-sql-driver/mysql/connection_test.go b/third/github.com/gf-third/mysql/connection_test.go similarity index 87% rename from third/github.com/go-sql-driver/mysql/connection_test.go rename to third/github.com/gf-third/mysql/connection_test.go index 2a1c8e888..19c17ff8b 100644 --- a/third/github.com/go-sql-driver/mysql/connection_test.go +++ b/third/github.com/gf-third/mysql/connection_test.go @@ -69,6 +69,24 @@ func TestInterpolateParamsPlaceholderInString(t *testing.T) { } } +func TestInterpolateParamsUint64(t *testing.T) { + mc := &mysqlConn{ + buf: newBuffer(nil), + maxAllowedPacket: maxPacketSize, + cfg: &Config{ + InterpolateParams: true, + }, + } + + q, err := mc.interpolateParams("SELECT ?", []driver.Value{uint64(42)}) + if err != nil { + t.Errorf("Expected err=nil, got err=%#v, q=%#v", err, q) + } + if q != "SELECT 42" { + t.Errorf("Expected uint64 interpolation to work, got q=%#v", q) + } +} + func TestCheckNamedValue(t *testing.T) { value := driver.NamedValue{Value: ^uint64(0)} x := &mysqlConn{} @@ -78,8 +96,8 @@ func TestCheckNamedValue(t *testing.T) { t.Fatal("uint64 high-bit not convertible", err) } - if value.Value != "18446744073709551615" { - t.Fatalf("uint64 high-bit not converted, got %#v %T", value.Value, value.Value) + if value.Value != ^uint64(0) { + t.Fatalf("uint64 high-bit converted, got %#v %T", value.Value, value.Value) } } diff --git a/third/github.com/go-sql-driver/mysql/driver.go b/third/github.com/gf-third/mysql/connector.go similarity index 63% rename from third/github.com/go-sql-driver/mysql/driver.go rename to third/github.com/gf-third/mysql/connector.go index 6fc777639..5aaaba43e 100644 --- a/third/github.com/go-sql-driver/mysql/driver.go +++ b/third/github.com/gf-third/mysql/connector.go @@ -1,57 +1,26 @@ -// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. -// Package mysql provides a MySQL driver for Go's database/sql package. -// -// The driver should be used via the database/sql package: -// -// import "database/sql" -// import _ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql" -// -// db, err := sql.Open("mysql", "user:password@/dbname") -// -// See https://github.com/go-sql-driver/mysql#usage for details package mysql import ( - "database/sql" + "context" "database/sql/driver" "net" - "sync" ) -// MySQLDriver is exported to make the driver directly accessible. -// In general the driver is used via the database/sql package. -type MySQLDriver struct{} - -// DialFunc is a function which can be used to establish the network connection. -// Custom dial functions must be registered with RegisterDial -type DialFunc func(addr string) (net.Conn, error) - -var ( - dialsLock sync.RWMutex - dials map[string]DialFunc -) - -// RegisterDial registers a custom dial function. It can then be used by the -// network address mynet(addr), where mynet is the registered new network. -// addr is passed as a parameter to the dial function. -func RegisterDial(net string, dial DialFunc) { - dialsLock.Lock() - defer dialsLock.Unlock() - if dials == nil { - dials = make(map[string]DialFunc) - } - dials[net] = dial +type connector struct { + cfg *Config // immutable private copy. } -// Open new Connection. -// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how -// the DSN string is formatted -func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { +// Connect implements driver.Connector interface. +// Connect returns a connection to the database. +func (c *connector) Connect(ctx context.Context) (driver.Conn, error) { var err error // New mysqlConn @@ -59,10 +28,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { maxAllowedPacket: maxPacketSize, maxWriteSize: maxPacketSize - 1, closech: make(chan struct{}), - } - mc.cfg, err = ParseDSN(dsn) - if err != nil { - return nil, err + cfg: c.cfg, } mc.parseTime = mc.cfg.ParseTime @@ -71,11 +37,12 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { dial, ok := dials[mc.cfg.Net] dialsLock.RUnlock() if ok { - mc.netConn, err = dial(mc.cfg.Addr) + mc.netConn, err = dial(ctx, mc.cfg.Addr) } else { nd := net.Dialer{Timeout: mc.cfg.Timeout} - mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr) + mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr) } + if err != nil { if nerr, ok := err.(net.Error); ok && nerr.Temporary() { errLog.Print("net.Error from Dial()': ", nerr.Error()) @@ -96,6 +63,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { // Call startWatcher for context support (From Go 1.8) mc.startWatcher() + if err := mc.watchCancel(ctx); err != nil { + return nil, err + } + defer mc.finish() mc.buf = newBuffer(mc.netConn) @@ -109,6 +80,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { mc.cleanup() return nil, err } + if plugin == "" { plugin = defaultAuthPlugin } @@ -164,6 +136,8 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { return mc, nil } -func init() { - sql.Register("mysql", &MySQLDriver{}) +// Driver implements driver.Connector interface. +// Driver returns &MySQLDriver{}. +func (c *connector) Driver() driver.Driver { + return &MySQLDriver{} } diff --git a/third/github.com/go-sql-driver/mysql/const.go b/third/github.com/gf-third/mysql/const.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/const.go rename to third/github.com/gf-third/mysql/const.go diff --git a/third/github.com/gf-third/mysql/driver.go b/third/github.com/gf-third/mysql/driver.go new file mode 100644 index 000000000..85c01e83d --- /dev/null +++ b/third/github.com/gf-third/mysql/driver.go @@ -0,0 +1,85 @@ +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package mysql provides a MySQL driver for Go's database/sql package. +// +// The driver should be used via the database/sql package: +// +// import "database/sql" +// import _ "github.com/go-sql-driver/mysql" +// +// db, err := sql.Open("mysql", "user:password@/dbname") +// +// See https://github.com/go-sql-driver/mysql#usage for details +package mysql + +import ( + "context" + "database/sql" + "database/sql/driver" + "net" + "sync" +) + +// MySQLDriver is exported to make the driver directly accessible. +// In general the driver is used via the database/sql package. +type MySQLDriver struct{} + +// DialFunc is a function which can be used to establish the network connection. +// Custom dial functions must be registered with RegisterDial +// +// Deprecated: users should register a DialContextFunc instead +type DialFunc func(addr string) (net.Conn, error) + +// DialContextFunc is a function which can be used to establish the network connection. +// Custom dial functions must be registered with RegisterDialContext +type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error) + +var ( + dialsLock sync.RWMutex + dials map[string]DialContextFunc +) + +// RegisterDialContext registers a custom dial function. It can then be used by the +// network address mynet(addr), where mynet is the registered new network. +// The current context for the connection and its address is passed to the dial function. +func RegisterDialContext(net string, dial DialContextFunc) { + dialsLock.Lock() + defer dialsLock.Unlock() + if dials == nil { + dials = make(map[string]DialContextFunc) + } + dials[net] = dial +} + +// RegisterDial registers a custom dial function. It can then be used by the +// network address mynet(addr), where mynet is the registered new network. +// addr is passed as a parameter to the dial function. +// +// Deprecated: users should call RegisterDialContext instead +func RegisterDial(network string, dial DialFunc) { + RegisterDialContext(network, func(_ context.Context, addr string) (net.Conn, error) { + return dial(addr) + }) +} + +// Open new Connection. +// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how +// the DSN string is formatted +func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { + cfg, err := ParseDSN(dsn) + if err != nil { + return nil, err + } + c := &connector{ + cfg: cfg, + } + return c.Connect(context.Background()) +} + +func init() { + sql.Register("gf-mysql", &MySQLDriver{}) +} diff --git a/third/github.com/gf-third/mysql/driver_go110.go b/third/github.com/gf-third/mysql/driver_go110.go new file mode 100644 index 000000000..eb5a8fe9b --- /dev/null +++ b/third/github.com/gf-third/mysql/driver_go110.go @@ -0,0 +1,37 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build go1.10 + +package mysql + +import ( + "database/sql/driver" +) + +// NewConnector returns new driver.Connector. +func NewConnector(cfg *Config) (driver.Connector, error) { + cfg = cfg.Clone() + // normalize the contents of cfg so calls to NewConnector have the same + // behavior as MySQLDriver.OpenConnector + if err := cfg.normalize(); err != nil { + return nil, err + } + return &connector{cfg: cfg}, nil +} + +// OpenConnector implements driver.DriverContext. +func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) { + cfg, err := ParseDSN(dsn) + if err != nil { + return nil, err + } + return &connector{ + cfg: cfg, + }, nil +} diff --git a/third/github.com/gf-third/mysql/driver_go110_test.go b/third/github.com/gf-third/mysql/driver_go110_test.go new file mode 100644 index 000000000..19a0e5956 --- /dev/null +++ b/third/github.com/gf-third/mysql/driver_go110_test.go @@ -0,0 +1,137 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build go1.10 + +package mysql + +import ( + "context" + "database/sql" + "database/sql/driver" + "fmt" + "net" + "testing" + "time" +) + +var _ driver.DriverContext = &MySQLDriver{} + +type dialCtxKey struct{} + +func TestConnectorObeysDialTimeouts(t *testing.T) { + if !available { + t.Skipf("MySQL server not running on %s", netAddr) + } + + RegisterDialContext("dialctxtest", func(ctx context.Context, addr string) (net.Conn, error) { + var d net.Dialer + if !ctx.Value(dialCtxKey{}).(bool) { + return nil, fmt.Errorf("test error: query context is not propagated to our dialer") + } + return d.DialContext(ctx, prot, addr) + }) + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@dialctxtest(%s)/%s?timeout=30s", user, pass, addr, dbname)) + if err != nil { + t.Fatalf("error connecting: %s", err.Error()) + } + defer db.Close() + + ctx := context.WithValue(context.Background(), dialCtxKey{}, true) + + _, err = db.ExecContext(ctx, "DO 1") + if err != nil { + t.Fatal(err) + } +} + +func configForTests(t *testing.T) *Config { + if !available { + t.Skipf("MySQL server not running on %s", netAddr) + } + + mycnf := NewConfig() + mycnf.User = user + mycnf.Passwd = pass + mycnf.Addr = addr + mycnf.Net = prot + mycnf.DBName = dbname + return mycnf +} + +func TestNewConnector(t *testing.T) { + mycnf := configForTests(t) + conn, err := NewConnector(mycnf) + if err != nil { + t.Fatal(err) + } + + db := sql.OpenDB(conn) + defer db.Close() + + if err := db.Ping(); err != nil { + t.Fatal(err) + } +} + +type slowConnection struct { + net.Conn + slowdown time.Duration +} + +func (sc *slowConnection) Read(b []byte) (int, error) { + time.Sleep(sc.slowdown) + return sc.Conn.Read(b) +} + +type connectorHijack struct { + driver.Connector + connErr error +} + +func (cw *connectorHijack) Connect(ctx context.Context) (driver.Conn, error) { + var conn driver.Conn + conn, cw.connErr = cw.Connector.Connect(ctx) + return conn, cw.connErr +} + +func TestConnectorTimeoutsDuringOpen(t *testing.T) { + RegisterDialContext("slowconn", func(ctx context.Context, addr string) (net.Conn, error) { + var d net.Dialer + conn, err := d.DialContext(ctx, prot, addr) + if err != nil { + return nil, err + } + return &slowConnection{Conn: conn, slowdown: 100 * time.Millisecond}, nil + }) + + mycnf := configForTests(t) + mycnf.Net = "slowconn" + + conn, err := NewConnector(mycnf) + if err != nil { + t.Fatal(err) + } + + hijack := &connectorHijack{Connector: conn} + + db := sql.OpenDB(hijack) + defer db.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + _, err = db.ExecContext(ctx, "DO 1") + if err != context.DeadlineExceeded { + t.Fatalf("ExecContext should have timed out") + } + if hijack.connErr != context.DeadlineExceeded { + t.Fatalf("(*Connector).Connect should have timed out") + } +} diff --git a/third/github.com/go-sql-driver/mysql/driver_test.go b/third/github.com/gf-third/mysql/driver_test.go similarity index 97% rename from third/github.com/go-sql-driver/mysql/driver_test.go rename to third/github.com/gf-third/mysql/driver_test.go index 46d1f7ff4..3dee1bab2 100644 --- a/third/github.com/go-sql-driver/mysql/driver_test.go +++ b/third/github.com/gf-third/mysql/driver_test.go @@ -212,6 +212,7 @@ func TestEmptyQuery(t *testing.T) { runTests(t, dsn, func(dbt *DBTest) { // just a comment, no query rows := dbt.mustQuery("--") + defer rows.Close() // will hang before #255 if rows.Next() { dbt.Errorf("next on rows must be false") @@ -230,6 +231,7 @@ func TestCRUD(t *testing.T) { if rows.Next() { dbt.Error("unexpected data in empty table") } + rows.Close() // Create Data res := dbt.mustExec("INSERT INTO test VALUES (1)") @@ -263,6 +265,7 @@ func TestCRUD(t *testing.T) { } else { dbt.Error("no data") } + rows.Close() // Update res = dbt.mustExec("UPDATE test SET value = ? WHERE value = ?", false, true) @@ -288,6 +291,7 @@ func TestCRUD(t *testing.T) { } else { dbt.Error("no data") } + rows.Close() // Delete res = dbt.mustExec("DELETE FROM test WHERE value = ?", false) @@ -351,6 +355,7 @@ func TestMultiQuery(t *testing.T) { } else { dbt.Error("no data") } + rows.Close() }) } @@ -377,6 +382,7 @@ func TestInt(t *testing.T) { } else { dbt.Errorf("%s: no data", v) } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") } @@ -396,6 +402,7 @@ func TestInt(t *testing.T) { } else { dbt.Errorf("%s ZEROFILL: no data", v) } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") } @@ -420,6 +427,7 @@ func TestFloat32(t *testing.T) { } else { dbt.Errorf("%s: no data", v) } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") } }) @@ -443,6 +451,7 @@ func TestFloat64(t *testing.T) { } else { dbt.Errorf("%s: no data", v) } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") } }) @@ -466,6 +475,7 @@ func TestFloat64Placeholder(t *testing.T) { } else { dbt.Errorf("%s: no data", v) } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") } }) @@ -492,6 +502,7 @@ func TestString(t *testing.T) { } else { dbt.Errorf("%s: no data", v) } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") } @@ -524,6 +535,7 @@ func TestRawBytes(t *testing.T) { v1 := []byte("aaa") v2 := []byte("bbb") rows := dbt.mustQuery("SELECT ?, ?", v1, v2) + defer rows.Close() if rows.Next() { var o1, o2 sql.RawBytes if err := rows.Scan(&o1, &o2); err != nil { @@ -572,6 +584,7 @@ func TestValuer(t *testing.T) { } else { dbt.Errorf("Valuer: no data") } + rows.Close() dbt.mustExec("DROP TABLE IF EXISTS test") }) @@ -884,6 +897,7 @@ func TestTimestampMicros(t *testing.T) { dbt.mustExec("INSERT INTO test SET value0=?, value1=?, value6=?", f0, f1, f6) var res0, res1, res6 string rows := dbt.mustQuery("SELECT * FROM test") + defer rows.Close() if !rows.Next() { dbt.Errorf("test contained no selectable values") } @@ -1042,6 +1056,7 @@ func TestNULL(t *testing.T) { var out interface{} rows := dbt.mustQuery("SELECT * FROM test") + defer rows.Close() if rows.Next() { rows.Scan(&out) if out != nil { @@ -1121,6 +1136,7 @@ func TestLongData(t *testing.T) { inS := in[:maxAllowedPacketSize-nonDataQueryLen] dbt.mustExec("INSERT INTO test VALUES('" + inS + "')") rows = dbt.mustQuery("SELECT value FROM test") + defer rows.Close() if rows.Next() { rows.Scan(&out) if inS != out { @@ -1139,6 +1155,7 @@ func TestLongData(t *testing.T) { // Long binary data dbt.mustExec("INSERT INTO test VALUES(?)", in) rows = dbt.mustQuery("SELECT value FROM test WHERE 1=?", 1) + defer rows.Close() if rows.Next() { rows.Scan(&out) if in != out { @@ -1314,6 +1331,7 @@ func TestTLS(t *testing.T) { } rows := dbt.mustQuery("SHOW STATUS LIKE 'Ssl_cipher'") + defer rows.Close() var variable, value *sql.RawBytes for rows.Next() { @@ -1430,7 +1448,7 @@ func TestCollation(t *testing.T) { t.Skipf("MySQL server not running on %s", netAddr) } - defaultCollation := "utf8_general_ci" + defaultCollation := "utf8mb4_general_ci" testCollations := []string{ "", // do not set defaultCollation, // driver default @@ -1474,9 +1492,9 @@ func TestColumnsWithAlias(t *testing.T) { if cols[0] != "A" { t.Fatalf("expected column name \"A\", got \"%s\"", cols[0]) } - rows.Close() rows = dbt.mustQuery("SELECT * FROM (SELECT 1 AS one) AS A") + defer rows.Close() cols, _ = rows.Columns() if len(cols) != 1 { t.Fatalf("expected 1 column, got %d", len(cols)) @@ -1520,6 +1538,7 @@ func TestTimezoneConversion(t *testing.T) { // Retrieve time from DB rows := dbt.mustQuery("SELECT ts FROM test") + defer rows.Close() if !rows.Next() { dbt.Fatal("did not get any rows out") } @@ -1827,7 +1846,7 @@ func TestConcurrent(t *testing.T) { } func testDialError(t *testing.T, dialErr error, expectErr error) { - RegisterDial("mydial", func(addr string) (net.Conn, error) { + RegisterDialContext("mydial", func(ctx context.Context, addr string) (net.Conn, error) { return nil, dialErr }) @@ -1865,8 +1884,9 @@ func TestCustomDial(t *testing.T) { } // our custom dial function which justs wraps net.Dial here - RegisterDial("mydial", func(addr string) (net.Conn, error) { - return net.Dial(prot, addr) + RegisterDialContext("mydial", func(ctx context.Context, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, prot, addr) }) db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname)) @@ -2017,6 +2037,7 @@ func TestInterruptBySignal(t *testing.T) { dbt.Errorf("expected val to be 42") } } + rows.Close() // binary protocol rows, err = dbt.db.Query("CALL test_signal(?)", 42) @@ -2030,6 +2051,7 @@ func TestInterruptBySignal(t *testing.T) { dbt.Errorf("expected val to be 42") } } + rows.Close() }) } @@ -2917,3 +2939,58 @@ func TestValuerWithValueReceiverGivenNilValue(t *testing.T) { // This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value() }) } + +// TestRawBytesAreNotModified checks for a race condition that arises when a query context +// is canceled while a user is calling rows.Scan. This is a more stringent test than the one +// proposed in https://github.com/golang/go/issues/23519. Here we're explicitly using +// `sql.RawBytes` to check the contents of our internal buffers are not modified after an implicit +// call to `Rows.Close`, so Context cancellation should **not** invalidate the backing buffers. +func TestRawBytesAreNotModified(t *testing.T) { + const blob = "abcdefghijklmnop" + const contextRaceIterations = 20 + const blobSize = defaultBufSize * 3 / 4 // Second row overwrites first row. + const insertRows = 4 + + var sqlBlobs = [2]string{ + strings.Repeat(blob, blobSize/len(blob)), + strings.Repeat(strings.ToUpper(blob), blobSize/len(blob)), + } + + runTests(t, dsn, func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (id int, value BLOB) CHARACTER SET utf8") + for i := 0; i < insertRows; i++ { + dbt.mustExec("INSERT INTO test VALUES (?, ?)", i+1, sqlBlobs[i&1]) + } + + for i := 0; i < contextRaceIterations; i++ { + func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + rows, err := dbt.db.QueryContext(ctx, `SELECT id, value FROM test`) + if err != nil { + t.Fatal(err) + } + + var b int + var raw sql.RawBytes + for rows.Next() { + if err := rows.Scan(&b, &raw); err != nil { + t.Fatal(err) + } + + before := string(raw) + // Ensure cancelling the query does not corrupt the contents of `raw` + cancel() + time.Sleep(time.Microsecond * 100) + after := string(raw) + + if before != after { + t.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i) + } + } + rows.Close() + }() + } + }) +} diff --git a/third/github.com/go-sql-driver/mysql/dsn.go b/third/github.com/gf-third/mysql/dsn.go similarity index 97% rename from third/github.com/go-sql-driver/mysql/dsn.go rename to third/github.com/gf-third/mysql/dsn.go index b9134722e..6e19ab717 100644 --- a/third/github.com/go-sql-driver/mysql/dsn.go +++ b/third/github.com/gf-third/mysql/dsn.go @@ -14,6 +14,7 @@ import ( "crypto/tls" "errors" "fmt" + "math/big" "net" "net/url" "sort" @@ -72,6 +73,26 @@ func NewConfig() *Config { } } +func (cfg *Config) Clone() *Config { + cp := *cfg + if cp.tls != nil { + cp.tls = cfg.tls.Clone() + } + if len(cp.Params) > 0 { + cp.Params = make(map[string]string, len(cfg.Params)) + for k, v := range cfg.Params { + cp.Params[k] = v + } + } + if cfg.pubKey != nil { + cp.pubKey = &rsa.PublicKey{ + N: new(big.Int).Set(cfg.pubKey.N), + E: cfg.pubKey.E, + } + } + return &cp +} + func (cfg *Config) normalize() error { if cfg.InterpolateParams && unsafeCollations[cfg.Collation] { return errInvalidDSNUnsafeCollation diff --git a/third/github.com/go-sql-driver/mysql/dsn_test.go b/third/github.com/gf-third/mysql/dsn_test.go similarity index 73% rename from third/github.com/go-sql-driver/mysql/dsn_test.go rename to third/github.com/gf-third/mysql/dsn_test.go index 1cd095496..fe1572dea 100644 --- a/third/github.com/go-sql-driver/mysql/dsn_test.go +++ b/third/github.com/gf-third/mysql/dsn_test.go @@ -22,55 +22,55 @@ var testDSNs = []struct { out *Config }{{ "username:password@protocol(address)/dbname?param=value", - &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", - &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true}, + &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true}, }, { "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true", - &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true}, + &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true}, }, { "user@unix(/path/to/socket)/dbname?charset=utf8", - &Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", - &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"}, + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"}, }, { "user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", - &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"}, + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"}, }, { "user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216", &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216}, }, { "user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0", - &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false}, + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false}, }, { "user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", - &Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "/dbname", - &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "@/", - &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "/", - &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "", - &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "user:p@/ssword@/", - &Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "unix/?arg=%2Fsome%2Fpath.ext", - &Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "tcp(127.0.0.1)/dbname", - &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, { "tcp(de:ad:be:ef::ca:fe)/dbname", - &Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, + &Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true}, }, } @@ -318,6 +318,46 @@ func TestParamsAreSorted(t *testing.T) { } } +func TestCloneConfig(t *testing.T) { + RegisterServerPubKey("testKey", testPubKeyRSA) + defer DeregisterServerPubKey("testKey") + + expectedServerName := "example.com" + dsn := "tcp(example.com:1234)/?tls=true&foobar=baz&serverPubKey=testKey" + cfg, err := ParseDSN(dsn) + if err != nil { + t.Fatal(err.Error()) + } + + cfg2 := cfg.Clone() + if cfg == cfg2 { + t.Errorf("Config.Clone did not create a separate config struct") + } + + if cfg2.tls.ServerName != expectedServerName { + t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName) + } + + cfg2.tls.ServerName = "example2.com" + if cfg.tls.ServerName == cfg2.tls.ServerName { + t.Errorf("changed cfg.tls.Server name should not propagate to original Config") + } + + if _, ok := cfg2.Params["foobar"]; !ok { + t.Errorf("cloned Config is missing custom params") + } + + delete(cfg2.Params, "foobar") + + if _, ok := cfg.Params["foobar"]; !ok { + t.Errorf("custom params in cloned Config should not propagate to original Config") + } + + if !reflect.DeepEqual(cfg.pubKey, cfg2.pubKey) { + t.Errorf("public key in Config should be identical") + } +} + func BenchmarkParseDSN(b *testing.B) { b.ReportAllocs() diff --git a/third/github.com/go-sql-driver/mysql/errors.go b/third/github.com/gf-third/mysql/errors.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/errors.go rename to third/github.com/gf-third/mysql/errors.go diff --git a/third/github.com/go-sql-driver/mysql/errors_test.go b/third/github.com/gf-third/mysql/errors_test.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/errors_test.go rename to third/github.com/gf-third/mysql/errors_test.go diff --git a/third/github.com/go-sql-driver/mysql/fields.go b/third/github.com/gf-third/mysql/fields.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/fields.go rename to third/github.com/gf-third/mysql/fields.go diff --git a/third/github.com/go-sql-driver/mysql/infile.go b/third/github.com/gf-third/mysql/infile.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/infile.go rename to third/github.com/gf-third/mysql/infile.go diff --git a/third/github.com/go-sql-driver/mysql/packets.go b/third/github.com/gf-third/mysql/packets.go similarity index 96% rename from third/github.com/go-sql-driver/mysql/packets.go rename to third/github.com/gf-third/mysql/packets.go index 5e0853767..cbed325f4 100644 --- a/third/github.com/go-sql-driver/mysql/packets.go +++ b/third/github.com/gf-third/mysql/packets.go @@ -96,6 +96,25 @@ func (mc *mysqlConn) writePacket(data []byte) error { return ErrPktTooLarge } + // Perform a stale connection check. We only perform this check for + // the first query on a connection that has been checked out of the + // connection pool: a fresh connection from the pool is more likely + // to be stale, and it has not performed any previous writes that + // could cause data corruption, so it's safe to return ErrBadConn + // if the check fails. + if mc.reset { + mc.reset = false + conn := mc.netConn + if mc.rawConn != nil { + conn = mc.rawConn + } + if err := connCheck(conn); err != nil { + errLog.Print("closing bad idle connection: ", err) + mc.Close() + return driver.ErrBadConn + } + } + for { var size int if pktLen >= maxPacketSize { @@ -332,6 +351,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string if err := tlsConn.Handshake(); err != nil { return err } + mc.rawConn = mc.netConn mc.netConn = tlsConn mc.buf.nc = tlsConn } @@ -991,6 +1011,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { ) } + case uint64: + paramTypes[i+i] = byte(fieldTypeLongLong) + paramTypes[i+i+1] = 0x80 // type is unsigned + + if cap(paramValues)-len(paramValues)-8 >= 0 { + paramValues = paramValues[:len(paramValues)+8] + binary.LittleEndian.PutUint64( + paramValues[len(paramValues)-8:], + uint64(v), + ) + } else { + paramValues = append(paramValues, + uint64ToBytes(uint64(v))..., + ) + } + case float64: paramTypes[i+i] = byte(fieldTypeDouble) paramTypes[i+i+1] = 0x00 diff --git a/third/github.com/go-sql-driver/mysql/packets_test.go b/third/github.com/gf-third/mysql/packets_test.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/packets_test.go rename to third/github.com/gf-third/mysql/packets_test.go diff --git a/third/github.com/go-sql-driver/mysql/result.go b/third/github.com/gf-third/mysql/result.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/result.go rename to third/github.com/gf-third/mysql/result.go diff --git a/third/github.com/go-sql-driver/mysql/rows.go b/third/github.com/gf-third/mysql/rows.go similarity index 92% rename from third/github.com/go-sql-driver/mysql/rows.go rename to third/github.com/gf-third/mysql/rows.go index d3b1e2822..888bdb5f0 100644 --- a/third/github.com/go-sql-driver/mysql/rows.go +++ b/third/github.com/gf-third/mysql/rows.go @@ -111,6 +111,13 @@ func (rows *mysqlRows) Close() (err error) { return err } + // flip the buffer for this connection if we need to drain it. + // note that for a successful query (i.e. one where rows.next() + // has been called until it returns false), `rows.mc` will be nil + // by the time the user calls `(*Rows).Close`, so we won't reach this + // see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47 + mc.buf.flip() + // Remove unread packets from stream if !rows.rs.done { err = mc.readUntilEOF() diff --git a/third/github.com/go-sql-driver/mysql/statement.go b/third/github.com/gf-third/mysql/statement.go similarity index 96% rename from third/github.com/go-sql-driver/mysql/statement.go rename to third/github.com/gf-third/mysql/statement.go index ce7fe4cd0..f7e370939 100644 --- a/third/github.com/go-sql-driver/mysql/statement.go +++ b/third/github.com/gf-third/mysql/statement.go @@ -13,7 +13,6 @@ import ( "fmt" "io" "reflect" - "strconv" ) type mysqlStmt struct { @@ -164,14 +163,8 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) { } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return rv.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: - return int64(rv.Uint()), nil - case reflect.Uint64: - u64 := rv.Uint() - if u64 >= 1<<63 { - return strconv.FormatUint(u64, 10), nil - } - return int64(u64), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint(), nil case reflect.Float32, reflect.Float64: return rv.Float(), nil case reflect.Bool: diff --git a/third/github.com/go-sql-driver/mysql/statement_test.go b/third/github.com/gf-third/mysql/statement_test.go similarity index 95% rename from third/github.com/go-sql-driver/mysql/statement_test.go rename to third/github.com/gf-third/mysql/statement_test.go index 98a6c1933..4b9914f8e 100644 --- a/third/github.com/go-sql-driver/mysql/statement_test.go +++ b/third/github.com/gf-third/mysql/statement_test.go @@ -110,7 +110,7 @@ func TestConvertUnsignedIntegers(t *testing.T) { t.Fatalf("%T type not convertible %s", value, err) } - if output != int64(42) { + if output != uint64(42) { t.Fatalf("%T type not converted, got %#v %T", value, output, output) } } @@ -120,7 +120,7 @@ func TestConvertUnsignedIntegers(t *testing.T) { t.Fatal("uint64 high-bit not convertible", err) } - if output != "18446744073709551615" { - t.Fatalf("uint64 high-bit not converted, got %#v %T", output, output) + if output != ^uint64(0) { + t.Fatalf("uint64 high-bit converted, got %#v %T", output, output) } } diff --git a/third/github.com/go-sql-driver/mysql/transaction.go b/third/github.com/gf-third/mysql/transaction.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/transaction.go rename to third/github.com/gf-third/mysql/transaction.go diff --git a/third/github.com/go-sql-driver/mysql/utils.go b/third/github.com/gf-third/mysql/utils.go similarity index 99% rename from third/github.com/go-sql-driver/mysql/utils.go rename to third/github.com/gf-third/mysql/utils.go index cb3650bb9..201691fe8 100644 --- a/third/github.com/go-sql-driver/mysql/utils.go +++ b/third/github.com/gf-third/mysql/utils.go @@ -684,7 +684,7 @@ type atomicBool struct { value uint32 } -// IsSet returns wether the current boolean value is true +// IsSet returns whether the current boolean value is true func (ab *atomicBool) IsSet() bool { return atomic.LoadUint32(&ab.value) > 0 } @@ -698,7 +698,7 @@ func (ab *atomicBool) Set(value bool) { } } -// TrySet sets the value of the bool and returns wether the value changed +// TrySet sets the value of the bool and returns whether the value changed func (ab *atomicBool) TrySet(value bool) bool { if value { return atomic.SwapUint32(&ab.value, 1) == 0 diff --git a/third/github.com/go-sql-driver/mysql/utils_test.go b/third/github.com/gf-third/mysql/utils_test.go similarity index 100% rename from third/github.com/go-sql-driver/mysql/utils_test.go rename to third/github.com/gf-third/mysql/utils_test.go diff --git a/third/github.com/go-sql-driver/mysql/.gitignore b/third/github.com/go-sql-driver/mysql/.gitignore deleted file mode 100644 index 2de28da16..000000000 --- a/third/github.com/go-sql-driver/mysql/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -Icon? -ehthumbs.db -Thumbs.db -.idea diff --git a/third/github.com/go-sql-driver/mysql/.travis.yml b/third/github.com/go-sql-driver/mysql/.travis.yml deleted file mode 100644 index 75505f144..000000000 --- a/third/github.com/go-sql-driver/mysql/.travis.yml +++ /dev/null @@ -1,107 +0,0 @@ -sudo: false -language: go -go: - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x - - master - -before_install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls - -before_script: - - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf - - sudo service mysql restart - - .travis/wait_mysql.sh - - mysql -e 'create database gotest;' - -matrix: - include: - - env: DB=MYSQL8 - sudo: required - dist: trusty - go: 1.10.x - services: - - docker - before_install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls - - docker pull mysql:8.0 - - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1 - - cp .travis/docker.cnf ~/.my.cnf - - .travis/wait_mysql.sh - before_script: - - export MYSQL_TEST_USER=gotest - - export MYSQL_TEST_PASS=secret - - export MYSQL_TEST_ADDR=127.0.0.1:3307 - - export MYSQL_TEST_CONCURRENT=1 - - - env: DB=MYSQL57 - sudo: required - dist: trusty - go: 1.10.x - services: - - docker - before_install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls - - docker pull mysql:5.7 - - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1 - - cp .travis/docker.cnf ~/.my.cnf - - .travis/wait_mysql.sh - before_script: - - export MYSQL_TEST_USER=gotest - - export MYSQL_TEST_PASS=secret - - export MYSQL_TEST_ADDR=127.0.0.1:3307 - - export MYSQL_TEST_CONCURRENT=1 - - - env: DB=MARIA55 - sudo: required - dist: trusty - go: 1.10.x - services: - - docker - before_install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls - - docker pull mariadb:5.5 - - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1 - - cp .travis/docker.cnf ~/.my.cnf - - .travis/wait_mysql.sh - before_script: - - export MYSQL_TEST_USER=gotest - - export MYSQL_TEST_PASS=secret - - export MYSQL_TEST_ADDR=127.0.0.1:3307 - - export MYSQL_TEST_CONCURRENT=1 - - - env: DB=MARIA10_1 - sudo: required - dist: trusty - go: 1.10.x - services: - - docker - before_install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls - - docker pull mariadb:10.1 - - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret - mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1 - - cp .travis/docker.cnf ~/.my.cnf - - .travis/wait_mysql.sh - before_script: - - export MYSQL_TEST_USER=gotest - - export MYSQL_TEST_PASS=secret - - export MYSQL_TEST_ADDR=127.0.0.1:3307 - - export MYSQL_TEST_CONCURRENT=1 - -script: - - go test -v -covermode=count -coverprofile=coverage.out - - go vet ./... - - .travis/gofmt.sh -after_script: - - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci diff --git a/third/github.com/go-sql-driver/mysql/collations.go b/third/github.com/go-sql-driver/mysql/collations.go deleted file mode 100644 index 136c9e4d1..000000000 --- a/third/github.com/go-sql-driver/mysql/collations.go +++ /dev/null @@ -1,251 +0,0 @@ -// Go MySQL Driver - A MySQL-Driver for Go's database/sql package -// -// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at http://mozilla.org/MPL/2.0/. - -package mysql - -const defaultCollation = "utf8_general_ci" -const binaryCollation = "binary" - -// A list of available collations mapped to the internal ID. -// To update this map use the following MySQL query: -// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS -var collations = map[string]byte{ - "big5_chinese_ci": 1, - "latin2_czech_cs": 2, - "dec8_swedish_ci": 3, - "cp850_general_ci": 4, - "latin1_german1_ci": 5, - "hp8_english_ci": 6, - "koi8r_general_ci": 7, - "latin1_swedish_ci": 8, - "latin2_general_ci": 9, - "swe7_swedish_ci": 10, - "ascii_general_ci": 11, - "ujis_japanese_ci": 12, - "sjis_japanese_ci": 13, - "cp1251_bulgarian_ci": 14, - "latin1_danish_ci": 15, - "hebrew_general_ci": 16, - "tis620_thai_ci": 18, - "euckr_korean_ci": 19, - "latin7_estonian_cs": 20, - "latin2_hungarian_ci": 21, - "koi8u_general_ci": 22, - "cp1251_ukrainian_ci": 23, - "gb2312_chinese_ci": 24, - "greek_general_ci": 25, - "cp1250_general_ci": 26, - "latin2_croatian_ci": 27, - "gbk_chinese_ci": 28, - "cp1257_lithuanian_ci": 29, - "latin5_turkish_ci": 30, - "latin1_german2_ci": 31, - "armscii8_general_ci": 32, - "utf8_general_ci": 33, - "cp1250_czech_cs": 34, - "ucs2_general_ci": 35, - "cp866_general_ci": 36, - "keybcs2_general_ci": 37, - "macce_general_ci": 38, - "macroman_general_ci": 39, - "cp852_general_ci": 40, - "latin7_general_ci": 41, - "latin7_general_cs": 42, - "macce_bin": 43, - "cp1250_croatian_ci": 44, - "utf8mb4_general_ci": 45, - "utf8mb4_bin": 46, - "latin1_bin": 47, - "latin1_general_ci": 48, - "latin1_general_cs": 49, - "cp1251_bin": 50, - "cp1251_general_ci": 51, - "cp1251_general_cs": 52, - "macroman_bin": 53, - "utf16_general_ci": 54, - "utf16_bin": 55, - "utf16le_general_ci": 56, - "cp1256_general_ci": 57, - "cp1257_bin": 58, - "cp1257_general_ci": 59, - "utf32_general_ci": 60, - "utf32_bin": 61, - "utf16le_bin": 62, - "binary": 63, - "armscii8_bin": 64, - "ascii_bin": 65, - "cp1250_bin": 66, - "cp1256_bin": 67, - "cp866_bin": 68, - "dec8_bin": 69, - "greek_bin": 70, - "hebrew_bin": 71, - "hp8_bin": 72, - "keybcs2_bin": 73, - "koi8r_bin": 74, - "koi8u_bin": 75, - "latin2_bin": 77, - "latin5_bin": 78, - "latin7_bin": 79, - "cp850_bin": 80, - "cp852_bin": 81, - "swe7_bin": 82, - "utf8_bin": 83, - "big5_bin": 84, - "euckr_bin": 85, - "gb2312_bin": 86, - "gbk_bin": 87, - "sjis_bin": 88, - "tis620_bin": 89, - "ucs2_bin": 90, - "ujis_bin": 91, - "geostd8_general_ci": 92, - "geostd8_bin": 93, - "latin1_spanish_ci": 94, - "cp932_japanese_ci": 95, - "cp932_bin": 96, - "eucjpms_japanese_ci": 97, - "eucjpms_bin": 98, - "cp1250_polish_ci": 99, - "utf16_unicode_ci": 101, - "utf16_icelandic_ci": 102, - "utf16_latvian_ci": 103, - "utf16_romanian_ci": 104, - "utf16_slovenian_ci": 105, - "utf16_polish_ci": 106, - "utf16_estonian_ci": 107, - "utf16_spanish_ci": 108, - "utf16_swedish_ci": 109, - "utf16_turkish_ci": 110, - "utf16_czech_ci": 111, - "utf16_danish_ci": 112, - "utf16_lithuanian_ci": 113, - "utf16_slovak_ci": 114, - "utf16_spanish2_ci": 115, - "utf16_roman_ci": 116, - "utf16_persian_ci": 117, - "utf16_esperanto_ci": 118, - "utf16_hungarian_ci": 119, - "utf16_sinhala_ci": 120, - "utf16_german2_ci": 121, - "utf16_croatian_ci": 122, - "utf16_unicode_520_ci": 123, - "utf16_vietnamese_ci": 124, - "ucs2_unicode_ci": 128, - "ucs2_icelandic_ci": 129, - "ucs2_latvian_ci": 130, - "ucs2_romanian_ci": 131, - "ucs2_slovenian_ci": 132, - "ucs2_polish_ci": 133, - "ucs2_estonian_ci": 134, - "ucs2_spanish_ci": 135, - "ucs2_swedish_ci": 136, - "ucs2_turkish_ci": 137, - "ucs2_czech_ci": 138, - "ucs2_danish_ci": 139, - "ucs2_lithuanian_ci": 140, - "ucs2_slovak_ci": 141, - "ucs2_spanish2_ci": 142, - "ucs2_roman_ci": 143, - "ucs2_persian_ci": 144, - "ucs2_esperanto_ci": 145, - "ucs2_hungarian_ci": 146, - "ucs2_sinhala_ci": 147, - "ucs2_german2_ci": 148, - "ucs2_croatian_ci": 149, - "ucs2_unicode_520_ci": 150, - "ucs2_vietnamese_ci": 151, - "ucs2_general_mysql500_ci": 159, - "utf32_unicode_ci": 160, - "utf32_icelandic_ci": 161, - "utf32_latvian_ci": 162, - "utf32_romanian_ci": 163, - "utf32_slovenian_ci": 164, - "utf32_polish_ci": 165, - "utf32_estonian_ci": 166, - "utf32_spanish_ci": 167, - "utf32_swedish_ci": 168, - "utf32_turkish_ci": 169, - "utf32_czech_ci": 170, - "utf32_danish_ci": 171, - "utf32_lithuanian_ci": 172, - "utf32_slovak_ci": 173, - "utf32_spanish2_ci": 174, - "utf32_roman_ci": 175, - "utf32_persian_ci": 176, - "utf32_esperanto_ci": 177, - "utf32_hungarian_ci": 178, - "utf32_sinhala_ci": 179, - "utf32_german2_ci": 180, - "utf32_croatian_ci": 181, - "utf32_unicode_520_ci": 182, - "utf32_vietnamese_ci": 183, - "utf8_unicode_ci": 192, - "utf8_icelandic_ci": 193, - "utf8_latvian_ci": 194, - "utf8_romanian_ci": 195, - "utf8_slovenian_ci": 196, - "utf8_polish_ci": 197, - "utf8_estonian_ci": 198, - "utf8_spanish_ci": 199, - "utf8_swedish_ci": 200, - "utf8_turkish_ci": 201, - "utf8_czech_ci": 202, - "utf8_danish_ci": 203, - "utf8_lithuanian_ci": 204, - "utf8_slovak_ci": 205, - "utf8_spanish2_ci": 206, - "utf8_roman_ci": 207, - "utf8_persian_ci": 208, - "utf8_esperanto_ci": 209, - "utf8_hungarian_ci": 210, - "utf8_sinhala_ci": 211, - "utf8_german2_ci": 212, - "utf8_croatian_ci": 213, - "utf8_unicode_520_ci": 214, - "utf8_vietnamese_ci": 215, - "utf8_general_mysql500_ci": 223, - "utf8mb4_unicode_ci": 224, - "utf8mb4_icelandic_ci": 225, - "utf8mb4_latvian_ci": 226, - "utf8mb4_romanian_ci": 227, - "utf8mb4_slovenian_ci": 228, - "utf8mb4_polish_ci": 229, - "utf8mb4_estonian_ci": 230, - "utf8mb4_spanish_ci": 231, - "utf8mb4_swedish_ci": 232, - "utf8mb4_turkish_ci": 233, - "utf8mb4_czech_ci": 234, - "utf8mb4_danish_ci": 235, - "utf8mb4_lithuanian_ci": 236, - "utf8mb4_slovak_ci": 237, - "utf8mb4_spanish2_ci": 238, - "utf8mb4_roman_ci": 239, - "utf8mb4_persian_ci": 240, - "utf8mb4_esperanto_ci": 241, - "utf8mb4_hungarian_ci": 242, - "utf8mb4_sinhala_ci": 243, - "utf8mb4_german2_ci": 244, - "utf8mb4_croatian_ci": 245, - "utf8mb4_unicode_520_ci": 246, - "utf8mb4_vietnamese_ci": 247, -} - -// A blacklist of collations which is unsafe to interpolate parameters. -// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes. -var unsafeCollations = map[string]bool{ - "big5_chinese_ci": true, - "sjis_japanese_ci": true, - "gbk_chinese_ci": true, - "big5_bin": true, - "gb2312_bin": true, - "gbk_bin": true, - "sjis_bin": true, - "cp932_japanese_ci": true, - "cp932_bin": true, -} diff --git a/version.go b/version.go index f24e75cfd..daa93f84d 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.6.6" +const VERSION = "v1.6.9" const AUTHORS = "john"