[インデックス 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の進化に関する知識として)