[インデックス 14864] ファイルの概要
このコミットは、Go言語の標準ライブラリである database/sql
パッケージにおける Stmt.Exec
メソッドの振る舞いを改善し、潜在的なバグを修正するものです。具体的には、driver.Stmt
インターフェースの NumInput
メソッドが返す値に基づいて、ステートメントに渡される引数の数をチェックするロジックが導入されています。これにより、データベースドライバが期待する引数の数と、実際に Exec
に渡される引数の数との不一致によるエラーを早期に検出できるようになります。
コミット
commit 7f0449a1086dce557c6071c951cfec8664e8d456
Author: Gwenael Treguier <gwenn.kahz@gmail.com>
Date: Fri Jan 11 13:28:33 2013 -0800
database/sql: check NumInput on Stmt.Exec
Fixes #3678.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6460087
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7f0449a1086dce557c6071c951cfec8664e8d456
元コミット内容
このコミットの元の内容は、database/sql
パッケージにおいて、Stmt.Exec
メソッドが実行される際に、データベースドライバが期待するプレースホルダの数と、実際に渡される引数の数が一致するかどうかを検証する機能を追加することです。これは、GoのIssue #3678で報告された問題に対応するもので、引数の不一致によって発生する可能性のあるランタイムエラーを防ぐことを目的としています。
変更の背景
この変更の背景には、Goの database/sql
パッケージが提供する抽象化レイヤーと、その下にある具体的なデータベースドライバとの間のインタラクションにおける課題がありました。以前のバージョンでは、Exec
メソッドに渡される引数の数が、SQLステートメント内のプレースホルダの数と一致しない場合、エラーがドライバレベルで発生し、そのエラーメッセージが必ずしもユーザーにとって分かりやすいものではありませんでした。
特に、Issue #3678では、database/sql
パッケージが driver.Stmt
インターフェースの NumInput
メソッドを適切に利用していないことが指摘されていました。NumInput
は、ドライバがステートメントが期待する引数の数を報告するためのメソッドです。この情報が利用されていないため、引数の数が間違っている場合に、より上位の database/sql
レイヤーで早期にエラーを検出できず、ドライバに処理を任せてしまっていました。これにより、デバッグが困難になるケースがありました。
このコミットは、NumInput
の情報を活用することで、database/sql
パッケージがより賢明に引数の数をチェックし、不適切な引数で Exec
が呼び出された場合に、より明確なエラーを返すようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の database/sql
パッケージに関する前提知識が必要です。
database/sql
パッケージ: Go言語の標準ライブラリで、SQLデータベースへの汎用的なインターフェースを提供します。このパッケージ自体は特定のデータベースの実装を含まず、データベースドライバを介して様々なデータベースと連携します。driver
パッケージ:database/sql
パッケージの内部で利用されるインターフェースを定義しています。データベースドライバは、このdriver
パッケージで定義されたインターフェースを実装することで、database/sql
パッケージと連携します。driver.Stmt
インターフェース: プリペアドステートメントを表すインターフェースです。データベースドライバは、このインターフェースを実装し、Exec
やQuery
などのメソッドを提供します。driver.Stmt.NumInput()
メソッド:driver.Stmt
インターフェースの一部であり、プリペアドステートメントが期待するプレースホルダの数を返します。ドライバがこの情報を提供できない場合(例えば、プレースホルダの数が可変である場合や、ドライバがこの機能に対応していない場合)、-1
を返します。- プリペアドステートメント (Prepared Statement): SQLインジェクション攻撃を防ぎ、クエリの実行効率を向上させるために使用されるデータベースの機能です。SQLクエリの構造を事前にデータベースに渡し、後からパラメータ(引数)をバインドして実行します。
技術的詳細
このコミットの技術的な詳細は、database/sql
パッケージが driver.Stmt.NumInput()
メソッドの戻り値をどのように利用して、Exec
メソッドに渡される引数の数を検証するかという点にあります。
変更前は、DB.exec
および Tx.Exec
メソッド内で、driverArgs
関数を呼び出して引数をドライバが理解できる形式に変換し、その後すぐに sti.Exec(dargs)
を呼び出してステートメントを実行していました。この際、引数の数の検証は database/sql
レイヤーでは行われず、ドライバに任されていました。
変更後は、DB.exec
、Tx.Exec
、および Stmt.Exec
の各メソッドが、共通の新しいヘルパー関数 resultFromStatement
を呼び出すようにリファクタリングされています。この resultFromStatement
関数が、引数の数に関する検証ロジックをカプセル化しています。
resultFromStatement
関数内では、まず si.NumInput()
を呼び出して、ドライバが期待する引数の数を取得します。
- もし
NumInput()
が-1
を返した場合、これはドライバが引数の数を特定できないことを意味します。この場合、database/sql
パッケージは引数の数に関する事前チェックを行わず、ドライバに処理を委ねます。これは、ドライバが可変長の引数をサポートしている場合や、プレースホルダの数が実行時に決定されるような複雑なステートメントを扱う場合に必要です。 - もし
NumInput()
が-1
以外の値を返した場合、それはドライバが期待する正確な引数の数を示しています。この場合、resultFromStatement
はlen(args)
(実際に渡された引数の数)とnumInput
を比較します。もし両者が一致しない場合、fmt.Errorf
を使用して「sql: expected %d arguments, got %d」のような明確なエラーメッセージを生成し、Exec
の呼び出し元に返します。これにより、ドライバに処理が渡される前に、より上位のレイヤーでエラーを検出できるようになります。
この変更により、database/sql
パッケージは、ドライバが提供するメタデータを活用して、より堅牢な引数検証を行うことができるようになりました。これにより、開発者は引数の不一致による問題をより早期に、より明確なエラーメッセージで特定できるようになります。
コアとなるコードの変更箇所
変更は src/pkg/database/sql/sql.go
ファイルに集中しています。
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -369,16 +369,7 @@ func (db *DB) exec(query string, args []interface{}) (res Result, err error) {
}
defer sti.Close()
- dargs, err := driverArgs(sti, args)
- if err != nil {
- return nil, err
- }
-
- resi, err := sti.Exec(dargs)
- if err != nil {
- return nil, err
- }
- return result{resi}, nil
+ return resultFromStatement(sti, args...)
}
// Query executes a query that returns rows, typically a SELECT.
@@ -608,16 +599,7 @@ func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) {
}
defer sti.Close()
- dargs, err := driverArgs(sti, args)
- if err != nil {
- return nil, err
- }
-
- resi, err := sti.Exec(dargs)
- if err != nil {
- return nil, err
- }
- return result{resi}, nil
+ return resultFromStatement(sti, args...)
}
// Query executes a query that returns rows, typically a SELECT.
@@ -682,6 +664,10 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {
}
defer releaseConn(nil)
+ return resultFromStatement(si, args...)\n+}\n+\n+func resultFromStatement(si driver.Stmt, args ...interface{}) (Result, error) {
// -1 means the driver doesn't know how to count the number of
// placeholders, so we won't sanity check input here and instead let the
// driver deal with errors.
コアとなるコードの解説
このコミットの主要な変更点は、DB.exec
、Tx.Exec
、および Stmt.Exec
の各メソッドから、引数の準備と Exec
の呼び出しロジックが削除され、新しく導入されたヘルパー関数 resultFromStatement
に委譲されている点です。
変更前:
DB.exec
と Tx.Exec
は、それぞれ driverArgs
を呼び出して引数を変換し、その後 sti.Exec
を直接呼び出していました。このロジックは重複しており、引数の数に関する検証は行われていませんでした。
変更後:
-
resultFromStatement
関数の導入:func resultFromStatement(si driver.Stmt, args ...interface{}) (Result, error) { // -1 means the driver doesn't know how to count the number of // placeholders, so we won't sanity check input here and instead let the // driver deal with errors. numInput := si.NumInput() if numInput != -1 && numInput != len(args) { return nil, fmt.Errorf("sql: expected %d arguments, got %d", numInput, len(args)) } dargs, err := driverArgs(si, args) if err != nil { return nil, err } resi, err := si.Exec(dargs) if err != nil { return nil, err } return result{resi}, nil }
この新しい関数が、引数の数に関する検証ロジックと、引数の変換 (
driverArgs
)、そして実際のdriver.Stmt.Exec
の呼び出しをカプセル化しています。numInput := si.NumInput()
: ドライバが期待する引数の数を取得します。if numInput != -1 && numInput != len(args)
: ここが新しい検証ロジックです。numInput != -1
: ドライバが引数の数を報告できる場合(つまり、-1
ではない場合)。numInput != len(args)
: 期待される引数の数と実際に渡された引数の数が異なる場合。- この両方の条件が真の場合、
fmt.Errorf
を使って明確なエラーメッセージを生成し、関数を終了します。
- 残りの部分は、以前の
Exec
メソッドにあった引数変換とドライバのExec
呼び出しのロジックです。
-
既存メソッドからの委譲:
DB.exec
、Tx.Exec
、およびStmt.Exec
の各メソッドは、冗長なコードを削除し、すべてresultFromStatement(sti, args...)
を呼び出すように変更されました。これにより、コードの重複が解消され、引数検証ロジックが一元化されました。
この変更により、database/sql
パッケージは、データベースドライバが提供する NumInput
の情報を利用して、より上位のレイヤーで引数の数を検証できるようになり、開発者にとってより分かりやすいエラーメッセージを提供できるようになりました。
関連リンク
- Go Issue #3678: https://github.com/golang/go/issues/3678
- Go CL 6460087: https://golang.org/cl/6460087
参考にした情報源リンク
- Go言語
database/sql
パッケージ公式ドキュメント: https://pkg.go.dev/database/sql - Go言語
database/sql/driver
パッケージ公式ドキュメント: https://pkg.go.dev/database/sql/driver - Go言語の
database/sql
パッケージの内部構造とドライバの作成に関する記事 (一般的な情報源): (具体的なURLはコミット内容からは特定できないため、一般的な情報源として記載)