[インデックス 11655] ファイルの概要
このコミットは、Go言語の標準ライブラリであるdatabase/sql
パッケージにおいて、データベースから取得した値をinterface{}
型にスキャン(変換)する機能を追加するものです。これにより、開発者は特定の型を事前に指定することなく、汎用的にデータベースの値を扱うことが可能になります。
コミット
- コミットハッシュ:
9c060b8d60f14d930e5eadd7c9968ee2ba4f4131
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Mon Feb 6 10:06:22 2012 -0800
- コミットメッセージ:
database/sql: permit scanning into interface{} See thread http://goo.gl/7zzzU for background. R=rsc CC=golang-dev https://golang.org/cl/5624051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9c060b8d60f14d930e5eadd7c9968ee2ba4f4131
元コミット内容
database/sql: permit scanning into interface{}
See thread http://goo.gl/7zzzU for background.
R=rsc
CC=golang-dev
https://golang.org/cl/5624051
変更の背景
この変更の背景には、database/sql
パッケージのRows.Scan
メソッドが、データベースから取得した値をGoの変数に変換する際に、より柔軟な型指定を求めるニーズがありました。以前は、Scan
メソッドの引数には具体的な型へのポインタ(例: *string
, *int64
)を渡す必要がありましたが、これにより、取得するデータの型が実行時まで不明な場合や、汎用的なデータ処理を行う場合に不便が生じていました。
コミットメッセージにあるhttp://goo.gl/7zzzU
のリンクは、この変更に関する議論が行われたスレッドへの参照ですが、私は外部のウェブサイトに直接アクセスしてその内容を読み取ることはできません。しかし、コミットメッセージとコードの変更内容から、この変更がScan
メソッドの引数として*interface{}
を受け入れるようにすることで、この柔軟性の問題を解決しようとしていることは明らかです。これにより、データベースの値を任意の型として受け取り、後で型アサーションなどを用いて適切な型に変換するといった、より動的な処理が可能になります。
前提知識の解説
Go言語のdatabase/sql
パッケージ
database/sql
パッケージは、Go言語でSQLデータベースを操作するための汎用的なインターフェースを提供します。このパッケージ自体は特定のデータベースドライバーを含まず、データベースドライバーは別途実装され、このパッケージのインターフェースに準拠することで利用可能になります。
主要な概念:
DB
: データベースへの接続プールを表します。Rows
: クエリの結果セットを表します。Row
: 単一の行の結果セットを表します。Scan
メソッド:Rows
またはRow
オブジェクトのメソッドで、データベースから取得した値をGoの変数に変換(スキャン)するために使用されます。引数には、スキャン先の変数のポインタを渡します。
interface{}
型
Go言語におけるinterface{}
(空インターフェース)は、任意の型の値を保持できる特別な型です。これは、他の言語におけるObject
型やAny
型に似ています。interface{}
型は、コンパイル時には型が特定できないが、実行時に様々な型の値を扱う必要がある場合に非常に有用です。
型変換と型アサーション
Goでは、異なる型間で値を変換する際には明示的な型変換が必要です。また、interface{}
型に格納された値の元の型を取り出すには、型アサーション(value.(Type)
)を使用します。
例:
var i interface{}
i = "hello"
s := i.(string) // 型アサーション
技術的詳細
このコミットは、主にdatabase/sql
パッケージ内の以下の3つのファイルに影響を与えています。
-
src/pkg/database/sql/convert.go
: このファイルは、データベースから読み取られた値をGoの特定の型に変換するロジックを扱います。今回の変更では、convertAssign
関数に*interface{}
型へのスキャンを許可するケースが追加されました。[]byte
型のソース値を*interface{}
にスキャンする場合、元のバイトスライスをコピーしてinterface{}
に格納します。これは、元のバイトスライスがデータベースドライバーによって再利用される可能性があるため、データの安全性を確保するための防御的コピーです。- その他の型のソース値を
*interface{}
にスキャンする場合、ソース値をそのままinterface{}
に格納します。
-
src/pkg/database/sql/convert_test.go
:convert.go
の変更を検証するためのテストが追加されました。conversionTest
構造体にwantiface interface{}
フィールドが追加され、interface{}
型へのスキャン結果を期待値として設定できるようになりました。scaniface interface{}
というグローバル変数が追加され、*interface{}
型へのスキャンターゲットとして使用されます。conversionTests
スライスに、float64
,int64
,string
,[]byte
,bool
,nil
といった様々な型の値を*interface{}
にスキャンするテストケースが追加されました。TestConversions
関数内で、*interface{}
型へのスキャン結果が期待値と一致するかどうかをreflect.DeepEqual
を用いて検証するロジックが追加されました。- 特に
[]byte
を*interface{}
にスキャンした場合に、元のバイトスライスがコピーされていることを確認するテストが追加されています。これは、[]byte
が参照型であるため、コピーが行われないと元のデータが変更された場合にスキャン結果も意図せず変更されてしまう可能性があるためです。
-
src/pkg/database/sql/sql.go
:Rows.Scan
メソッドのドキュメンテーションが更新されました。*interface{}
型へのスキャンに関する新しい説明が追加されました。これにより、*interface{}
にスキャンする場合、基になるドライバーが提供する値が変換なしでコピーされること、そして値が[]byte
型の場合はコピーが作成され、呼び出し元がその結果を所有することが明記されました。
コアとなるコードの変更箇所
src/pkg/database/sql/convert.go
--- a/src/pkg/database/sql/convert.go
+++ b/src/pkg/database/sql/convert.go
@@ -49,6 +49,11 @@ func convertAssign(dest, src interface{}) error {
case *string:
*d = string(s)
return nil
+ case *interface{}:
+ bcopy := make([]byte, len(s))
+ copy(bcopy, s)
+ *d = bcopy
+ return nil
case *[]byte:
*d = s
return nil
@@ -80,6 +85,9 @@ func convertAssign(dest, src interface{}) error {
*d = bv.(bool)
}
return err
+ case *interface{}:
+ *d = src
+ return nil
}
if scanner, ok := dest.(ScannerInto); ok {
src/pkg/database/sql/convert_test.go
--- a/src/pkg/database/sql/convert_test.go
+++ b/src/pkg/database/sql/convert_test.go
@@ -18,14 +18,15 @@ type conversionTest struct {
s, d interface{} // source and destination
// following are used if they're non-zero
- wantint int64
- wantuint uint64
- wantstr string
- wantf32 float32
- wantf64 float64
- wanttime time.Time
- wantbool bool // used if d is of type *bool
- wanterr string
+ wantint int64
+ wantuint uint64
+ wantstr string
+ wantf32 float32
+ wantf64 float64
+ wanttime time.Time
+ wantbool bool // used if d is of type *bool
+ wanterr string
+ wantiface interface{}
}
// Target variables for scanning into.
@@ -41,6 +42,7 @@ var (
scanf32 float32
scanf64 float64
scantime time.Time
+ scaniface interface{}
)
var conversionTests = []conversionTest{
@@ -95,6 +97,14 @@ var conversionTests = []conversionTest{
{s: float64(1.5), d: &scanf32, wantf32: float32(1.5)},
{s: "1.5", d: &scanf32, wantf32: float32(1.5)},
{s: "1.5", d: &scanf64, wantf64: float64(1.5)},
+
+ // To interface{}
+ {s: float64(1.5), d: &scaniface, wantiface: float64(1.5)},
+ {s: int64(1), d: &scaniface, wantiface: int64(1)},
+ {s: "str", d: &scaniface, wantiface: "str"},
+ {s: []byte("byteslice"), d: &scaniface, wantiface: []byte("byteslice")},
+ {s: true, d: &scaniface, wantiface: true},
+ {s: nil, d: &scaniface},
}
func intValue(intptr interface{}) int64 {
@@ -152,6 +162,18 @@ func TestConversions(t *testing.T) {
if !ct.wanttime.IsZero() && !ct.wanttime.Equal(timeValue(ct.d)) {
errf("want time %v, got %v", ct.wanttime, timeValue(ct.d))
}
+ if ifptr, ok := ct.d.(*interface{}); ok {
+ if !reflect.DeepEqual(ct.wantiface, scaniface) {
+ errf("want interface %#v, got %#v", ct.wantiface, scaniface)
+ continue
+ }
+ if srcBytes, ok := ct.s.([]byte); ok {
+ dstBytes := (*ifptr).([]byte)
+ if &dstBytes[0] == &srcBytes[0] {
+ errf("copy into interface{} didn't copy []byte data")
+ }
+ }
+ }
}
}
src/pkg/database/sql/sql.go
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -880,6 +880,10 @@ func (rs *Rows) Columns() ([]string, error) {
// be modified and held indefinitely. The copy can be avoided by using
// an argument of type *RawBytes instead; see the documentation for
// RawBytes for restrictions on its use.
+//
+// If an argument has type *interface{}, Scan copies the value
+// provided by the underlying driver without conversion. If the value
+// is of type []byte, a copy is made and the caller owns the result.
func (rs *Rows) Scan(dest ...interface{}) error {
if rs.closed {
return errors.New("sql: Rows closed")
コアとなるコードの解説
src/pkg/database/sql/convert.go
の変更
convertAssign
関数は、データベースから読み取ったsrc
(ソース)値を、Goの変数dest
(デスティネーション)に変換して割り当てる役割を担っています。
-
case *interface{}:
(バイトスライスからの変換)case *interface{}: bcopy := make([]byte, len(s)) copy(bcopy, s) *d = bcopy return nil
このブロックは、ソース値
s
が[]byte
型であり、かつデスティネーションd
が*interface{}
型である場合に実行されます。database/sql
パッケージでは、データベースドライバーが提供する[]byte
は、内部バッファへの参照である可能性があり、そのバッファは次の行の読み取りや他の操作によって上書きされる可能性があります。そのため、[]byte
をinterface{}
にスキャンする際には、データの整合性を保つために防御的コピーが行われます。make([]byte, len(s))
で新しいバイトスライスを作成し、copy(bcopy, s)
でソースの内容をコピーしています。これにより、呼び出し元はコピーされたバイトスライスを安全に所有し、元のドライバーのバッファの変更に影響されなくなります。 -
case *interface{}:
(その他の型からの変換)case *interface{}: *d = src return nil
このブロックは、ソース値
src
が[]byte
型以外であり、かつデスティネーションd
が*interface{}
型である場合に実行されます。 この場合、src
の値はそのまま*d
(interface{}
型のポインタが指す実体)に代入されます。[]byte
型のように参照のセマンティクスを持つ型ではないため、防御的コピーは不要です。これにより、データベースドライバーが提供する元の値が、型変換なしでinterface{}
に格納されます。
src/pkg/database/sql/convert_test.go
の変更
テストコードの変更は、convert.go
で追加された*interface{}
へのスキャン機能が正しく動作することを検証するためのものです。
-
conversionTest
構造体へのwantiface
フィールドの追加: これにより、interface{}
型へのスキャンが期待する結果をテストケースで指定できるようになりました。 -
scaniface
変数の追加:*interface{}
型へのスキャンターゲットとして使用されるグローバル変数です。 -
conversionTests
への新しいテストケースの追加:float64
,int64
,string
,[]byte
,bool
,nil
といった様々なGoの基本型が、*interface{}
にスキャンされたときに正しく値が格納されることを確認します。 -
TestConversions
関数内の検証ロジックの追加:if ifptr, ok := ct.d.(*interface{}); ok { if !reflect.DeepEqual(ct.wantiface, scaniface) { errf("want interface %#v, got %#v", ct.wantiface, scaniface) continue } if srcBytes, ok := ct.s.([]byte); ok { dstBytes := (*ifptr).([]byte) if &dstBytes[0] == &srcBytes[0] { errf("copy into interface{} didn't copy []byte data") } } }
この部分では、スキャンターゲットが
*interface{}
型である場合に、以下の2つの重要な検証を行います。reflect.DeepEqual
を使用して、スキャンされたscaniface
の値が、テストケースで定義された期待値ct.wantiface
と完全に一致するかどうかを確認します。これは、interface{}
に格納された値の型と値の両方が正しいことを保証します。- ソース値が
[]byte
型であった場合、スキャンされたinterface{}
内の[]byte
が、元のソースの[]byte
とは異なるメモリ領域にコピーされていることを確認します。&dstBytes[0] == &srcBytes[0]
という比較は、スライスの基底配列の先頭アドレスが同じかどうかをチェックすることで、コピーが行われたかどうかを判断しています。これにより、前述の防御的コピーが正しく機能していることを保証します。
src/pkg/database/sql/sql.go
の変更
Rows.Scan
メソッドのコメントが更新され、*interface{}
型へのスキャンに関する挙動が明示されました。
- 新しいコメントの追加:
このコメントは、// If an argument has type *interface{}, Scan copies the value // provided by the underlying driver without conversion. If the value // is of type []byte, a copy is made and the caller owns the result.
Scan
メソッドが*interface{}
型の引数を受け取った場合の動作を明確にしています。- 「基になるドライバーが提供する値を変換なしでコピーする」:これは、
interface{}
にスキャンする場合、database/sql
パッケージが追加の型変換ロジックを適用せず、ドライバーが提供する生のGoの型(例:int64
,string
,float64
など)をそのままinterface{}
に格納することを意味します。 - 「値が
[]byte
型の場合、コピーが作成され、呼び出し元がその結果を所有する」:これは、convert.go
で実装された防御的コピーの挙動を説明しており、[]byte
がスキャンされた場合に、呼び出し元が安全にそのデータを扱えることを保証します。
- 「基になるドライバーが提供する値を変換なしでコピーする」:これは、
これらの変更により、database/sql
パッケージは、より柔軟なデータスキャン機能を提供し、特に動的な型を持つデータを扱うアプリケーションにおいて、開発の利便性と堅牢性を向上させました。
関連リンク
- Go CL (Code Review) へのリンク:
https://golang.org/cl/5624051
参考にした情報源リンク
コミットメッセージに記載されているhttp://goo.gl/7zzzU
のリンクは、この変更の背景に関する議論スレッドへの参照ですが、私は外部のウェブサイトに直接アクセスしてその内容を読み取ることはできませんでした。