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

[インデックス 14867] ファイルの概要

このコミットは、Go言語の標準ライブラリである database/sql パッケージにおけるドキュメンテーションの改善と、使用例の追加を目的としています。具体的には、Query メソッドの引数に関する説明が追加され、Query および QueryRow メソッドの具体的な使用方法を示す新しいテストファイル(example_test.go)が導入されました。これにより、database/sql パッケージの利用者が、より安全かつ効率的にデータベース操作を行うための理解を深めることが期待されます。

コミット

commit 20130f141f869b3fa81a9bcdecd78c59b898459e
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Jan 11 14:46:49 2013 -0800

    database/sql: document args, add a couple examples
    
    Fixes #3460
    
    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/7096046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/20130f141f869b3fa81a9bcdecd78c59b898459e

元コミット内容

このコミットは、database/sql パッケージの Query メソッドの引数に関するドキュメンテーションを改善し、さらに QueryQueryRow メソッドの具体的な使用例を example_test.go という新しいファイルに追加しています。これにより、Go言語のデータベース操作に関する公式ドキュメントがより分かりやすくなり、開発者がこれらのAPIを正しく利用するための手助けとなります。

変更の背景

この変更は、GoのIssue #3460 に対応するものです。Issue #3460は、「database/sql: Query/QueryRow examples」というタイトルで、database/sql パッケージの Query および QueryRow メソッドに関する具体的な使用例が不足していることを指摘していました。特に、プレースホルダ引数の扱い方や、結果セットの処理方法について、より明確な例が求められていました。

database/sql パッケージは、GoアプリケーションからSQLデータベースにアクセスするための汎用的なインターフェースを提供しますが、その利用方法、特にパラメータのバインディングやエラーハンドリングについては、初心者にとって直感的ではない場合があります。公式ドキュメントに具体的なコード例が少ないと、開発者は外部のチュートリアルやフォーラムに頼る必要があり、学習コストが増大します。

このコミットは、このような課題を解決し、database/sql パッケージの使いやすさと理解度を向上させることを目的としています。具体的な使用例を提供することで、開発者はAPIの意図する使い方を迅速に把握し、一般的な落とし穴を避けることができるようになります。

前提知識の解説

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

database/sql パッケージは、Go言語でリレーショナルデータベースを操作するための標準ライブラリです。このパッケージ自体は特定のデータベースドライバを含んでおらず、データベースに接続するためには、PostgreSQL、MySQL、SQLiteなど、特定のデータベースに対応したドライバを別途インポートして利用します。

database/sql パッケージの主な特徴と概念は以下の通りです。

  • DB (データベース接続プール): sql.Open 関数でデータベースドライバと接続文字列を指定して *sql.DB オブジェクトを作成します。これはデータベースへの単一の接続ではなく、接続のプールを表します。アプリケーション全体で共有されるべきオブジェクトであり、複数のゴルーチンから安全に利用できます。
  • Stmt (プリペアドステートメント): DB.Prepare メソッドでSQLクエリをプリコンパイルし、*sql.Stmt オブジェクトを作成します。これにより、同じクエリを複数回実行する際のパフォーマンスが向上し、SQLインジェクション攻撃を防ぐことができます。
  • Rows (結果セット): DB.Query または Stmt.Query メソッドを実行すると、結果セットを表す *sql.Rows オブジェクトが返されます。Rows.Next() で次の行に移動し、Rows.Scan() で現在の行のデータをGoの変数にスキャンします。
  • Row (単一行の結果): DB.QueryRow または Stmt.QueryRow メソッドは、単一の行を返すことが期待されるクエリに使用されます。結果は直接 Scan() メソッドで変数にスキャンされます。行が見つからない場合は sql.ErrNoRows エラーが返されます。
  • Result (実行結果): DB.Exec または Stmt.Exec メソッドは、INSERT、UPDATE、DELETEなどのDML操作に使用され、sql.Result オブジェクトを返します。これには、影響を受けた行数や、自動生成されたIDなどの情報が含まれます。
  • プレースホルダ: SQLクエリ内で動的な値を埋め込むためにプレースホルダを使用します。database/sql パッケージは、ドライバがサポートするプレースホルダの形式(例: ?, $1, :name)を抽象化し、QueryExec メソッドの可変長引数として値を渡すことで、安全にパラメータをバインドします。これにより、SQLインジェクションのリスクを低減します。

Go言語の _test.go ファイルと Example 関数

Go言語では、テストコードは通常、テスト対象のソースファイルと同じディレクトリに _test.go というサフィックスを持つファイルとして配置されます。これらのファイルには、ユニットテスト、ベンチマークテスト、そして Example 関数が含まれます。

  • Example 関数: Example 関数は、Goのドキュメンテーションツール go docgodoc によって特別な方法で扱われます。これらの関数は、パッケージのドキュメントにコード例として表示されるだけでなく、通常のテストと同様に go test コマンドによって実行されます。Example 関数内の出力が、関数名の後に続くコメント(// Output:)と一致するかどうかが検証されます。これにより、ドキュメントに表示されるコード例が常に正しく動作することが保証されます。

このコミットで追加された example_test.go は、まさにこの Example 関数の仕組みを利用して、database/sql パッケージの QueryQueryRow メソッドの具体的な使用例を提供し、それがドキュメントに組み込まれるように設計されています。

技術的詳細

このコミットの技術的な変更点は大きく分けて二つあります。

  1. database/sql/sql.go における Query メソッドのドキュメンテーション追加: Query メソッドのコメントに「The args are for any placeholder parameters in the query.」という一文が追加されました。これは、Query メソッドの可変長引数 args ...interface{} が、SQLクエリ内のプレースホルダパラメータに対応するものであることを明示しています。この追加により、開発者は Query メソッドに渡す引数の目的をより明確に理解できるようになります。

  2. database/sql/example_test.go の新規追加と Example 関数の実装: example_test.go という新しいテストファイルが作成され、その中に ExampleDB_QueryExampleDB_QueryRow という二つの Example 関数が実装されました。これらの関数は、それぞれ DB.QueryDB.QueryRow メソッドの典型的な使用パターンを示しています。

    • ExampleDB_Query:

      • SELECT クエリで WHERE 句にプレースホルダ (?) を使用し、db.Query に年齢を引数として渡しています。
      • rows.Next() ループで結果セットをイテレートし、rows.Scan() で各行の name カラムを文字列変数にスキャンしています。
      • log.Fatal(err) を使用してエラーハンドリングを行っています。
      • rows.Err() を呼び出して、イテレーション中に発生した可能性のあるエラーをチェックしています。
      • fmt.Printf で結果を出力しています。
    • ExampleDB_QueryRow:

      • 単一の行を返すことが期待される SELECT クエリで WHERE 句にプレースホルダ (?) を使用し、db.QueryRow にIDを引数として渡しています。
      • QueryRow の結果を直接 Scan() メソッドで username 変数にスキャンしています。
      • switch ステートメントを使用して、sql.ErrNoRows(行が見つからない場合)とその他のエラーを適切にハンドリングしています。
      • fmt.Printf で結果を出力しています。

これらの Example 関数は、go doc コマンドで database/sql パッケージのドキュメントを表示した際に、具体的なコード例として表示されます。また、go test コマンドを実行すると、これらの例が実際に実行され、期待される出力と一致するかどうかが検証されるため、ドキュメントの正確性と信頼性が保証されます。

コアとなるコードの変更箇所

src/pkg/database/sql/example_test.go (新規追加)

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sql_test

import (
	"database/sql"
	"fmt"
	"log"
)

var db *sql.DB

func ExampleDB_Query() {
	age := 27
	rows, err := db.Query("SELECT name FROM users WHERE age=?", age)
	if err != nil {
		log.Fatal(err)
	}
	for rows.Next() {
		var name string
		if err := rows.Scan(&name); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s is %d\n", name, age)
	}
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}
}

func ExampleDB_QueryRow() {
	id := 123
	var username string
	err := db.QueryRow("SELECT username FROM users WHERE id=?", id).Scan(&username)
	switch {
	case err == sql.ErrNoRows:
		log.Printf("No user with that ID.")
	case err != nil:
		log.Fatal(err)
	default:
		fmt.Printf("Username is %s\n", username)
	}
}

src/pkg/database/sql/sql.go (変更)

--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -373,6 +373,7 @@ func (db *DB) exec(query string, args []interface{}) (res Result, err error) {
 }
 
 // Query executes a query that returns rows, typically a SELECT.
+// The args are for any placeholder parameters in the query.
 func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
 	stmt, err := db.Prepare(query)
 	if err != nil {

コアとなるコードの解説

src/pkg/database/sql/example_test.go

このファイルは、database/sql パッケージの利用方法を具体的に示すための新しいテストファイルです。_test.go サフィックスを持つため、go test コマンドで実行され、go doc コマンドでドキュメントとして表示されます。

  • package sql_test: パッケージ名が sql_test となっているのは、テスト対象の sql パッケージとは異なるパッケージとしてテストを実行するためです。これにより、外部から sql パッケージを利用する際の挙動をより正確にシミュレートできます。
  • var db *sql.DB: グローバル変数 db は、データベース接続を表す *sql.DB 型のポインタです。実際のアプリケーションでは、sql.Open を使って初期化されますが、この例では簡略化されています。
  • func ExampleDB_Query():
    • この関数は、複数の行を返す SELECT クエリの実行方法を示しています。
    • db.Query("SELECT name FROM users WHERE age=?", age): Query メソッドの第一引数にSQLクエリ文字列、第二引数以降にプレースホルダ (?) にバインドする値を渡します。ここでは age 変数が ? にバインドされます。
    • for rows.Next(): rows.Next() は、結果セットに次の行がある場合に true を返し、カーソルを次の行に進めます。結果セットのすべての行を処理するためにループで使用されます。
    • rows.Scan(&name): 現在の行のデータをGoの変数にスキャンします。引数はポインタである必要があります。ここでは、name カラムの値を name 変数にスキャンしています。
    • rows.Err(): rows.Next() ループの終了後、イテレーション中に発生した可能性のあるエラーをチェックするために呼び出すことが重要です。ネットワークエラーなど、Next()false を返しても Scan() が成功するようなケースでエラーを検出できます。
  • func ExampleDB_QueryRow():
    • この関数は、単一の行を返すことが期待される SELECT クエリの実行方法を示しています。
    • db.QueryRow("SELECT username FROM users WHERE id=?", id): QueryRow メソッドは、単一の行を返すことを想定しています。Query と同様に、SQLクエリとプレースホルダにバインドする値を渡します。
    • .Scan(&username): QueryRow*sql.Row オブジェクトを返し、その Scan メソッドを直接呼び出して結果をGoの変数にスキャンします。
    • switch 文によるエラーハンドリング:
      • err == sql.ErrNoRows: QueryRow は、クエリが結果を返さなかった場合に sql.ErrNoRows を返します。これはエラーではない一般的なケースとして扱われるべきです。
      • err != nil: その他のエラー(データベース接続エラー、SQL構文エラーなど)が発生した場合。
      • default: エラーがなく、データが正常にスキャンされた場合。

これらの例は、database/sql パッケージの基本的なクエリ実行フロー、プレースホルダの使用、結果セットの処理、そして適切なエラーハンドリングのパターンを明確に示しています。

src/pkg/database/sql/sql.go

このファイルは database/sql パッケージの主要な実装を含んでいます。変更は DB.Query メソッドのドキュメンテーションに限定されています。

  • // Query executes a query that returns rows, typically a SELECT.: 既存のコメント。
  • // The args are for any placeholder parameters in the query.: この行が追加されました。 この追加により、Query メソッドのシグネチャ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) における args ...interface{} の役割が明確になります。これは、SQLクエリ文字列内のプレースホルダ(例: ?$1)に対応する値であることを示しており、開発者が引数を渡す際の混乱を防ぎます。このドキュメンテーションの改善は、APIの可読性と使いやすさを向上させる上で非常に重要です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • GitHubのGoリポジトリのコミット履歴
  • Go言語のIssueトラッカー
  • database/sql パッケージに関する一般的なGo言語のチュートリアルやブログ記事(具体的なURLは省略しますが、一般的な知識として参照しました)