[インデックス 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/sql
package documentation: https://pkg.go.dev/database/sql - Go
database/sql/driver
package 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
reflect
package documentation (for understanding type reflection, though not directly used in the explanation, it's relevant forConvertValue
implementations): 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"