[インデックス 10654] ファイルの概要
このコミットは、Go言語の実験的なSQLパッケージ exp/sql における文字列変換ロジックの簡素化を目的としています。具体的には、strconv パッケージの数値パース関数(ParseInt, ParseUint, ParseFloat)の bitSize 引数を適切に利用することで、数値オーバーフローのチェックをより効率的かつGoの標準ライブラリの挙動に沿った形で行うように変更しています。これにより、冗長なオーバーフローチェックが削除され、コードの可読性と保守性が向上しています。
コミット
commit 9d52fe22b48d611adc2935e76920b430db757fd3
Author: David Symonds <dsymonds@golang.org>
Date: Thu Dec 8 08:08:00 2011 +1100
exp/sql: simplify some string conversions.
R=bradfitz
CC=golang-dev
https://golang.org/cl/5451112
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9d52fe22b48d611adc2935e76920b430db757fd3
元コミット内容
exp/sql: simplify some string conversions.
R=bradfitz
CC=golang-dev
https://golang.org/cl/5451112
変更の背景
この変更の背景には、Go言語の標準ライブラリである strconv パッケージの数値パース関数(ParseInt, ParseUint, ParseFloat)の設計思想と、reflect パッケージの型情報利用の最適化があります。
以前のコードでは、文字列から数値をパースする際に、まず strconv 関数で最大ビットサイズ(例: 64)を指定してパースし、その後、reflect パッケージの dv.OverflowInt(), dv.OverflowUint(), dv.OverflowFloat() といったメソッドを使って、実際に変換先の型(例: int8, uint16)に値が収まるかどうかのオーバーフローチェックを別途行っていました。
しかし、strconv パッケージの ParseInt, ParseUint, ParseFloat 関数は、第3引数に bitSize を取ります。この bitSize は、パースする数値が何ビットの整数または浮動小数点数として解釈されるべきかを指定するものです。例えば、ParseInt(s, 10, 8) と指定すれば、文字列 s を10進数としてパースし、結果が int8 の範囲に収まるかどうかを strconv 自身がチェックし、範囲外であればエラーを返します。
この機能を利用することで、exp/sql パッケージ内で独自に行っていたオーバーフローチェックが冗長であることが判明しました。reflect.Value.Type().Bits() メソッドを使用すれば、変換先の型のビットサイズを動的に取得できるため、これを strconv 関数に渡すことで、strconv 側で適切なオーバーフローチェックが行われるようになります。これにより、コードが簡素化され、よりGoのイディオムに沿った形になります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とパッケージに関する知識が必要です。
-
exp/sqlパッケージ:- Go言語の標準ライブラリの一部として、データベース操作のための汎用的なインターフェースを提供する
database/sqlパッケージがあります。 exp/sqlは、そのdatabase/sqlパッケージの初期段階、または実験的な機能を含むバージョンとして存在していました。このコミットが行われた2011年当時は、まだdatabase/sqlが成熟する前の段階であり、exp(experimental) というプレフィックスが付けられていました。現在ではdatabase/sqlが標準として広く使われています。- このパッケージの役割は、データベースから取得したデータをGoの型に変換(スキャン)したり、Goのデータをデータベースに書き込む際に適切な型に変換したりすることです。
- Go言語の標準ライブラリの一部として、データベース操作のための汎用的なインターフェースを提供する
-
strconvパッケージ:- Go言語の標準ライブラリで、文字列と基本的なデータ型(数値、真偽値など)との間の変換を提供します。
- 主要な関数:
strconv.ParseInt(s string, base int, bitSize int) (i int64, err error): 文字列sを指定されたbase(基数、例: 10進数なら10) でint64にパースします。bitSizeは、結果が収まるべきビット数を指定します(例:8ならint8の範囲、16ならint16の範囲)。指定されたbitSizeの範囲を超えるとエラーを返します。strconv.ParseUint(s string, base int, bitSize int) (u uint64, err error):ParseIntの符号なし整数版です。strconv.ParseFloat(s string, bitSize int) (f float64, err error): 文字列sを浮動小数点数にパースします。bitSizeは、結果がfloat32(32) またはfloat64(64) のどちらの精度で解釈されるべきかを指定します。
-
reflectパッケージ:- Go言語の標準ライブラリで、実行時にプログラムの構造(型、値、メソッドなど)を検査・操作するための機能を提供します。
reflect.Value: Goの変数の実行時の値を表します。reflect.Value.Kind():reflect.Valueが表す値の基本的な種類(例:reflect.Int,reflect.String,reflect.Structなど)を返します。reflect.Value.Type():reflect.Valueが表す値の具体的な型(例:int,string,MyStructなど)を返します。reflect.Type.Bits(): 数値型の場合、その型が占めるビット数を返します(例:int8なら8,int64なら64)。reflect.Value.OverflowInt(x int64) bool,OverflowUint(x uint64) bool,OverflowFloat(x float64) bool: これらのメソッドは、与えられた数値xがreflect.Valueが表す型に収まるかどうかをチェックします。このコミットでは、strconvのbitSize引数を使うことで、これらの明示的なオーバーフローチェックが不要になりました。
-
エラーハンドリング:
- Goでは、関数がエラーを返す場合、通常は最後の戻り値として
error型の値を返します。呼び出し元はif err != nilでエラーをチェックし、適切に処理します。 fmt.Errorf(): フォーマットされたエラー文字列を生成し、errorインターフェースを満たす値を返します。
- Goでは、関数がエラーを返す場合、通常は最後の戻り値として
技術的詳細
このコミットの技術的詳細な変更点は、src/pkg/exp/sql/convert.go ファイル内の convertAssign 関数に集中しています。この関数は、データベースから読み取られた値をGoの変数に割り当てる際の型変換ロジックを担っています。
変更前は、文字列から整数 (reflect.Int, reflect.Int8 など)、符号なし整数 (reflect.Uint, reflect.Uint8 など)、浮動小数点数 (reflect.Float32, reflect.Float64) への変換において、以下の2段階のチェックが行われていました。
strconvによるパース:strconv.ParseInt(s, 10, 64)のように、bitSizeを固定値(64)としてパースしていました。これは、パースされた値がint64やuint64の範囲に収まるかどうかのチェックは行いますが、例えばint8のようなより小さい型へのオーバーフローは検出しません。reflect.Value.Overflow*による追加チェック:strconvでパースされたint64やuint64の値が、実際に割り当てられるGoの型(dv.Kind()で示される型)の範囲に収まるかどうかを、dv.OverflowInt(i64)などのメソッドを使って明示的にチェックしていました。もしオーバーフローがあれば、カスタムのエラーメッセージを生成していました。
このコミットでは、このロジックが以下のように変更されました。
-
strconvのbitSize引数の動的な利用:strconv.ParseInt(s, 10, dv.Type().Bits())strconv.ParseUint(s, 10, dv.Type().Bits())strconv.ParseFloat(s, dv.Type().Bits())変更後、strconvのパース関数に渡すbitSize引数が、固定の64ではなく、変換先のGoの型の実際のビットサイズ (dv.Type().Bits()) になりました。 これにより、strconv関数自体が、パース時に変換先の型の範囲を考慮してオーバーフローを検出するようになります。例えば、uint8型への変換であればdv.Type().Bits()は8を返し、strconv.ParseUintはuint8の範囲を超える値をパースしようとするとエラーを返します。
-
冗長なオーバーフローチェックの削除:
if dv.OverflowInt(i64) { ... }if dv.OverflowUint(u64) { ... }if dv.OverflowFloat(f64) { ... }strconvが適切なbitSizeでパースを行うようになったため、これらのreflect.Value.Overflow*メソッドによる明示的なオーバーフローチェックは不要となり、削除されました。
-
エラーメッセージの変更:
- テストファイル
src/pkg/exp/sql/convert_test.goの変更が示すように、オーバーフロー時のエラーメッセージが変更されました。 - 変更前:
string "256" overflows uint8(カスタムエラー) - 変更後:
converting string "256" to a uint8: parsing "256": value out of range(strconvが返すエラーメッセージの一部) これは、オーバーフローの検出とエラー報告の責任がexp/sqlパッケージからstrconvパッケージに移ったことを明確に示しています。strconvが返すエラーは、より詳細なパースエラー情報を含んでいます。
- テストファイル
この変更により、コードはより簡潔になり、Goの標準ライブラリの機能を最大限に活用する形になりました。また、エラーハンドリングも strconv の標準的なエラー形式に統一され、一貫性が向上しています。
コアとなるコードの変更箇所
src/pkg/exp/sql/convert.go
--- a/src/pkg/exp/sql/convert.go
+++ b/src/pkg/exp/sql/convert.go
@@ -95,35 +95,26 @@ func convertAssign(dest, src interface{}) error {
switch dv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s := asString(src)
- i64, err := strconv.ParseInt(s, 10, 64)
+ i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
- if dv.OverflowInt(i64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
dv.SetInt(i64)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
s := asString(src)
- u64, err := strconv.ParseUint(s, 10, 64)
+ u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
- if dv.OverflowUint(u64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
dv.SetUint(u64)
return nil
case reflect.Float32, reflect.Float64:
s := asString(src)
- f64, err := strconv.ParseFloat(s, 64)
+ f64, err := strconv.ParseFloat(s, dv.Type().Bits())
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
- if dv.OverflowFloat(f64) {
- return fmt.Errorf("value %q overflows %s", s, dv.Kind())
- }
dv.SetFloat(f64)
return nil
}
src/pkg/exp/sql/convert_test.go
--- a/src/pkg/exp/sql/convert_test.go
+++ b/src/pkg/exp/sql/convert_test.go
@@ -55,7 +55,7 @@ var conversionTests = []conversionTest{
// Strings to integers
{s: "255", d: &scanuint8, wantuint: 255},
- {s: "256", d: &scanuint8, wanterr: `string "256" overflows uint8`},
+ {s: "256", d: &scanuint8, wanterr: `converting string "256" to a uint8: parsing "256": value out of range`},
{s: "256", d: &scanuint16, wantuint: 256},
{s: "-1", d: &scanint, wantint: -1},
{s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: parsing "foo": invalid syntax`},
コアとなるコードの解説
src/pkg/exp/sql/convert.go の convertAssign 関数は、データベースから取得した値をGoの変数に変換して割り当てる中心的なロジックを含んでいます。
変更の核心は、reflect.Int、reflect.Uint、reflect.Float の各ケースにおける strconv パース関数の呼び出し方です。
-
reflect.Int系の型への変換:- 変更前:
strconv.ParseInt(s, 10, 64)は、文字列sを10進数としてパースし、結果をint64の範囲で解釈していました。その後、if dv.OverflowInt(i64)で、実際に割り当てるdvの型(例:int8,int16)に値が収まるかを手動でチェックしていました。 - 変更後:
strconv.ParseInt(s, 10, dv.Type().Bits())となりました。ここでdv.Type().Bits()は、変換先の具体的なGoの型(例:int8なら8、int32なら32)のビットサイズを返します。strconv.ParseIntはこのbitSizeを利用して、パース時にそのビットサイズに収まるかどうかを自動的にチェックし、収まらない場合はエラーを返します。これにより、冗長なdv.OverflowIntのチェックが不要になりました。
- 変更前:
-
reflect.Uint系の型への変換:reflect.Intの場合と同様に、strconv.ParseUint(s, 10, dv.Type().Bits())に変更され、dv.OverflowUintのチェックが削除されました。これにより、符号なし整数のオーバーフローもstrconvが適切に処理するようになりました。
-
reflect.Float系の型への変換:- 同様に、
strconv.ParseFloat(s, dv.Type().Bits())に変更され、dv.OverflowFloatのチェックが削除されました。dv.Type().Bits()はfloat32なら32、float64なら64を返し、strconv.ParseFloatが適切な精度でパースとオーバーフローチェックを行います。
- 同様に、
src/pkg/exp/sql/convert_test.go の変更は、このロジック変更に伴うテストケースの期待値の更新です。特に uint8 への変換で 256 というオーバーフローする値をテストする際、以前はカスタムのエラーメッセージを期待していましたが、変更後は strconv が返す標準的な「value out of range」を含むエラーメッセージを期待するように修正されています。これは、エラーの発生源が exp/sql のカスタムロジックから strconv の組み込みロジックへと移行したことを明確に示しています。
このコミットは、Goの標準ライブラリの機能をより深く理解し、それを活用することで、コードの簡素化、堅牢性の向上、そしてGoのイディオムへの準拠を実現した良い例と言えます。
関連リンク
- Go言語
strconvパッケージのドキュメント: https://pkg.go.dev/strconv - Go言語
reflectパッケージのドキュメント: https://pkg.go.dev/reflect - Go言語
database/sqlパッケージのドキュメント: https://pkg.go.dev/database/sql (現在の標準SQLパッケージ)
参考にした情報源リンク
- Go言語の公式ドキュメント (上記リンク)
- Go言語のソースコード (GitHubリポジトリ)
- Go言語のChange List (CL) 5451112: https://golang.org/cl/5451112 (このコミットの元となったコードレビューのページ。当時の議論や背景がより詳細に記述されている可能性があります。)
strconv.ParseIntのbitSize引数に関する解説記事やGoのフォーラムの議論 (一般的なGoのプログラミング知識として)reflect.Value.Overflow*メソッドの利用に関する解説 (一般的なGoのプログラミング知識として)exp/sqlパッケージの歴史的経緯に関する情報 (Goの進化に関する知識として)
[インデックス 10654] ファイルの概要
このコミットは、Go言語の実験的なSQLパッケージ exp/sql における文字列変換ロジックの簡素化を目的としています。具体的には、strconv パッケージの数値パース関数(ParseInt, ParseUint, ParseFloat)の bitSize 引数を適切に利用することで、数値オーバーフローのチェックをより効率的かつGoの標準ライブラリの挙動に沿った形で行うように変更しています。これにより、冗長なオーバーフローチェックが削除され、コードの可読性と保守性が向上しています。
コミット
commit 9d52fe22b48d611adc2935e76920b430db757fd3
Author: David Symonds <dsymonds@golang.org>
Date: Thu Dec 8 08:08:00 2011 +1100
exp/sql: simplify some string conversions.
R=bradfitz
CC=golang-dev
https://golang.org/cl/5451112
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9d52fe22b48d611adc2935e76920b430db757fd3
元コミット内容
exp/sql: simplify some string conversions.
R=bradfitz
CC=golang-dev
https://golang.org/cl/5451112
変更の背景
この変更の背景には、Go言語の標準ライブラリである strconv パッケージの数値パース関数(ParseInt, ParseUint, ParseFloat)の設計思想と、reflect パッケージの型情報利用の最適化があります。
以前のコードでは、文字列から数値をパースする際に、まず strconv 関数で最大ビットサイズ(例: 64)を指定してパースし、その後、reflect パッケージの dv.OverflowInt(), dv.OverflowUint(), dv.OverflowFloat() といったメソッドを使って、実際に変換先の型(例: int8, uint16)に値が収まるかどうかのオーバーフローチェックを別途行っていました。
しかし、strconv パッケージの ParseInt, ParseUint, ParseFloat 関数は、第3引数に bitSize を取ります。この bitSize は、パースする数値が何ビットの整数または浮動小数点数として解釈されるべきかを指定するものです。例えば、ParseInt(s, 10, 8) と指定すれば、文字列 s を10進数としてパースし、結果が int8 の範囲に収まるかどうかを strconv 自身がチェックし、範囲外であればエラーを返します。
この機能を利用することで、exp/sql パッケージ内で独自に行っていたオーバーフローチェックが冗長であることが判明しました。reflect.Value.Type().Bits() メソッドを使用すれば、変換先の型のビットサイズを動的に取得できるため、これを strconv 関数に渡すことで、strconv 側で適切なオーバーフローチェックが行われるようになります。これにより、コードが簡素化され、よりGoのイディオムに沿った形になります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とパッケージに関する知識が必要です。
-
exp/sqlパッケージ:- Go言語の標準ライブラリの一部として、データベース操作のための汎用的なインターフェースを提供する
database/sqlパッケージがあります。 exp/sqlは、そのdatabase/sqlパッケージの初期段階、または実験的な機能を含むバージョンとして存在していました。このコミットが行われた2011年当時は、まだdatabase/sqlが成熟する前の段階であり、exp(experimental) というプレフィックスが付けられていました。現在ではdatabase/sqlが標準として広く使われています。- このパッケージの役割は、データベースから取得したデータをGoの型に変換(スキャン)したり、Goのデータをデータベースに書き込む際に適切な型に変換したりすることです。
- Go言語の標準ライブラリの一部として、データベース操作のための汎用的なインターフェースを提供する
-
strconvパッケージ:- Go言語の標準ライブラリで、文字列と基本的なデータ型(数値、真偽値など)との間の変換を提供します。
- 主要な関数:
strconv.ParseInt(s string, base int, bitSize int) (i int64, err error): 文字列sを指定されたbase(基数、例: 10進数なら10) でint64にパースします。bitSizeは、結果が収まるべきビット数を指定します(例:8ならint8の範囲、16ならint16の範囲)。指定されたbitSizeの範囲を超えるとエラーを返します。strconv.ParseUint(s string, base int, bitSize int) (u uint64, err error):ParseIntの符号なし整数版です。strconv.ParseFloat(s string, bitSize int) (f float64, err error): 文字列sを浮動小数点数にパースします。bitSizeは、結果がfloat32(32) またはfloat64(64) のどちらの精度で解釈されるべきかを指定します。
-
reflectパッケージ:- Go言語の標準ライブラリで、実行時にプログラムの構造(型、値、メソッドなど)を検査・操作するための機能を提供します。
reflect.Value: Goの変数の実行時の値を表します。reflect.Value.Kind():reflect.Valueが表す値の基本的な種類(例:reflect.Int,reflect.String,reflect.Structなど)を返します。reflect.Value.Type():reflect.Valueが表す値の具体的な型(例:int,string,MyStructなど)を返します。reflect.Type.Bits(): 数値型の場合、その型が占めるビット数を返します(例:int8なら8,int64なら64)。このメソッドは、Int,Int8,Int16,Int32,Int64,Uint,Uint8,Uint16,Uint32,Uint64,Float32,Float64,Complex64,Complex128のいずれかのKindでない場合にパニックを起こします。reflect.Value.OverflowInt(x int64) bool,OverflowUint(x uint64) bool,OverflowFloat(x float64) bool: これらのメソッドは、与えられた数値xがreflect.Valueが表す型に収まるかどうかをチェックします。このコミットでは、strconvのbitSize引数を使うことで、これらの明示的なオーバーフローチェックが不要になりました。
-
エラーハンドリング:
- Goでは、関数がエラーを返す場合、通常は最後の戻り値として
error型の値を返します。呼び出し元はif err != nilでエラーをチェックし、適切に処理します。 fmt.Errorf(): フォーマットされたエラー文字列を生成し、errorインターフェースを満たす値を返します。
- Goでは、関数がエラーを返す場合、通常は最後の戻り値として
技術的詳細
このコミットの技術的詳細な変更点は、src/pkg/exp/sql/convert.go ファイル内の convertAssign 関数に集中しています。この関数は、データベースから読み取られた値をGoの変数に割り当てる際の型変換ロジックを担っています。
変更前は、文字列から整数 (reflect.Int, reflect.Int8 など)、符号なし整数 (reflect.Uint, reflect.Uint8 など)、浮動小数点数 (reflect.Float32, reflect.Float64) への変換において、以下の2段階のチェックが行われていました。
strconvによるパース:strconv.ParseInt(s, 10, 64)のように、bitSizeを固定値(64)としてパースしていました。これは、パースされた値がint64やuint64の範囲に収まるかどうかのチェックは行いますが、例えばint8のようなより小さい型へのオーバーフローは検出しません。reflect.Value.Overflow*による追加チェック:strconvでパースされたint64やuint64の値が、実際に割り当てられるGoの型(dv.Kind()で示される型)の範囲に収まるかどうかを、dv.OverflowInt(i64)などのメソッドを使って明示的にチェックしていました。もしオーバーフローがあれば、カスタムのエラーメッセージを生成していました。
このコミットでは、このロジックが以下のように変更されました。
-
strconvのbitSize引数の動的な利用:strconv.ParseInt(s, 10, dv.Type().Bits())strconv.ParseUint(s, 10, dv.Type().Bits())strconv.ParseFloat(s, dv.Type().Bits())変更後、strconvのパース関数に渡すbitSize引数が、固定の64ではなく、変換先のGoの型の実際のビットサイズ (dv.Type().Bits()) になりました。 これにより、strconv関数自体が、パース時に変換先の型の範囲を考慮してオーバーフローを検出するようになります。例えば、uint8型への変換であればdv.Type().Bits()は8を返し、strconv.ParseUintはuint8の範囲を超える値をパースしようとするとエラーを返します。
-
冗長なオーバーフローチェックの削除:
if dv.OverflowInt(i64) { ... }if dv.OverflowUint(u64) { ... }if dv.OverflowFloat(f64) { ... }strconvが適切なbitSizeでパースを行うようになったため、これらのreflect.Value.Overflow*メソッドによる明示的なオーバーフローチェックは不要となり、削除されました。
-
エラーメッセージの変更:
- テストファイル
src/pkg/exp/sql/convert_test.goの変更が示すように、オーバーフロー時のエラーメッセージが変更されました。 - 変更前:
string "256" overflows uint8(カスタムエラー) - 変更後:
converting string "256" to a uint8: parsing "256": value out of range(strconvが返すエラーメッセージの一部) これは、オーバーフローの検出とエラー報告の責任がexp/sqlパッケージからstrconvパッケージに移ったことを明確に示しています。strconvが返すエラーは、より詳細なパースエラー情報を含んでいます。
- テストファイル
この変更により、コードはより簡潔になり、Goの標準ライブラリの機能を最大限に活用する形になりました。また、エラーハンドリングも strconv の標準的なエラー形式に統一され、一貫性が向上しています。
コアとなるコードの変更箇所
src/pkg/exp/sql/convert.go
--- a/src/pkg/exp/sql/convert.go
+++ b/src/pkg/exp/sql/convert.go
@@ -95,35 +95,26 @@ func convertAssign(dest, src interface{}) error {
switch dv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s := asString(src)
- i64, err := strconv.ParseInt(s, 10, 64)
+ i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
- if dv.OverflowInt(i64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
dv.SetInt(i64)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
s := asString(src)
- u64, err := strconv.ParseUint(s, 10, 64)
+ u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
- if dv.OverflowUint(u64) {
- return fmt.Errorf("string %q overflows %s", s, dv.Kind())
- }
dv.SetUint(u64)
return nil
case reflect.Float32, reflect.Float64:
s := asString(src)
- f64, err := strconv.ParseFloat(s, 64)
+ f64, err := strconv.ParseFloat(s, dv.Type().Bits())
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
- if dv.OverflowFloat(f64) {
- return fmt.Errorf("value %q overflows %s", s, dv.Kind())
- }
dv.SetFloat(f64)
return nil
}
src/pkg/exp/sql/convert_test.go
--- a/src/pkg/exp/sql/convert_test.go
+++ b/src/pkg/exp/sql/convert_test.go
@@ -55,7 +55,7 @@ var conversionTests = []conversionTest{
// Strings to integers
{s: "255", d: &scanuint8, wantuint: 255},
- {s: "256", d: &scanuint8, wanterr: `string "256" overflows uint8`},
+ {s: "256", d: &scanuint8, wanterr: `converting string "256" to a uint8: parsing "256": value out of range`},
{s: "256", d: &scanuint16, wantuint: 256},
{s: "-1", d: &scanint, wantint: -1},
{s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: parsing "foo": invalid syntax`},
コアとなるコードの解説
src/pkg/exp/sql/convert.go の convertAssign 関数は、データベースから取得した値をGoの変数に変換して割り当てる中心的なロジックを含んでいます。
変更の核心は、reflect.Int、reflect.Uint、reflect.Float の各ケースにおける strconv パース関数の呼び出し方です。
-
reflect.Int系の型への変換:- 変更前:
strconv.ParseInt(s, 10, 64)は、文字列sを10進数としてパースし、結果をint64の範囲で解釈していました。その後、if dv.OverflowInt(i64)で、実際に割り当てるdvの型(例:int8,int16)に値が収まるかを手動でチェックしていました。 - 変更後:
strconv.ParseInt(s, 10, dv.Type().Bits())となりました。ここでdv.Type().Bits()は、変換先の具体的なGoの型(例:int8なら8、int32なら32)のビットサイズを返します。strconv.ParseIntはこのbitSizeを利用して、パース時にそのビットサイズに収まるかどうかを自動的にチェックし、収まらない場合はエラーを返します。これにより、冗長なdv.OverflowIntのチェックが不要になりました。
- 変更前:
-
reflect.Uint系の型への変換:reflect.Intの場合と同様に、strconv.ParseUint(s, 10, dv.Type().Bits())に変更され、dv.OverflowUintのチェックが削除されました。これにより、符号なし整数のオーバーフローもstrconvが適切に処理するようになりました。
-
reflect.Float系の型への変換:- 同様に、
strconv.ParseFloat(s, dv.Type().Bits())に変更され、dv.OverflowFloatのチェックが削除されました。dv.Type().Bits()はfloat32なら32、float64なら64を返し、strconv.ParseFloatが適切な精度でパースとオーバーフローチェックを行います。
- 同様に、
src/pkg/exp/sql/convert_test.go の変更は、このロジック変更に伴うテストケースの期待値の更新です。特に uint8 への変換で 256 というオーバーフローする値をテストする際、以前はカスタムのエラーメッセージを期待していましたが、変更後は strconv が返す標準的な「value out of range」を含むエラーメッセージを期待するように修正されています。これは、エラーの発生源が exp/sql のカスタムロジックから strconv の組み込みロジックへと移行したことを明確に示しています。
このコミットは、Goの標準ライブラリの機能をより深く理解し、それを活用することで、コードの簡素化、堅牢性の向上、そしてGoのイディオムへの準拠を実現した良い例と言えます。
関連リンク
- Go言語
strconvパッケージのドキュメント: https://pkg.go.dev/strconv - Go言語
reflectパッケージのドキュメント: https://pkg.go.dev/reflect - Go言語
database/sqlパッケージのドキュメント: https://pkg.go.dev/database/sql (現在の標準SQLパッケージ)
参考にした情報源リンク
- Go言語の公式ドキュメント (上記リンク)
- Go言語のソースコード (GitHubリポジトリ)
- Go言語のChange List (CL) 5451112: https://go-review.googlesource.com/c/go/+/5451112 (このコミットの元となったコードレビューのページ。当時の議論や背景がより詳細に記述されている可能性があります。)
strconv.ParseIntのbitSize引数に関する解説記事やGoのフォーラムの議論 (一般的なGoのプログラミング知識として)reflect.Value.Overflow*メソッドの利用に関する解説 (一般的なGoのプログラミング知識として)exp/sqlパッケージの歴史的経緯に関する情報 (Goの進化に関する知識として)