实现一个简单的golang db driver

主要是为了学习下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/

posted on 2020-11-16 17:04  荣锋亮  阅读(1201)  评论(0编辑  收藏  举报

导航