Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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のインターフェースやメソッドの命名では、冗長な単語を避ける傾向があります。ScanIntoInto は、値が「スキャンされて(Scan)どこかへ(Into)格納される」という意図を示していますが、Scan という単語自体が既にその意味を含んでいるため、Into は冗長であると判断されました。

この変更は、GoのIssue #2843「database/sql: remove Into from ScannerInto/ScanInto」で議論され、提案されました。このIssueでは、ScanIntoInto が不要であり、Scan だけで十分であるという意見が多数を占めました。より簡潔で直感的な命名にすることで、APIの利用者がコードを読み書きする際の認知負荷を軽減し、Goの標準ライブラリ全体の一貫性を保つことが目的です。また、ドキュメントの誤りも同時に修正されています。

前提知識の解説

Go言語の database/sql パッケージ

database/sql パッケージは、Go言語でリレーショナルデータベースを操作するための汎用的なインターフェースを提供します。このパッケージ自体は特定のデータベースドライバを含まず、データベース固有の操作は database/sql/driver パッケージで定義されたインターフェースを実装する外部ドライバに委ねられます。

主な機能は以下の通りです。

  • DB: データベースへの接続プールを管理します。
  • Stmt: プリペアドステートメントを表します。SQLインジェクション攻撃を防ぎ、クエリのパフォーマンスを向上させます。
  • Rows: クエリ結果の行をイテレートするためのインターフェースです。
  • Row: 単一の行の結果を表します。
  • Scan メソッド: RowsRow から取得したデータベースの値を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 メソッドを、それぞれ ScannerScan にリネームしたことです。

命名の変更

  • インターフェース名: 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 の値が取りうる型に stringtime.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の変数に変換する際のロジックを担っており、destScanner インターフェースを実装しているかどうかをチェックし、実装していればその 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の変数に割り当てる際の型変換ロジックを処理します。変更前は destScannerInto インターフェースを実装しているかをチェックし、実装していれば ScanInto メソッドを呼び出していました。

変更後は、destScanner インターフェースを実装しているかをチェックし、実装していれば Scan メソッドを呼び出すように修正されています。これにより、新しい命名規則に準拠し、database/sql パッケージ全体で一貫した Scan メソッドの呼び出しが行われるようになります。

src/pkg/database/sql/sql.go の変更

  1. NullString, NullInt64, NullFloat64, NullBool 型の変更: これらの型は、データベースのNULL値をGoの型で安全に扱うためのヘルパー型です。変更前は ScannerInto インターフェースを実装し、ScanInto メソッドを持っていました。変更後は、Scanner インターフェースを実装し、メソッド名も Scan に変更されています。これにより、これらの型が database/sql パッケージの新しい Scan メソッドの呼び出し規約に適合します。

  2. Scanner インターフェースの定義変更:

    • インターフェース名が ScannerInto から Scanner に変更されました。
    • メソッド名が ScanInto(value interface{}) error から Scan(src interface{}) error に変更されました。
    • メソッドのコメントが更新され、src の値が取りうる型として stringtime.Time が明示的に追加されました。これは、データベースドライバがこれらの型も Scan メソッドに渡す可能性があることを明確にするためです。また、引数名も value から src に変更され、より意味が明確になりました。
  3. Stmt.QueryRow のコメント例の修正: Stmt.QueryRow の使用例のコメントが、Scan(&s) から Scan(&name) に修正されています。これは、Scan メソッドの引数として、スキャン先の変数名をより具体的に示すことで、ドキュメントの可読性を向上させるものです。

これらの変更は、database/sql パッケージのAPIをよりGoの慣習に沿ったものにし、簡潔さと一貫性を高めることを目的としています。

関連リンク

参考にした情報源リンク

  • 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が変更の直接的なトリガーとなったことは明らかです。