123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839 |
- // Package native is a thread unsafe engine for MyMySQL.
- package native
- import (
- "bufio"
- "fmt"
- "io"
- "net"
- "reflect"
- "strings"
- "time"
- "github.com/ziutek/mymysql/mysql"
- )
- type serverInfo struct {
- prot_ver byte
- serv_ver []byte
- thr_id uint32
- scramble [20]byte
- caps uint16
- lang byte
- }
- // MySQL connection handler
- type Conn struct {
- proto string // Network protocol
- laddr string // Local address
- raddr string // Remote (server) address
- user string // MySQL username
- passwd string // MySQL password
- dbname string // Database name
- net_conn net.Conn // MySQL connection
- rd *bufio.Reader
- wr *bufio.Writer
- info serverInfo // MySQL server information
- seq byte // MySQL sequence number
- unreaded_reply bool
- init_cmds []string // MySQL commands/queries executed after connect
- stmt_map map[uint32]*Stmt // For reprepare during reconnect
- // Current status of MySQL server connection
- status mysql.ConnStatus
- // Maximum packet size that client can accept from server.
- // Default 16*1024*1024-1. You may change it before connect.
- max_pkt_size int
- // Timeout for connect
- timeout time.Duration
- dialer mysql.Dialer
- // Return only types accepted by godrv
- narrowTypeSet bool
- // Store full information about fields in result
- fullFieldInfo bool
- // Debug logging. You may change it at any time.
- Debug bool
- }
- // Create new MySQL handler. The first three arguments are passed to net.Bind
- // for create connection. user and passwd are for authentication. Optional db
- // is database name (you may not specify it and use Use() method later).
- func New(proto, laddr, raddr, user, passwd string, db ...string) mysql.Conn {
- my := Conn{
- proto: proto,
- laddr: laddr,
- raddr: raddr,
- user: user,
- passwd: passwd,
- stmt_map: make(map[uint32]*Stmt),
- max_pkt_size: 16*1024*1024 - 1,
- timeout: 2 * time.Minute,
- fullFieldInfo: true,
- }
- if len(db) == 1 {
- my.dbname = db[0]
- } else if len(db) > 1 {
- panic("mymy.New: too many arguments")
- }
- return &my
- }
- func (my *Conn) Credentials() (user, passwd string) {
- return my.user, my.passwd
- }
- func (my *Conn) NarrowTypeSet(narrow bool) {
- my.narrowTypeSet = narrow
- }
- func (my *Conn) FullFieldInfo(full bool) {
- my.fullFieldInfo = full
- }
- // Creates new (not connected) connection using configuration from current
- // connection.
- func (my *Conn) Clone() mysql.Conn {
- var c *Conn
- if my.dbname == "" {
- c = New(my.proto, my.laddr, my.raddr, my.user, my.passwd).(*Conn)
- } else {
- c = New(my.proto, my.laddr, my.raddr, my.user, my.passwd, my.dbname).(*Conn)
- }
- c.max_pkt_size = my.max_pkt_size
- c.timeout = my.timeout
- c.Debug = my.Debug
- return c
- }
- // If new_size > 0 sets maximum packet size. Returns old size.
- func (my *Conn) SetMaxPktSize(new_size int) int {
- old_size := my.max_pkt_size
- if new_size > 0 {
- my.max_pkt_size = new_size
- }
- return old_size
- }
- // SetTimeout sets timeout for Connect and Reconnect
- func (my *Conn) SetTimeout(timeout time.Duration) {
- my.timeout = timeout
- }
- // NetConn return internall net.Conn
- func (my *Conn) NetConn() net.Conn {
- return my.net_conn
- }
- type timeoutError struct{}
- func (e *timeoutError) Error() string { return "i/o timeout" }
- func (e *timeoutError) Timeout() bool { return true }
- func (e *timeoutError) Temporary() bool { return true }
- type stringAddr struct {
- net, addr string
- }
- func (a stringAddr) Network() string { return a.net }
- func (a stringAddr) String() string { return a.addr }
- var DefaultDialer mysql.Dialer = func(proto, laddr, raddr string,
- timeout time.Duration) (net.Conn, error) {
- if proto == "" {
- proto = "unix"
- if strings.IndexRune(raddr, ':') != -1 {
- proto = "tcp"
- }
- }
- // Make a connection
- d := &net.Dialer{Timeout: timeout}
- if laddr != "" {
- var err error
- switch proto {
- case "tcp", "tcp4", "tcp6":
- d.LocalAddr, err = net.ResolveTCPAddr(proto, laddr)
- case "unix":
- d.LocalAddr, err = net.ResolveTCPAddr(proto, laddr)
- default:
- err = net.UnknownNetworkError(proto)
- }
- if err != nil {
- return nil, err
- }
- }
- return d.Dial(proto, raddr)
- }
- func (my *Conn) SetDialer(d mysql.Dialer) {
- my.dialer = d
- }
- func (my *Conn) connect() (err error) {
- defer catchError(&err)
- my.net_conn = nil
- if my.dialer != nil {
- my.net_conn, err = my.dialer(my.proto, my.laddr, my.raddr, my.timeout)
- if err != nil {
- my.net_conn = nil
- return
- }
- }
- if my.net_conn == nil {
- my.net_conn, err = DefaultDialer(my.proto, my.laddr, my.raddr, my.timeout)
- if err != nil {
- my.net_conn = nil
- return
- }
- }
- my.rd = bufio.NewReader(my.net_conn)
- my.wr = bufio.NewWriter(my.net_conn)
- // Initialisation
- my.init()
- my.auth()
- res := my.getResult(nil, nil)
- if res == nil {
- // Try old password
- my.oldPasswd()
- res = my.getResult(nil, nil)
- if res == nil {
- return mysql.ErrAuthentication
- }
- }
- // Execute all registered commands
- for _, cmd := range my.init_cmds {
- // Send command
- my.sendCmdStr(_COM_QUERY, cmd)
- // Get command response
- res := my.getResponse()
- // Read and discard all result rows
- row := res.MakeRow()
- for res != nil {
- // Only read rows if they exist
- if !res.StatusOnly() {
- //read each row in this set
- for {
- err = res.getRow(row)
- if err == io.EOF {
- break
- } else if err != nil {
- return
- }
- }
- }
- // Move to the next result
- if res, err = res.nextResult(); err != nil {
- return
- }
- }
- }
- return
- }
- // Establishes a connection with MySQL server version 4.1 or later.
- func (my *Conn) Connect() (err error) {
- if my.net_conn != nil {
- return mysql.ErrAlredyConn
- }
- return my.connect()
- }
- // Check if connection is established
- func (my *Conn) IsConnected() bool {
- return my.net_conn != nil
- }
- func (my *Conn) closeConn() (err error) {
- defer catchError(&err)
- // Always close and invalidate connection, even if
- // COM_QUIT returns an error
- defer func() {
- err = my.net_conn.Close()
- my.net_conn = nil // Mark that we disconnect
- }()
- // Close the connection
- my.sendCmd(_COM_QUIT)
- return
- }
- // Close connection to the server
- func (my *Conn) Close() (err error) {
- if my.net_conn == nil {
- return mysql.ErrNotConn
- }
- if my.unreaded_reply {
- return mysql.ErrUnreadedReply
- }
- return my.closeConn()
- }
- // Close and reopen connection.
- // Ignore unreaded rows, reprepare all prepared statements.
- func (my *Conn) Reconnect() (err error) {
- if my.net_conn != nil {
- // Close connection, ignore all errors
- my.closeConn()
- }
- // Reopen the connection.
- if err = my.connect(); err != nil {
- return
- }
- // Reprepare all prepared statements
- var (
- new_stmt *Stmt
- new_map = make(map[uint32]*Stmt)
- )
- for _, stmt := range my.stmt_map {
- new_stmt, err = my.prepare(stmt.sql)
- if err != nil {
- return
- }
- // Assume that fields set in new_stmt by prepare() are indentical to
- // corresponding fields in stmt. Why can they be different?
- stmt.id = new_stmt.id
- stmt.rebind = true
- new_map[stmt.id] = stmt
- }
- // Replace the stmt_map
- my.stmt_map = new_map
- return
- }
- // Change database
- func (my *Conn) Use(dbname string) (err error) {
- defer catchError(&err)
- if my.net_conn == nil {
- return mysql.ErrNotConn
- }
- if my.unreaded_reply {
- return mysql.ErrUnreadedReply
- }
- // Send command
- my.sendCmdStr(_COM_INIT_DB, dbname)
- // Get server response
- my.getResult(nil, nil)
- // Save new database name if no errors
- my.dbname = dbname
- return
- }
- func (my *Conn) getResponse() (res *Result) {
- res = my.getResult(nil, nil)
- if res == nil {
- panic(mysql.ErrBadResult)
- }
- my.unreaded_reply = !res.StatusOnly()
- return
- }
- // Start new query.
- //
- // If you specify the parameters, the SQL string will be a result of
- // fmt.Sprintf(sql, params...).
- // You must get all result rows (if they exists) before next query.
- func (my *Conn) Start(sql string, params ...interface{}) (res mysql.Result, err error) {
- defer catchError(&err)
- if my.net_conn == nil {
- return nil, mysql.ErrNotConn
- }
- if my.unreaded_reply {
- return nil, mysql.ErrUnreadedReply
- }
- if len(params) != 0 {
- sql = fmt.Sprintf(sql, params...)
- }
- // Send query
- my.sendCmdStr(_COM_QUERY, sql)
- // Get command response
- res = my.getResponse()
- return
- }
- func (res *Result) getRow(row mysql.Row) (err error) {
- defer catchError(&err)
- if res.my.getResult(res, row) != nil {
- return io.EOF
- }
- return nil
- }
- // Returns true if more results exixts. You don't have to call it before
- // NextResult method (NextResult returns nil if there is no more results).
- func (res *Result) MoreResults() bool {
- return res.status&mysql.SERVER_MORE_RESULTS_EXISTS != 0
- }
- // Get the data row from server. This method reads one row of result set
- // directly from network connection (without rows buffering on client side).
- // Returns io.EOF if there is no more rows in current result set.
- func (res *Result) ScanRow(row mysql.Row) error {
- if row == nil {
- return mysql.ErrRowLength
- }
- if res.eor_returned {
- return mysql.ErrReadAfterEOR
- }
- if res.StatusOnly() {
- // There is no fields in result (OK result)
- res.eor_returned = true
- return io.EOF
- }
- err := res.getRow(row)
- if err == io.EOF {
- res.eor_returned = true
- if !res.MoreResults() {
- res.my.unreaded_reply = false
- }
- }
- return err
- }
- // Like ScanRow but allocates memory for every row.
- // Returns nil row insted of io.EOF error.
- func (res *Result) GetRow() (mysql.Row, error) {
- return mysql.GetRow(res)
- }
- func (res *Result) nextResult() (next *Result, err error) {
- defer catchError(&err)
- if res.MoreResults() {
- next = res.my.getResponse()
- }
- return
- }
- // This function is used when last query was the multi result query or
- // procedure call. Returns the next result or nil if no more resuts exists.
- //
- // Statements within the procedure may produce unknown number of result sets.
- // The final result from the procedure is a status result that includes no
- // result set (Result.StatusOnly() == true) .
- func (res *Result) NextResult() (mysql.Result, error) {
- if !res.MoreResults() {
- return nil, nil
- }
- res, err := res.nextResult()
- return res, err
- }
- // Send MySQL PING to the server.
- func (my *Conn) Ping() (err error) {
- defer catchError(&err)
- if my.net_conn == nil {
- return mysql.ErrNotConn
- }
- if my.unreaded_reply {
- return mysql.ErrUnreadedReply
- }
- // Send command
- my.sendCmd(_COM_PING)
- // Get server response
- my.getResult(nil, nil)
- return
- }
- func (my *Conn) prepare(sql string) (stmt *Stmt, err error) {
- defer catchError(&err)
- // Send command
- my.sendCmdStr(_COM_STMT_PREPARE, sql)
- // Get server response
- stmt, ok := my.getPrepareResult(nil).(*Stmt)
- if !ok {
- return nil, mysql.ErrBadResult
- }
- if len(stmt.params) > 0 {
- // Get param fields
- my.getPrepareResult(stmt)
- }
- if len(stmt.fields) > 0 {
- // Get column fields
- my.getPrepareResult(stmt)
- }
- return
- }
- // Prepare server side statement. Return statement handler.
- func (my *Conn) Prepare(sql string) (mysql.Stmt, error) {
- if my.net_conn == nil {
- return nil, mysql.ErrNotConn
- }
- if my.unreaded_reply {
- return nil, mysql.ErrUnreadedReply
- }
- stmt, err := my.prepare(sql)
- if err != nil {
- return nil, err
- }
- // Connect statement with database handler
- my.stmt_map[stmt.id] = stmt
- // Save SQL for reconnect
- stmt.sql = sql
- return stmt, nil
- }
- // Bind input data for the parameter markers in the SQL statement that was
- // passed to Prepare.
- //
- // params may be a parameter list (slice), a struct or a pointer to the struct.
- // A struct field can by value or pointer to value. A parameter (slice element)
- // can be value, pointer to value or pointer to pointer to value.
- // Values may be of the folowind types: intXX, uintXX, floatXX, bool, []byte,
- // Blob, string, Time, Date, Time, Timestamp, Raw.
- func (stmt *Stmt) Bind(params ...interface{}) {
- stmt.rebind = true
- if len(params) == 1 {
- // Check for struct binding
- pval := reflect.ValueOf(params[0])
- kind := pval.Kind()
- if kind == reflect.Ptr {
- // Dereference pointer
- pval = pval.Elem()
- kind = pval.Kind()
- }
- typ := pval.Type()
- if kind == reflect.Struct &&
- typ != timeType &&
- typ != dateType &&
- typ != timestampType &&
- typ != rawType {
- // We have a struct to bind
- if pval.NumField() != stmt.param_count {
- panic(mysql.ErrBindCount)
- }
- if !pval.CanAddr() {
- // Make an addressable structure
- v := reflect.New(pval.Type()).Elem()
- v.Set(pval)
- pval = v
- }
- for ii := 0; ii < stmt.param_count; ii++ {
- stmt.params[ii] = bindValue(pval.Field(ii))
- }
- stmt.binded = true
- return
- }
- }
- // There isn't struct to bind
- if len(params) != stmt.param_count {
- panic(mysql.ErrBindCount)
- }
- for ii, par := range params {
- pval := reflect.ValueOf(par)
- if pval.IsValid() {
- if pval.Kind() == reflect.Ptr {
- // Dereference pointer - this value i addressable
- pval = pval.Elem()
- } else {
- // Make an addressable value
- v := reflect.New(pval.Type()).Elem()
- v.Set(pval)
- pval = v
- }
- }
- stmt.params[ii] = bindValue(pval)
- }
- stmt.binded = true
- }
- // Execute prepared statement. If statement requires parameters you may bind
- // them first or specify directly. After this command you may use GetRow to
- // retrieve data.
- func (stmt *Stmt) Run(params ...interface{}) (res mysql.Result, err error) {
- defer catchError(&err)
- if stmt.my.net_conn == nil {
- return nil, mysql.ErrNotConn
- }
- if stmt.my.unreaded_reply {
- return nil, mysql.ErrUnreadedReply
- }
- // Bind parameters if any
- if len(params) != 0 {
- stmt.Bind(params...)
- } else if stmt.param_count != 0 && !stmt.binded {
- panic(mysql.ErrBindCount)
- }
- // Send EXEC command with binded parameters
- stmt.sendCmdExec()
- // Get response
- r := stmt.my.getResponse()
- r.binary = true
- res = r
- return
- }
- // Destroy statement on server side. Client side handler is invalid after this
- // command.
- func (stmt *Stmt) Delete() (err error) {
- defer catchError(&err)
- if stmt.my.net_conn == nil {
- return mysql.ErrNotConn
- }
- if stmt.my.unreaded_reply {
- return mysql.ErrUnreadedReply
- }
- // Allways delete statement on client side, even if
- // the command return an error.
- defer func() {
- // Delete statement from stmt_map
- delete(stmt.my.stmt_map, stmt.id)
- // Invalidate handler
- *stmt = Stmt{}
- }()
- // Send command
- stmt.my.sendCmdU32(_COM_STMT_CLOSE, stmt.id)
- return
- }
- // Resets a prepared statement on server: data sent to the server, unbuffered
- // result sets and current errors.
- func (stmt *Stmt) Reset() (err error) {
- defer catchError(&err)
- if stmt.my.net_conn == nil {
- return mysql.ErrNotConn
- }
- if stmt.my.unreaded_reply {
- return mysql.ErrUnreadedReply
- }
- // Next exec must send type information. We set rebind flag regardless of
- // whether the command succeeds or not.
- stmt.rebind = true
- // Send command
- stmt.my.sendCmdU32(_COM_STMT_RESET, stmt.id)
- // Get result
- stmt.my.getResult(nil, nil)
- return
- }
- // Send long data to MySQL server in chunks.
- // You can call this method after Bind and before Exec. It can be called
- // multiple times for one parameter to send TEXT or BLOB data in chunks.
- //
- // pnum - Parameter number to associate the data with.
- //
- // data - Data source string, []byte or io.Reader.
- //
- // pkt_size - It must be must be greater than 6 and less or equal to MySQL
- // max_allowed_packet variable. You can obtain value of this variable
- // using such query: SHOW variables WHERE Variable_name = 'max_allowed_packet'
- // If data source is io.Reader then (pkt_size - 6) is size of a buffer that
- // will be allocated for reading.
- //
- // If you have data source of type string or []byte in one piece you may
- // properly set pkt_size and call this method once. If you have data in
- // multiple pieces you can call this method multiple times. If data source is
- // io.Reader you should properly set pkt_size. Data will be readed from
- // io.Reader and send in pieces to the server until EOF.
- func (stmt *Stmt) SendLongData(pnum int, data interface{}, pkt_size int) (err error) {
- defer catchError(&err)
- if stmt.my.net_conn == nil {
- return mysql.ErrNotConn
- }
- if stmt.my.unreaded_reply {
- return mysql.ErrUnreadedReply
- }
- if pnum < 0 || pnum >= stmt.param_count {
- return mysql.ErrWrongParamNum
- }
- if pkt_size -= 6; pkt_size < 0 {
- return mysql.ErrSmallPktSize
- }
- switch dd := data.(type) {
- case io.Reader:
- buf := make([]byte, pkt_size)
- for {
- nn, ee := dd.Read(buf)
- if nn != 0 {
- stmt.my.sendLongData(stmt.id, uint16(pnum), buf[0:nn])
- }
- if ee == io.EOF {
- return
- }
- if ee != nil {
- return ee
- }
- }
- case []byte:
- for len(dd) > pkt_size {
- stmt.my.sendLongData(stmt.id, uint16(pnum), dd[0:pkt_size])
- dd = dd[pkt_size:]
- }
- stmt.my.sendLongData(stmt.id, uint16(pnum), dd)
- return
- case string:
- for len(dd) > pkt_size {
- stmt.my.sendLongData(
- stmt.id,
- uint16(pnum),
- []byte(dd[0:pkt_size]),
- )
- dd = dd[pkt_size:]
- }
- stmt.my.sendLongData(stmt.id, uint16(pnum), []byte(dd))
- return
- }
- return mysql.ErrUnkDataType
- }
- // Returns the thread ID of the current connection.
- func (my *Conn) ThreadId() uint32 {
- return my.info.thr_id
- }
- // Register MySQL command/query to be executed immediately after connecting to
- // the server. You may register multiple commands. They will be executed in
- // the order of registration. Yhis method is mainly useful for reconnect.
- func (my *Conn) Register(sql string) {
- my.init_cmds = append(my.init_cmds, sql)
- }
- // See mysql.Query
- func (my *Conn) Query(sql string, params ...interface{}) ([]mysql.Row, mysql.Result, error) {
- return mysql.Query(my, sql, params...)
- }
- // See mysql.QueryFirst
- func (my *Conn) QueryFirst(sql string, params ...interface{}) (mysql.Row, mysql.Result, error) {
- return mysql.QueryFirst(my, sql, params...)
- }
- // See mysql.QueryLast
- func (my *Conn) QueryLast(sql string, params ...interface{}) (mysql.Row, mysql.Result, error) {
- return mysql.QueryLast(my, sql, params...)
- }
- // See mysql.Exec
- func (stmt *Stmt) Exec(params ...interface{}) ([]mysql.Row, mysql.Result, error) {
- return mysql.Exec(stmt, params...)
- }
- // See mysql.ExecFirst
- func (stmt *Stmt) ExecFirst(params ...interface{}) (mysql.Row, mysql.Result, error) {
- return mysql.ExecFirst(stmt, params...)
- }
- // See mysql.ExecLast
- func (stmt *Stmt) ExecLast(params ...interface{}) (mysql.Row, mysql.Result, error) {
- return mysql.ExecLast(stmt, params...)
- }
- // See mysql.End
- func (res *Result) End() error {
- return mysql.End(res)
- }
- // See mysql.GetFirstRow
- func (res *Result) GetFirstRow() (mysql.Row, error) {
- return mysql.GetFirstRow(res)
- }
- // See mysql.GetLastRow
- func (res *Result) GetLastRow() (mysql.Row, error) {
- return mysql.GetLastRow(res)
- }
- // See mysql.GetRows
- func (res *Result) GetRows() ([]mysql.Row, error) {
- return mysql.GetRows(res)
- }
- // Escapes special characters in the txt, so it is safe to place returned string
- // to Query method.
- func (my *Conn) Escape(txt string) string {
- return mysql.Escape(my, txt)
- }
- func (my *Conn) Status() mysql.ConnStatus {
- return my.status
- }
- type Transaction struct {
- *Conn
- }
- // Starts a new transaction
- func (my *Conn) Begin() (mysql.Transaction, error) {
- _, err := my.Start("START TRANSACTION")
- return &Transaction{my}, err
- }
- // Commit a transaction
- func (tr Transaction) Commit() error {
- _, err := tr.Start("COMMIT")
- tr.Conn = nil // Invalidate this transaction
- return err
- }
- // Rollback a transaction
- func (tr Transaction) Rollback() error {
- _, err := tr.Start("ROLLBACK")
- tr.Conn = nil // Invalidate this transaction
- return err
- }
- func (tr Transaction) IsValid() bool {
- return tr.Conn != nil
- }
- // Binds statement to the context of transaction. For native engine this is
- // identity function.
- func (tr Transaction) Do(st mysql.Stmt) mysql.Stmt {
- if s, ok := st.(*Stmt); !ok || s.my != tr.Conn {
- panic("Transaction and statement doesn't belong to the same connection")
- }
- return st
- }
- func init() {
- mysql.New = New
- }
|