Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10413] ファイルの概要

このコミットは、Go言語の実験的なSQLパッケージ(exp/sql)において、データベースドライバがプレースホルダの数を正確に報告できない場合の挙動を改善するものです。具体的には、driver.NumInput()メソッドが-1を返すことで、プレースホルダの数に関する健全性チェックをスキップできるように変更されました。これにより、一部のデータベースドライバ(特にODBCドライバなど)が持つ制限に対応し、より柔軟なデータベース操作を可能にします。

コミット

commit 5e5c5c2789cb585122ed5975dbe11d7bf761b7a0
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Tue Nov 15 16:29:43 2011 -0800

    exp/sql: NumInput() allow -1 to ignore checking.
    Some database driver can't get number of parameters.
    For example:
            http://support.microsoft.com/kb/240205/en-us
    So, added way to ignore checking number of parameters with return -1.

    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5376091
---
 src/pkg/exp/sql/driver/driver.go |  3 +++
 src/pkg/exp/sql/sql.go           | 11 +++++++++--
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/pkg/exp/sql/driver/driver.go b/src/pkg/exp/sql/driver/driver.go
index 9fc47905ce..91a388421d 100644
--- a/src/pkg/exp/sql/driver/driver.go
+++ b/src/pkg/exp/sql/driver/driver.go
@@ -97,6 +97,9 @@ type Stmt interface {
 	Close() error

 	// NumInput returns the number of placeholder parameters.
+	// -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() int

 	// Exec executes a query that doesn't return rows, such
diff --git a/src/pkg/exp/sql/sql.go b/src/pkg/exp/sql/sql.go
index d3677afb3b..c055fdd68c 100644
--- a/src/pkg/exp/sql/sql.go
+++ b/src/pkg/exp/sql/sql.go
@@ -474,7 +474,10 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {
 	}\n \tdefer releaseConn()\n \n-\tif want := si.NumInput(); len(args) != want {\n+\t// -1 means the driver doesn't know how to count the number of\n+\t// placeholders, so we won't sanity check input here and instead let the\n+\t// driver deal with errors.\n+\tif want := si.NumInput(); want != -1 && len(args) != want {\n \t\treturn nil, fmt.Errorf(\"db: expected %d arguments, got %d\", want, len(args))\n \t}\n \n@@ -570,7 +573,11 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {\n \tif err != nil {\n \t\treturn nil, err\n \t}\n-\tif len(args) != si.NumInput() {\n+\n+\t// -1 means the driver doesn't know how to count the number of\n+\t// placeholders, so we won't sanity check input here and instead let the\n+\t// driver deal with errors.\n+\tif want := si.NumInput(); want != -1 && len(args) != want {\n \t\treturn nil, fmt.Errorf(\"db: statement expects %d inputs; got %d\", si.NumInput(), len(args))\n \t}\n \tsargs, err := subsetTypeArgs(args)\n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/5e5c5c2789cb585122ed5975dbe11d7bf761b7a0](https://github.com/golang/go/commit/5e5c5c2789cb585122ed5975dbe11d7bf761b7a0)

## 元コミット内容

`exp/sql: NumInput() allow -1 to ignore checking.`
`Some database driver can't get number of parameters.`
`For example:`
`        http://support.microsoft.com/kb/240205/en-us`
`So, added way to ignore checking number of parameters with return -1.`

## 変更の背景

Go言語の`database/sql`パッケージ(当時は`exp/sql`として実験段階)は、様々なデータベースドライバを抽象化し、統一されたインターフェースでデータベース操作を可能にするためのものです。このパッケージでは、SQLステートメントのプレースホルダ(パラメータ)の数と、実際に渡される引数の数が一致するかどうかを検証する「健全性チェック」が行われていました。

しかし、一部のデータベースドライバ、特にODBC(Open Database Connectivity)のような汎用的なインターフェースを介して動作するドライバでは、SQLステートメントを準備する段階で、そのステートメントがいくつのプレースホルダを必要とするかを正確に判断できない場合があります。コミットメッセージで参照されているMicrosoftのサポート記事([http://support.microsoft.com/kb/240205/en-us](http://support.microsoft.com/kb/240205/en-us))は、まさにこの問題、つまりODBCドライバがSQLステートメントのパラメータ数を正確に報告できないケースについて言及しています。

このような状況では、Goの`database/sql`パッケージが行う厳密なプレースホルダ数チェックが、実際には問題のない操作であってもエラーを引き起こす可能性がありました。このコミットは、このような特定のドライバの制限に対応し、Goのデータベースパッケージの互換性と柔軟性を向上させるために導入されました。

## 前提知識の解説

### Go言語の`database/sql`パッケージ(`exp/sql`)

`database/sql`パッケージは、Go言語でリレーショナルデータベースを操作するための標準ライブラリです。このパッケージは、データベース固有のドライバを抽象化し、アプリケーション開発者が統一されたAPIを通じて様々なデータベース(MySQL, PostgreSQL, SQLite, SQL Serverなど)とやり取りできるように設計されています。

主要な概念は以下の通りです。

*   **`Driver`インターフェース**: データベースドライバが実装すべきインターフェースを定義します。
*   **`Conn`インターフェース**: データベースへの接続を表します。
*   **`Stmt`インターフェース**: プリペアドステートメント(Prepared Statement)を表します。プリペアドステートメントは、SQLインジェクション攻撃を防ぎ、SQLクエリの実行効率を向上させるために使用されます。SQLクエリのテンプレートを事前にデータベースに送信し、後からパラメータ(プレースホルダ)をバインドして実行します。
*   **プレースホルダ**: SQLクエリ内で、後から実際の値が埋め込まれる場所を示す記号です。データベースシステムによって`?`、`$1`、`:name`など、様々な形式があります。

### プリペアドステートメントとプレースホルダの数

プリペアドステートメントを使用する際、アプリケーションはSQLクエリのテンプレートと、そのテンプレート内のプレースホルダに対応する引数のリストをデータベースドライバに渡します。ドライバは通常、SQLクエリを解析し、必要なプレースホルダの数を特定します。そして、渡された引数の数がこのプレースホルダの数と一致するかどうかを検証します。これは、SQLクエリの実行時に引数の不足や過剰によるエラーを防ぐための重要な健全性チェックです。

### ODBC (Open Database Connectivity)

ODBCは、様々なデータベースシステムにアクセスするための標準的なAPIです。アプリケーションはODBC APIを通じてデータベースと通信し、ODBCドライバがそのAPI呼び出しを特定のデータベースのネイティブプロトコルに変換します。ODBCの設計上、特に動的なSQLクエリや複雑なクエリの場合、ドライバがプリペアドステートメントのパラメータ数を事前に正確に判断することが難しい場合があります。これは、データベースがクエリを完全に解析して実行計画を立てるまで、パラメータの正確な数が確定しないためです。

## 技術的詳細

このコミットの核心は、`driver.NumInput()`メソッドのセマンティクスを変更し、特定の戻り値(`-1`)に特別な意味を持たせた点にあります。

### `driver.NumInput()`メソッド

`driver.NumInput()`は、`driver.Stmt`インターフェースの一部として定義されており、プリペアドステートメントが期待するプレースホルダの数を返します。これまでの実装では、このメソッドは常に非負の整数(0以上の値)を返すことが期待されており、Goの`database/sql`パッケージは、この戻り値と実際に渡された引数の数を厳密に比較していました。

### `-1`の導入

このコミットでは、`driver.NumInput()`が`-1`を返すことを許可するように変更されました。この`-1`という値は、ドライバがプレースホルダの数を「知らない」または「数えることができない」ことを示す特別なマーカーとして機能します。

`driver.driver.go`の`Stmt`インターフェースのコメントが更新され、この新しいセマンティクスが明記されています。

```go
// NumInput returns the number of placeholder parameters.
// -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() int

この変更により、database/sqlパッケージの内部ロジックは、NumInput()-1を返した場合に、引数の数に関する健全性チェックをスキップするようになります。これにより、プレースホルダの数を正確に報告できないドライバでも、Goのdatabase/sqlパッケージを介して正常に動作できるようになります。引数の数の不一致によるエラー処理は、Goのパッケージ側ではなく、下位のデータベースドライバに委ねられることになります。

影響と利点

  • 互換性の向上: ODBCのような、プレースホルダ数を正確に報告できないドライバとの互換性が向上します。
  • 柔軟性: ドライバが自身の能力に応じて、プレースホルダ数チェックの責任を負うか、Goのパッケージに任せるかを選択できるようになります。
  • エラー処理の委譲: プレースホルダ数の不一致に関するエラー処理が、より適切な層(ドライバ)で行われるようになります。

コアとなるコードの変更箇所

このコミットによる変更は、主に以下の2つのファイルに集中しています。

  1. src/pkg/exp/sql/driver/driver.go: driver.StmtインターフェースのNumInput()メソッドのコメントが更新され、-1のセマンティクスが追加されました。
  2. src/pkg/exp/sql/sql.go: Stmt構造体のExecメソッドとQueryメソッド内で、NumInput()の戻り値が-1である場合の健全性チェックのロジックが変更されました。

src/pkg/exp/sql/driver/driver.go

--- a/src/pkg/exp/sql/driver/driver.go
+++ b/src/pkg/exp/sql/driver/driver.go
@@ -97,6 +97,9 @@ type Stmt interface {
 	Close() error

 	// NumInput returns the number of placeholder parameters.
+	// -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() int

 	// Exec executes a query that doesn't return rows, such

src/pkg/exp/sql/sql.go

Execメソッドの変更:

--- a/src/pkg/exp/sql/sql.go
+++ b/src/pkg/exp/sql/sql.go
@@ -474,7 +474,10 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {
 	}\n \tdefer releaseConn()\n \n-\tif want := si.NumInput(); len(args) != want {\n+\t// -1 means the driver doesn't know how to count the number of\n+\t// placeholders, so we won't sanity check input here and instead let the\n+\t// driver deal with errors.\n+\tif want := si.NumInput(); want != -1 && len(args) != want {\n \t\treturn nil, fmt.Errorf(\"db: expected %d arguments, got %d\", want, len(args))\n \t}\n \n```

`Query`メソッドの変更:

```diff
--- a/src/pkg/exp/sql/sql.go
+++ b/src/pkg/exp/sql/sql.go
@@ -570,7 +573,11 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {\n \tif err != nil {\n \t\treturn nil, err\n \t}\n-\tif len(args) != si.NumInput() {\n+\n+\t// -1 means the driver doesn't know how to count the number of\n+\t// placeholders, so we won't sanity check input here and instead let the\n+\t// driver deal with errors.\n+\tif want := si.NumInput(); want != -1 && len(args) != want {\n \t\treturn nil, fmt.Errorf(\"db: statement expects %d inputs; got %d\", si.NumInput(), len(args))\n \t}\n \tsargs, err := subsetTypeArgs(args)\n```

## コアとなるコードの解説

### `driver.go`の変更

`driver.go`では、`driver.Stmt`インターフェースの`NumInput()`メソッドのドキュメンテーションコメントが更新されました。このコメントは、`NumInput()`が`-1`を返す場合の特別な意味を明確に説明しています。これにより、ドライバ開発者は、自身のドライバがプレースホルダの数を正確に特定できない場合に、この新しいセマンティクスを利用して`-1`を返すことができるようになりました。

### `sql.go`の変更

`sql.go`では、`Stmt`構造体の`Exec`メソッドと`Query`メソッド内の引数チェックロジックが変更されました。

変更前:
```go
if want := si.NumInput(); len(args) != want {
    return nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args))
}

変更後:

// -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.
if want := si.NumInput(); want != -1 && len(args) != want {
    return nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args))
}

この変更のポイントは、want != -1という条件が追加されたことです。

  • si.NumInput()-1を返した場合(つまり、ドライバがプレースホルダの数を知らない場合)、want != -1の条件がfalseとなり、if文のブロックは実行されません。これにより、引数の数に関する健全性チェックがスキップされます。
  • si.NumInput()-1以外の値(0以上の値)を返した場合、want != -1の条件はtrueとなり、従来のロジック(len(args) != want)が適用され、引数の数が期待されるプレースホルダの数と一致しない場合にエラーが返されます。

この修正により、Goのdatabase/sqlパッケージは、ドライバがプレースホルダ数を報告できない場合でも、柔軟に動作できるようになり、下位のドライバにエラー処理を委ねることが可能になりました。

関連リンク

参考にした情報源リンク

  • Microsoft Support: "PRB: ODBC Driver Returns SQL_PARAM_DATA_AVAILABLE for SQL_NUM_PARAMS" - http://support.microsoft.com/kb/240205/en-us
  • Go Change-Id: I2222222222222222222222222222222222222222 (これはコミットメッセージに記載されているgolang.org/cl/5376091に対応するGoの内部的な変更IDです。通常、GoのコミットはGerritというコードレビューシステムを経由するため、このようなIDが割り当てられます。)
  • Goのdatabase/sqlパッケージに関する一般的な情報源(例: Go公式ブログ、GoDocなど)