[インデックス 10211] ファイルの概要
このコミットは、Go言語の実験的なexp/sql
パッケージにおける重要な改善と機能追加を目的としています。具体的には、データベーストランザクションの完了、データ型(特にfloat
とbool
)の扱いをより堅牢にするための型変換ロジックの強化、および関連するドキュメントの充実が図られています。これにより、exp/sql
パッケージがより実用的なデータベースアクセス層として機能するための基盤が固められました。
コミット
- コミットハッシュ:
8089e578124227a322a77f9c5599d2244f9d0cfc
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Wed Nov 2 11:46:04 2011 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc
元コミット内容
exp/sql: finish transactions, flesh out types, docs
Fixes #2328 (float, bool)
R=rsc, r
CC=golang-dev
https://golang.org/cl/5294067
変更の背景
このコミットの主な背景には、Go言語の標準ライブラリにデータベースアクセス機能を追加する初期段階での課題がありました。exp/sql
パッケージは、Goのデータベース/SQLインターフェースのプロトタイプとして開発されており、様々なデータベースドライバーが共通のインターフェースを通じて動作できるように設計されていました。
元々、exp/sql
パッケージは基本的なクエリ実行機能を提供していましたが、トランザクション管理や、データベースから取得した多様なデータ型(特に浮動小数点数や真偽値)をGoの適切な型に正確かつ安全に変換する機能が不十分でした。
コミットメッセージにあるFixes #2328 (float, bool)
は、この変更がGoのIssue 2328を解決するものであることを示しています。Issue 2328は、exp/sql
パッケージがfloat
型とbool
型のデータを適切に扱えないという問題点を指摘していました。具体的には、データベースから取得したこれらの型の値をGoの変数にスキャンする際に、正しい変換が行われず、エラーが発生したり、予期せぬ値になったりする可能性がありました。
このコミットは、これらの欠点を解消し、exp/sql
パッケージがより堅牢で実用的なデータベースアクセス層となることを目指しています。トランザクション機能の完全な実装は、複数のデータベース操作をアトミックに実行するために不可欠であり、データの一貫性と信頼性を保証します。また、float
とbool
の型変換の改善は、より広範なデータベーススキーマとデータ型に対応するために必要でした。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とデータベース関連の知識が必要です。
Go言語のdatabase/sql
パッケージ(およびexp/sql
)
Go言語の標準ライブラリには、データベースにアクセスするための汎用的なインターフェースを提供するdatabase/sql
パッケージがあります。このパッケージは、特定のデータベースシステムに依存しない抽象化レイヤーを提供し、データベースドライバーを介して実際のデータベース操作を行います。
sql.DB
: データベースへの接続プールを管理する構造体。sql.Conn
: データベースへの単一の接続を表すインターフェース。sql.Tx
: データベーストランザクションを表す構造体。トランザクションは、複数のデータベース操作を単一の論理的な作業単位としてグループ化し、すべて成功するか(コミット)、すべて失敗して元に戻すか(ロールバック)のいずれかを保証します。sql.Stmt
: プリペアドステートメントを表す構造体。SQLインジェクション攻撃を防ぎ、クエリの再利用によるパフォーマンス向上に役立ちます。sql.Rows
: クエリ結果の行を反復処理するための構造体。driver
パッケージ:database/sql
パッケージは、実際のデータベースとの通信を抽象化するためにdatabase/sql/driver
パッケージに定義されたインターフェースを使用します。各データベース(例: MySQL, PostgreSQL, SQLite)は、このdriver
インターフェースを実装した独自のドライバーを提供します。
このコミットが対象としているexp/sql
は、database/sql
パッケージが標準ライブラリに組み込まれる前の実験的な段階のものです。しかし、基本的な設計思想とインターフェースは現在のdatabase/sql
と共通しています。
reflect
パッケージ
Goのreflect
パッケージは、実行時にプログラムの構造を検査し、操作するための機能を提供します。これにより、型情報(reflect.Type
)や値情報(reflect.Value
)を動的に取得・設定できます。
reflect.ValueOf(interface{})
: 任意のGoの値をreflect.Value
型に変換します。reflect.Kind()
:reflect.Value
が表す値の基本的な種類(例:reflect.Int
,reflect.String
,reflect.Bool
)を返します。reflect.Indirect(reflect.Value)
: ポインタが指す先の値を取得します。SetInt()
,SetUint()
,SetFloat()
:reflect.Value
が表す整数、符号なし整数、浮動小数点数の値を設定します。OverflowInt()
,OverflowUint()
,OverflowFloat()
: 指定された値がreflect.Value
が表す型の範囲を超えているかどうかをチェックします。
exp/sql
パッケージでは、データベースから読み込んだ値をユーザーが指定したGoの型に変換する際に、このreflect
パッケージが広く利用されています。これにより、汎用的な型変換ロジックを実装できます。
ValueConverter
インターフェース
exp/sql/driver
パッケージにはValueConverter
インターフェースが定義されています。これは、データベースドライバーがGoの基本的な型(int64
, float64
, bool
, string
, []byte
, time.Time
)と、データベース固有の型との間で値を変換するためのメカニズムを提供します。このコミットでは、特にBool
型のValueConverter
の実装が詳細に記述されています。
SQLトランザクション
SQLトランザクションは、データベース操作の論理的な単位です。トランザクションは以下のACID特性を保証します。
- Atomicity (原子性): トランザクション内のすべての操作が成功するか、すべて失敗するかのいずれかです。部分的な成功はありません。
- Consistency (一貫性): トランザクションはデータベースを一貫性のある状態から別の一貫性のある状態へ移行させます。
- Isolation (独立性): 複数のトランザクションが同時に実行されても、それぞれが独立して実行されているかのように見えます。
- Durability (永続性): コミットされたトランザクションの結果は、システム障害が発生しても失われません。
Begin
、Commit
、Rollback
は、トランザクションを制御するための基本的な操作です。
技術的詳細
このコミットは、主に以下の3つの領域で技術的な改善を行っています。
-
型変換ロジックの強化 (
src/pkg/exp/sql/convert.go
):float
型とbool
型のサポート: 以前は適切に扱えなかったfloat
型とbool
型への変換が追加されました。*bool
への変換パスが追加され、driver.Bool.ConvertValue(src)
を使用して真偽値への変換を試みます。これは、文字列("true", "false", "1", "0"など)、整数(0, 1)、およびブール値自体からbool
への変換をサポートします。reflect.Float32
およびreflect.Float64
への変換パスが追加されました。文字列から浮動小数点数への変換(strconv.Atof64
)をサポートし、オーバーフローチェックも行います。
asString
関数の変更:asString
関数が、src
がstring
または[]byte
でない場合でも、fmt.Sprintf("%v", src)
を使用して任意の値を文字列に変換するように変更されました。これにより、数値型などが文字列として扱われるケースでの柔軟性が向上しました。以前はstring
または[]byte
以外は空文字列とfalse
を返していました。- エラーハンドリングの改善: 型変換時のエラーメッセージがより具体的になり、デバッグが容易になりました。特に、数値型への変換時の
strconv
エラーやオーバーフローエラーが明確に報告されます。
-
driver
インターフェースとValueConverter
の実装 (src/pkg/exp/sql/driver/driver.go
,src/pkg/exp/sql/driver/types.go
):driver.Open
とdriver.Close
のドキュメント更新:Open
メソッドがキャッシュされた接続を返す可能性があること、およびsql
パッケージがアイドル接続のプールを維持するため、ドライバー側で独自の接続キャッシュを行う必要がないことが明記されました。これは、接続管理の責任がsql
パッケージにあることを強調しています。Bool
ValueConverter
の完全な実装:driver.types.go
にBool
型のValueConverter
が完全に実装されました。- ブール値、文字列(
strconv.Atob
を使用)、バイトスライス、整数(0または1のみ)からbool
への変換ルールが定義されました。 - 0または1以外の整数、およびその他の型からの変換はエラーとなります。
- これにより、データベースから取得した真偽値が、様々な形式で表現されていてもGoの
bool
型に正しく変換されるようになりました。
- ブール値、文字列(
-
トランザクション機能の完成 (
src/pkg/exp/sql/sql.go
):DB.Begin()
の実装:DB.Begin()
メソッドがpanic(todo())
から実際のトランザクション開始ロジックに置き換えられました。- 接続プールから接続を取得し、その接続上で
driver.Conn.Begin()
を呼び出してトランザクションを開始します。 - トランザクション開始に失敗した場合は、取得した接続をプールに戻します。
Tx
構造体を初期化し、関連するdriver.Conn
とdriver.Tx
インスタンスを保持します。
- 接続プールから接続を取得し、その接続上で
Tx
構造体の拡張:db
(*DB
): 親のDB
インスタンスへの参照。ci
(driver.Conn
): トランザクションが使用する接続。txi
(driver.Tx
): 基盤となるドライバーのトランザクションインターフェース。cimu
(sync.Mutex
): 接続の使用を保護するためのミューテックス。done
(bool
): トランザクションがコミットまたはロールバックされたかどうかを示すフラグ。
Tx.Commit()
とTx.Rollback()
の実装:- これらのメソッドは、
tx.done
フラグをチェックし、既に終了しているトランザクションに対して操作が行われた場合にErrTransactionFinished
エラーを返します。 defer tx.close()
により、トランザクション終了時に接続をプールに戻す処理が保証されます。- 基盤となる
driver.Tx.Commit()
またはdriver.Tx.Rollback()
を呼び出します。
- これらのメソッドは、
Tx.Prepare()
,Tx.Exec()
,Tx.Query()
,Tx.QueryRow()
の実装:- これらのメソッドは、トランザクション内でプリペアドステートメントの準備、クエリの実行、行の取得を行うためのものです。
- トランザクションの
ci
(接続)を排他的に利用し、操作が完了したらreleaseConn()
を呼び出してロックを解除します。 Stmt
構造体もトランザクションのコンテキストを認識するように変更され、トランザクション内のステートメントは常にそのトランザクションの接続を使用するようになりました。
Stmt
構造体の変更:tx
(*Tx
): ステートメントがトランザクションの一部である場合に設定されます。txsi
(driver.Stmt
): トランザクション内のステートメントの場合に、そのトランザクションの接続にバインドされたドライバーのステートメントを保持します。connStmt()
メソッドがトランザクションのコンテキストを考慮するように変更され、トランザクション内のステートメントは常にそのトランザクションの接続を使用するようになりました。Stmt.Close()
もトランザクションの有無に応じて適切なクリーンアップを行うように修正されました。
Rows
構造体の変更:releaseConn func()
フィールドが追加され、Rows
が閉じられたときに接続をプールに戻すための関数を保持するようになりました。これにより、接続のライフサイクル管理がより明確になりました。
これらの変更により、exp/sql
パッケージは、トランザクションを安全かつ効率的に管理し、多様なデータ型をGoの型に正確に変換できるようになり、より実用的なデータベースアクセスライブラリとしての基盤が確立されました。
コアとなるコードの変更箇所
src/pkg/exp/sql/convert.go
--- a/src/pkg/exp/sql/convert.go
+++ b/src/pkg/exp/sql/convert.go
@@ -36,10 +37,11 @@ func convertAssign(dest, src interface{}) error {
}
}
- sv := reflect.ValueOf(src)
+ var sv reflect.Value
switch d := dest.(type) {
case *string:
+ sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
@@ -48,6 +50,12 @@ func convertAssign(dest, src interface{}) error {
*d = fmt.Sprintf("%v", src)
return nil
}
+ case *bool:
+ bv, err := driver.Bool.ConvertValue(src)
+ if err == nil {
+ *d = bv.(bool)
+ }
+ return err
}
if scanner, ok := dest.(ScannerInto); ok {
@@ -59,6 +67,10 @@ func convertAssign(dest, src interface{}) error {
return errors.New("destination not a pointer")
}
+ if !sv.IsValid() {
+ sv = reflect.ValueOf(src)
+ }
+
dv := reflect.Indirect(dpv)
if dv.Kind() == sv.Kind() {
dv.Set(sv)
@@ -67,40 +79,49 @@ func convertAssign(dest, src interface{}) error {
switch dv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if s, ok := asString(src); ok {
- i64, err := strconv.Atoi64(s)
- if err != nil {
- return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
- }
- if dv.OverflowInt(i64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
- dv.SetInt(i64)
- return nil
+ s := asString(src)
+ i64, err := strconv.Atoi64(s)
+ if err != nil {
+ return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
+ if dv.OverflowInt(i64) {
+ return fmt.Errorf("string %q overflows %s", s, dv.Kind())
+ }
+ dv.SetInt(i64)
+ return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- if s, ok := asString(src); ok {
- u64, err := strconv.Atoui64(s)
- if err != nil {
- return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
- }
- if dv.OverflowUint(u64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
- dv.SetUint(u64)
- return nil
+ s := asString(src)
+ u64, err := strconv.Atoui64(s)
+ if err != nil {
+ return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
+ if dv.OverflowUint(u64) {
+ return fmt.Errorf("string %q overflows %s", s, dv.Kind())
+ }
+ dv.SetUint(u64)
+ return nil
+ case reflect.Float32, reflect.Float64:
+ s := asString(src)
+ f64, err := strconv.Atof64(s)
+ if err != nil {
+ return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
+ }
+ if dv.OverflowFloat(f64) {
+ return fmt.Errorf("value %q overflows %s", s, dv.Kind())
+ }
+ dv.SetFloat(f64)
+ return nil
}\n
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)\n }\n \n-func asString(src interface{}) (s string, ok bool) {\n+func asString(src interface{}) string {\n switch v := src.(type) {\n case string:\n-\t\treturn v, true\n+\t\treturn v\n case []byte:\n-\t\treturn string(v), true\n+\t\treturn string(v)\n }\n-\treturn "", false\n+\treturn fmt.Sprintf("%v", src)\n }\n```
### `src/pkg/exp/sql/driver/types.go`
```diff
--- a/src/pkg/exp/sql/driver/types.go
+++ b/src/pkg/exp/sql/driver/types.go
@@ -19,15 +34,56 @@ type ValueConverter interface {
// Bool is a ValueConverter that converts input values to bools.\n //\n // The conversion rules are:\n-// - .... TODO(bradfitz): TBD\n+// - booleans are returned unchanged\n+// - for integer types,\n+// 1 is true\n+// 0 is false,\n+// other integers are an error\n+// - for strings and []byte, same rules as strconv.Atob\n+// - all other types are an error\n var Bool boolType\n \n type boolType struct{}\n \n var _ ValueConverter = boolType{}\n \n-func (boolType) ConvertValue(v interface{}) (interface{}, error) {\n-\treturn nil, fmt.Errorf("TODO(bradfitz): bool conversions")\n+func (boolType) String() string { return "Bool" }\n+\n+func (boolType) ConvertValue(src interface{}) (interface{}, error) {\n+\tswitch s := src.(type) {\n+\tcase bool:\n+\t\treturn s, nil\n+\tcase string:\n+\t\tb, err := strconv.Atob(s)\n+\t\tif err != nil {\n+\t\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)\n+\t\t}\n+\t\treturn b, nil\n+\tcase []byte:\n+\t\tb, err := strconv.Atob(string(s))\n+\t\tif err != nil {\n+\t\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)\n+\t\t}\n+\t\treturn b, nil\n+\t}\n+\n+\tsv := reflect.ValueOf(src)\n+\tswitch sv.Kind() {\n+\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n+\t\tiv := sv.Int()\n+\t\tif iv == 1 || iv == 0 {\n+\t\t\treturn iv == 1, nil\n+\t\t}\n+\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", iv)\n+\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n+\t\tuv := sv.Uint()\n+\t\tif uv == 1 || uv == 0 {\n+\t\t\treturn uv == 1, nil\n+\t\t}\n+\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", uv)\n+\t}\n+\n+\treturn nil, fmt.Errorf("sql/driver: couldn't convert %v (%T) into type bool", src, src)\n }\n \n // Int32 is a ValueConverter that converts input values to int64,\n```
### `src/pkg/exp/sql/sql.go`
```diff
--- a/src/pkg/exp/sql/sql.go
+++ b/src/pkg/exp/sql/sql.go
@@ -192,13 +191,13 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
// If the driver does not implement driver.Execer, we need
// a connection.\n- conn, err := db.conn()\n+ ci, err := db.conn()\n if err != nil {
return nil, err
}\n- defer db.putConn(conn)\n+ defer db.putConn(ci)\n \n- if execer, ok := conn.(driver.Execer); ok {\n+ if execer, ok := ci.(driver.Execer); ok {\n resi, err := execer.Exec(query, args)\n if err != nil {
return nil, err
}\n@@ -206,7 +205,7 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
return result{resi}, nil
}\n \n- sti, err := conn.Prepare(query)\n+ sti, err := ci.Prepare(query)\n if err != nil {
return nil, err
}\n@@ -233,18 +232,26 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
// Row's Scan method is called.\n func (db *DB) QueryRow(query string, args ...interface{}) *Row {\n rows, err := db.Query(query, args...)\n- if err != nil {\n- return &Row{err: err}\n- }\n- return &Row{rows: rows}\n+ return &Row{rows: rows, err: err}\n }\n \n-// Begin starts a transaction. The isolation level is dependent on\n+// Begin starts a transaction. The isolation level is dependent on\n // the driver.\n func (db *DB) Begin() (*Tx, error) {\n- // TODO(bradfitz): add another method for beginning a transaction\n- // at a specific isolation level.\n- panic(todo())\n+ ci, err := db.conn()\n+ if err != nil {\n+ return nil, err\n+ }\n+ txi, err := ci.Begin()\n+ if err != nil {\n+ db.putConn(ci)\n+ return nil, fmt.Errorf("sql: failed to Begin transaction: %v", err)\n+ }\n+ return &Tx{\n+ db: db,\n+ ci: ci,\n+ txi: txi,\n+ }, nil\n }\n \n // DriverDatabase returns the database's underlying driver.\n@@ -253,41 +260,158 @@ func (db *DB) Driver() driver.Driver {\n }\n \n // Tx is an in-progress database transaction.\n+//\n+// A transaction must end with a call to Commit or Rollback.\n+//\n+// After a call to Commit or Rollback, all operations on the\n+// transaction fail with ErrTransactionFinished.\n type Tx struct {\n+\tdb *DB\n+\n+\t// ci is owned exclusively until Commit or Rollback, at which point\n+\t// it's returned with putConn.\n+\tci driver.Conn\n+\ttxi driver.Tx\n+\n+\t// cimu is held while somebody is using ci (between grabConn\n+\t// and releaseConn)\n+\tcimu sync.Mutex\n+\n+\t// done transitions from false to true exactly once, on Commit\n+\t// or Rollback. once done, all operations fail with\n+\t// ErrTransactionFinished.\n+\tdone bool\n+}\n+\n+var ErrTransactionFinished = errors.New("sql: Transaction has already been committed or rolled back")\n+\n+func (tx *Tx) close() {\n+\tif tx.done {\n+\t\tpanic("double close") // internal error\n+\t}\n+\ttx.done = true\n+\ttx.db.putConn(tx.ci)\n+\ttx.ci = nil\n+\ttx.txi = nil\n+}\n+\n+func (tx *Tx) grabConn() (driver.Conn, error) {\n+\tif tx.done {\n+\t\treturn nil, ErrTransactionFinished\n+\t}\n+\ttx.cimu.Lock()\n+\treturn tx.ci, nil\n+}\n+\n+func (tx *Tx) releaseConn() {\n+\ttx.cimu.Unlock()\n }\n \n // Commit commits the transaction.\n func (tx *Tx) Commit() error {\n-\tpanic(todo())\n+\tif tx.done {\n+\t\treturn ErrTransactionFinished\n+\t}\n+\tdefer tx.close()\n+\treturn tx.txi.Commit()\n }\n \n // Rollback aborts the transaction.\n func (tx *Tx) Rollback() error {\n-\tpanic(todo())\n+\tif tx.done {\n+\t\treturn ErrTransactionFinished\n+\t}\n+\tdefer tx.close()\n+\treturn tx.txi.Rollback()\n }\n \n // Prepare creates a prepared statement.\n+//\n+// The statement is only valid within the scope of this transaction.\n func (tx *Tx) Prepare(query string) (*Stmt, error) {\n-\tpanic(todo())\n+\t// TODO(bradfitz): the restriction that the returned statement\n+\t// is only valid for this Transaction is lame and negates a\n+\t// lot of the benefit of prepared statements. We could be\n+\t// more efficient here and either provide a method to take an\n+\t// existing Stmt (created on perhaps a different Conn), and\n+\t// re-create it on this Conn if necessary. Or, better: keep a\n+\t// map in DB of query string to Stmts, and have Stmt.Execute\n+\t// do the right thing and re-prepare if the Conn in use\n+\t// doesn't have that prepared statement. But we'll want to\n+\t// avoid caching the statement in the case where we only call\n+\t// conn.Prepare implicitly (such as in db.Exec or tx.Exec),\n+\t// but the caller package can't be holding a reference to the\n+\t// returned statement. Perhaps just looking at the reference\n+\t// count (by noting Stmt.Close) would be enough. We might also\n+\t// want a finalizer on Stmt to drop the reference count.\n+\tci, err := tx.grabConn()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer tx.releaseConn()\n+\n+\tsi, err := ci.Prepare(query)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tstmt := &Stmt{\n+\t\tdb: tx.db,\n+\t\ttx: tx,\n+\t\ttxsi: si,\n+\t\tquery: query,\n+\t}\n+\treturn stmt, nil\n }\n \n // Exec executes a query that doesn't return rows.\n // For example: an INSERT and UPDATE.\n-func (tx *Tx) Exec(query string, args ...interface{}) {\n-\tpanic(todo())\n+func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) {\n+\tci, err := tx.grabConn()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer tx.releaseConn()\n+\n+\tif execer, ok := ci.(driver.Execer); ok {\n+\t\tresi, err := execer.Exec(query, args)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\treturn result{resi}, nil\n+\t}\n+\n+\tsti, err := ci.Prepare(query)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer sti.Close()\n+\tresi, err := sti.Exec(args)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn result{resi}, nil\n }\n \n // Query executes a query that returns rows, typically a SELECT.\n func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {\n-\tpanic(todo())\n+\tif tx.done {\n+\t\treturn nil, ErrTransactionFinished\n+\t}\n+\tstmt, err := tx.Prepare(query)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer stmt.Close()\n+\treturn stmt.Query(args...)\n }\n \n // QueryRow executes a query that is expected to return at most one row.\n // QueryRow always return a non-nil value. Errors are deferred until\n // Row's Scan method is called.\n func (tx *Tx) QueryRow(query string, args ...interface{}) *Row {\n-\tpanic(todo())\n+\trows, err := tx.Query(query, args...)\n+\treturn &Row{rows: rows, err: err}\n }\n \n // connStmt is a prepared statement on a particular connection.\n@@ -302,24 +426,28 @@ type Stmt struct {\n \tdb *DB // where we came from\n \tquery string // that created the Sttm\n \n-\tmu sync.Mutex\n+\t// If in a transaction, else both nil:\n+\ttx *Tx\n+\ttxsi driver.Stmt\n+\n+\tmu sync.Mutex // protects the rest of the fields\n \tclosed bool\n-\tcss []connStmt // can use any that have idle connections\n-}\n \n-func todo() string {\n-\t_, file, line, _ := runtime.Caller(1)\n-\treturn fmt.Sprintf("%s:%d: TODO: implement", file, line)\n+\t// css is a list of underlying driver statement interfaces\n+\t// that are valid on particular connections. This is only\n+\t// used if tx == nil and one is found that has idle\n+\t// connections. If tx != nil, txsi is always used.\n+\tcss []connStmt\n }\n \n // Exec executes a prepared statement with the given arguments and\n // returns a Result summarizing the effect of the statement.\n func (s *Stmt) Exec(args ...interface{}) (Result, error) {\n-\tci, si, err := s.connStmt()\n+\t_, releaseConn, si, err := s.connStmt()\n \tif err != nil {\n \t\treturn nil, err\n \t}\n-\tdefer s.db.putConn(ci)\n+\tdefer releaseConn()\n \n \tif want := si.NumInput(); len(args) != want {\n \t\treturn nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args))\n@@ -353,11 +481,29 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {\n \treturn result{resi}, nil\n }\n \n-func (s *Stmt) connStmt(args ...interface{}) (driver.Conn, driver.Stmt, error) {\n+// connStmt returns a free driver connection on which to execute the\n+// statement, a function to call to release the connection, and a\n+// statement bound to that connection.\n+func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, err error) {\n \ts.mu.Lock()\n \tif s.closed {\n-\t\treturn nil, nil, errors.New("db: statement is closed")\n+\t\ts.mu.Unlock()\n+\t\terr = errors.New("db: statement is closed")\n+\t\treturn\n \t}\n+\n+\t// In a transaction, we always use the connection that the\n+\t// transaction was created on.\n+\tif s.tx != nil {\n+\t\ts.mu.Unlock()\n+\t\tci, err = s.tx.grabConn() // blocks, waiting for the connection.\n+\t\tif err != nil {\n+\t\t\treturn\n+\t\t}\n+\t\treleaseConn = func() { s.tx.releaseConn() }\n+\t\treturn ci, releaseConn, s.txsi, nil\n+\t}\n+\n \tvar cs connStmt\n \tmatch := false\n \tfor _, v := range s.css {\
@@ -375,11 +521,11 @@ func (s *Stmt) connStmt(args ...interface{}) (driver.Conn, driver.Stmt, error) {\n \tif !match {\n \t\tci, err := s.db.conn()\n \t\tif err != nil {\n-\t\t\treturn nil, nil, err\n+\t\t\treturn nil, nil, nil, err\n \t\t}\n \t\tsi, err := ci.Prepare(s.query)\n \t\tif err != nil {\n-\t\t\treturn nil, nil, err\n+\t\t\treturn nil, nil, nil, err\n \t\t}\n \t\ts.mu.Lock()\n \t\tcs = connStmt{ci, si}\n@@ -387,13 +533,15 @@ func (s *Stmt) connStmt(args ...interface{}) (driver.Conn, driver.Stmt, error) {\n \t\ts.mu.Unlock()\n \t}\n \n-\treturn cs.ci, cs.si, nil\n+\tconn := cs.ci\n+\treleaseConn = func() { s.db.putConn(conn) }\n+\treturn conn, releaseConn, cs.si, nil\n }\n \n // Query executes a prepared query statement with the given arguments\n // and returns the query results as a *Rows.\n func (s *Stmt) Query(args ...interface{}) (*Rows, error) {\n-\tci, si, err := s.connStmt(args...)\n+\tci, releaseConn, si, err := s.connStmt()\n \tif err != nil {\n \t\treturn nil, err\n \t}\n@@ -405,11 +553,13 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {\n \t\ts.db.putConn(ci)\n \t\treturn nil, err\n \t}\n-\t// Note: ownership of ci passes to the *Rows\n+\t// Note: ownership of ci passes to the *Rows, to be freed\n+\t// with releaseConn.\n \trows := &Rows{\n-\t\tdb: s.db,\n-\t\tci: ci,\n-\t\trowsi: rowsi,\n+\t\tdb: s.db,\n+\t\tci: ci,\n+\t\treleaseConn: releaseConn,\n+\t\trowsi: rowsi,\n \t}\n \treturn rows, nil\n }\n@@ -436,19 +586,24 @@ func (s *Stmt) QueryRow(args ...interface{}) *Row {\n // Close closes the statement.\n func (s *Stmt) Close() error {\n \ts.mu.Lock()\n-\tdefer s.mu.Unlock() // TODO(bradfitz): move this unlock after 'closed = true'?\n+\tdefer s.mu.Unlock()\n \tif s.closed {\n \t\treturn nil\n \t}\n \ts.closed = true\n-\tfor _, v := range s.css {\n-\t\tif ci, match := s.db.connIfFree(v.ci); match {\n-\t\t\tv.si.Close()\n-\t\t\ts.db.putConn(ci)\n-\t\t} else {\n-\t\t\t// TODO(bradfitz): care that we can't close\n-\t\t\t// this statement because the statement's\n-\t\t\t// connection is in use?\n+\n+\tif s.tx != nil {\n+\t\ts.txsi.Close()\n+\t} else {\n+\t\tfor _, v := range s.css {\n+\t\t\tif ci, match := s.db.connIfFree(v.ci); match {\n+\t\t\t\tv.si.Close()\n+\t\t\t\ts.db.putConn(ci)\n+\t\t\t} else {\n+\t\t\t\t// TODO(bradfitz): care that we can't close\n+\t\t\t\t// this statement because the statement's\n+\t\t\t\t// connection is in use?\n+\t\t\t}\n \t\t}\n \t}\n \treturn nil\n@@ -468,9 +623,10 @@ func (s *Stmt) Close() error {\n // err = rows.Error() // get any Error encountered during iteration\n // ...\n type Rows struct {\n-\tdb *DB\n-\tci driver.Conn // owned; must be returned when Rows is closed\n-\trowsi driver.Rows\n+\tdb *DB\n+\tci driver.Conn // owned; must call putconn when closed to release\n+\treleaseConn func()\n+\trowsi driver.Rows\n \n \tclosed bool\n \tlastcols []interface{}\n@@ -538,7 +694,7 @@ func (rs *Rows) Close() error {\n \t}\n \trs.closed = true\n \terr := rs.rowsi.Close()\n-\trs.db.putConn(rs.ci)\n+\trs.releaseConn()\n \treturn err\n }\n \n```
## コアとなるコードの解説
### `src/pkg/exp/sql/convert.go`の変更点
* **`convertAssign`関数の`*bool`と`float`型への対応**:
* `case *bool:`ブロックが追加され、`driver.Bool.ConvertValue(src)`を呼び出して、ソース値をブール値に変換しようとします。これは、データベースから読み込んだ値(例: "true", 1, 0)をGoの`bool`型に変換するために重要です。
* `case reflect.Float32, reflect.Float64:`ブロックが追加され、文字列から浮動小数点数への変換(`strconv.Atof64`)と、オーバーフローチェック(`dv.OverflowFloat`)を行うようになりました。これにより、データベースの浮動小数点数型をGoの`float32`や`float64`に安全にスキャンできます。
* **`asString`関数の変更**:
* 関数のシグネチャが`func asString(src interface{}) string`に変更され、戻り値から`ok bool`が削除されました。
* `src`が`string`または`[]byte`でない場合、以前は空文字列を返していましたが、`fmt.Sprintf("%v", src)`を使用して任意の値を文字列に変換するようになりました。これにより、数値型などが文字列として扱われるケースでの柔軟性が向上し、`strconv.Atoi64`や`strconv.Atoui64`、`strconv.Atof64`がより広範な入力に対応できるようになりました。
### `src/pkg/exp/sql/driver/types.go`の変更点
* **`Bool` `ValueConverter`の実装**:
* `boolType`構造体に`String()`メソッドが追加され、デバッグ時の可読性が向上しました。
* `ConvertValue`メソッドが完全に実装されました。
* `bool`型はそのまま返されます。
* `string`型と`[]byte`型は`strconv.Atob`(文字列をブール値に変換)を使用して変換されます。
* `reflect.Int`や`reflect.Uint`などの整数型は、値が`0`または`1`の場合にそれぞれ`false`または`true`に変換されます。それ以外の整数値はエラーとなります。
* その他の型はすべてエラーとなります。
* この実装により、データベースドライバーは、様々な形式で表現された真偽値をGoの`bool`型に一貫して変換できるようになりました。
### `src/pkg/exp/sql/sql.go`の変更点
* **`DB.Begin()`の実装**:
* `db.conn()`で接続を取得し、`ci.Begin()`でトランザクションを開始します。
* `Tx`構造体を初期化し、`db`、取得した`ci`(接続)、および`txi`(ドライバーのトランザクションインターフェース)を格納します。
* トランザクション開始に失敗した場合、取得した接続を`db.putConn(ci)`でプールに戻します。
* **`Tx`構造体の追加とメソッドの実装**:
* `Tx`構造体は、トランザクションの状態(`done`フラグ)、関連する`DB`、`driver.Conn`、`driver.Tx`、および接続の排他制御のための`sync.Mutex` (`cimu`) を保持します。
* `ErrTransactionFinished`という新しいエラーが定義され、終了したトランザクションへの操作を検出します。
* `tx.close()`ヘルパー関数は、トランザクションが終了した際に`done`フラグを設定し、接続をプールに戻します。
* `tx.grabConn()`と`tx.releaseConn()`は、トランザクションの接続を安全に取得・解放するためのメソッドです。
* `Tx.Commit()`と`Tx.Rollback()`は、`tx.done`をチェックし、`defer tx.close()`で接続の解放を保証しつつ、基盤となる`driver.Tx`のメソッドを呼び出します。
* `Tx.Prepare()`, `Tx.Exec()`, `Tx.Query()`, `Tx.QueryRow()`は、トランザクションのコンテキスト内でデータベース操作を実行するためのメソッドです。これらは`tx.grabConn()`と`tx.releaseConn()`を使用して、トランザクションの接続を排他的に利用します。
* **`Stmt`構造体の変更**:
* `tx *Tx`フィールドが追加され、ステートメントがトランザクションの一部であるかどうかを示します。
* `txsi driver.Stmt`フィールドが追加され、トランザクション内のステートメントの場合に、そのトランザクションの接続にバインドされたドライバーのステートメントを保持します。
* `connStmt()`メソッドが大幅に変更され、`s.tx != nil`の場合(トランザクション内のステートメントの場合)は、常にそのトランザクションの接続(`s.tx.ci`)を使用するようにしました。これにより、トランザクションの分離性が保証されます。
* `Stmt.Exec()`と`Stmt.Query()`は、`connStmt()`から返される`releaseConn`関数を`defer`で呼び出すことで、接続の解放を適切に行うようになりました。
* `Stmt.Close()`も、ステートメントがトランザクションの一部であるかどうかに応じて、適切なクリーンアップ(`s.txsi.Close()`または`s.css`内のステートメントのクローズ)を行うように修正されました。
* **`Rows`構造体の変更**:
* `releaseConn func()`フィールドが追加され、`Rows`が閉じられたときに`rs.ci`(接続)をプールに戻すための関数を保持するようになりました。これにより、`Rows`のライフサイクルと接続の解放が密接に連携するようになりました。
これらの変更は、`exp/sql`パッケージの堅牢性と機能性を大幅に向上させ、Go言語でのデータベースプログラミングをより安全で効率的なものにするための重要なステップでした。
## 関連リンク
* GitHubコミットページ: [https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc](https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc)
* Go Issue 2328: [https://golang.org/issue/2328](https://golang.org/issue/2328)
* Gerrit Change-Id: [https://golang.org/cl/5294067](https://golang.org/cl/5294067)
## 参考にした情報源リンク
* Go言語の`database/sql`パッケージに関する公式ドキュメントやチュートリアル(当時の`exp/sql`の設計思想を理解するため)
* Go言語の`reflect`パッケージに関する公式ドキュメント
* SQLトランザクションに関する一般的なデータベースの概念
* `strconv`パッケージのドキュメント(`Atoi64`, `Atoui64`, `Atof64`, `Atob`など)
* Go言語の`sync`パッケージ(`Mutex`の利用)
(注: 2011年当時の情報源は直接リンクできない場合がありますが、上記の概念は現在のGoのドキュメントでも確認できます。)
I have completed the comprehensive technical explanation of the commit, following all the instructions and including all the required sections.
I have completed the comprehensive technical explanation of the commit, following all the instructions and including all the required sections.承知いたしました。コミット情報を取得し、包括的な技術解説を生成します。
# [インデックス 10211] ファイルの概要
このコミットは、Go言語の実験的な`exp/sql`パッケージにおける重要な改善と機能追加を目的としています。具体的には、データベーストランザクションの完了、データ型(特に`float`と`bool`)の扱いをより堅牢にするための型変換ロジックの強化、および関連するドキュメントの充実が図られています。これにより、`exp/sql`パッケージがより実用的なデータベースアクセス層として機能するための基盤が固められました。
## コミット
* **コミットハッシュ**: `8089e578124227a322a77f9c5599d2244f9d0cfc`
* **Author**: Brad Fitzpatrick <bradfitz@golang.org>
* **Date**: Wed Nov 2 11:46:04 2011 -0700
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc](https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc)
## 元コミット内容
exp/sql: finish transactions, flesh out types, docs
Fixes #2328 (float, bool)
R=rsc, r CC=golang-dev https://golang.org/cl/5294067
## 変更の背景
このコミットの主な背景には、Go言語の標準ライブラリにデータベースアクセス機能を追加する初期段階での課題がありました。`exp/sql`パッケージは、Goのデータベース/SQLインターフェースのプロトタイプとして開発されており、様々なデータベースドライバーが共通のインターフェースを通じて動作できるように設計されていました。
元々、`exp/sql`パッケージは基本的なクエリ実行機能を提供していましたが、トランザクション管理や、データベースから取得した多様なデータ型(特に浮動小数点数や真偽値)をGoの適切な型に正確かつ安全に変換する機能が不十分でした。
コミットメッセージにある`Fixes #2328 (float, bool)`は、この変更がGoのIssue 2328を解決するものであることを示しています。Issue 2328は、`exp/sql`パッケージが`float`型と`bool`型のデータを適切に扱えないという問題点を指摘していました。具体的には、データベースから取得したこれらの型の値をGoの変数にスキャンする際に、正しい変換が行われず、エラーが発生したり、予期せぬ値になったりする可能性がありました。
このコミットは、これらの欠点を解消し、`exp/sql`パッケージがより堅牢で実用的なデータベースアクセス層となることを目指しています。トランザクション機能の完全な実装は、複数のデータベース操作をアトミックに実行するために不可欠であり、データの一貫性と信頼性を保証します。また、`float`と`bool`の型変換の改善は、より広範なデータベーススキーマとデータ型に対応するために必要でした。
## 前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とデータベース関連の知識が必要です。
### Go言語の`database/sql`パッケージ(および`exp/sql`)
Go言語の標準ライブラリには、データベースにアクセスするための汎用的なインターフェースを提供する`database/sql`パッケージがあります。このパッケージは、特定のデータベースシステムに依存しない抽象化レイヤーを提供し、データベースドライバーを介して実際のデータベース操作を行います。
* **`sql.DB`**: データベースへの接続プールを管理する構造体。これは、実際のデータベース接続を直接表すものではなく、接続のオープン、クローズ、およびプーリングを内部で処理します。複数のゴルーチンから安全に利用できます。
* **`sql.Conn`**: データベースへの単一の接続を表すインターフェース。
* **`sql.Tx`**: データベーストランザクションを表す構造体。トランザクションは、複数のデータベース操作を単一の論理的な作業単位としてグループ化し、すべて成功するか(コミット)、すべて失敗して元に戻すか(ロールバック)のいずれかを保証します。
* **`sql.Stmt`**: プリペアドステートメントを表す構造体。SQLインジェクション攻撃を防ぎ、クエリの再利用によるパフォーマンス向上に役立ちます。
* **`sql.Rows`**: クエリ結果の行を反復処理するための構造体。
* **`driver`パッケージ**: `database/sql`パッケージは、実際のデータベースとの通信を抽象化するために`database/sql/driver`パッケージに定義されたインターフェースを使用します。各データベース(例: MySQL, PostgreSQL, SQLite)は、この`driver`インターフェースを実装した独自のドライバーを提供します。
このコミットが対象としている`exp/sql`は、`database/sql`パッケージが標準ライブラリに組み込まれる前の実験的な段階のものです。しかし、基本的な設計思想とインターフェースは現在の`database/sql`と共通しています。
### `reflect`パッケージ
Goの`reflect`パッケージは、実行時にプログラムの構造を検査し、操作するための機能を提供します。これにより、型情報(`reflect.Type`)や値情報(`reflect.Value`)を動的に取得・設定できます。
* **`reflect.ValueOf(interface{})`**: 任意のGoの値を`reflect.Value`型に変換します。
* **`reflect.Kind()`**: `reflect.Value`が表す値の基本的な種類(例: `reflect.Int`, `reflect.String`, `reflect.Bool`)を返します。
* **`reflect.Indirect(reflect.Value)`**: ポインタが指す先の値を取得します。
* **`SetInt()`, `SetUint()`, `SetFloat()`**: `reflect.Value`が表す整数、符号なし整数、浮動小数点数の値を設定します。
* **`OverflowInt()`, `OverflowUint()`, `OverflowFloat()`**: 指定された値が`reflect.Value`が表す型の範囲を超えているかどうかをチェックします。
`exp/sql`パッケージでは、データベースから読み込んだ値をユーザーが指定したGoの型に変換する際に、この`reflect`パッケージが広く利用されています。これにより、汎用的な型変換ロジックを実装できます。
### `ValueConverter`インターフェース
`exp/sql/driver`パッケージには`ValueConverter`インターフェースが定義されています。これは、データベースドライバーがGoの基本的な型(`int64`, `float64`, `bool`, `string`, `[]byte`, `time.Time`)と、データベース固有の型との間で値を変換するためのメカニズムを提供します。このコミットでは、特に`Bool`型の`ValueConverter`の実装が詳細に記述されています。
### SQLトランザクション
SQLトランザクションは、データベース操作の論理的な単位です。トランザクションは以下のACID特性を保証します。
* **Atomicity (原子性)**: トランザクション内のすべての操作が成功するか、すべて失敗するかのいずれかです。部分的な成功はありません。
* **Consistency (一貫性)**: トランザクションはデータベースを一貫性のある状態から別の一貫性のある状態へ移行させます。
* **Isolation (独立性)**: 複数のトランザクションが同時に実行されても、それぞれが独立して実行されているかのように見えます。
* **Durability (永続性)**: コミットされたトランザクションの結果は、システム障害が発生しても失われません。
`Begin`、`Commit`、`Rollback`は、トランザクションを制御するための基本的な操作です。
## 技術的詳細
このコミットは、主に以下の3つの領域で技術的な改善を行っています。
1. **型変換ロジックの強化 (`src/pkg/exp/sql/convert.go`)**:
* **`float`型と`bool`型のサポート**: 以前は適切に扱えなかった`float`型と`bool`型への変換が追加されました。
* `*bool`への変換パスが追加され、`driver.Bool.ConvertValue(src)`を使用して真偽値への変換を試みます。これは、文字列("true", "false", "1", "0"など)、整数(0, 1)、およびブール値自体から`bool`への変換をサポートします。
* `reflect.Float32`および`reflect.Float64`への変換パスが追加されました。文字列から浮動小数点数への変換(`strconv.Atof64`)をサポートし、オーバーフローチェックも行います。
* **`asString`関数の変更**: `asString`関数が、`src`が`string`または`[]byte`でない場合でも、`fmt.Sprintf("%v", src)`を使用して任意の値を文字列に変換するように変更されました。これにより、数値型などが文字列として扱われるケースでの柔軟性が向上しました。以前は`string`または`[]byte`以外は空文字列と`false`を返していました。
* **エラーハンドリングの改善**: 型変換時のエラーメッセージがより具体的になり、デバッグが容易になりました。特に、数値型への変換時の`strconv`エラーやオーバーフローエラーが明確に報告されます。
2. **`driver`インターフェースと`ValueConverter`の実装 (`src/pkg/exp/sql/driver/driver.go`, `src/pkg/exp/sql/driver/types.go`)**:
* **`driver.Open`と`driver.Close`のドキュメント更新**: `Open`メソッドがキャッシュされた接続を返す可能性があること、および`sql`パッケージがアイドル接続のプールを維持するため、ドライバー側で独自の接続キャッシュを行う必要がないことが明記されました。これは、接続管理の責任が`sql`パッケージにあることを強調しています。
* **`Bool` `ValueConverter`の完全な実装**: `driver.types.go`に`Bool`型の`ValueConverter`が完全に実装されました。
* ブール値、文字列(`strconv.Atob`を使用)、バイトスライス、整数(0または1のみ)から`bool`への変換ルールが定義されました。
* 0または1以外の整数、およびその他の型からの変換はエラーとなります。
* これにより、データベースから取得した真偽値が、様々な形式で表現されていてもGoの`bool`型に正しく変換されるようになりました。
3. **トランザクション機能の完成 (`src/pkg/exp/sql/sql.go`)**:
* **`DB.Begin()`の実装**: `DB.Begin()`メソッドが`panic(todo())`から実際のトランザクション開始ロジックに置き換えられました。
* 接続プールから接続を取得し、その接続上で`driver.Conn.Begin()`を呼び出してトランザクションを開始します。
* トランザクション開始に失敗した場合は、取得した接続をプールに戻します。
* `Tx`構造体を初期化し、関連する`driver.Conn`と`driver.Tx`インスタンスを保持します。
* **`Tx`構造体の拡張**:
* `db` (`*DB`): 親の`DB`インスタンスへの参照。
* `ci` (`driver.Conn`): トランザクションが使用する接続。
* `txi` (`driver.Tx`): 基盤となるドライバーのトランザクションインターフェース。
* `cimu` (`sync.Mutex`): 接続の使用を保護するためのミューテックス。
* `done` (`bool`): トランザクションがコミットまたはロールバックされたかどうかを示すフラグ。
* **`Tx.Commit()`と`Tx.Rollback()`の実装**:
* これらのメソッドは、`tx.done`フラグをチェックし、既に終了しているトランザクションに対して操作が行われた場合に`ErrTransactionFinished`エラーを返します。
* `defer tx.close()`により、トランザクション終了時に接続をプールに戻す処理が保証されます。
* 基盤となる`driver.Tx.Commit()`または`driver.Tx.Rollback()`を呼び出します。
* **`Tx.Prepare()`, `Tx.Exec()`, `Tx.Query()`, `Tx.QueryRow()`の実装**:
* これらのメソッドは、トランザクション内でプリペアドステートメントの準備、クエリの実行、行の取得を行うためのものです。
* トランザクションの`ci`(接続)を排他的に利用し、操作が完了したら`releaseConn()`を呼び出してロックを解除します。
* `Stmt`構造体もトランザクションのコンテキストを認識するように変更され、トランザクション内のステートメントは常にそのトランザクションの接続を使用するようになりました。
* **`Stmt`構造体の変更**:
* `tx` (`*Tx`): ステートメントがトランザクションの一部である場合に設定されます。
* `txsi` (`driver.Stmt`): トランザクション内のステートメントの場合に、そのトランザクションの接続にバインドされたドライバーのステートメントを保持します。
* `connStmt()`メソッドがトランザクションのコンテキストを考慮するように変更され、トランザクション内のステートメントは常にそのトランザクションの接続を使用するようになりました。
* `Stmt.Close()`もトランザクションの有無に応じて適切なクリーンアップを行うように修正されました。
* **`Rows`構造体の変更**:
* `releaseConn func()`フィールドが追加され、`Rows`が閉じられたときに接続をプールに戻すための関数を保持するようになりました。これにより、接続のライフサイクル管理がより明確になりました。
これらの変更により、`exp/sql`パッケージは、トランザクションを安全かつ効率的に管理し、多様なデータ型をGoの型に正確に変換できるようになり、より実用的なデータベースアクセスライブラリとしての基盤が確立されました。
## コアとなるコードの変更箇所
### `src/pkg/exp/sql/convert.go`
```diff
--- a/src/pkg/exp/sql/convert.go
+++ b/src/pkg/exp/sql/convert.go
@@ -36,10 +37,11 @@ func convertAssign(dest, src interface{}) error {
}
}
- sv := reflect.ValueOf(src)
+ var sv reflect.Value
switch d := dest.(type) {
case *string:
+ sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
@@ -48,6 +50,12 @@ func convertAssign(dest, src interface{}) error {
*d = fmt.Sprintf("%v", src)
return nil
}
+ case *bool:
+ bv, err := driver.Bool.ConvertValue(src)
+ if err == nil {
+ *d = bv.(bool)
+ }
+ return err
}
if scanner, ok := dest.(ScannerInto); ok {
@@ -59,6 +67,10 @@ func convertAssign(dest, src interface{}) error {
return errors.New("destination not a pointer")
}
+ if !sv.IsValid() {
+ sv = reflect.ValueOf(src)
+ }
+
dv := reflect.Indirect(dpv)
if dv.Kind() == sv.Kind() {
dv.Set(sv)
@@ -67,40 +79,49 @@ func convertAssign(dest, src interface{}) error {
switch dv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if s, ok := asString(src); ok {
- i64, err := strconv.Atoi64(s)
- if err != nil {
- return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
- }
- if dv.OverflowInt(i64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
- dv.SetInt(i64)
- return nil
+ s := asString(src)
+ i64, err := strconv.Atoi64(s)
+ if err != nil {
+ return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
+ if dv.OverflowInt(i64) {
+ return fmt.Errorf("string %q overflows %s", s, dv.Kind())
+ }
+ dv.SetInt(i64)
+ return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- if s, ok := asString(src); ok {
- u64, err := strconv.Atoui64(s)
- if err != nil {
- return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
- }
- if dv.OverflowUint(u64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
- dv.SetUint(u64)
- return nil
+ s := asString(src)
+ u64, err := strconv.Atoui64(s)
+ if err != nil {
+ return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
+ if dv.OverflowUint(u64) {
+ return fmt.Errorf("string %q overflows %s", s, dv.Kind())
+ }
+ dv.SetUint(u64)
+ return nil
+ case reflect.Float32, reflect.Float64:
+ s := asString(src)
+ f64, err := strconv.Atof64(s)
+ if err != nil {
+ return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
+ }
+ if dv.OverflowFloat(f64) {
+ return fmt.Errorf("value %q overflows %s", s, dv.Kind())
+ }
+ dv.SetFloat(f64)
+ return nil
}\n
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)\n }\n \n-func asString(src interface{}) (s string, ok bool) {\n+func asString(src interface{}) string {\n switch v := src.(type) {\n case string:\n-\t\treturn v, true\n+\t\treturn v\n case []byte:\n-\t\treturn string(v), true\n+\t\treturn string(v)\n }\n-\treturn "", false\n+\treturn fmt.Sprintf("%v", src)\n }\n```
### `src/pkg/exp/sql/driver/types.go`
```diff
--- a/src/pkg/exp/sql/driver/types.go
+++ b/src/pkg/exp/sql/driver/types.go
@@ -19,15 +34,56 @@ type ValueConverter interface {
// Bool is a ValueConverter that converts input values to bools.\n //\n // The conversion rules are:\n-// - .... TODO(bradfitz): TBD\n+// - booleans are returned unchanged\n+// - for integer types,\n+// 1 is true\n+// 0 is false,\n+// other integers are an error\n+// - for strings and []byte, same rules as strconv.Atob\n+// - all other types are an error\n var Bool boolType\n \n type boolType struct{}\n \n var _ ValueConverter = boolType{}\n \n-func (boolType) ConvertValue(v interface{}) (interface{}, error) {\n-\treturn nil, fmt.Errorf("TODO(bradfitz): bool conversions")\n+func (boolType) String() string { return "Bool" }\n+\n+func (boolType) ConvertValue(src interface{}) (interface{}, error) {\n+\tswitch s := src.(type) {\n+\tcase bool:\n+\t\treturn s, nil\n+\tcase string:\n+\t\tb, err := strconv.Atob(s)\n+\t\tif err != nil {\n+\t\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)\n+\t\t}\n+\t\treturn b, nil\n+\tcase []byte:\n+\t\tb, err := strconv.Atob(string(s))\n+\t\tif err != nil {\n+\t\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)\n+\t\t}\n+\t\treturn b, nil\n+\t}\n+\n+\tsv := reflect.ValueOf(src)\n+\tswitch sv.Kind() {\n+\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n+\t\tiv := sv.Int()\n+\t\tif iv == 1 || iv == 0 {\n+\t\t\treturn iv == 1, nil\n+\t\t}\n+\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", iv)\n+\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n+\t\tuv := sv.Uint()\n+\t\tif uv == 1 || uv == 0 {\n+\t\t\treturn uv == 1, nil\n+\t\t}\n+\t\treturn nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", uv)\n+\t}\n+\n+\treturn nil, fmt.Errorf("sql/driver: couldn't convert %v (%T) into type bool", src, src)\n }\n \n // Int32 is a ValueConverter that converts input values to int64,\n```
### `src/pkg/exp/sql/sql.go`
```diff
--- a/src/pkg/exp/sql/sql.go
+++ b/src/pkg/exp/sql/sql.go
@@ -192,13 +191,13 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
// If the driver does not implement driver.Execer, we need
// a connection.\n- conn, err := db.conn()\n+ ci, err := db.conn()\n if err != nil {
return nil, err
}\n- defer db.putConn(conn)\n+ defer db.putConn(ci)\n \n- if execer, ok := conn.(driver.Execer); ok {\n+ if execer, ok := ci.(driver.Execer); ok {\n resi, err := execer.Exec(query, args)\n if err != nil {
return nil, err
}\n@@ -206,7 +205,7 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
return result{resi}, nil
}\n \n- sti, err := conn.Prepare(query)\n+ sti, err := ci.Prepare(query)\n if err != nil {
return nil, err
}\n@@ -233,18 +232,26 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
// Row's Scan method is called.\n func (db *DB) QueryRow(query string, args ...interface{}) *Row {\n rows, err := db.Query(query, args...)\n- if err != nil {\n- return &Row{err: err}\n- }\n- return &Row{rows: rows}\n+ return &Row{rows: rows, err: err}\n }\n \n-// Begin starts a transaction. The isolation level is dependent on\n+// Begin starts a transaction. The isolation level is dependent on\n // the driver.\n func (db *DB) Begin() (*Tx, error) {\n- // TODO(bradfitz): add another method for beginning a transaction\n- // at a specific isolation level.\n- panic(todo())\n+ ci, err := db.conn()\n+ if err != nil {
+ return nil, err
+ }\n+ txi, err := ci.Begin()\n+ if err != nil {
+ db.putConn(ci)\n+ return nil, fmt.Errorf("sql: failed to Begin transaction: %v", err)\n+ }\n+ return &Tx{\n+ db: db,\n+ ci: ci,\n+ txi: txi,\n+ }, nil\n }\n \n // DriverDatabase returns the database's underlying driver.\n@@ -253,41 +260,158 @@ func (db *DB) Driver() driver.Driver {\n }\n \n // Tx is an in-progress database transaction.\n+//\n+// A transaction must end with a call to Commit or Rollback.\n+//\n+// After a call to Commit or Rollback, all operations on the\n+// transaction fail with ErrTransactionFinished.\n type Tx struct {\n+\tdb *DB\n+\n+\t// ci is owned exclusively until Commit or Rollback, at which point\n+\t// it's returned with putConn.\n+\tci driver.Conn\n+\ttxi driver.Tx\n+\n+\t// cimu is held while somebody is using ci (between grabConn\n+\t// and releaseConn)\n+\tcimu sync.Mutex\n+\n+\t// done transitions from false to true exactly once, on Commit\n+\t// or Rollback. once done, all operations fail with\n+\t// ErrTransactionFinished.\n+\tdone bool\n+}\n+\n+var ErrTransactionFinished = errors.New("sql: Transaction has already been committed or rolled back")\n+\n+func (tx *Tx) close() {\n+\tif tx.done {\n+\t\tpanic("double close") // internal error\n+\t}\n+\ttx.done = true\n+\ttx.db.putConn(tx.ci)\n+\ttx.ci = nil\n+\ttx.txi = nil\n+}\n+\n+func (tx *Tx) grabConn() (driver.Conn, error) {\n+\tif tx.done {\n+\t\treturn nil, ErrTransactionFinished\n+\t}\n+\ttx.cimu.Lock()\n+\treturn tx.ci, nil\n+}\n+\n+func (tx *Tx) releaseConn() {\n+\ttx.cimu.Unlock()\n }\n \n // Commit commits the transaction.\n func (tx *Tx) Commit() error {\n-\tpanic(todo())\n+\tif tx.done {\n+\t\treturn ErrTransactionFinished\n+\t}\n+\tdefer tx.close()\n+\treturn tx.txi.Commit()\n }\n \n // Rollback aborts the transaction.\n func (tx *Tx) Rollback() error {\n-\tpanic(todo())\n+\tif tx.done {\n+\t\treturn ErrTransactionFinished\n+\t}\n+\tdefer tx.close()\n+\treturn tx.txi.Rollback()\n }\n \n // Prepare creates a prepared statement.\n+//\n+// The statement is only valid within the scope of this transaction.\n func (tx *Tx) Prepare(query string) (*Stmt, error) {\n-\tpanic(todo())\n+\t// TODO(bradfitz): the restriction that the returned statement\n+\t// is only valid for this Transaction is lame and negates a\n+\t// lot of the benefit of prepared statements. We could be\n+\t// more efficient here and either provide a method to take an\n+\t// existing Stmt (created on perhaps a different Conn), and\n+\t// re-create it on this Conn if necessary. Or, better: keep a\n+\t// map in DB of query string to Stmts, and have Stmt.Execute\n+\t// do the right thing and re-prepare if the Conn in use\n+\t// doesn't have that prepared statement. But we'll want to\n+\t// avoid caching the statement in the case where we only call\n+\t// conn.Prepare implicitly (such as in db.Exec or tx.Exec),\n+\t// but the caller package can't be holding a reference to the\n+\t// returned statement. Perhaps just looking at the reference\n+\t// count (by noting Stmt.Close) would be enough. We might also\n+\t// want a finalizer on Stmt to drop the reference count.\n+\tci, err := tx.grabConn()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer tx.releaseConn()\n+\n+\tsi, err := ci.Prepare(query)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tstmt := &Stmt{\n+\t\tdb: tx.db,\n+\t\ttx: tx,\n+\t\ttxsi: si,\n+\t\tquery: query,\n+\t}\n+\treturn stmt, nil\n }\n \n // Exec executes a query that doesn't return rows.\n // For example: an INSERT and UPDATE.\n-func (tx *Tx) Exec(query string, args ...interface{}) {\n-\tpanic(todo())\n+func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) {\n+\tci, err := tx.grabConn()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer tx.releaseConn()\n+\n+\tif execer, ok := ci.(driver.Execer); ok {\n+\t\tresi, err := execer.Exec(query, args)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\treturn result{resi}, nil\n+\t}\n+\n+\tsti, err := ci.Prepare(query)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer sti.Close()\n+\tresi, err := sti.Exec(args)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn result{resi}, nil\n }\n \n // Query executes a query that returns rows, typically a SELECT.\n func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {\n-\tpanic(todo())\n+\tif tx.done {\n+\t\treturn nil, ErrTransactionFinished\n+\t}\n+\tstmt, err := tx.Prepare(query)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer stmt.Close()\n+\treturn stmt.Query(args...)\n }\n \n // QueryRow executes a query that is expected to return at most one row.\n // QueryRow always return a non-nil value. Errors are deferred until\n // Row's Scan method is called.\n func (tx *Tx) QueryRow(query string, args ...interface{}) *Row {\n-\tpanic(todo())\n+\trows, err := tx.Query(query, args...)\n+\treturn &Row{rows: rows, err: err}\n }\n \n // connStmt is a prepared statement on a particular connection.\n@@ -302,24 +426,28 @@ type Stmt struct {\n \tdb *DB // where we came from\n \tquery string // that created the Sttm\n \n-\tmu sync.Mutex\n+\t// If in a transaction, else both nil:\n+\ttx *Tx\n+\ttxsi driver.Stmt\n+\n+\tmu sync.Mutex // protects the rest of the fields\n \tclosed bool\n-\tcss []connStmt // can use any that have idle connections\n-}\n \n-func todo() string {\n-\t_, file, line, _ := runtime.Caller(1)\n-\treturn fmt.Sprintf("%s:%d: TODO: implement", file, line)\n+\t// css is a list of underlying driver statement interfaces\n+\t// that are valid on particular connections. This is only\n+\t// used if tx == nil and one is found that has idle\n+\t// connections. If tx != nil, txsi is always used.\n+\tcss []connStmt\n }\n \n // Exec executes a prepared statement with the given arguments and\ // returns a Result summarizing the effect of the statement.\n func (s *Stmt) Exec(args ...interface{}) (Result, error) {\n-\tci, si, err := s.connStmt()\n+\t_, releaseConn, si, err := s.connStmt()\n \tif err != nil {\n \t\treturn nil, err\n \t}\n-\tdefer s.db.putConn(ci)\n+\tdefer releaseConn()\n \n \tif want := si.NumInput(); len(args) != want {\n \t\treturn nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args))\n@@ -353,11 +481,29 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {\n \treturn result{resi}, nil\n }\n \n-func (s *Stmt) connStmt(args ...interface{}) (driver.Conn, driver.Stmt, error) {\n+// connStmt returns a free driver connection on which to execute the\n+// statement, a function to call to release the connection, and a\n+// statement bound to that connection.\n+func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, err error) {\n \ts.mu.Lock()\n \tif s.closed {\n-\t\treturn nil, nil, errors.New("db: statement is closed")\n+\t\ts.mu.Unlock()\n+\t\terr = errors.New("db: statement is closed")\n+\t\treturn\n \t}\n+\n+\t// In a transaction, we always use the connection that the\n+\t// transaction was created on.\n+\tif s.tx != nil {\n+\t\ts.mu.Unlock()\n+\t\tci, err = s.tx.grabConn() // blocks, waiting for the connection.\n+\t\tif err != nil {\n+\t\t\treturn\n+\t\t}\n+\t\treleaseConn = func() { s.tx.releaseConn() }\n+\t\treturn ci, releaseConn, s.txsi, nil\n+\t}\n+\n \tvar cs connStmt\n \tmatch := false\n \tfor _, v := range s.css {\
@@ -375,11 +521,11 @@ func (s *Stmt) connStmt(args ...interface{}) (driver.Conn, driver.Stmt, error) {\n \tif !match {\n \t\tci, err := s.db.conn()\n \t\tif err != nil {\n-\t\t\treturn nil, nil, err\n+\t\t\treturn nil, nil, nil, err\n \t\t}\n \t\tsi, err := ci.Prepare(s.query)\n \t\tif err != nil {\n-\t\t\treturn nil, nil, err\n+\t\t\treturn nil, nil, nil, err\n \t\t}\n \t\ts.mu.Lock()\n \t\tcs = connStmt{ci, si}\n@@ -387,13 +533,15 @@ func (s *Stmt) connStmt(args ...interface{}) (driver.Conn, driver.Stmt, error) {\n \t\ts.mu.Unlock()\n \t}\n \n-\treturn cs.ci, cs.si, nil\n+\tconn := cs.ci\n+\treleaseConn = func() { s.db.putConn(conn) }\n+\treturn conn, releaseConn, cs.si, nil\n }\n \n // Query executes a prepared query statement with the given arguments\ // and returns the query results as a *Rows.\n func (s *Stmt) Query(args ...interface{}) (*Rows, error) {\n-\tci, si, err := s.connStmt(args...)\n+\tci, releaseConn, si, err := s.connStmt()\n \tif err != nil {\n \t\treturn nil, err\n \t}\n@@ -405,11 +553,13 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {\n \t\ts.db.putConn(ci)\n \t\treturn nil, err\n \t}\n-\t// Note: ownership of ci passes to the *Rows\n+\t// Note: ownership of ci passes to the *Rows, to be freed\n+\t// with releaseConn.\n \trows := &Rows{\n-\t\tdb: s.db,\n-\t\tci: ci,\n-\t\trowsi: rowsi,\n+\t\tdb: s.db,\n+\t\tci: ci,\n+\t\treleaseConn: releaseConn,\n+\t\trowsi: rowsi,\n \t}\n \treturn rows, nil\n }\n@@ -436,19 +586,24 @@ func (s *Stmt) QueryRow(args ...interface{}) *Row {\n // Close closes the statement.\n func (s *Stmt) Close() error {\n \ts.mu.Lock()\n-\tdefer s.mu.Unlock() // TODO(bradfitz): move this unlock after 'closed = true'?\n+\tdefer s.mu.Unlock()\n \tif s.closed {\n \t\treturn nil\n \t}\n \ts.closed = true\n-\tfor _, v := range s.css {\n-\t\tif ci, match := s.db.connIfFree(v.ci); match {\n-\t\t\tv.si.Close()\n-\t\t\ts.db.putConn(ci)\n-\t\t} else {\n-\t\t\t// TODO(bradfitz): care that we can't close\n-\t\t\t// this statement because the statement's\n-\t\t\t// connection is in use?\n+\n+\tif s.tx != nil {\n+\t\ts.txsi.Close()\n+\t} else {\n+\t\tfor _, v := range s.css {\n+\t\t\tif ci, match := s.db.connIfFree(v.ci); match {\n+\t\t\t\tv.si.Close()\n+\t\t\t\ts.db.putConn(ci)\n+\t\t\t} else {\n+\t\t\t\t// TODO(bradfitz): care that we can't close\n+\t\t\t\t// this statement because the statement's\n+\t\t\t\t// connection is in use?\n+\t\t\t}\n \t\t}\n \t}\n \treturn nil\n@@ -468,9 +623,10 @@ func (s *Stmt) Close() error {\n // err = rows.Error() // get any Error encountered during iteration\n // ...\n type Rows struct {\n-\tdb *DB\n-\tci driver.Conn // owned; must be returned when Rows is closed\n-\trowsi driver.Rows\n+\tdb *DB\n+\tci driver.Conn // owned; must call putconn when closed to release\n+\treleaseConn func()\n+\trowsi driver.Rows\n \n \tclosed bool\n \tlastcols []interface{}\n@@ -538,7 +694,7 @@ func (rs *Rows) Close() error {\n \t}\n \trs.closed = true\n \terr := rs.rowsi.Close()\n-\trs.db.putConn(rs.ci)\n+\trs.releaseConn()\n \treturn err\n }\n \n```
## コアとなるコードの解説
### `src/pkg/exp/sql/convert.go`の変更点
* **`convertAssign`関数の`*bool`と`float`型への対応**:
* `case *bool:`ブロックが追加され、`driver.Bool.ConvertValue(src)`を呼び出して、ソース値をブール値に変換しようとします。これは、データベースから読み込んだ値(例: "true", 1, 0)をGoの`bool`型に変換するために重要です。`driver.Bool.ConvertValue`は、`exp/sql/driver/types.go`で定義されている`ValueConverter`インターフェースの実装であり、様々な入力形式から`bool`への変換ロジックをカプセル化しています。
* `case reflect.Float32, reflect.Float64:`ブロックが追加され、文字列から浮動小数点数への変換(`strconv.Atof64`)と、オーバーフローチェック(`dv.OverflowFloat`)を行うようになりました。これにより、データベースの浮動小数点数型をGoの`float32`や`float64`に安全にスキャンできます。
* **`asString`関数の変更**:
* 関数のシグネチャが`func asString(src interface{}) string`に変更され、戻り値から`ok bool`が削除されました。これは、この関数が常に文字列を返すことを意味します。
* `src`が`string`または`[]byte`でない場合、以前は空文字列を返していましたが、`fmt.Sprintf("%v", src)`を使用して任意の値を文字列に変換するようになりました。これにより、数値型などが文字列として扱われるケースでの柔軟性が向上し、`strconv.Atoi64`や`strconv.Atoui64`、`strconv.Atof64`がより広範な入力に対応できるようになりました。
### `src/pkg/exp/sql/driver/types.go`の変更点
* **`Bool` `ValueConverter`の実装**:
* `boolType`構造体に`String()`メソッドが追加され、デバッグ時の可読性が向上しました。
* `ConvertValue`メソッドが完全に実装されました。
* `bool`型はそのまま返されます。
* `string`型と`[]byte`型は`strconv.Atob`(文字列をブール値に変換)を使用して変換されます。これにより、"true", "false", "T", "F", "1", "0"などの文字列が適切に解釈されます。
* `reflect.Int`や`reflect.Uint`などの整数型は、値が`0`または`1`の場合にそれぞれ`false`または`true`に変換されます。それ以外の整数値はエラーとなります。これは、多くのデータベースが真偽値を0または1で表現することに対応しています。
* その他の型はすべてエラーとなります。
* この実装により、データベースドライバーは、様々な形式で表現された真偽値をGoの`bool`型に一貫して変換できるようになりました。
### `src/pkg/exp/sql/sql.go`の変更点
* **`DB.Begin()`の実装**:
* `db.conn()`で接続プールから利用可能な接続を取得し、その接続上で`ci.Begin()`を呼び出してトランザクションを開始します。
* `Tx`構造体を初期化し、`db`(親の`DB`インスタンス)、取得した`ci`(接続)、および`txi`(ドライバーのトランザクションインターフェース)を格納します。
* トランザクション開始に失敗した場合、取得した接続を`db.putConn(ci)`でプールに戻し、エラーを返します。
* **`Tx`構造体の追加とメソッドの実装**:
* `Tx`構造体は、トランザクションの状態(`done`フラグ)、関連する`DB`、`driver.Conn`、`driver.Tx`、および接続の排他制御のための`sync.Mutex` (`cimu`) を保持します。
* `ErrTransactionFinished`という新しいエラーが定義され、既にコミットまたはロールバックされたトランザクションに対して操作が行われた場合にこのエラーを返します。これにより、トランザクションの誤用を防ぎます。
* `tx.close()`ヘルパー関数は、トランザクションが終了した際に`done`フラグを`true`に設定し、使用していた接続を`db.putConn(tx.ci)`で接続プールに戻します。
* `tx.grabConn()`と`tx.releaseConn()`は、トランザクションの接続を安全に取得・解放するためのメソッドです。`grabConn()`は`cimu`ミューテックスをロックし、`releaseConn()`はロックを解除します。これにより、複数のゴルーチンが同時に同じトランザクションの接続にアクセスすることを防ぎます。
* `Tx.Commit()`と`Tx.Rollback()`は、`tx.done`をチェックし、`defer tx.close()`で接続の解放を保証しつつ、基盤となる`driver.Tx`の`Commit()`または`Rollback()`メソッドを呼び出します。
* `Tx.Prepare()`, `Tx.Exec()`, `Tx.Query()`, `Tx.QueryRow()`は、トランザクションのコンテキスト内でデータベース操作を実行するためのメソッドです。これらは`tx.grabConn()`と`tx.releaseConn()`を使用して、トランザクションの接続を排他的に利用し、トランザクションの分離性を維持します。
* **`Stmt`構造体の変更**:
* `tx *Tx`フィールドが追加され、ステートメントがトランザクションの一部であるかどうかを示します。
* `txsi driver.Stmt`フィールドが追加され、トランザクション内のステートメントの場合に、そのトランザクションの接続にバインドされたドライバーのステートメントを保持します。
* `connStmt()`メソッドが大幅に変更され、`s.tx != nil`の場合(トランザクション内のステートメントの場合)は、常にそのトランザクションの接続(`s.tx.ci`)を使用するようにしました。これにより、トランザクションの分離性が保証されます。
* `Stmt.Exec()`と`Stmt.Query()`は、`connStmt()`から返される`releaseConn`関数を`defer`で呼び出すことで、接続の解放を適切に行うようになりました。
* `Stmt.Close()`も、ステートメントがトランザクションの一部であるかどうかに応じて、適切なクリーンアップ(`s.txsi.Close()`または`s.css`内のステートメントのクローズ)を行うように修正されました。
* **`Rows`構造体の変更**:
* `releaseConn func()`フィールドが追加され、`Rows`が閉じられたときに`rs.ci`(接続)をプールに戻すための関数を保持するようになりました。これにより、`Rows`のライフサイクルと接続の解放が密接に連携するようになりました。`Rows.Close()`が呼び出された際に、この`releaseConn`関数が実行され、接続が適切にプールに戻されます。
これらの変更は、`exp/sql`パッケージの堅牢性と機能性を大幅に向上させ、Go言語でのデータベースプログラミングをより安全で効率的なものにするための重要なステップでした。
## 関連リンク
* GitHubコミットページ: [https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc](https://github.com/golang/go/commit/8089e578124227a322a77f9c5599d2244f9d0cfc)
* Go Issue 2328: [https://golang.org/issue/2328](https://golang.org/issue/2328)
* Gerrit Change-Id: [https://golang.org/cl/5294067](https://golang.org/cl/5294067)
## 参考にした情報源リンク
* Go言語の`database/sql`パッケージに関する公式ドキュメントやチュートリアル(当時の`exp/sql`の設計思想を理解するため)
* Go言語の`reflect`パッケージに関する公式ドキュメント
* SQLトランザクションに関する一般的なデータベースの概念
* `strconv`パッケージのドキュメント(`Atoi64`, `Atoui64`, `Atof64`, `Atob`など)
* Go言語の`sync`パッケージ(`Mutex`の利用)
* Web検索結果: "Go exp/sql package design 2011", "Go database/sql driver interface design", "Go reflect package usage for type conversion", "Go issue 2328 float bool sql"
(注: 2011年当時の情報源は直接リンクできない場合がありますが、上記の概念は現在のGoのドキュメントでも確認できます。)