[インデックス 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のアーカイブから見つけるのが困難な場合があります。しかし、コミットメッセージに