303 lines
6.2 KiB
Go
303 lines
6.2 KiB
Go
|
package mysql
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"time"
|
||
|
"unicode"
|
||
|
)
|
||
|
|
||
|
// Version returns mymysql version string
|
||
|
func Version() string {
|
||
|
return "1.5.3"
|
||
|
}
|
||
|
|
||
|
func syntaxError(ln int) error {
|
||
|
return fmt.Errorf("syntax error at line: %d", ln)
|
||
|
}
|
||
|
|
||
|
// Creates new conneection handler using configuration in cfgFile. Returns
|
||
|
// connection handler and map contains unknown options.
|
||
|
//
|
||
|
// Config file format(example):
|
||
|
//
|
||
|
// # mymysql options (if some option isn't specified it defaults to "")
|
||
|
//
|
||
|
// DbRaddr 127.0.0.1:3306
|
||
|
// # DbRaddr /var/run/mysqld/mysqld.sock
|
||
|
// DbUser testuser
|
||
|
// DbPass TestPasswd9
|
||
|
// # optional: DbName test
|
||
|
// # optional: DbEncd utf8
|
||
|
// # optional: DbLaddr 127.0.0.1:0
|
||
|
// # optional: DbTimeout 15s
|
||
|
//
|
||
|
// # Your options (returned in unk)
|
||
|
//
|
||
|
// MyOpt some text
|
||
|
func NewFromCF(cfgFile string) (con Conn, unk map[string]string, err error) {
|
||
|
var cf *os.File
|
||
|
cf, err = os.Open(cfgFile)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
br := bufio.NewReader(cf)
|
||
|
um := make(map[string]string)
|
||
|
var proto, laddr, raddr, user, pass, name, encd, to string
|
||
|
for i := 1; ; i++ {
|
||
|
buf, isPrefix, e := br.ReadLine()
|
||
|
if e != nil {
|
||
|
if e == io.EOF {
|
||
|
break
|
||
|
}
|
||
|
err = e
|
||
|
return
|
||
|
}
|
||
|
l := string(buf)
|
||
|
if isPrefix {
|
||
|
err = fmt.Errorf("line %d is too long", i)
|
||
|
return
|
||
|
}
|
||
|
l = strings.TrimFunc(l, unicode.IsSpace)
|
||
|
if len(l) == 0 || l[0] == '#' {
|
||
|
continue
|
||
|
}
|
||
|
n := strings.IndexFunc(l, unicode.IsSpace)
|
||
|
if n == -1 {
|
||
|
err = fmt.Errorf("syntax error at line: %d", i)
|
||
|
return
|
||
|
}
|
||
|
v := l[:n]
|
||
|
l = strings.TrimLeftFunc(l[n:], unicode.IsSpace)
|
||
|
switch v {
|
||
|
case "DbLaddr":
|
||
|
laddr = l
|
||
|
case "DbRaddr":
|
||
|
raddr = l
|
||
|
proto = "tcp"
|
||
|
if strings.IndexRune(l, ':') == -1 {
|
||
|
proto = "unix"
|
||
|
}
|
||
|
case "DbUser":
|
||
|
user = l
|
||
|
case "DbPass":
|
||
|
pass = l
|
||
|
case "DbName":
|
||
|
name = l
|
||
|
case "DbEncd":
|
||
|
encd = l
|
||
|
case "DbTimeout":
|
||
|
to = l
|
||
|
default:
|
||
|
um[v] = l
|
||
|
}
|
||
|
}
|
||
|
if raddr == "" {
|
||
|
err = errors.New("DbRaddr option is empty")
|
||
|
return
|
||
|
}
|
||
|
unk = um
|
||
|
if name != "" {
|
||
|
con = New(proto, laddr, raddr, user, pass, name)
|
||
|
} else {
|
||
|
con = New(proto, laddr, raddr, user, pass)
|
||
|
}
|
||
|
if encd != "" {
|
||
|
con.Register(fmt.Sprintf("SET NAMES %s", encd))
|
||
|
}
|
||
|
if to != "" {
|
||
|
var timeout time.Duration
|
||
|
timeout, err = time.ParseDuration(to)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
con.SetTimeout(timeout)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls Start and next calls GetRow as long as it reads all rows from the
|
||
|
// result. Next it returns all readed rows as the slice of rows.
|
||
|
func Query(c Conn, sql string, params ...interface{}) (rows []Row, res Result, err error) {
|
||
|
res, err = c.Start(sql, params...)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
rows, err = GetRows(res)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls Start and next calls GetFirstRow
|
||
|
func QueryFirst(c Conn, sql string, params ...interface{}) (row Row, res Result, err error) {
|
||
|
res, err = c.Start(sql, params...)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
row, err = GetFirstRow(res)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls Start and next calls GetLastRow
|
||
|
func QueryLast(c Conn, sql string, params ...interface{}) (row Row, res Result, err error) {
|
||
|
res, err = c.Start(sql, params...)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
row, err = GetLastRow(res)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls Run and next call GetRow as long as it reads all rows from the
|
||
|
// result. Next it returns all readed rows as the slice of rows.
|
||
|
func Exec(s Stmt, params ...interface{}) (rows []Row, res Result, err error) {
|
||
|
res, err = s.Run(params...)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
rows, err = GetRows(res)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls Run and next call GetFirstRow
|
||
|
func ExecFirst(s Stmt, params ...interface{}) (row Row, res Result, err error) {
|
||
|
res, err = s.Run(params...)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
row, err = GetFirstRow(res)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls Run and next call GetLastRow
|
||
|
func ExecLast(s Stmt, params ...interface{}) (row Row, res Result, err error) {
|
||
|
res, err = s.Run(params...)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
row, err = GetLastRow(res)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Calls r.MakeRow and next r.ScanRow. Doesn't return io.EOF error (returns nil
|
||
|
// row insted).
|
||
|
func GetRow(r Result) (Row, error) {
|
||
|
row := r.MakeRow()
|
||
|
err := r.ScanRow(row)
|
||
|
if err != nil {
|
||
|
if err == io.EOF {
|
||
|
return nil, nil
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
return row, nil
|
||
|
}
|
||
|
|
||
|
// Reads all rows from result and returns them as slice.
|
||
|
func GetRows(r Result) (rows []Row, err error) {
|
||
|
var row Row
|
||
|
for {
|
||
|
row, err = r.GetRow()
|
||
|
if err != nil || row == nil {
|
||
|
break
|
||
|
}
|
||
|
rows = append(rows, row)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Returns last row and discard others
|
||
|
func GetLastRow(r Result) (Row, error) {
|
||
|
row := r.MakeRow()
|
||
|
err := r.ScanRow(row)
|
||
|
if err == io.EOF {
|
||
|
return nil, nil
|
||
|
}
|
||
|
for err == nil {
|
||
|
err = r.ScanRow(row)
|
||
|
}
|
||
|
if err == io.EOF {
|
||
|
return row, nil
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Read all unreaded rows and discard them. This function is useful if you
|
||
|
// don't want to use the remaining rows. It has an impact only on current
|
||
|
// result. If there is multi result query, you must use NextResult method and
|
||
|
// read/discard all rows in this result, before use other method that sends
|
||
|
// data to the server. You can't use this function if last GetRow returned nil.
|
||
|
func End(r Result) error {
|
||
|
_, err := GetLastRow(r)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Returns first row and discard others
|
||
|
func GetFirstRow(r Result) (row Row, err error) {
|
||
|
row, err = r.GetRow()
|
||
|
if err == nil && row != nil {
|
||
|
err = r.End()
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func escapeString(txt string) string {
|
||
|
var (
|
||
|
esc string
|
||
|
buf bytes.Buffer
|
||
|
)
|
||
|
last := 0
|
||
|
for ii, bb := range txt {
|
||
|
switch bb {
|
||
|
case 0:
|
||
|
esc = `\0`
|
||
|
case '\n':
|
||
|
esc = `\n`
|
||
|
case '\r':
|
||
|
esc = `\r`
|
||
|
case '\\':
|
||
|
esc = `\\`
|
||
|
case '\'':
|
||
|
esc = `\'`
|
||
|
case '"':
|
||
|
esc = `\"`
|
||
|
case '\032':
|
||
|
esc = `\Z`
|
||
|
default:
|
||
|
continue
|
||
|
}
|
||
|
io.WriteString(&buf, txt[last:ii])
|
||
|
io.WriteString(&buf, esc)
|
||
|
last = ii + 1
|
||
|
}
|
||
|
io.WriteString(&buf, txt[last:])
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func escapeQuotes(txt string) string {
|
||
|
var buf bytes.Buffer
|
||
|
last := 0
|
||
|
for ii, bb := range txt {
|
||
|
if bb == '\'' {
|
||
|
io.WriteString(&buf, txt[last:ii])
|
||
|
io.WriteString(&buf, `''`)
|
||
|
last = ii + 1
|
||
|
}
|
||
|
}
|
||
|
io.WriteString(&buf, txt[last:])
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
// Escapes special characters in the txt, so it is safe to place returned string
|
||
|
// to Query method.
|
||
|
func Escape(c Conn, txt string) string {
|
||
|
if c.Status()&SERVER_STATUS_NO_BACKSLASH_ESCAPES != 0 {
|
||
|
return escapeQuotes(txt)
|
||
|
}
|
||
|
return escapeString(txt)
|
||
|
}
|