Blog.1 database.sql.driver

在事務操做中,要求事務的各個階段都使用一個Conn鏈接。在鏈接被關閉以前,還須要執行rollback操做。sql

文章翻譯了Go源碼下database.sql.driver的接口規範,具體實現能夠查看源碼。數據庫

// 包driver定義了數據驅動要實現的接口,具體的實現會在包sql中用到。
//
// 更多仍是使用包sql中的代碼
package driver

import (
    "context"
    "errors"
    "reflect"
)

// Value必須是一個驅動能夠處理的值、NamedValueChecker接口可以處理的類型
// 或者下面這些類型的實例
//
//   int64
//   float64
//   bool
//   []byte
//   string
//   time.Time
//
// 若是驅動支持遊標,返回值可能也實現Rows接口。舉例,當用戶
// 執行"select cursor(select * from my_table) from dual"。
// 若是返回的Rows被Close掉了,遊標指向的數據也會被Close掉。
type Value interface{}

// NameValue 同時包括name和value
type NamedValue struct {
    // 若是Name不爲空,它應該被用於參數標識符,而非序號位置。
    //
    // Name 沒有符號前綴
    Name string

    // 參數從1開始的序號位置,而且老是被設置
    Ordinal int

    // Value是參數值
    Value Value
}

// Driver是一個必須被各個數據庫driver實現的接口
//
// 數據庫驅動能夠實現DriverContext來訪問上下文,而且只解析一次鏈接池的名稱,
// 而非每一個鏈接都解析一次。
// 
type Driver interface {
    // Open返回數據庫的一個新鏈接,參數name是驅動特定格式的字符串
    //
    // Open也能夠返回一個緩存的鏈接(以前被close掉的),但這樣
    // 作其實不必。sql包爲了鏈接重複使用維護了一個空閒鏈接池
    //
    // 返回的鏈接一次只被一個goroutinue中使用
    Open(name string) (Conn, error)
}

// 若是Driver實現了DriverContext接口,那麼sql.DB就會調用OpenConnector
// 來獲取一個Connector,調用Connector的Conn方法來獲取每一個須要的鏈接,
// 以此代替調用Drive的Open方法。這樣容許drivers僅解析一次name,同時提供
// 對每一個鏈接上下文的訪問
type DriverContext interface {
    // OpenConnector解析name的方式必須跟Driver.Open的方式保持一致
    OpenConnector(name string) (Connector, error)
}

// 一個Connector表示一個固定配置的driver,可以建立任意數量的等效Conn,
// 供多個goroutinue使用
//
// 一個Connector能被傳遞給sql.OpenDB方法,去容許驅動實現本身的sql.DB。
// 或者經過調用DriverContext的OpenConnector方法,來返回一個Connector,
// 這樣容許驅動訪問鏈接的上下文,避免頻繁的解析驅動配置。
type Connector interface {
    // Connect返回一個數據庫的鏈接
    // Connect可能返回一個以前緩存的鏈接(以前被close掉了),但這樣去
    // 作實際上是不必的。sql包維護了一個高效重複使用的空閒鏈接池。
    //
    //
    // 被提供的context.Context參數僅僅被用於建立鏈接的目的
    //(看net.DialContext),不該該被存儲或用於其餘別的目的。
    //
    // 返回的鏈接一次只能被一個goroutine使用
    Connect(context.Context) (Conn, error)

    // Driver返回Connector的底層驅動,在sql.DB中,主要用於維護
    // 驅動的擴展性
    Driver() Driver
}

// ErrSkip 可能被一些可選接口的方法返回,用於在運行時標識該路徑無效。
// 包sql應該繼續去執行,就當類型沒有實現這個接口同樣。
// ErrSkip 只有在被明確說明後纔會被支持
// 
var ErrSkip = errors.New("driver: skip fast-path; continue as if unimplemented")

// 當驅動給sql包標識一個driver.Conn處於壞的狀態時,ErrBadConn應該被返回(
// 好比服務端已經關閉了這個鏈接),sql包已經使用一個新的鏈接進行重試
//
// 爲了不重複操做,若是服務端可能已經完成操做的話,ErrBadConn不該該被返回。
// 即便服務端返回了一個錯誤,你也不該該返回ErrBadConn
var ErrBadConn = errors.New("driver: bad connection")

// Pinger是一個可選的接口,它可能會被Conn實現
// 
// 若是Conn沒有實現Pinger接口,那麼sql包的DB.Ping和DB.PingContext
// 將會執行檢查,是否至少存在一個可用鏈接
//
// 若是Conn.Ping返回了ErrBadConn,DB.Ping 和 DB.PingContext將會從鏈接池中
// 將Conn溢出
type Pinger interface {
    Ping(ctx context.Context) error
}

// Execer 是一個能夠被Conn實現的,可選的接口
//
// 若是Conn實現了ExecerContext或Excer,
// 包sql下的DB.Exec將首先prepare查詢語句,執行而後關閉。
//
// Exec可能返回ErrSkip錯誤
//
// 棄用:Drivers應該實現ExecerContext接口來替代Execer
type Execer interface {
    Exec(query string, args []Value) (Result, error)
}

// ExecerContext是能夠被Conn實現的、可選的接口
//
// 若是Conn並無實現ExecerContext接口,那sql包的DB.Exec將會向後調用Excer
// 若是Conn也沒有實現Execer接口,
// DB.Exec將首先prepare查詢,執行語句、而後關閉語句
//
// ExecerContext 可能返回 ErrSkip錯誤.
//
// ExecerContext必須認真對待context的超時,當context被取消時,須要返回。
// 
type ExecerContext interface {
    ExecContext(ctx context.Context, query string, args []NamedValue) (Result, error)
}

// Queryer 是一個可選的接口,Conn可能會實現它。
//
// 若是Conn既沒有實現QueryerContext,也沒有實現Queryer
// 那麼sql包的DB.Query首先會prepare一個查詢語句,而後執行語句,關閉語句
//
// Query可能會返回 ErrSkip錯誤.
//
// 棄用:Drivers應該實現QueryerContext接口來替代Queryer
type Queryer interface {
    Query(query string, args []Value) (Rows, error)
}

// QueryerContext 是一個可選的接口,Conn可能會實現它
//
// 若是Conn沒有實現QueryerContext,那麼sql包在執行DB.Query會降級調用Queryer;
// 若是Conn也沒有實現Queryer,DB.Query 首先會prepare一個查詢語句,而後執行這個語句
// 而後再關閉它
//
// QueryerContext可能會返回 ErrSkip.
//
// QueryerContext必須認真對待context的超時,當context被cancel掉時,須要返回
type QueryerContext interface {
    QueryContext(ctx context.Context, query string, args []NamedValue) (Rows, error)
}

// Conn是一條數據庫的鏈接,它不能在多個goroutine中同時使用。
//
// Conn被假定爲是有狀態的
type Conn interface {
    // Prepare返回一個準備好的語句,綁定到這個鏈接上
    Prepare(query string) (Stmt, error)

    // Close會使當前準備好的語句和事物失效,並可能中止它們執行,
    // 將這個鏈接標記爲再也不使用
    //
    // 由於sql包維護了一個空閒鏈接池,僅當前有多餘的空閒鏈接時,
    // 纔會調用Close。對於驅動來講,實現本身的鏈接緩存是沒必要要的
    Close() error

    // Begin 啓動並返回一個新的事務
    //
    // 棄用:驅動應該經過實現ConnBeginTx來替換Begin
    Begin() (Tx, error)
}

// ConnPrepareContext經過使用context,增強了Conn接口
type ConnPrepareContext interface {
    // context被用來對語句作預處理
    // 在語句自己中是不能夠存儲context的
    PrepareContext(ctx context.Context, query string) (Stmt, error)
}

// IsolationLevel在TxOptions類型中記錄事物的隔離級別
//
// 這個類型應該認被爲跟sql.IsolationLevel是一致的,以及定義這個類型的其餘值。
// 
type IsolationLevel int

// TxOptions 設置事物的選項
//
// 這個類型應該被認爲跟sql.TxOptions是一致的
type TxOptions struct {
    Isolation IsolationLevel
    ReadOnly  bool
}

// ConnBeginTx經過context和TxOptions提升了Conn接口
type ConnBeginTx interface {
    // BeginTx啓動並返回一個新的事務
    // 若是context被用戶取消了,sql包會在丟棄和關閉這個鏈接以前
    // 執行Tx.Rollback
    //
    // 函數必須檢查opts.Isolation,肯定是否存在設置的額隔離級別。
    // 若是驅動不支持一個非默認的隔離級別和被設置的級別,或者
    // 存在一個非默認的隔離級別是不支持的,必須返回一個錯誤
    //
    // 函數也必須檢查opts.ReadOnly,若是ReadOnly值爲真,
    // 若是支持設置的話,則設置只讀事務屬性。若是不支持的話,返回error
    // 
    BeginTx(ctx context.Context, opts TxOptions) (Tx, error)
}

// Conn可能會實現SessionResetter接口,用於重置當前鏈接上的會話狀態
// 並將當前鏈接標識爲壞鏈接
type SessionResetter interface {
    // 當鏈接在鏈接池中時,調用ResetSession方法。該鏈接不會再承載任何查詢操做
    // 直接方法返回
    //
    // 若是鏈接是壞的,方法應該返回driver.ErrBadConn錯誤,來阻止鏈接被放回到
    // 鏈接池。其餘別的錯誤將會被丟棄
    ResetSession(ctx context.Context) error
}

// Result是查詢執行的結果
type Result interface {
    // LastInsertId返回數據庫自動生成的ID,好比,用主鍵插入表的操做
    LastInsertId() (int64, error)

    // RowsAffected返回查詢影響的行數
    RowsAffected() (int64, error)
}

// Stmt是一個準備好的語句,它被綁定到一個Conn,且不能被多個goroutine併發
// 使用
type Stmt interface {
    // Close關閉這個語句
    //
    // 截止到Go 1.1,若是Stmt在被一些查詢使用,Stmt將不會被關閉
    Close() error

    // NumInput返回佔位符的個數
    //
    // 若是NumInput返回值大於等於0,sql包會明智的檢查調用者的參數個數,
    // 在Exec或Query被調用以前,返回錯誤給調用者。
    //
    // 若是驅動不知道佔位符的個數,NumInput可能會返回-1。在這種狀況下,
    // sql包將不會檢查Exec和Query的參數個數
    NumInput() int

    // Exec執行一個不返回數據行的查詢,好比INSERT或UPDATE
    //
    // 棄用:驅動應實現StmtExecContext來替代
    Exec(args []Value) (Result, error)

    // Query執行一個返回數據行的查詢,好比SELECT
    //
    // 棄用:驅動應實現StmtQueryContext來替代
    Query(args []Value) (Rows, error)
}

// StmtExecContext升級了Stmt接口,它提供了一個有context的Exec,
type StmtExecContext interface {
    // ExecContext執行一個不返回數據行的查詢,好比INSERT或UPDATE
    //
    // ExecContext必須遵照context超時,當context被取消時,函數須要返回
    ExecContext(ctx context.Context, args []NamedValue) (Result, error)
}

// StmtQueryContext升級了Stmt接口,它提供了一個有context的Query
type StmtQueryContext interface {
    // QueryContext執行一個返回數據行的查詢,好比SELECT
    //
    // ExecContext必須遵照context超時,當context被取消時,函數須要返回
    QueryContext(ctx context.Context, args []NamedValue) (Rows, error)
}

// ErrRemoveArgument可能被NamedValueChecker返回,用來指示sql包不要
// 給驅動的query接口傳遞這個參數。
// 當接收到不是查詢參數的特定屬性或結構時,返回該錯誤
var ErrRemoveArgument = errors.New("driver: remove argument from query")

// Conn或Stmt可選擇是否要實現NamedValueChecker接口。接口提供給驅動
// 更多的控制,去處理超出Go和數據庫容許的默認值類型。
//
// 對於值的檢查對象,sql包按以下順序進行檢查。當第一次發現匹配時中止:
// Stmt.NamedValueChecker, Conn.NamedValueChecker, Stmt.ColumnConverter,
// DefaultParameterConverter.
//
// 若是CheckNamedValue返回ErrRemoveArgument錯誤,那麼這個NamedValue將不會
// 被包含在最終的查詢參數中。這可能會被用於給查詢傳遞特殊的選項。
//
// 若是ErrSkip錯誤被返回,則使用列轉換器錯誤檢查路徑做爲參數。
// 驅動可能但願在耗盡本身特殊case後返回ErrSkip
type NamedValueChecker interface {
    // 在傳遞參數給驅動以前,CheckNamedValue會被調用。
    // 在任何ColumnConverter的地方也會調用CheckNamedValue。
    // CheckNamedValue必須根據驅動的須要作類型校驗和轉換
    CheckNamedValue(*NamedValue) error
}

// 若是語句知道本身列的類型,而且可以從任何類型轉換爲驅動的Value,
// 那麼Stmt 能夠選擇性的實現ColumnConverter
// 
// 棄用:驅動應實現NamedValueChecker
type ColumnConverter interface {
    // 根據提供的列序號,ColumnConverter返回一個 ValueConverter。
    // 若是該列是未知的或者不須要被特殊處理,方法返回DefaultValueConverter
    ColumnConverter(idx int) ValueConverter
}

// Rows是一個查詢結果的迭代器
type Rows interface {
    // Columns返回列的名字集,它的個數是從slice的長度中推斷出來的。
    // 若是不知道特定的列名,應該爲該條目返回一個空的字符串
    Columns() []string

    // 關閉行迭代器
    Close() error

    // Next用於把數據集中的下一行填入提供的slice中。該slice的
    // 長度應該跟Columns()的長度一致
    //
    // 當沒有數據行時,Next應該返回io.EOF
    // 
    // dest不該被聲明在Next以外(應該做爲Next的一個成員變量)
    // 關閉Rows時應特別注意,不要修改dest中緩衝區的值
    Next(dest []Value) error
}

// RowsNextResultSet擴展了Rows接口,它提供了一個方式,讓驅動向前移動
// 到下一個結果集
type RowsNextResultSet interface {
    Rows

    // 在當前結果集的末尾調用HasNextResultSet,報告當前結果集以後是否還存在
    // 別的結果集
    HasNextResultSet() bool

    // NextResultSet向前移動驅動到下一個結果集,即便當前結果集
    // 仍然存在剩餘的數據行
    //
    // 當再也不有數據集時,NextResultSet應該返回io.EOF
    NextResultSet() error
}

// RowsColumnTypeScanType能夠經過Rows實現,它應該返回可用於掃描的數據類型。
// 好比,數據庫的列類型`bigint`應該返回"reflect.TypeOf(int64(0))".
type RowsColumnTypeScanType interface {
    Rows
    ColumnTypeScanType(index int) reflect.Type
}

// RowsColumnTypeDatabaseTypeName能夠經過Rows實現。它應該返回不包括字段長度的數據庫類型,
// 類型名應該全大寫。諸如:"VARCHAR", "NVARCHAR", "VARCHAR2", "CHAR", "TEXT",
// "DECIMAL", "SMALLINT", "INT", "BIGINT", "BOOL", "[]BIGINT", "JSONB", "XML",
// "TIMESTAMP".
type RowsColumnTypeDatabaseTypeName interface {
    Rows
    ColumnTypeDatabaseTypeName(index int) string
}

// RowsColumnTypeLength能夠經過Rows實現,若是列是可變長度的類型,它應該返回
// 類型的長度。若是列是不可變長度的類型,ok返回false。
// 若是類型長度只受系統限制,則應該返回math.MaxInt64
// 下面是變量類型的返回值示例
//   TEXT          (math.MaxInt64, true)
//   varchar(10)   (10, true)
//   nvarchar(10)  (10, true)
//   decimal       (0, false)
//   int           (0, false)
//   bytea(30)     (30, true)
type RowsColumnTypeLength interface {
    Rows
    ColumnTypeLength(index int) (length int64, ok bool)
}

// RowsColumnTypeNullable能夠經過Rows實現。若是指定的列能夠爲空,則nullable
// 返回true。相反,若是列不能爲空,nullable應該返回false。
// 若是不知道該列是否能夠爲空,ok應該返回false
type RowsColumnTypeNullable interface {
    Rows
    ColumnTypeNullable(index int) (nullable, ok bool)
}

// RowsColumnTypePrecisionScale能夠被Rows實現,對於decimal類型,它應該返回
// 精度和小數點右邊的範圍。若是類型不適用,ok應返回false
// 下面是不一樣類型的返回值示例:
//   decimal(38, 4)    (38, 4, true)
//   int               (0, 0, false)
//   decimal           (math.MaxInt64, math.MaxInt64, true)
type RowsColumnTypePrecisionScale interface {
    Rows
    ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool)
}

// Tx是一個事務
type Tx interface {
    Commit() error
    Rollback() error
}

// RowsAffected實現了INSERT或UPDATE操做的結果
// 表示被影響的行數
type RowsAffected int64

var _ Result = RowsAffected(0)

func (RowsAffected) LastInsertId() (int64, error) {
    return 0, errors.New("LastInsertId is not supported by this driver")
}

func (v RowsAffected) RowsAffected() (int64, error) {
    return int64(v), nil
}

// ResultNoRows是一個預約義的結果,在一個DDL操做(好比CREATE TABLE)執行成功
// 時返回。調用該類型的LastInsertId和LastInsertId方法會返回錯誤
var ResultNoRows noRows

type noRows struct{}

var _ Result = noRows{}

func (noRows) LastInsertId() (int64, error) {
    return 0, errors.New("no LastInsertId available after DDL statement")
}

func (noRows) RowsAffected() (int64, error) {
    return 0, errors.New("no RowsAffected available after DDL statement")
}
相關文章
相關標籤/搜索