Skip to content

Latest commit

 

History

History
211 lines (135 loc) · 13 KB

File metadata and controls

211 lines (135 loc) · 13 KB

5.1 Интерфейс database/sql

Как я уже говорил, Go не предоставляет официальных драйверов баз данных, как это делает, например, PHP, но у него есть некоторые стандарты интерфейсов драйверов для разработчиков драйверов баз данных. Если ваш код будет разрабатываться в соответствии с этими стандартами, вам не придется его менять в случае изменения базы данных. Давайте посмотрим, что представляют из себя эти стандарты интерфейсов.

sql.Register

Эта функция из пакета database/sql обеспечивает регистрацию драйверов баз данных сторонних разработчиков. Все сторонние драйвера должны вызывать функцию Register(name string, driver driver.Driver) в функции init() для того чтобы обеспечить регистрацию драйвера.

Давайте посмотрим на соответствующий код в драйверах mymysql и sqlite3:

//https://github.com/mattn/go-sqlite3 driver
func init() {
    sql.Register("sqlite3", &SQLiteDriver{})
}

//https://github.com/mikespook/mymysql driver
// Driver automatically registered in database/sql
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
func init() {
    Register("SET NAMES utf8")
    sql.Register("mymysql", &d)
}

Мы видим, что все сторонние драйвера вызывают данную функцию для саморегистрации. При этом Go использует карту для сохранения информации о пользовательском драйвере внутри database/sql.

var drivers = make(map[string]driver.Driver)

drivers[name] = driver

Таким образом, данная функция может зарегистрировать любое количество драйверов с разными именами.

Если посмотреть примеры использования сторонних драйверов, мы всегда увидим следующий код:

import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)

Бланк _, в примере, приводит в замешательство многих начинающих программистов, но он является примером отличного дизайна языка Go. Как вы знаете, этот идентификатор предназначен для удаления значений из функции return. Так же вы должны знать, что вы должны использовать все импортированные пакеты в коде. В данном случае, использование бланка при импорте означает, что вам необходимо исполнить функцию init() данного пакета без его прямого использования, которая, в свою очередь, используется для регистрации драйвера базы данных.

driver.Driver

Driver - это интерфейс, который имеет метод Open(name string) который возвращает интерфейс Conn.

type Driver interface {
    Open(name string) (Conn, error)
}

Conn - это одноразовое соединение. Это означает, что его можно использовать только один раз в одной горутине. Следующй код вызовет исключение:

...
go goroutineA (Conn)  // запрос
go goroutineB (Conn)  // вставка
...

потому, что Go не имеет представления в какой горутине выполняется операция. Операция "запрос" может получить результаты "вставки" и наоборот.

Все сторонние драйверы должны реализовывать эту функцию для разбора имени соединения и корректного возврата результатов.

driver.Conn

Driver.Conn - это интерфейс подключения базы данных с несколькими методами.

type Conn interface {
    Prepare(query string) (Stmt, error)
    Close() error
    Begin() (Tx, error)
}
  • Prepare - возвращает статус подготовки соответствующих команд SQL для запроса, удаления и т.д.
  • Close - закрывает текущее соединение и освобождает ресурсы. Большинство сторонних драйверов реализуют пулы соединений самостоятельно, поэтому вам не стоит подключать кэш соединений если вы не хотите иметь неожиданных ошибок.
  • Begin - запускает и возвращает дескриптор новой транзакции Tx, вы можете использовать его для запросов, апдейтов, отката транзакций и т.д.

driver.Stmt

Stmt - это интерфейс состояния готовности, соответствующий данному соединению, поэтому он может быть использован только в одной горутине, как и Conn.

type Stmt interface {
    Close() error
    NumInput() int
    Exec(args []Value) (Result, error)
    Query(args []Value) (Rows, error)
}
  • Close - закрывает текущее соединение, но по прежнему возвращает строки данных, если запрос продолжает выполнятся.
  • NumInput - возвращает число обязательных аргументов, драйвер базы данных должен проверить аргументы вызывающей стороны и вернуть результат больше 0 и -1 в случае, если драйвер не смог определить количество аргументов.
  • Exec выполняет SQL - команды update/insert, которые были подготовлены в Prepare. Возвращает Result.
  • Query - выполняет SQL - команды select, которые были подготовлены в Prepare. Возвращает Result.

driver.Tx

Данный интерфейс, как правило, управляет операциями commit и roll back. Драйвер базы данных должен реализовать эти два метода.

type Tx interface {
    Commit() error
    Rollback() error
}

driver.Execer

Это необязательный интерфейс.

type Execer interface {
    Exec(query string, args []Value) (Result, error)
}

Если драйвер не реализует этот интерфейс, то при вызове DB.Exec, он автоматически вызывает Prepare и возвращает Stmt, выполняет Exec из Stmt затем закрывает Stmt.

driver.Result

Этот интерфейс возвращает результаты операций update/insert.

type Result interface {
    LastInsertId() (int64, error)
    RowsAffected() (int64, error)
}
  • LastInsertId - возвращает автоинкрементный Id после операций вставки в базу данных.
  • RowsAffected - возвращает номера строк, затронутых операцией update/insert.

driver.Rows

driver.Rows - это интерфейс для возвращения результатов набора операций запроса.

type Rows interface {
    Columns() []string
    Close() error
    Next(dest []Value) error
}
  • Columns - возвращает информацию о наименовании столбцов таблицы.
  • Close - закрывает итератор строк.
  • Next - возвращает следующее поле таблицы и связывает с dest, все строки должны быть преобразованы к массиву байт. Если доступные данные закончились, возникает ошибка io.EOF.

diriver.RowsAffected

Данный тип является псевдонимом int64 для одноименного метода реализованного в интерфейсе Result.

type RowsAffected int64

func (RowsAffected) LastInsertId() (int64, error)

func (v RowsAffected) RowsAffected() (int64, error)

driver.Value

Это пустой интерфейс, который может содержать любой тип данных.

type Value interface{}

Value должно быть содержать значения, совместимые с драйвером, или быть nil, поэтому оно должно быть одним из следующих типов:

int64
float64
bool
[]byte
string   [*] За исключением `Rows.Next`, который не может возвращать строку.
time.Time

driver.ValueConverter

ValueConverter - это интерфейс для преобразования наших данных к типам driver.Value.

type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}

Обычно он используется в драйверах баз данных и предоставляет массу полезных особенностей:

  • Преобразует driver.Value в соответствующее типу поля базы данных. Например преобразует int64 в uint16.
  • Преобразует результаты запросов к driver.Value.
  • Преобразует driver.Value к определенному пользователем значению в функции scan.

driver.Valuer

Valuer - определяет интерфейс для возврата driver.Value.

type Valuer interface {
    Value() (Value, error)
}

Многие типы реализуют этот интерфейс для преобразования типов между собой и driver.Value.

На данном этапе вы должны иметь представление о том, как разработать драйвер базы данных. После того, как вы реализуете интерфейсы для различных операций, таких как добавление, удаление, обновление и так далее, останется решить проблему коммуникации с конкретной базой данных.

database/sql

database/sql определяет высокоуровневые методы для более удобной работы с базами данных (выше чем драйвера) и предлагает вам реализовать пул соединений.

type DB struct {
    driver   driver.Driver
    dsn      string
    mu       sync.Mutex // защищает и закрывает freeConn
    freeConn []driver.Conn
    closed   bool
}   

Как вы видите, функция Open возвращает тип DB, который содержит freeConn, - это и есть простой пулл. Его реализация очень проста и несколько уродлива. Он использует defer db.putConn(ci, err) в функции Db.prepare для помещения соединения в пулл соединений. Каждый раз, когда вызывается функция Conn, он проверяет длину freeConn и, если она больше нуля, - это означает, что соединение можно повторно использовать и оно напрямую возвращается вам. В противном случае он создает новое соединение и возвращает его.

Ссылки