Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Зачем и как написать свой database/sql драйвер Алексей Палажченко Percona

Slide 3

Slide 3 text

Зачем? • Написание нового драйвера для SQL СУБД • или исправление существующего • Написание «наддрайвера» • для observability и отладки • Написание драйвера для NoSQL СУБД • или даже не для СУБД

Slide 4

Slide 4 text

Зачем? • Написание нового драйвера для SQL СУБД • https://github.com/AlekSi/mysqlx • Написание «наддрайвера» • https://github.com/luna-duclos/instrumentedsql • Написание драйвера для NoSQL СУБД • https://github.com/AlekSi/sqlcsv

Slide 5

Slide 5 text

Как?

Slide 6

Slide 6 text

Как?

Slide 7

Slide 7 text

• func IsScanValue(v interface{}) bool • func IsValue(v interface{}) bool • type ColumnConverter • type Conn • type Driver • type Execer • type NotNull • func (n NotNull) ConvertValue(v interface{}) (Value, error) • type Null • func (n Null) ConvertValue(v interface{}) (Value, error) • type Result • type Rows • type RowsAffected • func (RowsAffected) LastInsertId() (int64, error) • func (v RowsAffected) RowsAffected() (int64, error) • type Stmt • type Tx • type Value • type ValueConverter • type Valuer

Slide 8

Slide 8 text

• func IsScanValue(v interface{}) bool • func IsValue(v interface{}) bool • type ColumnConverter • type Conn • type ConnBeginTx • type ConnPrepareContext • type Connector • type Driver • type DriverContext • type Execer • type ExecerContext • type IsolationLevel • type NamedValue • type NamedValueChecker • type NotNull • func (n NotNull) ConvertValue(v interface{}) (Value, error) • type Null • func (n Null) ConvertValue(v interface{}) (Value, error) • type Pinger • type Queryer • type QueryerContext • type Result • type Rows • type RowsAffected • func (RowsAffected) LastInsertId() (int64, error) • func (v RowsAffected) RowsAffected() (int64, error) • type RowsColumnTypeDatabaseTypeName • type RowsColumnTypeLength • type RowsColumnTypeNullable • type RowsColumnTypePrecisionScale • type RowsColumnTypeScanType • type RowsNextResultSet • type SessionResetter • type Stmt • type StmtExecContext • type StmtQueryContext • type Tx • type TxOptions • type Value • type ValueConverter • type Valuer

Slide 9

Slide 9 text

3 источника знаний • Код database/sql и database/sql/driver • Код существующих драйверов • но полно плохого и старого • Кровь, пот и падающий прод

Slide 10

Slide 10 text

Driver // Database drivers
 // may implement DriverContext […]
 type Driver interface {
 Open(name string) (Conn, error)
 }

Slide 11

Slide 11 text

Driver type driver struct{}

Slide 12

Slide 12 text

Conn type Conn interface {
 Prepare(query string) (Stmt, error)
 Close() error
 
 // Deprecated: Drivers should
 // implement ConnBeginTx instead
 // (or additionally).
 Begin() (Tx, error)
 }

Slide 13

Slide 13 text

Conn type conn struct {
 transport net.Conn
 }

Slide 14

Slide 14 text

Stmt type Stmt interface {
 Close() error
 
 // NumInput may also return -1 […]
 NumInput() int
 
 // Deprecated: Drivers should implement
 // StmtExecContext instead (or additionally).
 Exec(args []Value) (Result, error)
 // Deprecated: Drivers should implement
 // StmtQueryContext instead (or additionally).
 Query(args []Value) (Rows, error)
 }

Slide 15

Slide 15 text

Stmt type stmt struct {
 conn *conn
 query string
 }

Slide 16

Slide 16 text

Value Value is a value that drivers must be able to handle. It is either nil, a type handled by a database driver's NamedValueChecker interface, or an instance of one of these types:
 int64
 float64
 bool
 []byte
 string
 time.Time type Value interface{}

Slide 17

Slide 17 text

Result type Result interface {
 LastInsertId() (int64, error)
 RowsAffected() (int64, error)
 }

Slide 18

Slide 18 text

Rows type Rows interface {
 Columns() []string
 Close() error
 Next(dest []Value) error
 }

Slide 19

Slide 19 text

ErrBadConn ErrBadConn should be returned by a driver to signal to the sql package that a driver.Conn is in a bad state […] and the sql package should retry on a new connection. var ErrBadConn = errors.New("[…]")

Slide 20

Slide 20 text

Rows type rows struct {
 conn *conn
 // […]
 }

Slide 21

Slide 21 text

Минимум • driver.Open(name string) (Conn, error) • conn.Prepare(query string) (Stmt, error) • stmt.Exec(args []Value) (Result, error) • stmt.Query(args []Value) (Rows, error) • rows.Next(dest []Value) error

Slide 22

Slide 22 text

QueryerContext If a Conn does not implement QueryerContext […] DB.Query will first prepare a query, execute the statement, and then close the statement. QueryerContext may return ErrSkip. type QueryerContext interface {
 QueryContext(context.Context, string,
 []NamedValue) (Rows, error)
 }

Slide 23

Slide 23 text

ErrSkip ErrSkip may be returned by some optional interfaces' methods to indicate at runtime that the fast path is unavailable and the sql package should continue as if the optional interface was not implemented. ErrSkip is only supported where explicitly documented. var ErrSkip = errors.New("[…]")

Slide 24

Slide 24 text

NamedValue type NamedValue struct {
 Name string
 Ordinal int
 Value Value
 }

Slide 25

Slide 25 text

NamedValueChecker type NamedValueChecker interface {
 CheckNamedValue(*NamedValue) error
 }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Valuer и Scanner type JSON interface{} func (j JSON) Value() (driver.Value, error)
 func (j *JSON) Scan(value interface{}) error var _ driver.Valuer = JSON{}
 var _ sql.Scanner = &JSON{}

Slide 28

Slide 28 text

Connector type Connector interface {
 Connect(context.Context) (Conn, error)
 Driver() Driver
 }

Slide 29

Slide 29 text

Connector type Config struct {
 tlsConfig *tls.Config
 // […]
 } func (c *Config) Connect(ctx) (Conn, error)

Slide 30

Slide 30 text

SessionResetter type SessionResetter interface {
 ResetSession(ctx context.Context) error
 }

Slide 31

Slide 31 text

SessionResetter db.SetMaxIdleConns(1)
 db.SetMaxOpenConns(1)
 db.SetConnMaxLifetime(0)

Slide 32

Slide 32 text

Наддрайверы

Slide 33

Slide 33 text

Зачем наддрайверы? • Логирование • Метрики • Профилирование • Трейсинг • Отладка

Slide 34

Slide 34 text

Опциональные интерфейсы func (c wrappedConn) Query(query string,
 args []driver.Value)
 (driver.Rows, error) {
 
 queryer, ok := c.parent.(driver.Queryer)
 if ok {
 return queryer.Query(query, args)
 }
 // ???

Slide 35

Slide 35 text

Опциональные интерфейсы func (c wrappedConn) Query(query string,
 args []driver.Value)
 (driver.Rows, error) {
 
 queryer, ok := c.parent.(driver.Queryer)
 if ok {
 return queryer.Query(query, args)
 }
 return nil, driver.ErrSkip

Slide 36

Slide 36 text

Опциональные интерфейсы • reflect • слишком много багов :( • Кодогенерация • только для конкретного драйвера • Реализация как в database/sql • SessionResetter – возвращаем ErrBadConn

Slide 37

Slide 37 text

Context // Deprecated: Drivers should implement ExecerContext instead.
 type Execer interface {
 Exec(string, []Value) (Result, error)
 } type ExecerContext interface {
 ExecContext(ctx, string, []NamedValue)
 (Result, error)
 }

Slide 38

Slide 38 text

Самый главный совет

Slide 39

Slide 39 text

Тестируйте • На реальных системах, с минимумом моков • На всех комбинациях Go и версий систем • Docker Compose отлично для этого подходит

Slide 40

Slide 40 text

Спасибо! Вопросы?

Slide 41

Slide 41 text

GopherCon Russia 2019 13 апреля, Москва gophercon-russia.ru