Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go Conference mini in Sendai 2026: database/sql...

Avatar for natsumi natsumi
February 20, 2026
640

Go Conference mini in Sendai 2026: database/sql/driverを理解してカスタムデータベースドライバーを作る

Avatar for natsumi

natsumi

February 20, 2026
Tweet

Transcript

  1. © 2024 ANDPAD All Rights Reserved. 2 自己紹介 小島 夏海

    (こじま なつみ) : replu : replu5 • アンドパッドで社内向けの通知基盤の開発・運用 • ブースがあるのでぜひ来てね
  2. © 2024 ANDPAD All Rights Reserved. セッション内容 3 • セッションで得られるもの

    ◦ database/sql パッケージ と database/sql/driver パッケージの関係 ◦ database/sql/driverについての理解 ◦ 既存のドライバーをカスタマイズしたドライバーを作る方法 ◦ 自作ドライバーの実装が問題ないか確認する方法 • セッションで得られないもの ◦ 0からドライバーを実装する方法
  3. © 2024 ANDPAD All Rights Reserved. カスタムドライバーってなに 5 • 既存のデータベースドライバーに機能を追加したドライバー

    ◦ 例えば ▪ DB操作時にログを出力したい ▪ クエリによってwriter/readerどちらにクエリを発行するか切り替えたい • DBとやりとりする部分は基本的に既存ドライバーに任せる database/sql 既存ドライバー DB カスタムドライバー
  4. © 2024 ANDPAD All Rights Reserved. なぜカスタムドライバーを作りたいのか 6 • DB操作すべてを起因として実行したい処理は共通化して管理したい

    ◦ アプリケーション側のDB操作を実行するすべての箇所で実装するのは面倒 • 既存のドライバー・DBライブラリがサポートしていない機能を ドライバーで実行したい ◦ 例えばsqlcを使用し、MySQLに対してリクエスト実行時にログを出力したい ▪ MySQLのドライバーはログ出力をサポートしていない ▪ sqlc pluginという手段もあるがgoのコード生成部分はinternal ディレクトリ内 にあり、既存コードが活用できない • 既存のドライバー・DBライブラリがサポートしていない機能を 追加したい時にどうするか ◦ PRを送る ▪ 汎用的な需要がある機能ならよさそう • 既存機能への影響がある場合などはマージが難しいこともありえる ◦ 既存ドライバーに機能を追加したカスタムドライバーを作成する ▪ ニッチな課題やドメイン固有の課題の解決には向いている •
  5. © 2024 ANDPAD All Rights Reserved. どうやって既存ドライバーに機能を追加していくか 7 • forkする

    ◦ 本体に追従するコストが大きい ◦ ドライバー本体に対する深い理解が必要 • 既存のドライバーをラップしたドライバー(カスタムドライバー)を作成する ◦ DBとやりとりする部分は基本的に既存ドライバーに任せる ◦ ラップ部分は共通のため複数のドライバーに対応可能 ◦ DB操作に関してのテストを既存ドライバーとカスタムドライバーの両方で 実行することで既存ドライバーのバージョンアップの影響がないかは確認可能 database/sql 既存ドライバー DB カスタムドライバー
  6. © 2024 ANDPAD All Rights Reserved. GoにおけるDB操作の構成 8 db, err

    := sql.Open("mysql", os.Getenv("DataSource")) rows, err := db.QueryContext(ctx, "SELECT name FROM users") db, err := sql.Open("postgres", os.Getenv("DataSource")) rows, err := db.QueryContext(ctx, "SELECT name FROM users") db, err := sql.Open("sqlite3", os.Getenv("DataSource")) rows, err := db.QueryContext(ctx, "SELECT name FROM users") go-sql-driver/mysql の場合 mattn/go-sqlite3 の場合 lib/pq の場合 8
  7. © 2024 ANDPAD All Rights Reserved. GoにおけるDB操作の構成 >> go tool

    doc database/sql Open package sql // import "database/sql" func Open(driverName, dataSourceName string) (*DB, error) Open opens a database specified by its database driver name and a driver-specific data source name, usually consisting of at least a database name and connection information.  日本語訳 Open関数は、データベースドライバ名と、ドライバ固有のデータソース名(通常、少なくとも データベース名と接続情報で構成されます)を指定することで、データベースを開きます。 9
  8. © 2024 ANDPAD All Rights Reserved. GoにおけるDB操作の構成 10 ああ Application

    Code ああ database/sql ああ go-sql-driver/mysql ああ lib/pq ああ mattn/go-sqlite3
  9. © 2024 ANDPAD All Rights Reserved. GoにおけるDB操作の構成 11 ああ Application

    Code ああ database/sql ああ go-sql-driver/mysql ああ lib/pq ああ mattn/go-sqlite3 database/sql/driver パッケージに interfaceが定義されている
  10. © 2024 ANDPAD All Rights Reserved. database/sql パッケージとは 12 •

    データベース操作の統一的な抽象化レイヤーを提供 • コネクションプール管理 • 型変換(driver.Value ⇔ Go型) • 詳しくは Go Conference 2022 Springでsivchariさんが発表している 「database/sqlパッケージを理解する」[1]を参照 [1] https://gocon.jp/2022spring/sessions/a8-s/
  11. © 2024 ANDPAD All Rights Reserved. database/sql/driver パッケージとは 13 •

    データベース ドライバが実装すべきインターフェイスを定義 • database/sql パッケージはこのインターフェイスを介してドライバと やり取りする ああ database/sql ああ go-sql-driver/mysql ああ lib/pq ああ mattn/go-sqlite3 database/sql/driver パッケージに interfaceが定義されている
  12. © 2024 ANDPAD All Rights Reserved. >> go doc database/sql/driver

    | grep type | grep interface database/sql/driver に定義されている interface 15 type ColumnConverter interface{ ... } type Conn interface{ ... } type ConnBeginTx interface{ ... } type ConnPrepareContext interface{ ... } type Connector interface{ ... } type Driver interface{ ... } type DriverContext interface{ ... } type Execer interface{ ... } type ExecerContext interface{ ... } type NamedValueChecker interface{ ... } type Pinger interface{ ... } type Queryer interface{ ... } type QueryerContext interface{ ... } type Result interface{ ... } type Rows interface{ ... } type RowsColumnTypeDatabaseTypeName interface{ ... } type RowsColumnTypeLength interface{ ... } type RowsColumnTypeNullable interface{ ... } type RowsColumnTypePrecisionScale interface{ ... } type RowsColumnTypeScanType interface{ ... } type RowsNextResultSet interface{ ... } type SessionResetter interface{ ... } type Stmt interface{ ... } type StmtExecContext interface{ ... } type StmtQueryContext interface{ ... } type Tx interface{ ... } type Validator interface{ ... } type ValueConverter interface{ ... } type Valuer interface{ ... }
  13. © 2024 ANDPAD All Rights Reserved. >> go doc database/sql/driver

    | grep type | grep interface database/sql/driver に定義されている interface 16 type ColumnConverter interface{ ... } type Conn interface{ ... } type ConnBeginTx interface{ ... } type ConnPrepareContext interface{ ... } type Connector interface{ ... } type Driver interface{ ... } type DriverContext interface{ ... } type Execer interface{ ... } type ExecerContext interface{ ... } type NamedValueChecker interface{ ... } type Pinger interface{ ... } type Queryer interface{ ... } type QueryerContext interface{ ... } type Result interface{ ... } type Rows interface{ ... } type RowsColumnTypeDatabaseTypeName interface{ ... } type RowsColumnTypeLength interface{ ... } type RowsColumnTypeNullable interface{ ... } type RowsColumnTypePrecisionScale interface{ ... } type RowsColumnTypeScanType interface{ ... } type RowsNextResultSet interface{ ... } type SessionResetter interface{ ... } type Stmt interface{ ... } type StmtExecContext interface{ ... } type StmtQueryContext interface{ ... } type Tx interface{ ... } type Validator interface{ ... } type ValueConverter interface{ ... } type Valuer interface{ ... } 非推奨
  14. © 2024 ANDPAD All Rights Reserved. いつから定義が存在していたか 17 interface名 追加された

    バージョン Conn 1 ConnBeginTx 1.8 ConnPrepareContext 1.8 Connector 1.10 Driver 1 DriverContext 1.10 ExecerContext 1.8 NamedValueChecker 1.9 Pinger 1.8 QueryerContext 1.8 Result 1 Rows 1 interface名 追加された バージョン RowsColumnTypeDatabaseTypeName 1.8 RowsColumnTypeLength 1.8 RowsColumnTypeNullable 1.8 RowsColumnTypePrecisionScale 1.8 RowsColumnTypeScanType 1.8 RowsNextResultSet 1.8 SessionResetter 1.10 Stmt 1 StmtExecContext 1.8 StmtQueryContext 1.8 Tx 1 Validator 1.15 ValueConverter 1 Valuer 1
  15. © 2024 ANDPAD All Rights Reserved. いつから定義が存在していたか 18 interface名 追加された

    バージョン Conn 1 ConnBeginTx 1.8 ConnPrepareContext 1.8 Connector 1.10 Driver 1 DriverContext 1.10 ExecerContext 1.8 NamedValueChecker 1.9 Pinger 1.8 QueryerContext 1.8 Result 1 Rows 1 interface名 追加された バージョン RowsColumnTypeDatabaseTypeName 1.8 RowsColumnTypeLength 1.8 RowsColumnTypeNullable 1.8 RowsColumnTypePrecisionScale 1.8 RowsColumnTypeScanType 1.8 RowsNextResultSet 1.8 SessionResetter 1.10 Stmt 1 StmtExecContext 1.8 StmtQueryContext 1.8 Tx 1 Validator 1.15 ValueConverter 1 Valuer 1
  16. © 2024 ANDPAD All Rights Reserved. DB操作に関わるもの • Driver :

    ドライバーの起点、DSN文字列から接続を1本生成するエントリポイント • Conn : DBとの接続を表す、ステートメント準備・トランザクション開始・切断を担う • Stmt : プリペアドステートメントの実行と結果取得 • Result : INSERT/UPDATE/DELETE の実行結果 • Rows : 結果セットのイテレーション • Tx : トランザクションのコミット・ロールバック 型変換に関わるもの • ValueConverter : 任意の値を driver.Value に変換 • Valuer : ユーザー定義型が自身を driver.Value に変換するために実装 database/sql/driver のinterface (go1) 19
  17. © 2024 ANDPAD All Rights Reserved. 各interface間の関係 20 Driver Conn

    Tx Stmt Result Rows Open Begin Prepare Exec Query sql.Open
  18. © 2024 ANDPAD All Rights Reserved. 各interface間の関係 21 Driver Conn

    Tx Stmt Result Rows Open Begin Prepare Exec Query Exec Query Connector OpenConnector Connect sql.Open sql.OpenDB
  19. © 2024 ANDPAD All Rights Reserved. Driver interfaceに関連するinterface 22 Drivers

    should implement Connector and DriverContext interfaces. If a Driver implements DriverContext, then database/sql.DB will call OpenConnector to obtain a Connector and then invoke that Connector's Connect method to obtain each needed connection, instead of invoking the Driver's Open method for each connection. A Connector can be passed to database/sql.OpenDB, to allow drivers to implement their own database/sql.DB constructors, or returned by DriverContext's OpenConnector method, to allow drivers access to context and to avoid repeated parsing of driver configuration.
  20. © 2024 ANDPAD All Rights Reserved. Driver interfaceに関連するinterface 23 日本語約

    ドライバーは、Connector interface とDriverContext interfaceを実装す る必要があります。 もしドライバーがDriverContextを実装している場合、database/sql.DBは OpenConnectorを呼び出してConnectorを取得し、そのConnectorの Connectメソッドを使って必要な接続をそれぞれ確立します。この際、ドラ イバーのOpenメソッドが各接続で呼び出されることはありません。 Connectorは、database/sql.OpenDB に渡すことで、ドライバが独自の database/sql.DB コンストラクタを実装できるようになります。また、 DriverContext の OpenConnector メソッドから返されることで、ドライ バがコンテキストにアクセスできるようになり、ドライバ設定の繰り返し解 析を回避できます。
  21. © 2024 ANDPAD All Rights Reserved. Driver interfaceに関連するinterface まとめ 24

    • Driver ◦ sql.Openで使用される ◦ DSN文字列から接続を1本生成するエントリポイント • DriverContext ◦ sql.Openで使用される ◦ sql.Openの中でOpenConnectorメソッドを呼び出し、Driver.Connectorを取得し、 そのConnectorのConnectメソッドを使って必要な接続を確立する ◦ ドライバが接続時にコンテキストにアクセスできるようになり、ドライバ設定の解析が 一度だけで済むようにできる • Connector ◦ sql.OpenDBで使用される ◦ Driverの実装次第で接続方法としてDSN文字列以外もサポートできる ◦ ドライバが接続時にコンテキストにアクセスできるようになり、ドライバ設定の解析が 一度だけで済むようにできる
  22. © 2024 ANDPAD All Rights Reserved. Conn interfaceに関連するinterface 25 All

    Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator. If named parameters or context are supported, the driver's Conn should implement: ExecerContext, QueryerContext, ConnPrepareContext, and ConnBeginTx. To support custom data types, implement NamedValueChecker.
  23. © 2024 ANDPAD All Rights Reserved. Conn interfaceに関連するinterface 26 日本語約

    すべてのConn実装は、Pinger、SessionResetter、Validatorの各 interfaceを実装する必要があります。 名前付きパラメータまたはコンテキストをサポートしている場合、ドライバ のConnはExecerContext、QueryerContext、ConnPrepareContext、 ConnBeginTxを実装する必要があります。 カスタムデータ型をサポートするには、NamedValueCheckerを実装してく ださい。
  24. © 2024 ANDPAD All Rights Reserved. Conn interfaceに関連するinterface まとめ 27

    • Conn: DBとの接続を表す、ステートメント準備・トランザクション開始・ 切断を担う • 実装が必須のもの ◦ Pinger: sql.DB.Ping・sql.DB.PingContext をサポート ◦ SessionResetter: 接続に関連付けられたセッションのリセットをサポート ◦ Validator: 接続が有効か・破棄する必要があるかを確認し、結果をドライバーが通知可能 に • オプショナル ◦ ExecerContext: コンテキストサポートのExec ◦ QueryerContext: コンテキストサポートのQuery ◦ ConnPrepareContext: コンテキストサポートのPrepare ◦ ConnBeginTx: コンテキストサポートのTx ◦ NamedValueChecker: カスタムデータ型をサポート
  25. © 2024 ANDPAD All Rights Reserved. Stmt interfaceに関連するinterface 28 To

    support custom data types, implement NamedValueChecker. StmtExecContext enhances the Stmt interface by providing Exec with context. StmtQueryContext enhances the Stmt interface by providing Query with context.
  26. © 2024 ANDPAD All Rights Reserved. Stmt interfaceに関連するinterface 29 日本語訳

    カスタムデータ型をサポートするには、NamedValueCheckerを実装してく ださい。 StmtExecContext は、Exec メソッドにコンテキストを提供することで、 Stmt interfaceを拡張します。 StmtQueryContext は、Query メソッドにコンテキストを提供すること で、Stmt interfaceを拡張します。
  27. © 2024 ANDPAD All Rights Reserved. Stmt interfaceに関連するinterface まとめ 30

    • Stmt : プリペアドステートメントの実行と結果取得 • オプショナル ◦ StmtExecContext: コンテキストサポートのExec ◦ StmtQueryContext: コンテキストサポートのQuery ◦ NamedValueChecker: カスタムデータ型をサポート
  28. © 2024 ANDPAD All Rights Reserved. Rows interfaceに関連するinterface 31 If

    multiple result sets are supported, Rows should implement RowsNextResultSet. If the driver knows how to describe the types present in the returned result it should implement the following interfaces: RowsColumnTypeScanType, RowsColumnTypeDatabaseTypeName, RowsColumnTypeLength, RowsColumnTypeNullable, and RowsColumnTypePrecisionScale. A given row value may also return a Rows type, which may represent a database cursor value.
  29. © 2024 ANDPAD All Rights Reserved. Rows interfaceに関連するinterface 32 日本語訳

    複数の結果セットがサポートされている場合、RowsはRowsNextResultSet を実装する必要があります。返された結果に含まれる型をドライバーが認識 している場合は、RowsColumnTypeScanType、 RowsColumnTypeDatabaseTypeName、RowsColumnTypeLength、 RowsColumnTypeNullable、およびRowsColumnTypePrecisionScaleの interfaceを実装する必要があります。また、特定の行値はRows型を返すこ とがあり、これはデータベースのカーソル値を表す場合があります。
  30. © 2024 ANDPAD All Rights Reserved. Rows interfaceに関連するinterface まとめ 33

    • Rows : 結果セットのイテレーション • オプショナル ◦ RowsNextResultSet: 複数結果セットサポート ◦ 結果に含まれる型に関するもの ▪ RowsColumnTypeScanType: 型をスキャンするために使用する適切なGoの型を返す ▪ RowsColumnTypeDatabaseTypeName: DB固有の型名(例: "BIGINT")を返す ▪ RowsColumnTypeLength: 可変長カラムの最大長を返す ▪ RowsColumnTypeNullable: カラムがNULL許容かを返す ▪ RowsColumnTypePrecisionScale: DECIMAL等の精度・スケールを返す
  31. © 2024 ANDPAD All Rights Reserved. 拡張されていないinterface まとめ 34 •

    Result : INSERT/UPDATE/DELETE の実行結果 • Tx : トランザクションのコミット・ロールバック • ValueConverter : 任意の値を driver.Value に変換 • Valuer : ユーザー定義型が自身を driver.Value に変換するために実装
  32. © 2024 ANDPAD All Rights Reserved. 35 database/sqlはinterfaceの拡張をどう扱っているか 1 func

    (db *DB) queryDC(...) (*Rows, error) { 2 // Conn がQueryerContext・Queryerを実装しているか確認 3 queryerCtx, ok := dc.ci.(driver.QueryerContext) 4 var queryer driver.Queryer 5 if !ok { 6 queryer, ok = dc.ci.(driver.Queryer) 7 } 8 if ok { 9 // QueryerContext or Queryer interfaceが実装されている場合 10 // Prepare を省略して直接クエリ実行 11 rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs) 12 } 13 // フォールバック: Prepare → Stmt.Query → Stmt.Close 14 si, err = ctxDriverPrepare(ctx, dc.ci, query) 15 }
  33. © 2024 ANDPAD All Rights Reserved. 36 database/sqlはinterfaceの拡張をどう扱っているか 1 func

    (db *DB) queryDC(...) (*Rows, error) { 2 // Conn がQueryerContext・Queryerを実装しているか確認 3 queryerCtx, ok := dc.ci.(driver.QueryerContext) 4 var queryer driver.Queryer 5 if !ok { 6 queryer, ok = dc.ci.(driver.Queryer) 7 } 8 if ok { 9 // QueryerContext or Queryer interfaceが実装されている場合 10 // Prepare を省略して直接クエリ実行 11 rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs) 12 } 13 // フォールバック: Prepare → Stmt.Query → Stmt.Close 14 si, err = ctxDriverPrepare(ctx, dc.ci, query) 15 }
  34. © 2024 ANDPAD All Rights Reserved. ここまでのまとめ 37 • database/sql

    パッケージとdatabase/sql/driver パッケージの関係 ◦ database/sqlパッケージはdatabase/sql/driver パッケージに定義されてい interfaceを介してドライバーとやりとり • database/sql/driverの各interfaceの関係
  35. © 2024 ANDPAD All Rights Reserved. カスタムドライバーを作っていく 38 • クエリの実行時にログをだす

    ◦ DB操作時にログを出したいので処理を動かしたい箇所Driver・Conn・Stmt・Tx database/sql 既存ドライバー DB カスタムドライバー
  36. © 2024 ANDPAD All Rights Reserved. この範囲は既存 ドライバーを そのまま使用する 今回ラップしたい範囲

    基本方針 39 Driver Conn Tx Stmt Result Rows Open Begin Prepare Exec Query Exec Query Connector OpenConnector Connect sql.Open sql.OpenDB
  37. © 2024 ANDPAD All Rights Reserved. ドライバーをラップしていく 40 1. type

    CustomDriver struct{} 2. 3. func (d *CustomDriver) Open(name string) (driver.Conn, error) { 4. mysqlDriver := &mysql.MySQLDriver{} 5. conn, err := mysqlDriver.Open(name) 6. 7. return &customConn{conn}, nil 8. } 9. 10. type customConn struct { 11. conn driver.Conn 12. } 13. 14. func (c *customConn) Prepare(query string) (driver.Stmt, error) { 15. stmt, err := c.conn.Prepare(query) 16. 17. return &customStmt{stmt: stmt}, nil 18. } 19. 20. func (c *customConn) Close() error { 21. return c.conn.Close() 22. }
  38. © 2024 ANDPAD All Rights Reserved. ドライバーをラップしていく 41 1. type

    CustomDriver struct{} 2. 3. func (d *CustomDriver) Open(name string) (driver.Conn, error) { 4. mysqlDriver := &mysql.MySQLDriver{} 5. conn, err := mysqlDriver.Open(name) 6. 7. return &customConn{conn}, nil 8. } 9. 10. type customConn struct { 11. conn driver.Conn 12. } 13. 14. func (c *customConn) Prepare(query string) (driver.Stmt, error) { 15. stmt, err := c.conn.Prepare(query) 16. 17. return &customStmt{stmt: stmt}, nil 18. } 19. 20. func (c *customConn) Close() error { 21. return c.conn.Close() 22. }
  39. © 2024 ANDPAD All Rights Reserved. ドライバーをラップしていく 42 1. type

    customStmt struct { 2. stmt driver.Stmt 3. } 4. 5. func (s *customStmt) Close() error { 6. return s.stmt.Close() 7. } 8. 9. func (s *customStmt) QueryContext( 10. ctx context.Context, 11. args []driver.NamedValue) (driver.Rows, error) { 12. casted, ok := s.stmt.(driver.StmtQueryContext); 13. if !ok { 14. // fallback 15. dargs := make([]driver.Value, len(args)) 16. for i, nv := range args { dargs[i] = nv.Value } 17. 18. return s.Query(dargs) 19. } 20. 21. return casted.QueryContext(ctx, args) 22. }
  40. © 2024 ANDPAD All Rights Reserved. ドライバーをラップしていく 43 1. type

    customStmt struct { 2. stmt driver.Stmt 3. } 4. 5. func (s *customStmt) Close() error { 6. return s.stmt.Close() 7. } 8. 9. func (s *customStmt) QueryContext( 10. ctx context.Context, 11. args []driver.NamedValue) (driver.Rows, error) { 12. casted, ok := s.stmt.(driver.StmtQueryContext); 13. if !ok { 14. // fallback 15. dargs := make([]driver.Value, len(args)) 16. for i, nv := range args { dargs[i] = nv.Value } 17. 18. return s.Query(dargs) 19. } 20. 21. return casted.QueryContext(ctx, args) 22. }
  41. © 2024 ANDPAD All Rights Reserved. loggerの埋め込み 44 1. func

    main() { 2. sql.Register("custom-driver", NewCustomDriver(logger)) 3. db, err := sql.Open("custom-driver", os.Getenv("DataSource")) 4. } 5. 6. func NewCustomDriver(logger *slog.Logger) *CustomDriver { 7. return &CustomDriver{logger: logger} 8. } 9. 10. func (d *CustomDriver) Open(name string) (driver.Conn, error) { 11. mysqlDriver := &mysql.MySQLDriver{} 12. conn, err := mysqlDriver.Open(name) 13. 14. return &customConn{ 15. conn: conn, 16. logger: d.logger, 17. }, nil 18. }
  42. © 2024 ANDPAD All Rights Reserved. loggerの埋め込み 45 1. func

    main() { 2. sql.Register("custom-driver", NewCustomDriver(logger)) 3. db, err := sql.Open("custom-driver", os.Getenv("DataSource")) 4. } 5. 6. func NewCustomDriver(logger *slog.Logger) *CustomDriver { 7. return &CustomDriver{logger: logger} 8. } 9. 10. func (d *CustomDriver) Open(name string) (driver.Conn, error) { 11. mysqlDriver := &mysql.MySQLDriver{} 12. conn, err := mysqlDriver.Open(name) 13. 14. return &customConn{ 15. conn: conn, 16. logger: d.logger, 17. }, nil 18. }
  43. © 2024 ANDPAD All Rights Reserved. loggerの埋め込み 46 1. func

    (s *customStmt) QueryContext( 2. ctx context.Context, 3. args []driver.NamedValue) (driver.Rows, error) { 4. casted, ok := s.stmt.(driver.StmtQueryContext) 5. if !ok { 6. // fallback 7. dargs := make([]driver.Value, len(args)) 8. for i, nv := range args { dargs[i] = nv.Value } 9. return s.Query(dargs) 10. } 11. 12. rows, err := casted.QueryContext(ctx, args) 13. if err != nil { 14. s.logger.Error("query failed", slog.String("query", s.query), 15. slog.Any("args", args), slog.Any("error", err)) 16. } else { 17. s.logger.Info("queried success", slog.String("query", s.query), 18. slog.Any("args", args)) 19. } 20. 21. return rows, err 22. }
  44. © 2024 ANDPAD All Rights Reserved. 複数ドライバー対応 47 1. func

    main(){ 2. sql.Register("custom-logging", NewLoggingDriver(&pq.Driver{}, logger)) 3. db, err := sql.Open("custom-logging", os.Env("DataSource")) 4. } 5. 6. type CustomDriver struct { 7. driver driver.Driver 8. logger *slog.Logger 9. } 10. 11. func NewLoggingDriver(drv driver.Driver, logger *slog.Logger) *CustomDriver { 12. return &CustomDriver{driver: drv, logger: logger} 13. } 14. 15. func (d *CustomDriver) Open(name string) (driver.Conn, error) { 16. conn, err := d.driver.Open(name) 17. 18. return &customConn{ 19. conn: conn, 20. logger: d.logger, 21. }, nil 22. }
  45. © 2024 ANDPAD All Rights Reserved. 複数ドライバー対応 48 1. func

    main(){ 2. sql.Register("custom-logging", NewLoggingDriver(&pq.Driver{}, logger)) 3. db, err := sql.Open("custom-logging", os.Env("DataSource")) 4. } 5. 6. type CustomDriver struct { 7. driver driver.Driver 8. logger *slog.Logger 9. } 10. 11. func NewLoggingDriver(drv driver.Driver, logger *slog.Logger) *CustomDriver { 12. return &CustomDriver{driver: drv, logger: logger} 13. } 14. 15. func (d *CustomDriver) Open(name string) (driver.Conn, error) { 16. conn, err := d.driver.Open(name) 17. 18. return &customConn{ 19. conn: conn, 20. logger: d.logger, 21. }, nil 22. }
  46. © 2024 ANDPAD All Rights Reserved. 複数ドライバー対応 49 1. func

    main(){ 2. connector, err := mysql.MySQLDriver{}.OpenConnector(os.Env("DataSource")) 3. db := sql.OpenDB(NewCustomConnector(connector, logger)) 4. } 5. 6. func NewCustomConnector( 7. connector driver.Connector, 8. logger *slog.Logger) *CustomConnector { 9. 10. return &CustomConnector{ 11. connector: connector, 12. driver: &CustomDriver{driver: connector.Driver(), logger: logger}, 13. logger: logger, 14. } 15. } 16. 17. func (cc *CustomConnector) Connect(ctx context.Context) (driver.Conn, error) { 18. conn, err := cc.connector.Connect(ctx) 19. 20. return &customConn{conn: conn, logger: cc.logger}, nil 21. }
  47. © 2024 ANDPAD All Rights Reserved. 複数ドライバー対応 50 1. func

    main(){ 2. connector, err := mysql.MySQLDriver{}.OpenConnector(os.Env("DataSource")) 3. db := sql.OpenDB(NewCustomConnector(connector, logger)) 4. } 5. 6. func NewCustomConnector( 7. connector driver.Connector, 8. logger *slog.Logger) *CustomConnector { 9. 10. return &CustomConnector{ 11. connector: connector, 12. driver: &CustomDriver{driver: connector.Driver(), logger: logger}, 13. logger: logger, 14. } 15. } 16. 17. func (cc *CustomConnector) Connect(ctx context.Context) (driver.Conn, error) { 18. conn, err := cc.connector.Connect(ctx) 19. 20. return &customConn{conn: conn, logger: cc.logger}, nil 21. }
  48. © 2024 ANDPAD All Rights Reserved. driver.ErrSkip 51 一部のオプショナル interface

    は driver.ErrSkip を返すことで database/sql パッケージでフォールバック処理が走る 対象はdatabase/sql/driver パッケージのgo docに書かれており下記の5つ • Connのオプション ◦ Execer.Exec(非推奨) ◦ ExecerContext.ExecContext ◦ Queryer.Query(非推奨) ◦ QueryerContext.QueryContext • ConnとStmtのオプション ◦ NamedValueChecker.CheckNamedValue
  49. © 2024 ANDPAD All Rights Reserved. driver.ErrSkip 52 1. func

    (c *customConn) QueryContext(...) (driver.Rows, error) { 2. queryerCtx, ok := c.conn.(driver.QueryerContext) 3. if !ok { 4. return nil, driver.ErrSkip 5. } 6. 7. rows, err := queryerCtx.QueryContext(ctx, query, args) 8. // そのまま処理してもErrSkipを返せるが、無駄にログがでるので確認し早期リターン 9. if err == driver.ErrSkip { 10. return nil, driver.ErrSkip 11. } 12. 13. // ログ出力 14. 15. return rows, err 16. }
  50. © 2024 ANDPAD All Rights Reserved. driver.ErrSkipを返せない箇所で実装されていない場合 53 • Pinger,

    SessionResetter, Validator などは実装が必須なため ErrSkip による委譲の仕組みがdatabase/sql パッケージにない (インターフェイスを実装していない場合はdatabase/sql パッケージで最低限の対応が動く) • カスタムドライバーで interface を宣言したら自分で責任を持つ 必要がある ◦ 基本的にはラップするドライバー側で実装されているので問題になることはないが
  51. © 2024 ANDPAD All Rights Reserved. driver.ErrSkipを返せない箇所で実装されていない場合 54 1. func

    (c *customConn) Ping(ctx context.Context) error { 2. if pinger, ok := c.conn.(driver.Pinger); ok { 3. return pinger.Ping(ctx) 4. } 5. 6. // フォールバック: 軽量クエリで疎通確認 7. rows, err := c.QueryContext(ctx, "SELECT 1", nil) 8. if err == nil { err = rows.Close() } 9. return err 10. } 11. 12. func (c *customConn) ResetSession(ctx context.Context) error { 13. if resetter, ok := c.conn.(driver.SessionResetter); ok { 14. return resetter.ResetSession(ctx) 15. } 16. 17. // フォールバック: リセット不要とみなす 18. return nil 19. }
  52. © 2024 ANDPAD All Rights Reserved. 実行してみる 55 1. func

    main() { 2. ctx := context.Background() 3. 4. logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ 5. Level: slog.LevelInfo, 6. })) 7. 8. connector, err := mysql.MySQLDriver{}.OpenConnector(os.GetEnv("DataSource")) 9. db := sql.OpenDB(NewCustomConnector(connector, logger)) 10. 11. queries := sqlc.New(db) 12. res, err = queries.GetUserByName(ctx, new("foo")) 13. logger.Info(fmt.Sprintf("id: %d, name: %q", res.ID, res.Name)) 14. }
  53. © 2024 ANDPAD All Rights Reserved. カスタムドライバーのテストの観点 56 • database/sql/driverのinterfaceを実装できているか

    ◦ コンパイル時にinterfaceに適合しているかアサーションする 1. var ( 2. _ driver.Driver = (*CustomDriver)(nil) 3. _ driver.DriverContext = (*CustomDriver)(nil) 4. )
  54. © 2024 ANDPAD All Rights Reserved. カスタムドライバーのテストの観点 57 • カスタム元のドライバーに機能が移譲できているか

    ◦ bradfitz/go-sql-test[2]を使用する ▪ Goのdatabase/sql パッケージ用の様々なデータベースドライバーを テストするためのプロジェクト ▪ 最終更新は12年前 • 更新されていないため新しい機能のテストは存在しない ▪ 標準的なSQL操作を網羅的にテスト可能 ▪ Go Moduleに対応していないため実行するのひと手間かかる ◦ 独自にテストを作成する ▪ オリジナルのドライバーとカスタムドライバーそれぞれ対して同じテストを実行 して結果が一致すればよい • 追加した機能が意図した挙動をしているか ◦ 今回の例でいうと意図したログがでているかテストする [2]https://github.com/bradfitz/go-sql-test
  55. © 2024 ANDPAD All Rights Reserved. パフォーマンス 58 • go-sql-driver/mysql・lib/pqと作成したカスタムドライバーで性能を比較

    • 1ドライバあたりStmtを経由するパターンとしないパターンの2パターン
  56. © 2024 ANDPAD All Rights Reserved. まとめ 59 • database/sql/driver

    パッケージ ◦ データベース ドライバが実装すべきインターフェイスを定義 ◦ database/sql パッケージはこのインターフェイスを介してドライバとやり取りする • DBとのやり取りは既存のドライバーに任せつつ、追加の機能を独自に 実装するドライバー(カスタムドライバー)の作り方を紹介 ◦ サンプルとしてDB操作時にログを出力するドライバーの例を示した ◦ カスタムドライバーと既存ドライバーのパフォーマンスを比較 ▪ 処理時間はほぼ変わらないがメモリの使用量が1.5倍ほど (追加する機能によって大きく変化する)
  57. © 2024 ANDPAD All Rights Reserved. 実用的ではないが最低限のドライバーの実装 62 1. func

    init() { sql.Register("custom-driver", &Driver{}) } 2. 3. type Driver struct{} 4. 5. func (d *Driver) Open(name string) (driver.Conn, error) { 6. return &Conn{}, nil 7. } 8. 9. type Conn struct{} 10. 11. func (c *Conn) Prepare(query string) (driver.Stmt, error) { 12. return &Stmt{}, nil 13. } 14. 15. func (c *Conn) Close() error { return nil } 16. 17. func (c *Conn) Begin() (driver.Tx, error) { return nil, nil }
  58. © 2024 ANDPAD All Rights Reserved. 実用的ではないが最低限のドライバーの実装 63 1. type

    Stmt struct {} 2. 3. func (s *Stmt) Close() error { return nil } 4. 5. func (s *Stmt) NumInput() int { return -1} 6. 7. func (s *Stmt) Exec(args []driver.Value) (driver.Result, error) { 8. return nil, nil 9. } 10. 11. func (s *Stmt) Query(args []driver.Value) (driver.Rows, error) { 12. return &Rows{ 13. index: 0, 14. data: [][]driver.Value{ {1, "Alice"}, {2, "Bob"} }, 15. }, nil 16. }
  59. © 2024 ANDPAD All Rights Reserved. 実用的ではないが最低限のドライバーの実装 64 1. type

    Rows struct { 2. index int 3. data [][]driver.Value 4. } 5. 6. func (r *Rows) Columns() []string { return []string{ "id", "name" }} 7. 8. func (r *Rows) Close() error { return nil } 9. 10. func (r *Rows) Next(dest []driver.Value) error { 11. if r.index >= len(r.data) { return io.EOF } 12. copy(dest, r.data[r.index]) 13. r.index++ 14. return nil 15. }
  60. © 2024 ANDPAD All Rights Reserved. 実際に最低限のドライバーを使ってみる 65 1. func

    main(){ 2. db, err := sql.Open("custom-driver", "") 3. stmt, err := db.Prepare("") 4. rows, err := stmt.Query("") 5. for rows.Next() { 6. var id int 7. var name string 8. rows.Scan(&id, &name) 9. fmt.Println(id, name) 10. } 11. }
  61. © 2024 ANDPAD All Rights Reserved. ラップ時の注意 66 ラップ元のドライバーが実装しているinterfaceの機能すべてに対して ラップをしないと機能が劣化していまう

    例えばラップ元のドライバーのConnがQueryerContextを実装している場合 ラップ側でQueryerContextを実装しないと database/sql パッケージから はQueryerContextは実装されていないことになる • 複数のドライバーを対象とする場合 ◦ 使用する可能性のあるインターフェイスは基本的にすべて実装すべき • 特定のドライバーを対象とする場合 ◦ ラップ元のドライバーが実装しているinterfaceだけを実装すればよい
  62. © 2024 ANDPAD All Rights Reserved. 既存ドライバーがどのinterfaceを実装しているか確認 67 • driver側の具象型が公開されているパターン

    ◦ コンパイル時にinterfaceに適合しているかアサーションする • driver側の具象型が公開されていないパターン ◦ コードを確認する ▪ 元のコードの中でコンパイル時にinterfaceに適合しているかアサーションして いる場合はそこを確認するだけでよい ◦ DB接続して返ってきた型に対してリフレクションを使って確認する ◦ linkname directiveで非公開の具象型を参照できないか? ▪ linkname directiveの対象は変数と関数であり構造体は対象にできない[3] [3]https://pkg.go.dev/cmd/compile#hdr-Linkname_Directive
  63. © 2024 ANDPAD All Rights Reserved. 既存ドライバーがどのinterfaceを実装しているか確認 68 • driver側の具象型が公開されていないパターン

    ◦ DB接続して返ってきた型に対してリフレクションを使って確認する 1. conn, err := mysql.MySQLDriver{}.Open(os.Getenv("DataSource")) 2. t := reflect.TypeOf(conn) 3. it := reflect.TypeOf((*driver.Conn)(nil)).Elem() 4. if t.Implements(it) { 5. fmt.Print("✓ driver.Conn") 6. } else { 7. fmt.Println("✗ driver.Conn") 8. }
  64. © 2024 ANDPAD All Rights Reserved. go-sql-driver/mysqlに対して確認した結果 69 • mysql.MySQLDriver

    ✓ driver.Driver ✓ driver.DriverContext • *mysql.connector ✓ driver.Connector • *mysql.mysqlConn ✓ driver.Conn ✓ driver.Pinger ✓ driver.SessionResetter ✓ driver.Validator ✓ driver.ExecerContext ✓ driver.QueryerContext ✓ driver.ConnPrepareContext ✓ driver.ConnBeginTx ✓ driver.NamedValueChecker • *mysql.mysqlStmt ✓ driver.Stmt ✓ driver.StmtExecContext ✓ driver.StmtQueryContext ✓ driver.NamedValueChecker • *mysql.binaryRows,*mysql.textRows ✓ driver.Rows ✓ driver.RowsNextResultSet ✓ driver.RowsColumnTypeDatabaseTypeName ✘ driver.RowsColumnTypeLength ✓ driver.RowsColumnTypeNullable ✓ driver.RowsColumnTypePrecisionScale ✓ driver.RowsColumnTypeScanType
  65. © 2024 ANDPAD All Rights Reserved. lib/pqに対して確認した結果 70 • pq.Driver

    ✓ driver.Driver ✘ driver.DriverContext • *pq.Connector ✓ driver.Connector • *pq.conn ✓ driver.Conn ✓ driver.Pinger ✓ driver.SessionResetter ✓ driver.Validator ✓ driver.ExecerContext ✓ driver.QueryerContext ✓ driver.ConnPrepareContext ✓ driver.ConnBeginTx ✓ driver.NamedValueChecker • *pq.stmt ✓ driver.Stmt ✓ driver.StmtExecContext ✓ driver.StmtQueryContext ✘ driver.NamedValueChecker • *pq.rows ✓ driver.Rows ✓ driver.RowsNextResultSet ✓ driver.RowsColumnTypeDatabaseTypeName ✓ driver.RowsColumnTypeLength ✘ driver.RowsColumnTypeNullable ✓ driver.RowsColumnTypePrecisionScale ✓ driver.RowsColumnTypeScanType