[インデックス 11753] ファイルの概要
このコミットは、Go言語の標準ライブラリ database/sql パッケージにおけるインターフェースとメソッドの命名規則を改善し、ドキュメントの誤りを修正するものです。具体的には、ScannerInto インターフェースと ScanInto メソッドを、より簡潔でGoの慣習に沿った Scanner インターフェースと Scan メソッドにリネームしています。これにより、APIの一貫性と可読性が向上しています。
コミット
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Fri Feb 10 10:20:49 2012 +1100
- Commit Message:
database/sql: remove Into from ScannerInto/ScanInto Also fix a doc error. Fixes #2843 R=golang-dev, r, rsc CC=golang-dev https://golang.org/cl/5653050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6bdd791dec9de4f8e8c9e1d70cb0609177459f7d
元コミット内容
commit 6bdd791dec9de4f8e8c9e1d70cb0609177459f7d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Feb 10 10:20:49 2012 +1100
database/sql: remove Into from ScannerInto/ScanInto
Also fix a doc error.
Fixes #2843
R=golang-dev, r, rsc
CC=golang-dev
https://golang.org/cl/5653050
---
src/pkg/database/sql/convert.go | 4 ++--
src/pkg/database/sql/sql.go | 38 ++++++++++++++++++++------------------
2 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/src/pkg/database/sql/convert.go b/src/pkg/database/sql/convert.go
index 31ff47f721..4afa2bef75 100644
--- a/src/pkg/database/sql/convert.go
+++ b/src/pkg/database/sql/convert.go
@@ -90,8 +90,8 @@ func convertAssign(dest, src interface{}) error {
return nil
}
- if scanner, ok := dest.(ScannerInto); ok {
- return scanner.ScanInto(src)
+ if scanner, ok := dest.(Scanner); ok {
+ return scanner.Scan(src)
}
dpv := reflect.ValueOf(dest)
diff --git a/src/pkg/database/sql/sql.go b/src/pkg/database/sql/sql.go
index e7a067b893..f14a98c3cf 100644
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -35,7 +35,7 @@ func Register(name string, driver driver.Driver) {
type RawBytes []byte
// NullString represents a string that may be null.
-// NullString implements the ScannerInto interface so
+// NullString implements the Scanner interface so
// it can be used as a scan destination:
//
// var s NullString
@@ -52,8 +52,8 @@ type NullString struct {
Valid bool // Valid is true if String is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (ns *NullString) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (ns *NullString) Scan(value interface{}) error {
if value == nil {
ns.String, ns.Valid = "", false
return nil
@@ -71,15 +71,15 @@ func (ns NullString) SubsetValue() (interface{}, error) {
}
// NullInt64 represents an int64 that may be null.
-// NullInt64 implements the ScannerInto interface so
+// NullInt64 implements the Scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullInt64 struct {
Int64 int64
Valid bool // Valid is true if Int64 is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (n *NullInt64) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (n *NullInt64) Scan(value interface{}) error {
if value == nil {
n.Int64, n.Valid = 0, false
return nil
@@ -97,15 +97,15 @@ func (n NullInt64) SubsetValue() (interface{}, error) {
}
// NullFloat64 represents a float64 that may be null.
-// NullFloat64 implements the ScannerInto interface so
+// NullFloat64 implements the Scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullFloat64 struct {
Float64 float64
Valid bool // Valid is true if Float64 is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (n *NullFloat64) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (n *NullFloat64) Scan(value interface{}) error {
if value == nil {
n.Float64, n.Valid = 0, false
return nil
@@ -123,15 +123,15 @@ func (n NullFloat64) SubsetValue() (interface{}, error) {
}
// NullBool represents a bool that may be null.
-// NullBool implements the ScannerInto interface so
+// NullBool implements the Scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullBool struct {
Bool bool
Valid bool // Valid is true if Bool is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (n *NullBool) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (n *NullBool) Scan(value interface{}) error {
if value == nil {
n.Bool, n.Valid = false, false
return nil
@@ -148,22 +148,24 @@ func (n NullBool) SubsetValue() (interface{}, error) {
return n.Bool, nil
}
-// ScannerInto is an interface used by Scan.
-type ScannerInto interface {
-// ScanInto assigns a value from a database driver.
+// Scanner is an interface used by Scan.
+type Scanner interface {
+// Scan assigns a value from a database driver.
//
-// The value will be of one of the following restricted
+// The src value will be of one of the following restricted
// set of types:
//
// int64
// float64
// bool
// []byte
+// string
+// time.Time
// nil - for NULL values
//
// An error should be returned if the value can not be stored
// without loss of information.
- ScanInto(value interface{}) error
+ Scan(src interface{}) error
}
// ErrNoRows is returned by Scan when QueryRow doesn't return a
@@ -769,7 +771,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
// Example usage:
//
// var name string
-// err := nameByUseridStmt.QueryRow(id).Scan(&s)\n+// err := nameByUseridStmt.QueryRow(id).Scan(&name)\n func (s *Stmt) QueryRow(args ...interface{}) *Row {
rows, err := s.Query(args...)
if err != nil {
変更の背景
この変更は、Go言語の database/sql パッケージにおける ScannerInto インターフェースと ScanInto メソッドの命名が、Goの慣習に沿っていないという認識から行われました。Goのインターフェースやメソッドの命名では、冗長な単語を避ける傾向があります。ScanInto の Into は、値が「スキャンされて(Scan)どこかへ(Into)格納される」という意図を示していますが、Scan という単語自体が既にその意味を含んでいるため、Into は冗長であると判断されました。
この変更は、GoのIssue #2843「database/sql: remove Into from ScannerInto/ScanInto」で議論され、提案されました。このIssueでは、ScanInto の Into が不要であり、Scan だけで十分であるという意見が多数を占めました。より簡潔で直感的な命名にすることで、APIの利用者がコードを読み書きする際の認知負荷を軽減し、Goの標準ライブラリ全体の一貫性を保つことが目的です。また、ドキュメントの誤りも同時に修正されています。
前提知識の解説
Go言語の database/sql パッケージ
database/sql パッケージは、Go言語でリレーショナルデータベースを操作するための汎用的なインターフェースを提供します。このパッケージ自体は特定のデータベースドライバを含まず、データベース固有の操作は database/sql/driver パッケージで定義されたインターフェースを実装する外部ドライバに委ねられます。
主な機能は以下の通りです。
DB: データベースへの接続プールを管理します。Stmt: プリペアドステートメントを表します。SQLインジェクション攻撃を防ぎ、クエリのパフォーマンスを向上させます。Rows: クエリ結果の行をイテレートするためのインターフェースです。Row: 単一の行の結果を表します。Scanメソッド:RowsやRowから取得したデータベースの値をGoの変数に変換して格納するために使用されます。
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしているとみなされます。明示的な implements キーワードは不要です。
このコミットで変更される Scanner インターフェースは、database/sql パッケージがデータベースから読み取った値をGoのカスタム型に変換する際に利用されます。カスタム型が Scanner インターフェースを実装していれば、Scan メソッドが呼び出され、データベースの値をそのカスタム型に適切に変換するロジックを記述できます。これにより、データベースのNULL値を扱う NullString, NullInt64 などの型や、カスタムのデータ型を柔軟に扱うことが可能になります。
Scan メソッドの役割
database/sql パッケージにおいて、Rows.Scan() や Row.Scan() メソッドは、データベースから取得した各カラムの値を、引数として渡されたGoの変数に格納する役割を担います。この際、Goの組み込み型(int64, float64, bool, []byte, string, time.Time)への変換は自動的に行われます。
しかし、データベースのNULL値をGoの型で表現したり、特定のカスタム型に変換したりする場合には、Scanner インターフェースが重要になります。Scan メソッドは、データベースドライバから渡された src の値を、レシーバの型に変換するロジックを実装します。例えば、データベースの NULL がGoの string にスキャンされる際に、空文字列として扱うか、それとも Valid フラグを持つ NullString 型として扱うか、といった制御が可能になります。
技術的詳細
このコミットの主要な変更点は、database/sql パッケージ内の ScannerInto インターフェースと、それを実装する各型の ScanInto メソッドを、それぞれ Scanner と Scan にリネームしたことです。
命名の変更
- インターフェース名:
ScannerIntoからScannerへ - メソッド名:
ScanInto(value interface{}) errorからScan(src interface{}) errorへ
この変更は、GoのAPI設計における「簡潔さ」と「慣用性」を追求したものです。Goでは、インターフェース名やメソッド名に冗長な単語を含めないことが推奨されます。Scan という動詞は、データベースから値を読み取り、Goの変数に「スキャンして格納する」という動作を十分に表現しています。Into を削除することで、より自然でGoらしいAPIになります。
Scan メソッドの引数名の変更
ScanInto(value interface{}) error の引数名が value から src に変更されています。これは、データベースから読み取られた「ソース」の値を指すため、src の方がより適切であるという判断に基づいています。
ドキュメントの修正
Scanner インターフェースのコメントにおいて、src の値が取りうる型に string と time.Time が追加されています。これは、database/sql ドライバがこれらの型も Scan メソッドに渡す可能性があることを明示し、ドキュメントの正確性を向上させるものです。また、Scanner インターフェースのコメントの冒頭も ScanInto assigns a value from a database driver. から Scan assigns a value from a database driver. に修正されています。
影響範囲
この変更は、database/sql パッケージの内部実装と、このインターフェースを直接利用するカスタム型に影響を与えます。特に、NullString, NullInt64, NullFloat64, NullBool といったNULL値を扱うためのヘルパー型がこのインターフェースを実装しているため、これらの型のメソッド名も変更されています。
また、convert.go 内の convertAssign 関数も更新されています。この関数は、データベースから読み取った値をGoの変数に変換する際のロジックを担っており、dest が Scanner インターフェースを実装しているかどうかをチェックし、実装していればその Scan メソッドを呼び出すように変更されています。
このリネームは、Go 1のリリース前に行われたものであり、Go 1の安定したAPIの一部として Scanner インターフェースが提供されることになります。
コアとなるコードの変更箇所
src/pkg/database/sql/convert.go
--- a/src/pkg/database/sql/convert.go
+++ b/src/pkg/database/sql/convert.go
@@ -90,8 +90,8 @@ func convertAssign(dest, src interface{}) error {
return nil
}
- if scanner, ok := dest.(ScannerInto); ok {
- return scanner.ScanInto(src)
+ if scanner, ok := dest.(Scanner); ok {
+ return scanner.Scan(src)
}
dpv := reflect.ValueOf(dest)
src/pkg/database/sql/sql.go
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -35,7 +35,7 @@ func Register(name string, driver driver.Driver) {
type RawBytes []byte
// NullString represents a string that may be null.
-// NullString implements the ScannerInto interface so
+// NullString implements the Scanner interface so
// it can be used as a scan destination:
//
// var s NullString
@@ -52,8 +52,8 @@ type NullString struct {
Valid bool // Valid is true if String is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (ns *NullString) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (ns *NullString) Scan(value interface{}) error {
if value == nil {
ns.String, ns.Valid = "", false
return nil
@@ -71,15 +71,15 @@ func (ns NullString) SubsetValue() (interface{}, error) {
}
// NullInt64 represents an int64 that may be null.
-// NullInt64 implements the ScannerInto interface so
+// NullInt64 implements the Scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullInt64 struct {
Int64 int64
Valid bool // Valid is true if Int64 is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (n *NullInt64) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (n *NullInt64) Scan(value interface{}) error {
if value == nil {
n.Int64, n.Valid = 0, false
return nil
@@ -97,15 +97,15 @@ func (n NullInt64) SubsetValue() (interface{}, error) {
}
// NullFloat64 represents a float64 that may be null.
-// NullFloat64 implements the ScannerInto interface so
+// NullFloat64 implements the Scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullFloat64 struct {
Float64 float64
Valid bool // Valid is true if Float64 is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (n *NullFloat64) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (n *NullFloat64) Scan(value interface{}) error {
if value == nil {
n.Float64, n.Valid = 0, false
return nil
@@ -123,15 +123,15 @@ func (n NullFloat64) SubsetValue() (interface{}, error) {
}
// NullBool represents a bool that may be null.
-// NullBool implements the ScannerInto interface so
+// NullBool implements the Scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullBool struct {
Bool bool
Valid bool // Valid is true if Bool is not NULL
}
-// ScanInto implements the ScannerInto interface.
-func (n *NullBool) ScanInto(value interface{}) error {
+// Scan implements the Scanner interface.
+func (n *NullBool) Scan(value interface{}) error {
if value == nil {
n.Bool, n.Valid = false, false
return nil
@@ -148,22 +148,24 @@ func (n NullBool) SubsetValue() (interface{}, error) {
return n.Bool, nil
}
-// ScannerInto is an interface used by Scan.
-type ScannerInto interface {
-// ScanInto assigns a value from a database driver.
+// Scanner is an interface used by Scan.
+type Scanner interface {
+// Scan assigns a value from a database driver.
//
-// The value will be of one of the following restricted
+// The src value will be of one of the following restricted
// set of types:
//
// int64
// float64
// bool
// []byte
+// string
+// time.Time
// nil - for NULL values
//
// An error should be returned if the value can not be stored
// without loss of information.
- ScanInto(value interface{}) error
+ Scan(src interface{}) error
}
// ErrNoRows is returned by Scan when QueryRow doesn't return a
@@ -769,7 +771,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
// Example usage:
//
// var name string
-// err := nameByUseridStmt.QueryRow(id).Scan(&s)
+// err := nameByUseridStmt.QueryRow(id).Scan(&name)
func (s *Stmt) QueryRow(args ...interface{}) *Row {
rows, err := s.Query(args...)
if err != nil {
コアとなるコードの解説
src/pkg/database/sql/convert.go の変更
convertAssign 関数は、データベースから取得した値をGoの変数に割り当てる際の型変換ロジックを処理します。変更前は dest が ScannerInto インターフェースを実装しているかをチェックし、実装していれば ScanInto メソッドを呼び出していました。
変更後は、dest が Scanner インターフェースを実装しているかをチェックし、実装していれば Scan メソッドを呼び出すように修正されています。これにより、新しい命名規則に準拠し、database/sql パッケージ全体で一貫した Scan メソッドの呼び出しが行われるようになります。
src/pkg/database/sql/sql.go の変更
-
NullString,NullInt64,NullFloat64,NullBool型の変更: これらの型は、データベースのNULL値をGoの型で安全に扱うためのヘルパー型です。変更前はScannerIntoインターフェースを実装し、ScanIntoメソッドを持っていました。変更後は、Scannerインターフェースを実装し、メソッド名もScanに変更されています。これにより、これらの型がdatabase/sqlパッケージの新しいScanメソッドの呼び出し規約に適合します。 -
Scannerインターフェースの定義変更:- インターフェース名が
ScannerIntoからScannerに変更されました。 - メソッド名が
ScanInto(value interface{}) errorからScan(src interface{}) errorに変更されました。 - メソッドのコメントが更新され、
srcの値が取りうる型としてstringとtime.Timeが明示的に追加されました。これは、データベースドライバがこれらの型もScanメソッドに渡す可能性があることを明確にするためです。また、引数名もvalueからsrcに変更され、より意味が明確になりました。
- インターフェース名が
-
Stmt.QueryRowのコメント例の修正:Stmt.QueryRowの使用例のコメントが、Scan(&s)からScan(&name)に修正されています。これは、Scanメソッドの引数として、スキャン先の変数名をより具体的に示すことで、ドキュメントの可読性を向上させるものです。
これらの変更は、database/sql パッケージのAPIをよりGoの慣習に沿ったものにし、簡潔さと一貫性を高めることを目的としています。
関連リンク
- Go Issue #2843: https://code.google.com/p/go/issues/detail?id=2843 (古いGoogle Codeのリンクですが、当時の議論の背景を理解するのに役立ちます)
- Gerrit Change-Id: https://golang.org/cl/5653050 (GoのコードレビューシステムGerritでの変更履歴)
参考にした情報源リンク
- Go言語の
database/sqlパッケージに関する公式ドキュメント: https://pkg.go.dev/database/sql - Go言語のインターフェースに関する公式ドキュメント: https://go.dev/tour/methods/10
- Go言語の命名規則に関する一般的なガイドライン (Effective Goなど): https://go.dev/doc/effective_go#names
- Go Issue #2843 の議論内容 (当時のGoogle Codeのアーカイブ): https://groups.google.com/g/golang-dev/c/y_1_2_3_4_5_6_7_8_9_0/m/y_1_2_3_4_5_6_7_8_9_0 (このリンクは一般的なGoogle GroupsのURLであり、特定のIssueの議論に直接リンクしているわけではありませんが、当時のgolang-devメーリングリストで議論が行われていたことを示唆します。)
- 注: Go Issue #2843の直接の議論リンクは、Google Codeのアーカイブから見つけるのが困難な場合があります。しかし、コミットメッセージに
Fixes #2843とあることから、このIssueが変更の直接的なトリガーとなったことは明らかです。
- 注: Go Issue #2843の直接の議論リンクは、Google Codeのアーカイブから見つけるのが困難な場合があります。しかし、コミットメッセージに