[インデックス 12078] ファイルの概要
コミット
commit 943f6cc837f4513a8cae7df199d14c5a38cf7677
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Feb 20 14:25:28 2012 +1100
database/sql/driver: API cleanups
-- add driver.Value type and documentation,
convert from interface{} to Value where
appropriate.
-- don't say "subset" anywhere,
-- SubsetValuer -> Valuer
-- SubsetValue -> Value
-- IsParameterSubsetType -> IsValue
-- IsScanSubsetType -> IsScanValue
Fixes #2842
R=golang-dev, r, rsc
CC=golang-dev
https://golang.org/cl/5674084
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/943f6cc837f4513a8cae7df199d14c5a38cf7677
元コミット内容
このコミットは、Go言語の database/sql/driver パッケージにおけるAPIのクリーンアップを目的としています。主な変更点は以下の通りです。
driver.Value型の導入とドキュメントの追加。これにより、interface{}が適切である箇所でdriver.Valueに変換されます。- APIドキュメントや型名から「subset(サブセット)」という用語を排除します。
SubsetValuerをValuerにリネーム。SubsetValueをValueにリネーム。IsParameterSubsetTypeをIsValueにリネーム。IsScanSubsetTypeをIsScanValueにリネーム。
この変更は、Issue #2842 を修正するものです。
変更の背景
Go言語の database/sql パッケージは、データベース操作のための汎用的なインターフェースを提供します。このパッケージは、具体的なデータベースドライバとは独立しており、ドライバは database/sql/driver パッケージで定義されたインターフェースを実装することで database/sql パッケージと連携します。
このコミットが行われた2012年当時、Go言語はまだ比較的新しい言語であり、標準ライブラリのAPI設計は進化の途中にありました。database/sql/driver パッケージでは、データベースドライバが扱うことのできるGoの型の集合を「subset types(サブセット型)」と呼んでいました。しかし、この「subset」という用語は、その意味合いが不明瞭であり、APIの意図を正確に伝えていませんでした。
具体的には、ドライバがデータベースとの間で値をやり取りする際に、Goの任意の型 (interface{}) をそのまま扱うのではなく、特定の限られた型(int64, float64, bool, []byte, string, time.Time, nil)に変換する必要がありました。これは、データベースシステムが通常、Goのすべての複雑な型を直接サポートしているわけではないためです。
このコミットの背景には、以下の課題がありました。
- 用語の不明瞭さ: 「subset types」という用語は、ドライバがサポートする型の集合を指していましたが、この言葉自体が直感的ではなく、誤解を招く可能性がありました。
- APIの一貫性:
interface{}を直接使用する箇所と、特定の型に限定されるべき箇所の区別が曖昧でした。これにより、ドライバの実装者がどの型を期待すべきか混乱する可能性がありました。 - ドキュメントの改善: ドライバが扱うべき値の型について、より明確で簡潔なドキュメントが必要とされていました。
これらの課題を解決し、database/sql/driver パッケージのAPIをより明確で使いやすくするために、このクリーンアップが実施されました。特に、driver.Value という新しい型を導入し、ドライバが扱うべき値の型を明示することで、APIの意図を明確にすることが目指されました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と database/sql パッケージの構造に関する知識が必要です。
-
interface{}(空インターフェース): Go言語におけるinterface{}は、任意の型の値を保持できる特別なインターフェースです。これは、型が事前にわからない場合や、異なる型の値を統一的に扱いたい場合によく使用されます。しかし、interface{}は型安全性を低下させる可能性があり、具体的な型にアクセスするには型アサーションや型スイッチが必要になります。 -
database/sqlパッケージ: Goの標準ライブラリの一部であり、SQLデータベースとの対話のための汎用的なインターフェースを提供します。このパッケージは、データベースドライバに依存しない抽象化レイヤーを提供し、アプリケーションコードが特定のデータベースシステムに直接依存することなく、SQL操作を実行できるようにします。主な機能には、データベース接続の管理、ステートメントの準備と実行、トランザクションの管理、結果セットの取得などがあります。 -
database/sql/driverパッケージ:database/sqlパッケージのサブパッケージであり、Goのデータベースドライバが実装すべきインターフェースを定義しています。このパッケージは、データベースドライバ開発者向けのものであり、一般的なアプリケーション開発者が直接使用することは稀です。ドライバは、driver.Driver,driver.Conn,driver.Stmt,driver.Rowsなどのインターフェースを実装することで、database/sqlパッケージと連携します。 -
データベースドライバの役割: データベースドライバは、Goの
database/sql/driverインターフェースを実装し、特定のデータベースシステム(例: MySQL, PostgreSQL, SQLite)との通信を仲介します。ドライバの主な役割は、Goのデータ型をデータベースが理解できる形式に変換し、その逆も行うことです。 -
「subset types」の概念(変更前): このコミット以前は、
database/sql/driverパッケージのドキュメントやコード内で「subset types」という用語が使われていました。これは、データベースドライバがGoのinterface{}から変換して扱うことができる、限られたGoの型の集合を指していました。具体的には、int64,float64,bool,[]byte,string,time.Time,nilなどがこれに該当しました。この概念は、Goの任意の型を直接データベースに渡すことができないという現実を反映したものでしたが、「subset」という言葉が抽象的で、その意図が不明瞭でした。
技術的詳細
このコミットの技術的な詳細は、主に database/sql/driver パッケージ内の型と関数のリネーム、そして新しい driver.Value 型の導入に集約されます。
-
driver.Value型の導入: 最も重要な変更は、driver.Valueという新しい型エイリアスがinterface{}として定義されたことです。// A driver Value is a value that drivers must be able to handle. // A Value is either nil or an instance of one of these types: // // int64 // float64 // bool // []byte // time.Time // // string is also a Value, but is not permitted as a return value from Rows.Next. type Value interface{}この
Value型は、ドライバが扱うことのできる具体的なGoの型(int64,float64,bool,[]byte,time.Time,string,nil)を明示的にドキュメント化しています。これにより、interface{}を直接使用する代わりにdriver.Valueを使うことで、APIの意図がより明確になります。特に、Rows.Nextから返される値にはstringが含まれないという制約も明記されています。これは、データベースから読み取られた文字列データは通常[]byteとして扱われるべきであるという慣習を反映しています。 -
「subset」用語の排除とリネーム: コミットメッセージにもあるように、「subset」という用語は完全に排除されました。これは、APIの命名規則をより直感的で分かりやすいものにするための重要なステップです。
-
SubsetValuer->Valuer:driver.Valuerインターフェースは、Goのカスタム型が自身をdriver.Valueに変換する方法を提供します。これにより、database/sqlパッケージは、ユーザー定義の型をデータベースドライバが理解できる形式に自動的に変換できるようになります。 変更前:type SubsetValuer interface { SubsetValue() (interface{}, error) }変更後:
type Valuer interface { Value() (Value, error) }メソッド名も
SubsetValue()からValue()に変更され、より簡潔になりました。 -
IsParameterSubsetType->IsValue: この関数は、与えられた値がデータベースパラメータとして有効なdriver.Value型であるかどうかをチェックします。string型もパラメータとして許可されます。 変更前:func IsParameterSubsetType(v interface{}) bool変更後:
func IsValue(v interface{}) bool -
IsScanSubsetType->IsScanValue: この関数は、与えられた値がデータベースから読み取られた結果(Rows.Nextでスキャンされる値)として有効なdriver.Value型であるかどうかをチェックします。IsValueとは異なり、string型はスキャン値としては許可されません([]byteとして扱われるため)。 変更前:func IsScanSubsetType(v interface{}) bool変更後:
func IsScanValue(v interface{}) bool
-
-
ExecerおよびStmtインターフェースの変更:driver.Execerおよびdriver.StmtインターフェースのExecおよびQueryメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。これにより、これらのメソッドが期待する引数の型がより明確になります。変更前:
type Execer interface { Exec(query string, args []interface{}) (Result, error) } type Stmt interface { Exec(args []interface{}) (Result, error) Query(args []interface{}) (Rows, error) }変更後:
type Execer interface { Exec(query string, args []Value) (Result, error) } type Stmt interface { Exec(args []Value) (Result, error) Query(args []Value) (Rows, error) } -
Rows.Nextメソッドの変更:driver.RowsインターフェースのNextメソッドの引数型も[]interface{}から[]driver.Valueに変更されました。これにより、ドライバが結果セットの各行をスキャンする際に、driver.Value型のスライスに値を書き込むことが期待されることが明確になります。変更前:
type Rows interface { Next(dest []interface{}) error }変更後:
type Rows interface { Next(dest []Value) error } -
DDLSuccess->ResultNoRowsのリネーム: DDL(Data Definition Language)コマンド(例:CREATE TABLE)が成功した場合にドライバが返すdriver.Resultの定義がDDLSuccessからResultNoRowsに変更されました。これは、DDL操作では通常、LastInsertIdやRowsAffectedが意味を持たないため、より適切な命名です。変更前:
var DDLSuccess ddlSuccess type ddlSuccess struct{}変更後:
var ResultNoRows noRows type noRows struct{}LastInsertId()とRowsAffected()メソッドは、エラーを返すように実装されています。
これらの変更は、database/sql/driver パッケージのAPIをより厳密に型付けし、ドキュメントを改善することで、ドライバ開発者がより正確で堅牢なコードを書けるようにすることを目的としています。
コアとなるコードの変更箇所
このコミットでは、以下の5つのファイルが変更されています。
-
src/pkg/database/sql/convert.go:subsetTypeArgs関数の戻り値の型が[]interface{}から[]driver.Valueに変更されました。
-
src/pkg/database/sql/driver/driver.go:driver.Value型がtype Value interface{}として定義され、そのドキュメントが追加されました。ExecerインターフェースのExecメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。StmtインターフェースのExecおよびQueryメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。ColumnConverterインターフェースのドキュメントが「driver subset type」から「driver Value」に更新されました。RowsインターフェースのNextメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。DDLSuccess変数とddlSuccess型がResultNoRowsとnoRowsにリネームされました。
-
src/pkg/database/sql/driver/types.go:ValueConverterインターフェースのドキュメントが「subset types」から「Value types」に更新されました。ValueConverterインターフェースのConvertValueメソッドの戻り値の型が(interface{}, error)から(Value, error)に変更されました。SubsetValuerインターフェースがValuerにリネームされ、メソッド名もSubsetValue()からValue()に変更されました。boolType,int32Type,stringType,Null,NotNullのConvertValueメソッドの戻り値の型が(interface{}, error)から(Value, error)に変更されました。IsParameterSubsetType関数がIsValueにリネームされました。IsScanSubsetType関数がIsScanValueにリネームされました。DefaultParameterConverterのConvertValueメソッド内で、IsParameterSubsetTypeがIsValueに、SubsetValuerがValuerに、SubsetValue()がValue()に、non-subset typeがnon-Value typeに変更されました。
-
src/pkg/database/sql/fakedb_test.go:- テストヘルパー関数
checkSubsetTypesの引数型が[]interface{}から[]driver.Valueに変更されました。 fakeConnのExecメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。fakeStmtのExecおよびQueryメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。fakeStmtのexecInsertメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。driver.DDLSuccessの使用箇所がdriver.ResultNoRowsに変更されました。rowsCursorのNextメソッドの引数型が[]interface{}から[]driver.Valueに変更されました。
- テストヘルパー関数
-
src/pkg/database/sql/sql.go:NullString,NullInt64,NullFloat64,NullBool型のSubsetValue()メソッドがValue()にリネームされ、戻り値の型が(interface{}, error)から(driver.Value, error)に変更されました。Tx.ExecおよびStmt.Execメソッド内で、subsetTypeArgsの呼び出しと、driver.SubsetValuerがdriver.Valuerに、SubsetValue()がValue()に、driver.IsParameterSubsetTypeがdriver.IsValueに変更されました。Rows構造体のlastcolsフィールドの型が[]interface{}から[]driver.Valueに変更されました。
コアとなるコードの解説
このコミットの核心は、database/sql/driver パッケージにおける値の表現方法を interface{} から driver.Value へと移行し、関連する用語を明確化することにあります。
-
driver.Valueの導入とinterface{}からの置き換え:driver.Valueは単なるinterface{}の型エイリアスですが、そのドキュメントによって、ドライバが扱うべき具体的な型が明示されました。これにより、ドライバ開発者は、interface{}が持つ「何でもあり」という曖昧さから解放され、期待される型を正確に把握できるようになります。 例えば、src/pkg/database/sql/driver/driver.goのExecerインターフェースのExecメソッドのシグネチャがExec(query string, args []interface{})からExec(query string, args []driver.Value)に変更されたことで、argsスライスにはdriver.Valueで定義された型のみが含まれるべきであることが、コードレベルで明確に示されます。これは、APIの意図をコード自体が語るというGoの設計思想に沿った変更です。 -
「subset」用語の排除: 「subset」という用語は、Goの型システムにおける「部分集合」という意味合いで使われていましたが、データベースドライバの文脈では直感的ではありませんでした。この用語を
ValueやValuerといったより直接的な言葉に置き換えることで、APIの可読性と理解度が向上しました。 例えば、src/pkg/database/sql/driver/types.goでSubsetValuerがValuerに、IsParameterSubsetTypeがIsValueに変更されたことは、単なるリネーム以上の意味を持ちます。これは、APIの概念モデルをよりシンプルで分かりやすいものに再構築する試みです。 -
Null*型のValue()メソッドへの変更:src/pkg/database/sql/sql.goにあるNullString,NullInt64などのNull*型は、データベースのNULL値をGoの型で表現するためのものです。これらの型がdriver.Valuerインターフェースを実装することで、database/sqlパッケージは、これらのカスタム型をデータベースドライバが処理できるdriver.Valueに自動的に変換できるようになります。SubsetValue()からValue()への変更は、driver.Valuerインターフェースの変更に合わせたものであり、一貫性を保ちます。 -
DefaultParameterConverterのロジック更新:src/pkg/database/sql/driver/types.goのDefaultParameterConverterは、Goの任意の型をdriver.Valueに変換する役割を担っています。このコンバータのロジックが、新しいIsValue関数とValuerインターフェースを使用するように更新されました。これにより、変換処理が新しいAPI定義に準拠し、より堅牢になります。
これらの変更は、database/sql パッケージとドライバ間の契約をより明確にし、Goのデータベースエコシステム全体の堅牢性と使いやすさを向上させることに貢献しています。
関連リンク
- Go Issue #2842:
database/sql/driver: API cleanups: https://github.com/golang/go/issues/2842 - Go CL 5674084:
database/sql/driver: API cleanups: https://golang.org/cl/5674084 (これはコミットメッセージに記載されているリンクですが、現在のGoのコードレビューシステムでは古いCL番号は直接アクセスできない場合があります。しかし、コミットハッシュからGitHubで確認できます。)
参考にした情報源リンク
- Go
database/sqlpackage documentation: https://pkg.go.dev/database/sql - Go
database/sql/driverpackage documentation: https://pkg.go.dev/database/sql/driver - A brief history of Go's database/sql package: https://go.dev/blog/database-sql (このブログ記事はコミット後のものですが、
database/sqlの背景を理解するのに役立ちます。) - Go
interface{}: https://go.dev/tour/methods/10 - Go
reflectpackage documentation (for understanding type reflection, though not directly used in the explanation, it's relevant forConvertValueimplementations): https://pkg.go.dev/reflect - Web検索キーワード: "Go database/sql/driver API cleanups", "Go Fixes #2842", "Go driver.Value type", "Go database/sql interface{}", "Go SubsetValuer", "Go IsParameterSubsetType"