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

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

コミット

commit 943f6cc837f4513a8cae7df199d14c5a38cf7677
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Feb 20 14:25:28 2012 +1100

    database/sql/driver: API cleanups
    
    -- add driver.Value type and documentation,
       convert from interface{} to Value where
       appropriate.
    -- don't say "subset" anywhere,
    -- SubsetValuer -> Valuer
    -- SubsetValue -> Value
    -- IsParameterSubsetType -> IsValue
    -- IsScanSubsetType -> IsScanValue
    
    Fixes #2842
    
    R=golang-dev, r, rsc
    CC=golang-dev
    https://golang.org/cl/5674084

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

https://github.com/golang/go/commit/943f6cc837f4513a8cae7df199d14c5a38cf7677

元コミット内容

このコミットは、Go言語の database/sql/driver パッケージにおけるAPIのクリーンアップを目的としています。主な変更点は以下の通りです。

  • driver.Value 型の導入とドキュメントの追加。これにより、interface{} が適切である箇所で driver.Value に変換されます。
  • APIドキュメントや型名から「subset(サブセット)」という用語を排除します。
    • SubsetValuerValuer にリネーム。
    • SubsetValueValue にリネーム。
    • IsParameterSubsetTypeIsValue にリネーム。
    • IsScanSubsetTypeIsScanValue にリネーム。

この変更は、Issue #2842 を修正するものです。

変更の背景

Go言語の database/sql パッケージは、データベース操作のための汎用的なインターフェースを提供します。このパッケージは、具体的なデータベースドライバとは独立しており、ドライバは database/sql/driver パッケージで定義されたインターフェースを実装することで database/sql パッケージと連携します。

このコミットが行われた2012年当時、Go言語はまだ比較的新しい言語であり、標準ライブラリのAPI設計は進化の途中にありました。database/sql/driver パッケージでは、データベースドライバが扱うことのできるGoの型の集合を「subset types(サブセット型)」と呼んでいました。しかし、この「subset」という用語は、その意味合いが不明瞭であり、APIの意図を正確に伝えていませんでした。

具体的には、ドライバがデータベースとの間で値をやり取りする際に、Goの任意の型 (interface{}) をそのまま扱うのではなく、特定の限られた型(int64, float64, bool, []byte, string, time.Time, nil)に変換する必要がありました。これは、データベースシステムが通常、Goのすべての複雑な型を直接サポートしているわけではないためです。

このコミットの背景には、以下の課題がありました。

  1. 用語の不明瞭さ: 「subset types」という用語は、ドライバがサポートする型の集合を指していましたが、この言葉自体が直感的ではなく、誤解を招く可能性がありました。
  2. APIの一貫性: interface{} を直接使用する箇所と、特定の型に限定されるべき箇所の区別が曖昧でした。これにより、ドライバの実装者がどの型を期待すべきか混乱する可能性がありました。
  3. ドキュメントの改善: ドライバが扱うべき値の型について、より明確で簡潔なドキュメントが必要とされていました。

これらの課題を解決し、database/sql/driver パッケージのAPIをより明確で使いやすくするために、このクリーンアップが実施されました。特に、driver.Value という新しい型を導入し、ドライバが扱うべき値の型を明示することで、APIの意図を明確にすることが目指されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と database/sql パッケージの構造に関する知識が必要です。

  1. interface{} (空インターフェース): Go言語における interface{} は、任意の型の値を保持できる特別なインターフェースです。これは、型が事前にわからない場合や、異なる型の値を統一的に扱いたい場合によく使用されます。しかし、interface{} は型安全性を低下させる可能性があり、具体的な型にアクセスするには型アサーションや型スイッチが必要になります。

  2. database/sql パッケージ: Goの標準ライブラリの一部であり、SQLデータベースとの対話のための汎用的なインターフェースを提供します。このパッケージは、データベースドライバに依存しない抽象化レイヤーを提供し、アプリケーションコードが特定のデータベースシステムに直接依存することなく、SQL操作を実行できるようにします。主な機能には、データベース接続の管理、ステートメントの準備と実行、トランザクションの管理、結果セットの取得などがあります。

  3. database/sql/driver パッケージ: database/sql パッケージのサブパッケージであり、Goのデータベースドライバが実装すべきインターフェースを定義しています。このパッケージは、データベースドライバ開発者向けのものであり、一般的なアプリケーション開発者が直接使用することは稀です。ドライバは、driver.Driver, driver.Conn, driver.Stmt, driver.Rows などのインターフェースを実装することで、database/sql パッケージと連携します。

  4. データベースドライバの役割: データベースドライバは、Goの database/sql/driver インターフェースを実装し、特定のデータベースシステム(例: MySQL, PostgreSQL, SQLite)との通信を仲介します。ドライバの主な役割は、Goのデータ型をデータベースが理解できる形式に変換し、その逆も行うことです。

  5. 「subset types」の概念(変更前): このコミット以前は、database/sql/driver パッケージのドキュメントやコード内で「subset types」という用語が使われていました。これは、データベースドライバがGoの interface{} から変換して扱うことができる、限られたGoの型の集合を指していました。具体的には、int64, float64, bool, []byte, string, time.Time, nil などがこれに該当しました。この概念は、Goの任意の型を直接データベースに渡すことができないという現実を反映したものでしたが、「subset」という言葉が抽象的で、その意図が不明瞭でした。

技術的詳細

このコミットの技術的な詳細は、主に database/sql/driver パッケージ内の型と関数のリネーム、そして新しい driver.Value 型の導入に集約されます。

  1. driver.Value 型の導入: 最も重要な変更は、driver.Value という新しい型エイリアスが interface{} として定義されたことです。

    // A driver Value is a value that drivers must be able to handle.
    // A Value is either nil or an instance of one of these types:
    //
    //   int64
    //   float64
    //   bool
    //   []byte
    //   time.Time
    //
    // string is also a Value, but is not permitted as a return value from Rows.Next.
    type Value interface{}
    

    この Value 型は、ドライバが扱うことのできる具体的なGoの型(int64, float64, bool, []byte, time.Time, string, nil)を明示的にドキュメント化しています。これにより、interface{} を直接使用する代わりに driver.Value を使うことで、APIの意図がより明確になります。特に、Rows.Next から返される値には string が含まれないという制約も明記されています。これは、データベースから読み取られた文字列データは通常 []byte として扱われるべきであるという慣習を反映しています。

  2. 「subset」用語の排除とリネーム: コミットメッセージにもあるように、「subset」という用語は完全に排除されました。これは、APIの命名規則をより直感的で分かりやすいものにするための重要なステップです。

    • SubsetValuer -> Valuer: driver.Valuer インターフェースは、Goのカスタム型が自身を driver.Value に変換する方法を提供します。これにより、database/sql パッケージは、ユーザー定義の型をデータベースドライバが理解できる形式に自動的に変換できるようになります。 変更前:

      type SubsetValuer interface {
          SubsetValue() (interface{}, error)
      }
      

      変更後:

      type Valuer interface {
          Value() (Value, error)
      }
      

      メソッド名も SubsetValue() から Value() に変更され、より簡潔になりました。

    • IsParameterSubsetType -> IsValue: この関数は、与えられた値がデータベースパラメータとして有効な driver.Value 型であるかどうかをチェックします。string 型もパラメータとして許可されます。 変更前:

      func IsParameterSubsetType(v interface{}) bool
      

      変更後:

      func IsValue(v interface{}) bool
      
    • IsScanSubsetType -> IsScanValue: この関数は、与えられた値がデータベースから読み取られた結果(Rows.Next でスキャンされる値)として有効な driver.Value 型であるかどうかをチェックします。IsValue とは異なり、string 型はスキャン値としては許可されません([]byte として扱われるため)。 変更前:

      func IsScanSubsetType(v interface{}) bool
      

      変更後:

      func IsScanValue(v interface{}) bool
      
  3. Execer および Stmt インターフェースの変更: driver.Execer および driver.Stmt インターフェースの Exec および Query メソッドの引数型が []interface{} から []driver.Value に変更されました。これにより、これらのメソッドが期待する引数の型がより明確になります。

    変更前:

    type Execer interface {
        Exec(query string, args []interface{}) (Result, error)
    }
    
    type Stmt interface {
        Exec(args []interface{}) (Result, error)
        Query(args []interface{}) (Rows, error)
    }
    

    変更後:

    type Execer interface {
        Exec(query string, args []Value) (Result, error)
    }
    
    type Stmt interface {
        Exec(args []Value) (Result, error)
        Query(args []Value) (Rows, error)
    }
    
  4. Rows.Next メソッドの変更: driver.Rows インターフェースの Next メソッドの引数型も []interface{} から []driver.Value に変更されました。これにより、ドライバが結果セットの各行をスキャンする際に、driver.Value 型のスライスに値を書き込むことが期待されることが明確になります。

    変更前:

    type Rows interface {
        Next(dest []interface{}) error
    }
    

    変更後:

    type Rows interface {
        Next(dest []Value) error
    }
    
  5. DDLSuccess -> ResultNoRows のリネーム: DDL(Data Definition Language)コマンド(例: CREATE TABLE)が成功した場合にドライバが返す driver.Result の定義が DDLSuccess から ResultNoRows に変更されました。これは、DDL操作では通常、LastInsertIdRowsAffected が意味を持たないため、より適切な命名です。

    変更前:

    var DDLSuccess ddlSuccess
    type ddlSuccess struct{}
    

    変更後:

    var ResultNoRows noRows
    type noRows struct{}
    

    LastInsertId()RowsAffected() メソッドは、エラーを返すように実装されています。

これらの変更は、database/sql/driver パッケージのAPIをより厳密に型付けし、ドキュメントを改善することで、ドライバ開発者がより正確で堅牢なコードを書けるようにすることを目的としています。

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

このコミットでは、以下の5つのファイルが変更されています。

  1. src/pkg/database/sql/convert.go:

    • subsetTypeArgs 関数の戻り値の型が []interface{} から []driver.Value に変更されました。
  2. src/pkg/database/sql/driver/driver.go:

    • driver.Value 型が type Value interface{} として定義され、そのドキュメントが追加されました。
    • Execer インターフェースの Exec メソッドの引数型が []interface{} から []driver.Value に変更されました。
    • Stmt インターフェースの Exec および Query メソッドの引数型が []interface{} から []driver.Value に変更されました。
    • ColumnConverter インターフェースのドキュメントが「driver subset type」から「driver Value」に更新されました。
    • Rows インターフェースの Next メソッドの引数型が []interface{} から []driver.Value に変更されました。
    • DDLSuccess 変数と ddlSuccess 型が ResultNoRowsnoRows にリネームされました。
  3. src/pkg/database/sql/driver/types.go:

    • ValueConverter インターフェースのドキュメントが「subset types」から「Value types」に更新されました。
    • ValueConverter インターフェースの ConvertValue メソッドの戻り値の型が (interface{}, error) から (Value, error) に変更されました。
    • SubsetValuer インターフェースが Valuer にリネームされ、メソッド名も SubsetValue() から Value() に変更されました。
    • boolType, int32Type, stringType, Null, NotNullConvertValue メソッドの戻り値の型が (interface{}, error) から (Value, error) に変更されました。
    • IsParameterSubsetType 関数が IsValue にリネームされました。
    • IsScanSubsetType 関数が IsScanValue にリネームされました。
    • DefaultParameterConverterConvertValue メソッド内で、IsParameterSubsetTypeIsValue に、SubsetValuerValuer に、SubsetValue()Value() に、non-subset typenon-Value type に変更されました。
  4. src/pkg/database/sql/fakedb_test.go:

    • テストヘルパー関数 checkSubsetTypes の引数型が []interface{} から []driver.Value に変更されました。
    • fakeConnExec メソッドの引数型が []interface{} から []driver.Value に変更されました。
    • fakeStmtExec および Query メソッドの引数型が []interface{} から []driver.Value に変更されました。
    • fakeStmtexecInsert メソッドの引数型が []interface{} から []driver.Value に変更されました。
    • driver.DDLSuccess の使用箇所が driver.ResultNoRows に変更されました。
    • rowsCursorNext メソッドの引数型が []interface{} から []driver.Value に変更されました。
  5. src/pkg/database/sql/sql.go:

    • NullString, NullInt64, NullFloat64, NullBool 型の SubsetValue() メソッドが Value() にリネームされ、戻り値の型が (interface{}, error) から (driver.Value, error) に変更されました。
    • Tx.Exec および Stmt.Exec メソッド内で、subsetTypeArgs の呼び出しと、driver.SubsetValuerdriver.Valuer に、SubsetValue()Value() に、driver.IsParameterSubsetTypedriver.IsValue に変更されました。
    • Rows 構造体の lastcols フィールドの型が []interface{} から []driver.Value に変更されました。

コアとなるコードの解説

このコミットの核心は、database/sql/driver パッケージにおける値の表現方法を interface{} から driver.Value へと移行し、関連する用語を明確化することにあります。

  1. driver.Value の導入と interface{} からの置き換え: driver.Value は単なる interface{} の型エイリアスですが、そのドキュメントによって、ドライバが扱うべき具体的な型が明示されました。これにより、ドライバ開発者は、interface{} が持つ「何でもあり」という曖昧さから解放され、期待される型を正確に把握できるようになります。 例えば、src/pkg/database/sql/driver/driver.goExecer インターフェースの Exec メソッドのシグネチャが Exec(query string, args []interface{}) から Exec(query string, args []driver.Value) に変更されたことで、args スライスには driver.Value で定義された型のみが含まれるべきであることが、コードレベルで明確に示されます。これは、APIの意図をコード自体が語るというGoの設計思想に沿った変更です。

  2. 「subset」用語の排除: 「subset」という用語は、Goの型システムにおける「部分集合」という意味合いで使われていましたが、データベースドライバの文脈では直感的ではありませんでした。この用語を ValueValuer といったより直接的な言葉に置き換えることで、APIの可読性と理解度が向上しました。 例えば、src/pkg/database/sql/driver/types.goSubsetValuerValuer に、IsParameterSubsetTypeIsValue に変更されたことは、単なるリネーム以上の意味を持ちます。これは、APIの概念モデルをよりシンプルで分かりやすいものに再構築する試みです。

  3. Null* 型の Value() メソッドへの変更: src/pkg/database/sql/sql.go にある NullString, NullInt64 などの Null* 型は、データベースのNULL値をGoの型で表現するためのものです。これらの型が driver.Valuer インターフェースを実装することで、database/sql パッケージは、これらのカスタム型をデータベースドライバが処理できる driver.Value に自動的に変換できるようになります。SubsetValue() から Value() への変更は、driver.Valuer インターフェースの変更に合わせたものであり、一貫性を保ちます。

  4. DefaultParameterConverter のロジック更新: src/pkg/database/sql/driver/types.goDefaultParameterConverter は、Goの任意の型を driver.Value に変換する役割を担っています。このコンバータのロジックが、新しい IsValue 関数と Valuer インターフェースを使用するように更新されました。これにより、変換処理が新しいAPI定義に準拠し、より堅牢になります。

これらの変更は、database/sql パッケージとドライバ間の契約をより明確にし、Goのデータベースエコシステム全体の堅牢性と使いやすさを向上させることに貢献しています。

関連リンク

  • Go Issue #2842: database/sql/driver: API cleanups: https://github.com/golang/go/issues/2842
  • Go CL 5674084: database/sql/driver: API cleanups: https://golang.org/cl/5674084 (これはコミットメッセージに記載されているリンクですが、現在のGoのコードレビューシステムでは古いCL番号は直接アクセスできない場合があります。しかし、コミットハッシュからGitHubで確認できます。)

参考にした情報源リンク