主要是为了学习下golang db driver的运行原理,所以尝试编写了一个简单的db driver
原理说明
如果有java开发经验的话,应该知道java的jdbc 驱动是基于spi 开发的,我们参考jdbc驱动的说明,就能实现一个简单的jdbc驱动
golang 的db driver 实现上类似spi,我们首先需要注册我们自定义的driver,然后就是driver.Conn 的实现,主要包含了以下接口
driver.Conn
type Conn interface {
// Prepare returns a prepared statement, bound to this connection.
Prepare(query string) (Stmt, error)
// Close invalidates and potentially stops any current
// prepared statements and transactions, marking this
// connection as no longer in use.
//
// Because the sql package maintains a free pool of
// connections and only calls Close when there's a surplus of
// idle connections, it shouldn't be necessary for drivers to
// do their own connection caching.
//
// Drivers must ensure all network calls made by Close
// do not block indefinitely (e.g. apply a timeout).
Close() error
// Begin starts and returns a new transaction.
//
// Deprecated: Drivers should implement ConnBeginTx instead (or additionally).
Begin() (Tx, error)
}
driver.Stmt
// Stmt is a prepared statement. It is bound to a Conn and not
// used by multiple goroutines concurrently.
type Stmt interface {
// Close closes the statement.
//
// As of Go 1.1, a Stmt will not be closed if it's in use
// by any queries.
//
// Drivers must ensure all network calls made by Close
// do not block indefinitely (e.g. apply a timeout).
Close() error
// NumInput returns the number of placeholder parameters.
//
// If NumInput returns >= 0, the sql package will sanity check
// argument counts from callers and return errors to the caller
// before the statement's Exec or Query methods are called.
//
// NumInput may also return -1, if the driver doesn't know
// its number of placeholders. In that case, the sql package
// will not sanity check Exec or Query argument counts.
NumInput() int
// Exec executes a query that doesn't return rows, such
// as an INSERT or UPDATE.
//
// Deprecated: Drivers should implement StmtExecContext instead (or additionally).
Exec(args []Value) (Result, error)
// Query executes a query that may return rows, such as a
// SELECT.
//
// Deprecated: Drivers should implement StmtQueryContext instead (or additionally).
Query(args []Value) (Rows, error)
}
对于查询需要实现driver.Rows
type Rows interface {
// Columns returns the names of the columns. The number of
// columns of the result is inferred from the length of the
// slice. If a particular column name isn't known, an empty
// string should be returned for that entry.
Columns() []string
// Close closes the rows iterator.
Close() error
// Next is called to populate the next row of data into
// the provided slice. The provided slice will be the same
// size as the Columns() are wide.
//
// Next should return io.EOF when there are no more rows.
//
// The dest should not be written to outside of Next. Care
// should be taken when closing Rows not to modify
// a buffer held in dest.
Next(dest []Value) error
}
简单实现
通过以上接口的说明,我们发现实现一个简单的db driver难度并不是很大,主要实现我们的几个接口就可以了,以下是参考代码的说明
- Driver 接口
基本就是一个空的结构,让后实现一个Open 方法,返回自己实现的driver.Conn
package mydb
import (
"database/sql/driver"
"log"
)
// Driver mydb driver for implement database/sql/driver
type Driver struct {
}
func init() {
log.Println("driver is call ")
}
// Open for implement driver interface
func (driver *Driver) Open(name string) (driver.Conn, error) {
log.Println("exec open driver")
return &Conn{}, nil
}
- 自定义Conn代码
package mydb
import (
"database/sql/driver"
"errors"
)
// Conn for db open
type Conn struct {
}
// Prepare statement for prepare exec
func (c *Conn) Prepare(query string) (driver.Stmt, error) {
return &MyStmt{}, nil
}
// Close close db connection
func (c *Conn) Close() error {
return errors.New("can't close connection")
}
// Begin begin
func (c *Conn) Begin() (driver.Tx, error) {
return nil, errors.New("not support tx")
}
- driver.Stmt 实现
package mydb
import (
"database/sql/driver"
"errors"
"log"
)
// MyStmt for sql statement
type MyStmt struct {
}
// Close implement for stmt
func (stmt *MyStmt) Close() error {
return nil
}
// Query implement for Query
func (stmt *MyStmt) Query(args []driver.Value) (driver.Rows, error) {
log.Println("do query", args)
myrows := MyRowS{
Size: 3,
}
return &myrows, nil
}
// NumInput row numbers
func (stmt *MyStmt) NumInput() int {
// don't know how many row numbers
return -1
}
// Exec exec implement
func (stmt *MyStmt) Exec(args []driver.Value) (driver.Result, error) {
return nil, errors.New("some wrong")
}
- driver.Rows 自定义实现
为了简单,Columns 以及Next 数据写死了。。。。,实际可以自己扩展下
package mydb
import (
"database/sql/driver"
"io"
)
// MyRowS myRowS implemmet for driver.Rows
type MyRowS struct {
Size int64
}
// Columns returns the names of the columns. The number of
// columns of the result is inferred from the length of the
// slice. If a particular column name isn't known, an empty
// string should be returned for that entry.
func (r *MyRowS) Columns() []string {
return []string{
"name",
"age",
"version",
}
}
// Close closes the rows iterator.
func (r *MyRowS) Close() error {
return nil
}
// Next is called to populate the next row of data into
// the provided slice. The provided slice will be the same
// size as the Columns() are wide.
//
// Next should return io.EOF when there are no more rows.
//
// The dest should not be written to outside of Next. Care
// should be taken when closing Rows not to modify
// a buffer held in dest.
func (r *MyRowS) Next(dest []driver.Value) error {
if r.Size == 0 {
return io.EOF
}
name := "dalong"
age := 333
version := "v1"
dest[0] = name
dest[1] = age
dest[2] = version
r.Size--
return nil
}
- 注册driver
package mydb
import (
"database/sql"
"log"
)
func init() {
log.Println("register mydb driver")
sql.Register("mydb", &Driver{})
}
- 单元测试
func TestDb(t *testing.T) {
db, err := sql.Open("mydb", "mydb://dalong@127.0.0.1/demoapp")
if err != nil {
t.Errorf("some error %s", err.Error())
}
rows, err := db.Query("select name,age,version from demoapp")
if err != nil {
log.Fatal("some wrong for query", err.Error())
}
for rows.Next() {
var user mydb.MyUser
if err := rows.Scan(&user.Name, &user.Age, &user.Version); err != nil {
log.Println("scan value erro", err.Error())
} else {
log.Println(user)
}
}
}
- 测试效果
说明
以上是一个简单的学习整理,实现的功能比较简单,但是通过次demo 至少可以了解下golang db driver 的开发流程,当然以上的
db driver 是最简单模式的,实际上golang 还支持基于context 模式的db driver,查看sql 的Open 方法也能看到
参考资料
https://pkg.go.dev/github.com/rongfengliang/mysqldriver
https://github.com/rongfengliang/mysqldriver
https://golang.org/pkg/database/sql/driver/