mirror of
https://gitee.com/johng/gf
synced 2026-06-10 03:23:59 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e6c2e790d | |||
| 34c761e9db | |||
| 87e3813636 | |||
| 361ff0315c | |||
| 2bb227d058 | |||
| 99dc69e839 | |||
| c9537af062 | |||
| 7ae03729f3 | |||
| ea7e2ec5ec | |||
| 237c58f2b0 | |||
| efa23e4a1d | |||
| c109cee7ef | |||
| e111d39c54 |
@ -76,6 +76,7 @@ func main() {
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
- [qq976739120](https://github.com/qq976739120)
|
||||
- [touzijiao](https://github.com/touzijiao)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
|
||||
@ -94,6 +94,7 @@ func main() {
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
- [qq976739120](https://github.com/qq976739120)
|
||||
- [touzijiao](https://github.com/touzijiao)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
|
||||
265
g/container/gvar/gvar_test.go
Normal file
265
g/container/gvar/gvar_test.go
Normal file
@ -0,0 +1,265 @@
|
||||
package gvar_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
)
|
||||
|
||||
func TestReadOnly(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
obj := gvar.New(nil, true)
|
||||
var result string
|
||||
|
||||
switch obj.ReadOnly().(type) {
|
||||
case gvar.VarRead:
|
||||
result = "yes"
|
||||
default:
|
||||
result = "no"
|
||||
}
|
||||
|
||||
gtest.Assert(result, "yes")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New("old", true)
|
||||
objOneOld, _ := objOne.Set("new").(string)
|
||||
gtest.Assert(objOneOld, "old")
|
||||
|
||||
objTwo := gvar.New("old", false)
|
||||
objTwoOld, _ := objTwo.Set("new").(string)
|
||||
gtest.Assert(objTwoOld, "old")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVal(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New(1, true)
|
||||
objOneOld, _ := objOne.Val().(int)
|
||||
gtest.Assert(objOneOld, 1)
|
||||
|
||||
objTwo := gvar.New(1, false)
|
||||
objTwoOld, _ := objTwo.Val().(int)
|
||||
gtest.Assert(objTwoOld, 1)
|
||||
})
|
||||
}
|
||||
func TestInterface(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New(1, true)
|
||||
objOneOld, _ := objOne.Interface().(int)
|
||||
gtest.Assert(objOneOld, 1)
|
||||
|
||||
objTwo := gvar.New(1, false)
|
||||
objTwoOld, _ := objTwo.Interface().(int)
|
||||
gtest.Assert(objTwoOld, 1)
|
||||
})
|
||||
}
|
||||
func TestIsNil(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New(nil, true)
|
||||
gtest.Assert(objOne.IsNil(), true)
|
||||
|
||||
objTwo := gvar.New("noNil", false)
|
||||
gtest.Assert(objTwo.IsNil(), false)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
x := int32(1)
|
||||
bytesBuffer := bytes.NewBuffer([]byte{})
|
||||
binary.Write(bytesBuffer, binary.BigEndian, x)
|
||||
|
||||
objOne := gvar.New(bytesBuffer.Bytes(), true)
|
||||
|
||||
bBuf := bytes.NewBuffer(objOne.Bytes())
|
||||
var y int32
|
||||
binary.Read(bBuf, binary.BigEndian, &y)
|
||||
|
||||
gtest.Assert(x, y)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var str string = "hello"
|
||||
objOne := gvar.New(str, true)
|
||||
gtest.Assert(objOne.String(), str)
|
||||
|
||||
})
|
||||
}
|
||||
func TestBool(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var ok bool = true
|
||||
objOne := gvar.New(ok, true)
|
||||
gtest.Assert(objOne.Bool(), ok)
|
||||
|
||||
ok = false
|
||||
objTwo := gvar.New(ok, true)
|
||||
gtest.Assert(objTwo.Bool(), ok)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt8(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int8 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int8(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt16(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int16 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int16(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt32(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int32 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int32(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int64 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int64(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint8(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint8 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint8(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint16(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint16 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint16(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint32(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint32 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint32(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint64 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint64(), num)
|
||||
|
||||
})
|
||||
}
|
||||
func TestFloat32(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num float32 = 1.1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Float32(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat64(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num float64 = 1.1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Float64(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInts(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var arr = []int{1, 2, 3, 4, 5}
|
||||
objOne := gvar.New(arr, true)
|
||||
gtest.Assert(objOne.Ints()[0], arr[0])
|
||||
})
|
||||
}
|
||||
func TestFloats(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var arr = []float64{1, 2, 3, 4, 5}
|
||||
objOne := gvar.New(arr, true)
|
||||
gtest.Assert(objOne.Floats()[0], arr[0])
|
||||
})
|
||||
}
|
||||
func TestStrings(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var arr = []string{"hello", "world"}
|
||||
objOne := gvar.New(arr, true)
|
||||
gtest.Assert(objOne.Strings()[0], arr[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var timeUnix int64 = 1556242660
|
||||
objOne := gvar.New(timeUnix, true)
|
||||
gtest.Assert(objOne.Time().Unix(), timeUnix)
|
||||
})
|
||||
}
|
||||
|
||||
type StTest struct {
|
||||
Test int
|
||||
}
|
||||
|
||||
func TestStruct(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
Kv := make(map[string]int, 1)
|
||||
Kv["Test"] = 100
|
||||
|
||||
testObj := &StTest{}
|
||||
|
||||
objOne := gvar.New(Kv, true)
|
||||
|
||||
objOne.Struct(testObj)
|
||||
|
||||
gtest.Assert(testObj.Test, Kv["Test"])
|
||||
})
|
||||
}
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
115
g/net/gtcp/gtcp_conn_pkg.go
Normal file
115
g/net/gtcp/gtcp_conn_pkg.go
Normal file
@ -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...)
|
||||
}
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
// (面向短链接)带超时时间的数据发送
|
||||
|
||||
49
g/net/gtcp/gtcp_func_pkg.go
Normal file
49
g/net/gtcp/gtcp_func_pkg.go
Normal file
@ -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...)
|
||||
}
|
||||
72
g/net/gtcp/gtcp_pool_pkg.go
Normal file
72
g/net/gtcp/gtcp_pool_pkg.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
115
g/net/gudp/gudp_conn_pkg.go
Normal file
115
g/net/gudp/gudp_conn_pkg.go
Normal file
@ -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...)
|
||||
}
|
||||
@ -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
|
||||
|
||||
49
g/net/gudp/gudp_func_pkg.go
Normal file
49
g/net/gudp/gudp_func_pkg.go
Normal file
@ -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...)
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
|
||||
38
geg/net/gtcp/pkg_operations/common/funcs/funcs.go
Normal file
38
geg/net/gtcp/pkg_operations/common/funcs/funcs.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
60
geg/net/gtcp/pkg_operations/common/gtcp_common_client.go
Normal file
60
geg/net/gtcp/pkg_operations/common/gtcp_common_client.go
Normal file
@ -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()
|
||||
}
|
||||
45
geg/net/gtcp/pkg_operations/common/gtcp_common_server.go
Normal file
45
geg/net/gtcp/pkg_operations/common/gtcp_common_server.go
Normal file
@ -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())
|
||||
}
|
||||
6
geg/net/gtcp/pkg_operations/common/types/types.go
Normal file
6
geg/net/gtcp/pkg_operations/common/types/types.go
Normal file
@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
type Msg struct {
|
||||
Act string // 操作
|
||||
Data string // 数据
|
||||
}
|
||||
39
geg/net/gtcp/pkg_operations/gtcp_server_client1.go
Normal file
39
geg/net/gtcp/pkg_operations/gtcp_server_client1.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
38
geg/net/gtcp/pkg_operations/gtcp_server_client2.go
Normal file
38
geg/net/gtcp/pkg_operations/gtcp_server_client2.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
42
geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_client.go
Normal file
42
geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_client.go
Normal file
@ -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))
|
||||
}
|
||||
}
|
||||
31
geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go
Normal file
31
geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go
Normal file
@ -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()
|
||||
}
|
||||
12
geg/net/gtcp/pkg_operations/monitor/types/types.go
Normal file
12
geg/net/gtcp/pkg_operations/monitor/types/types.go
Normal file
@ -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 // 上报时间(时间戳)
|
||||
}
|
||||
@ -5,7 +5,5 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
array := make([]interface{}, 0, 10)
|
||||
array[8] = 1
|
||||
fmt.Println(array)
|
||||
fmt.Println("10" > "4")
|
||||
}
|
||||
@ -27,6 +27,7 @@ Daniël van Eeden <git at myname.nl>
|
||||
Dave Protasowski <dprotaso at gmail.com>
|
||||
DisposaBoy <disposaboy at dby.me>
|
||||
Egor Smolyakov <egorsmkv at gmail.com>
|
||||
Erwan Martin <hello at erwan.io>
|
||||
Evan Shaw <evan at vendhq.com>
|
||||
Frederick Mayle <frederickmayle at gmail.com>
|
||||
Gustavo Kristic <gkristic at gmail.com>
|
||||
@ -34,6 +35,7 @@ Hajime Nakagami <nakagami at gmail.com>
|
||||
Hanno Braun <mail at hannobraun.com>
|
||||
Henri Yandell <flamefew at gmail.com>
|
||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||
Huyiguang <hyg at webterren.com>
|
||||
ICHINOSE Shogo <shogo82148 at gmail.com>
|
||||
Ilia Cimpoes <ichimpoesh at gmail.com>
|
||||
INADA Naoki <songofacandy at gmail.com>
|
||||
@ -41,6 +43,7 @@ Jacek Szwec <szwec.jacek at gmail.com>
|
||||
James Harr <james.harr at gmail.com>
|
||||
Jeff Hodges <jeff at somethingsimilar.com>
|
||||
Jeffrey Charles <jeffreycharles at gmail.com>
|
||||
Jerome Meyer <jxmeyer at gmail.com>
|
||||
Jian Zhen <zhenjl at gmail.com>
|
||||
Joshua Prunier <joshua.prunier at gmail.com>
|
||||
Julien Lefevre <julien.lefevr at gmail.com>
|
||||
@ -70,11 +73,13 @@ Richard Wilkes <wilkes at me.com>
|
||||
Robert Russell <robert at rrbrussell.com>
|
||||
Runrioter Wung <runrioter at gmail.com>
|
||||
Shuode Li <elemount at qq.com>
|
||||
Simon J Mudd <sjmudd at pobox.com>
|
||||
Soroush Pour <me at soroushjp.com>
|
||||
Stan Putrya <root.vagner at gmail.com>
|
||||
Stanley Gunawan <gunawan.stanley at gmail.com>
|
||||
Steven Hartland <steven.hartland at multiplay.co.uk>
|
||||
Thomas Wodarek <wodarekwebpage at gmail.com>
|
||||
Tim Ruffles <timruffles at gmail.com>
|
||||
Tom Jenkinson <tom at tjenkinson.me>
|
||||
Xiangyu Hu <xiangyu.hu at outlook.com>
|
||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||
@ -85,6 +90,7 @@ Zhenye Xie <xiezhenye at gmail.com>
|
||||
|
||||
Barracuda Networks, Inc.
|
||||
Counting Ltd.
|
||||
GitHub Inc.
|
||||
Google Inc.
|
||||
InfoSum Ltd.
|
||||
Keybase Inc.
|
||||
@ -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: <name>
|
||||
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, <name>
|
||||
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).
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
265
third/github.com/gf-third/mysql/collations.go
Normal file
265
third/github.com/gf-third/mysql/collations.go
Normal file
@ -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,
|
||||
}
|
||||
53
third/github.com/gf-third/mysql/conncheck.go
Normal file
53
third/github.com/gf-third/mysql/conncheck.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
17
third/github.com/gf-third/mysql/conncheck_dummy.go
Normal file
17
third/github.com/gf-third/mysql/conncheck_dummy.go
Normal file
@ -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
|
||||
}
|
||||
38
third/github.com/gf-third/mysql/conncheck_test.go
Normal file
38
third/github.com/gf-third/mysql/conncheck_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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{}
|
||||
}
|
||||
85
third/github.com/gf-third/mysql/driver.go
Normal file
85
third/github.com/gf-third/mysql/driver.go
Normal file
@ -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{})
|
||||
}
|
||||
37
third/github.com/gf-third/mysql/driver_go110.go
Normal file
37
third/github.com/gf-third/mysql/driver_go110.go
Normal file
@ -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
|
||||
}
|
||||
137
third/github.com/gf-third/mysql/driver_go110_test.go
Normal file
137
third/github.com/gf-third/mysql/driver_go110_test.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
@ -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()
|
||||
@ -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:
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -1,9 +0,0 @@
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.idea
|
||||
@ -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
|
||||
@ -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,
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.6.5"
|
||||
const VERSION = "v1.6.8"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
Reference in New Issue
Block a user