實現一個簡單的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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM