• 实现一个简单的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/

  • 相关阅读:
    HDU 1677
    HDU 1672 Cuckoo Hashing
    HDU 2586 + HDU 4912 最近公共祖先
    最大流 Dinic + Sap 模板
    网络流算法小结(转)
    Malformed network data报错解决方法
    js window.open 参数设置
    java图片高质量缩放类
    struts2 I18n问题 国际化
    java.lang.Exception: Socket bind failed 服务器端口冲突-->修改端口
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/13985952.html
Copyright © 2020-2023  润新知