MySQL Client API written entirely in Go
Sorry for my poor English. If you can help with improving the English in this documentation, please contact me.
This package contains MySQL client API written entirely in Go. It is designed to work with the MySQL protocol version 4.1 or greater. It definitely works well with MySQL server version 5.0 and 5.1 (I use these versions of MySQL servers for my applications). Some people claim that mymysql works with older versions of MySQL protocol too.
v1.5.4: Bugs fixed in native and godrv packages.
v1.5.3: Bugs fixed in new godrv code.
v1.5.1: Conn.NetConn method added.
v1.5: Needs Go 1.1 (time.ParseInLocation and net.Dialer) to compile.
v1.4: Stmt.ResetParams, Stmt.Map and Stmt.NumFields methods disappeared.
New Stmt.Fields method. godrv implements driver.Queryer interface which
improves performance when compiled with Go tip.
v1.3: Overall performance improved by factor 1.5 to 1.8. All Encode* functions
now accept properly sized []byte slice as first argument.
v1.2: Faster execution of simple queries in mymysql/godrv. EscapeString
method renamed to Escape.
v1.1: Client error codes moved from mymysql/native package to mymysql/mysql.
v1.0: Transactions added to autorc, new Transaction.IsValid method. I think this library is mature enough to release it as v1.0
v0.4.11: Add Reconnect, Register, SetMaxPktSize, Bind to autorc.
v0.4.10: New Clone method for create connection from other connection.
v0.4.9: New method for create connection from configuration in file: NewFromCF.
v0.4.8: New methods for obtain only first/last row from result set. Better implementation of discarding rows in End method.
v0.4.7: ScanRow and MakeRow methods added. ScanRow is more efficient than GetRow because it doesn't allocate memory for every row received from the server. godrv Value.Next method now uses the new ScanRow method.
v0.4.6: StatusOnly method added to mysql.Result.
v0.4.5: New autorc.Conn.PrepareOnce method.
v0.4.4:
v0.4.3:
v0.4.2:
row.Int64/row.Uint64 methods added.
Rename BindParams to Bind.
v0.4.1:
BindParams supports Go bool type.
v0.4:
mysql.New and other functions returns mostly interface types. So all previously exported members were converted to methods (with except mysql.Row and mysql.Field - their definition didn't changed).
Transactions added. If you use *mymysql/thrsafe" engine transactions are fully thread safe.
Driver for exp/sql.
To install all subpackages of mymysql you need to get three of them:
$ go get github.com/ziutek/mymysql/thrsafe
$ go get github.com/ziutek/mymysql/autorc
$ go get github.com/ziutek/mymysql/godrvor just run one command to get all subpackages:
$ go get -v github.com/ziutek/mymysql/...go get automagically selects the proper version of mymysql for your Go release. After this command mymysql is ready to use.
For testing you will need to create the test database and a test user:
mysql> create database test;
mysql> grant all privileges on test.* to testuser@localhost;
mysql> set password for testuser@localhost = password("TestPasswd9");Make sure that MySQL max_allowed_packet variable in my.cnf is equal or greater than 34M (In order to test long packets).
The default MySQL server address is 127.0.0.1:3306.
Next run tests:
$ cd $GOPATH/src/github.com/ziutek/mymysql
$ ./all.bash testpackage main
import (
"os"
"github.com/ziutek/mymysql/mysql"
_ "github.com/ziutek/mymysql/native" // Native engine
// _ "github.com/ziutek/mymysql/thrsafe" // Thread safe engine
)
func main() {
db := mysql.New("tcp", "", "127.0.0.1:3306", user, pass, dbname)
err := db.Connect()
if err != nil {
panic(err)
}
rows, res, err := db.Query("select * from X where id > %d", 20)
if err != nil {
panic(err)
}
for _, row := range rows {
for _, col := range row {
if col == nil {
// col has NULL value
} else {
// Do something with text in col (type []byte)
}
}
// You can get specific value from a row
val1 := row[1].([]byte)
// You can use it directly if conversion isn't needed
os.Stdout.Write(val1)
// You can get converted value
number := row.Int(0) // Zero value
str := row.Str(1) // First value
bignum := row.MustUint(2) // Second value
// You may get values by column name
first := res.Map("FirstColumn")
second := res.Map("SecondColumn")
val1, val2 := row.Int(first), row.Str(second)
}
}If you do not want to load the entire result into memory you may use Start and GetRow methods:
res, err := db.Start("select * from X")
checkError(err)
// Print fields names
for _, field := range res.Fields() {
fmt.Print(field.Name, " ")
}
fmt.Println()
// Print all rows
for {
row, err := res.GetRow()
checkError(err)
if row == nil {
// No more rows
break
}
// Print all cols
for _, col := range row {
if col == nil {
fmt.Print("")
} else {
os.Stdout.Write(col.([]byte))
}
fmt.Print(" ")
}
fmt.Println()
}GetRow method allocates a new chunk of memory for every received row. If your query returns hundreds of rows you should opt for the ScanRow method to avoid unnecessary allocations:
// Print all rows
row := res.MakeRow()
for {
err := res.ScanRow(row)
if err == io.EOF {
// No more rows
break
}
checkError(err)
// Print all cols
// [...]
}You can use Run or Exec method for prepared statements:
stmt, err := db.Prepare("insert into X values (?, ?)")
checkError(err)
type Data struct {
Id int
Tax *float32 // nil means NULL
}
data = new(Data)
for {
err := getData(data)
if err == endOfData {
break
}
checkError(err)
_, err = stmt.Run(data.Id, data.Tax)
checkError(err)
}getData is the function which retrieves data from somewhere and set Id and Tax fields of the Data struct. In the case of Tax field getData may assign a pointer the retrieved variable or nil if NULL should be stored in database.
If you pass parameters to Run or Exec method, the data is rebound on every method call. This isn't efficient if the statement will be executed more than once. You can bind parameters and use Run or Exec method without parameters, to avoid these unnecessary rebinds. Warning! If you use Bind in multithreaded applications, you should ensure that no other thread will use Bind for the same statement, until you no longer need bound parameters.
The simplest way to bind parameters is:
stmt.Bind(data.Id, data.Tax)but you can't use it in our example, because parameters bound this way can't be changed by getData function. You may modify bindings like this:
stmt.Bind(&data.Id, &data.Tax)and now it should work properly. But in our example there is better solution:
stmt.Bind(data)If Bind method has one parameter, and this parameter is a struct or a pointer to the struct, it treats all fields of this struct as parameters and binds them.
This is the improved code of the previous example:
data = new(Data)
stmt.Bind(data)
for {
err := getData(data)
if isEndOfData(error) {
break
}
checkError(err)
_, err = stmt.Run()
checkError(err)
}_, err = db.Start("CREATE TABLE web (url VARCHAR(80), content LONGBLOB)")
checkError(err)
ins, err := db.Prepare("INSERT INTO web VALUES (?, ?)")
checkError(err)
var url string
ins.Bind(&url, []byte(nil)) // []byte(nil) for properly type binding
for {
// Read URL from stdin
url = ""
fmt.Scanln(&url)
if len(url) == 0 {
// Stop reading if URL is blank line
break
}
// Make a connection
resp, err := http.Get(url)
checkError(err)
// We can retrieve response directly into database because
// the resp.Body implements io.Reader. Use 8 kB buffer.
err = ins.SendLongData(1, resp.Body, 8192)
checkError(err)
// Execute insert statement
_, err = ins.Run()
checkError(err)
}res, err := db.Start("select id from M; select name from M")
checkError(err)
// Get result from first select
for {
row, err := res.GetRow()
checkError(err)
if row == nil {
// End of first result
break
}
// Do something with with the data
functionThatUseId(row.Int(0))
}
// Get result from second select
res, err = res.NextResult()
checkError(err)
if res == nil {
panic("Hmm, there is no result. Why?!")
}
for {
row, err := res.GetRow()
checkError(err)
if row == nil {
// End of second result
break
}
// Do something with with the data
functionThatUseName(row.Str(0))
}import (
"github.com/ziutek/mymysql/mysql"
_ "github.com/ziutek/mymysql/thrsafe" // for thread safe transactions
)
// [...]
// Statement prepared before transaction begins
ins, err := db.Prepare("insert A values (?, ?)")
checkError(err)
// Begin a new transaction
tr, err := db.Begin()
checkError(err)
// Now db is locked, so any method that uses db and sends commands to
// MySQL server will be blocked until Commit or Rollback is called.
// Commands in transaction are thread safe to
go func() {
_, err = tr.Start("insert A values (1, 'jeden')")
checkError(err)
} ()
_, err = tr.Start("insert A values (2, 'dwa')")
checkError(err)
// You can't use statements prepared before transaction in the usual way,
// because the connection is locked by the Begin method. You must bind the statement
// to the transaction before using it.
_, err = tr.Do(ins).Run(3, "three")
checkError(err)
// For a greater number of calls
ti := tr.Do(ins)
_, err = ti.Run(4, "four")
checkError(err)
_, err = ti.Run(5, "five")
checkError(err)
// At the end you can Commit or Rollback. tr is invalidated and using it
// after Commit/Rollback will cause a panic.
tr.Commit()import (
"github.com/ziutek/mymysql/autorc"
_ "github.com/ziutek/mymysql/thrsafe" // You may also use the native engine
)
// [...]
db := autorc.New("tcp", "", "127.0.0.1:3306", user, pass, dbname)
// Initilisation commands. They will be executed after each connect.
db.Register("set names utf8")
// There is no need to explicitly connect to the MySQL server
rows, res, err := db.Query("SELECT * FROM R")
checkError(err)
// Now we are connected.
// It does not matter if connection will be interrupted during sleep, eg
// due to server reboot or network down.
time.Sleep(9e9)
// If we can reconnect in no more than db.MaxRetries attempts this
// statement will be prepared.
sel, err := db.Prepare("SELECT name FROM R where id > ?")
checkError(err)
// We can destroy our connection server side
_, _, err = db.Query("kill %d", db.Raw.ThreadId())
checkError(err)
// But it doesn't matter
sel.Bind(2)
rows, res, err = sel.Exec()
checkError(err)import (
"database/sql"
_"github.com/ziutek/mymysql/godrv"
)
// [...]
// Open new connection. The URNo open issues yet, or sync has not completed.