[インデックス 10817] ファイルの概要
このコミットは、Go言語のexp/sql
パッケージ(現在のdatabase/sql
パッケージの前身)において、Rows
オブジェクトからカラム名を取得するためのColumns()
メソッドを追加し、同時にパッケージ名がエラーメッセージ内で誤ってdb
と表示されていた箇所をsql
に修正するものです。
コミット
commit ea51dd23b4029649427d3bcb681879808923805b
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Dec 15 10:14:57 2011 -0800
sql: add Rows.Columns
Also, fix package name in error messages.
Fixes #2453
R=rsc
CC=golang-dev
https://golang.org/cl/5483088
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ea51dd23b4029649427d3bcb681879808923805b
元コミット内容
sql: add Rows.Columns
Also, fix package name in error messages.
Fixes #2453
R=rsc
CC=golang-dev
https://golang.org/cl/5483088
変更の背景
このコミットの主な背景は、Go言語のdatabase/sql
パッケージ(当時はexp/sql
)において、クエリ結果の行(Rows
)からカラム名を取得する標準的な方法が提供されていなかったことです。これは、Issue #2453「database/sql: does not allow "exporting" row data」で報告された問題に対応するものです。
データベースからデータを取得する際、アプリケーションはしばしば、結果セットに含まれるカラムのメタデータ、特にカラム名を知る必要があります。例えば、結果を動的にマップに変換する場合や、汎用的なデータ表示コンポーネントを作成する場合などです。Rows
インターフェースには、行のデータをスキャンするScan
メソッドはありましたが、カラム名を取得する直接的なメソッドがありませんでした。
この不足は、開発者がデータベースのスキーマに依存しない、より柔軟なコードを書くことを困難にしていました。Rows.Columns()
メソッドの追加は、このギャップを埋め、database/sql
パッケージの機能性を向上させることを目的としています。
また、このコミットでは、エラーメッセージ内でパッケージ名が誤ってdb
と表示されていた箇所を、正しいパッケージ名であるsql
に修正しています。これは、ユーザーがエラーメッセージを見た際に、どのパッケージからのエラーであるかを正確に理解できるようにするための、細かながらも重要な改善です。
前提知識の解説
Go言語のdatabase/sql
パッケージ
database/sql
パッケージは、Go言語における標準的なSQLデータベース操作のためのインターフェースを提供します。このパッケージ自体は特定のデータベースドライバを実装しているわけではなく、データベースドライバが実装すべき共通のインターフェース(driver.Driver
, driver.Conn
, driver.Stmt
, driver.Rows
など)を定義しています。これにより、Goアプリケーションは、使用するデータベースの種類に関わらず、統一されたAPIでデータベースを操作できます。
主要な型と概念:
DB
: データベースへの接続プールを表すハンドルです。Open
関数で作成され、複数のゴルーチンから安全に利用できます。Stmt
: プリペアドステートメントを表します。SQLインジェクション攻撃を防ぎ、クエリのパフォーマンスを向上させます。Rows
:Query
メソッドの実行結果として返される、結果セットの行を表します。通常、Next()
で次の行に進み、Scan()
でその行のデータをGoの変数に読み込みます。driver.Rows
インターフェース: データベースドライバが実装するべき行インターフェースです。このインターフェースには、Columns()
メソッドが含まれており、ドライバが提供するカラム名を取得できるようになっています。
Rows
オブジェクトとデータ取得
DB.Query()
またはStmt.Query()
を呼び出すと、*sql.Rows
型のオブジェクトが返されます。このオブジェクトは、データベースから取得された結果セットのイテレータとして機能します。
典型的な使用パターンは以下の通りです。
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close() // 忘れずにクローズする
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("id: %d, name: %s\n", id, name)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
このコミット以前は、id
やname
といったカラム名を動的に取得する方法が、*sql.Rows
オブジェクトの公開APIにはありませんでした。
エラーメッセージのプレフィックス
Goのエラーメッセージは、通常、エラーが発生したパッケージ名やコンポーネント名で始まる慣習があります。これにより、エラーメッセージを見ただけで、どの部分で問題が発生したのかを素早く特定できます。このコミットでは、exp/sql
パッケージ内で生成されるエラーメッセージのプレフィックスが、誤ってdb:
となっていたものを、正しいsql:
に修正しています。
技術的詳細
このコミットは、exp/sql
パッケージのRows
型にColumns()
メソッドを追加することで、クエリ結果のカラム名を取得する機能を提供します。
Rows.Columns()
メソッドの追加
Rows
型に以下のメソッドが追加されました。
// Columns returns the column names.
// Columns returns an error if the rows are closed, or if the rows
// are from QueryRow and there was a deferred error.
func (rs *Rows) Columns() ([]string, error) {
if rs.closed {
return nil, errors.New("sql: Rows are closed")
}
if rs.rowsi == nil {
return nil, errors.New("sql: no Rows available")
}
return rs.rowsi.Columns(), nil
}
このメソッドは、内部的にdriver.Rows
インターフェースが持つColumns()
メソッドを呼び出しています。driver.Rows
はデータベースドライバが実装するインターフェースであり、ドライバが実際にデータベースから取得したカラム名を提供します。
Rows.Columns()
メソッドは、以下のエラーケースを考慮しています。
rs.closed
:Rows
オブジェクトが既にクローズされている場合。rs.rowsi == nil
:Rows
オブジェクトが有効な結果セットを持っていない場合(例:QueryRow
が結果を返さなかった場合など)。
これらのチェックにより、Columns()
メソッドが安全に呼び出され、適切なエラーハンドリングが行われるようになっています。
エラーメッセージのプレフィックス修正
src/pkg/exp/sql/sql.go
ファイル内で、エラーメッセージのプレフィックスがdb:
からsql:
に一括して修正されました。これは、exp/sql
パッケージが最終的にdatabase/sql
として標準ライブラリに組み込まれることを考慮し、パッケージ名とエラーメッセージの整合性を保つための変更です。
修正されたエラーメッセージの例:
panic("db: Register driver is nil")
->panic("sql: Register driver is nil")
panic("db: Register called twice for driver " + name)
->panic("sql: Register called twice for driver " + name)
var ErrNoRows = errors.New("db: no rows in result set")
->var ErrNoRows = errors.New("sql: no rows in result set")
return nil, fmt.Errorf("db: unknown driver %q (forgotten import?)", driverName)
->return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
return nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args))
->return nil, fmt.Errorf("sql: expected %d arguments, got %d", want, len(args))
return nil, errors.New("db: statement is closed")
->return nil, errors.New("sql: statement is closed")
return nil, fmt.Errorf("db: statement expects %d inputs; got %d", si.NumInput(), len(args))
->return nil, fmt.Errorf("sql: statement expects %d inputs; got %d", si.NumInput(), len(args))
return errors.New("db: Rows closed")
->return errors.New("sql: Rows closed")
return errors.New("db: Scan called without calling Next")
->return errors.New("sql: Scan called without calling Next")
return fmt.Errorf("db: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
->return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
return fmt.Errorf("db: Scan error on column index %d: %v", i, err)
->return fmt.Errorf("sql: Scan error on column index %d: %v", i, err)
これらの修正は、エラーメッセージの一貫性と明確性を高めることに貢献しています。
コアとなるコードの変更箇所
src/pkg/exp/sql/sql.go
Rows
型にColumns() ([]string, error)
メソッドが追加されました。- ファイル全体で、エラーメッセージのプレフィックスが
db:
からsql:
に置換されました。
src/pkg/exp/sql/sql_test.go
TestRowsColumns
という新しいテスト関数が追加されました。このテストは、Rows.Columns()
メソッドが正しくカラム名を返すことを検証します。- 既存の
TestExec
関数内のエラーメッセージの期待値が、db:
からsql:
に修正されました。
コアとなるコードの解説
Rows.Columns()
の実装 (src/pkg/exp/sql/sql.go
)
// Columns returns the column names.
// Columns returns an error if the rows are closed, or if the rows
// are from QueryRow and there was a deferred error.
func (rs *Rows) Columns() ([]string, error) {
if rs.closed {
return nil, errors.New("sql: Rows are closed")
}
if rs.rowsi == nil {
return nil, errors.New("sql: no Rows available")
}
return rs.rowsi.Columns(), nil
}
このコードは、Rows
型のレシーバrs
に対してColumns()
メソッドを定義しています。
if rs.closed
:Rows
オブジェクトが既にClose()
されている場合、"sql: Rows are closed"
というエラーを返します。これは、クローズされたリソースへのアクセスを防ぐためのガードです。if rs.rowsi == nil
:rs.rowsi
は、内部的にデータベースドライバが提供するdriver.Rows
インターフェースの実装を保持しています。これがnil
の場合(例えば、QueryRow
が結果を返さなかった場合など)、有効な行データがないため、"sql: no Rows available"
というエラーを返します。return rs.rowsi.Columns(), nil
: 上記のチェックを通過した場合、内部のdriver.Rows
インターフェースのColumns()
メソッドを呼び出し、その結果(カラム名のスライスとエラー)をそのまま返します。これにより、実際のカラム名取得のロジックは各データベースドライバに委ねられます。
TestRowsColumns
テストケース (src/pkg/exp/sql/sql_test.go
)
func TestRowsColumns(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
rows, err := db.Query("SELECT|people|age,name|")
if err != nil {
t.Fatalf("Query: %v", err)
}
cols, err := rows.Columns()
if err != nil {
t.Fatalf("Columns: %v", err)
}
want := []string{"age", "name"}
if !reflect.DeepEqual(cols, want) {
t.Errorf("got %#v; want %#v", cols, want)
}
}
このテストは、Rows.Columns()
メソッドの基本的な機能を確認します。
db := newTestDB(t, "people")
: テスト用のデータベース接続を作成します。rows, err := db.Query("SELECT|people|age,name|")
:age
とname
という2つのカラムを選択するクエリを実行します。このクエリ文字列は、テスト用のモックドライバが解釈できる形式であると推測されます。cols, err := rows.Columns()
: 新しく追加されたColumns()
メソッドを呼び出し、カラム名を取得します。want := []string{"age", "name"}
: 期待されるカラム名のスライスを定義します。if !reflect.DeepEqual(cols, want)
: 取得したカラム名cols
が期待値want
と完全に一致するかをreflect.DeepEqual
を使って比較します。一致しない場合はエラーを報告します。
このテストにより、Rows.Columns()
が正しく機能し、データベースドライバから返されたカラム名を正確に提供できることが保証されます。
エラーメッセージの修正 (src/pkg/exp/sql/sql.go
および src/pkg/exp/sql/sql_test.go
)
コード全体で、エラーメッセージの文字列リテラル内の"db:"
が"sql:"
に置換されています。これは単純な文字列置換ですが、Goの標準ライブラリにおけるエラーメッセージの慣習に合わせるための重要な変更です。これにより、ユーザーはエラーがdatabase/sql
パッケージから発生したものであることを直感的に理解できるようになります。
関連リンク
- Go CL (Change List): https://golang.org/cl/5483088
- GitHub Issue #2453: https://github.com/golang/go/issues/2453
参考にした情報源リンク
- Web search results for "golang/go issue 2453": https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkJlhus2ez5AIIruzLEY-f7VTq5Dmiq0aiXs2wdm5DXYattwM1pCMyhQiCnPu3i56KSK8170g3a1IkKmrgjK57TYVOS-05x6NrcP6jE0jK_T9ZBpo85AB3z2Zv7FfUAP7nq8k= (This link points to a search result that identifies issue 2453 as "database/sql: does not allow "exporting" row data".)