diff options
author | Ian Lance Taylor <iant@golang.org> | 2019-09-06 18:12:46 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2019-09-06 18:12:46 +0000 |
commit | aa8901e9bb0399d2c16f988ba2fe46eb0c0c5d13 (patch) | |
tree | 7e63b06d1eec92beec6997c9d3ab47a5d6a835be /libgo/go/database | |
parent | 920ea3b8ba3164b61ac9490dfdfceb6936eda6dd (diff) | |
download | gcc-aa8901e9bb0399d2c16f988ba2fe46eb0c0c5d13.tar.gz |
libgo: update to Go 1.13beta1 release
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/193497
From-SVN: r275473
Diffstat (limited to 'libgo/go/database')
-rw-r--r-- | libgo/go/database/sql/convert.go | 60 | ||||
-rw-r--r-- | libgo/go/database/sql/convert_test.go | 114 | ||||
-rw-r--r-- | libgo/go/database/sql/driver/driver.go | 2 | ||||
-rw-r--r-- | libgo/go/database/sql/driver/types.go | 17 | ||||
-rw-r--r-- | libgo/go/database/sql/driver/types_test.go | 14 | ||||
-rw-r--r-- | libgo/go/database/sql/fakedb_test.go | 8 | ||||
-rw-r--r-- | libgo/go/database/sql/sql.go | 89 | ||||
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 90 |
8 files changed, 377 insertions, 17 deletions
diff --git a/libgo/go/database/sql/convert.go b/libgo/go/database/sql/convert.go index c450d987a46..4c056a1edad 100644 --- a/libgo/go/database/sql/convert.go +++ b/libgo/go/database/sql/convert.go @@ -98,10 +98,12 @@ func defaultCheckNamedValue(nv *driver.NamedValue) (err error) { return err } -// driverArgs converts arguments from callers of Stmt.Exec and +// driverArgsConnLocked converts arguments from callers of Stmt.Exec and // Stmt.Query into driver Values. // // The statement ds may be nil, if no statement is available. +// +// ci must be locked. func driverArgsConnLocked(ci driver.Conn, ds *driverStmt, args []interface{}) ([]driver.NamedValue, error) { nvargs := make([]driver.NamedValue, len(args)) @@ -286,6 +288,11 @@ func convertAssignRows(dest, src interface{}, rows *Rows) error { *d = s.AppendFormat((*d)[:0], time.RFC3339Nano) return nil } + case decimalDecompose: + switch d := dest.(type) { + case decimalCompose: + return d.Compose(s.Decompose(nil)) + } case nil: switch d := dest.(type) { case *interface{}: @@ -420,6 +427,9 @@ func convertAssignRows(dest, src interface{}, rows *Rows) error { dv.Set(reflect.New(dv.Type().Elem())) return convertAssignRows(dv.Interface(), src, rows) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } s := asString(src) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) if err != nil { @@ -429,6 +439,9 @@ func convertAssignRows(dest, src interface{}, rows *Rows) error { dv.SetInt(i64) return nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } s := asString(src) u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) if err != nil { @@ -438,6 +451,9 @@ func convertAssignRows(dest, src interface{}, rows *Rows) error { dv.SetUint(u64) return nil case reflect.Float32, reflect.Float64: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } s := asString(src) f64, err := strconv.ParseFloat(s, dv.Type().Bits()) if err != nil { @@ -447,6 +463,9 @@ func convertAssignRows(dest, src interface{}, rows *Rows) error { dv.SetFloat(f64) return nil case reflect.String: + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) + } switch v := src.(type) { case string: dv.SetString(v) @@ -539,3 +558,42 @@ func callValuerValue(vr driver.Valuer) (v driver.Value, err error) { } return vr.Value() } + +// decimal composes or decomposes a decimal value to and from individual parts. +// There are four parts: a boolean negative flag, a form byte with three possible states +// (finite=0, infinite=1, NaN=2), a base-2 big-endian integer +// coefficient (also known as a significand) as a []byte, and an int32 exponent. +// These are composed into a final value as "decimal = (neg) (form=finite) coefficient * 10 ^ exponent". +// A zero length coefficient is a zero value. +// The big-endian integer coefficent stores the most significant byte first (at coefficent[0]). +// If the form is not finite the coefficient and exponent should be ignored. +// The negative parameter may be set to true for any form, although implementations are not required +// to respect the negative parameter in the non-finite form. +// +// Implementations may choose to set the negative parameter to true on a zero or NaN value, +// but implementations that do not differentiate between negative and positive +// zero or NaN values should ignore the negative parameter without error. +// If an implementation does not support Infinity it may be converted into a NaN without error. +// If a value is set that is larger than what is supported by an implementation, +// an error must be returned. +// Implementations must return an error if a NaN or Infinity is attempted to be set while neither +// are supported. +// +// NOTE(kardianos): This is an experimental interface. See https://golang.org/issue/30870 +type decimal interface { + decimalDecompose + decimalCompose +} + +type decimalDecompose interface { + // Decompose returns the internal decimal state in parts. + // If the provided buf has sufficient capacity, buf may be returned as the coefficient with + // the value set and length set as appropriate. + Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) +} + +type decimalCompose interface { + // Compose sets the internal decimal value from parts. If the value cannot be + // represented then an error should be returned. + Compose(form byte, negative bool, coefficient []byte, exponent int32) error +} diff --git a/libgo/go/database/sql/convert_test.go b/libgo/go/database/sql/convert_test.go index b0aff7b1685..8a82891c253 100644 --- a/libgo/go/database/sql/convert_test.go +++ b/libgo/go/database/sql/convert_test.go @@ -439,7 +439,7 @@ func TestDriverArgs(t *testing.T) { 0: { args: []interface{}{Valuer_V("foo")}, want: []driver.NamedValue{ - driver.NamedValue{ + { Ordinal: 1, Value: "FOO", }, @@ -448,7 +448,7 @@ func TestDriverArgs(t *testing.T) { 1: { args: []interface{}{nilValuerVPtr}, want: []driver.NamedValue{ - driver.NamedValue{ + { Ordinal: 1, Value: nil, }, @@ -457,7 +457,7 @@ func TestDriverArgs(t *testing.T) { 2: { args: []interface{}{nilValuerPPtr}, want: []driver.NamedValue{ - driver.NamedValue{ + { Ordinal: 1, Value: "nil-to-str", }, @@ -466,7 +466,7 @@ func TestDriverArgs(t *testing.T) { 3: { args: []interface{}{"plain-str"}, want: []driver.NamedValue{ - driver.NamedValue{ + { Ordinal: 1, Value: "plain-str", }, @@ -475,7 +475,7 @@ func TestDriverArgs(t *testing.T) { 4: { args: []interface{}{nilStrPtr}, want: []driver.NamedValue{ - driver.NamedValue{ + { Ordinal: 1, Value: nil, }, @@ -494,3 +494,107 @@ func TestDriverArgs(t *testing.T) { } } } + +type dec struct { + form byte + neg bool + coefficient [16]byte + exponent int32 +} + +func (d dec) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) { + coef := make([]byte, 16) + copy(coef, d.coefficient[:]) + return d.form, d.neg, coef, d.exponent +} + +func (d *dec) Compose(form byte, negative bool, coefficient []byte, exponent int32) error { + switch form { + default: + return fmt.Errorf("unknown form %d", form) + case 1, 2: + d.form = form + d.neg = negative + return nil + case 0: + } + d.form = form + d.neg = negative + d.exponent = exponent + + // This isn't strictly correct, as the extra bytes could be all zero, + // ignore this for this test. + if len(coefficient) > 16 { + return fmt.Errorf("coefficent too large") + } + copy(d.coefficient[:], coefficient) + + return nil +} + +type decFinite struct { + neg bool + coefficient [16]byte + exponent int32 +} + +func (d decFinite) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) { + coef := make([]byte, 16) + copy(coef, d.coefficient[:]) + return 0, d.neg, coef, d.exponent +} + +func (d *decFinite) Compose(form byte, negative bool, coefficient []byte, exponent int32) error { + switch form { + default: + return fmt.Errorf("unknown form %d", form) + case 1, 2: + return fmt.Errorf("unsupported form %d", form) + case 0: + } + d.neg = negative + d.exponent = exponent + + // This isn't strictly correct, as the extra bytes could be all zero, + // ignore this for this test. + if len(coefficient) > 16 { + return fmt.Errorf("coefficent too large") + } + copy(d.coefficient[:], coefficient) + + return nil +} + +func TestDecimal(t *testing.T) { + list := []struct { + name string + in decimalDecompose + out dec + err bool + }{ + {name: "same", in: dec{exponent: -6}, out: dec{exponent: -6}}, + + // Ensure reflection is not used to assign the value by using different types. + {name: "diff", in: decFinite{exponent: -6}, out: dec{exponent: -6}}, + + {name: "bad-form", in: dec{form: 200}, err: true}, + } + for _, item := range list { + t.Run(item.name, func(t *testing.T) { + out := dec{} + err := convertAssign(&out, item.in) + if item.err { + if err == nil { + t.Fatalf("unexpected nil error") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(out, item.out) { + t.Fatalf("got %#v want %#v", out, item.out) + } + }) + } +} diff --git a/libgo/go/database/sql/driver/driver.go b/libgo/go/database/sql/driver/driver.go index ecc6547bf37..316e7cea375 100644 --- a/libgo/go/database/sql/driver/driver.go +++ b/libgo/go/database/sql/driver/driver.go @@ -26,7 +26,7 @@ import ( // time.Time // // If the driver supports cursors, a returned Value may also implement the Rows interface -// in this package. This is used when, for example, when a user selects a cursor +// in this package. This is used, for example, when a user selects a cursor // such as "select cursor(select * from my_table) from dual". If the Rows // from the select is closed, the cursor Rows will also be closed. type Value interface{} diff --git a/libgo/go/database/sql/driver/types.go b/libgo/go/database/sql/driver/types.go index 8b3cb6c8f61..24c3a454836 100644 --- a/libgo/go/database/sql/driver/types.go +++ b/libgo/go/database/sql/driver/types.go @@ -38,6 +38,7 @@ type ValueConverter interface { // themselves to a driver Value. type Valuer interface { // Value returns a driver Value. + // Value must not panic. Value() (Value, error) } @@ -179,6 +180,8 @@ func IsValue(v interface{}) bool { switch v.(type) { case []byte, bool, float64, int64, string, time.Time: return true + case decimalDecompose: + return true } return false } @@ -235,7 +238,8 @@ func (defaultConverter) ConvertValue(v interface{}) (Value, error) { return v, nil } - if vr, ok := v.(Valuer); ok { + switch vr := v.(type) { + case Valuer: sv, err := callValuerValue(vr) if err != nil { return nil, err @@ -244,6 +248,10 @@ func (defaultConverter) ConvertValue(v interface{}) (Value, error) { return nil, fmt.Errorf("non-Value type %T returned from Value", sv) } return sv, nil + + // For now, continue to prefer the Valuer interface over the decimal decompose interface. + case decimalDecompose: + return vr, nil } rv := reflect.ValueOf(v) @@ -280,3 +288,10 @@ func (defaultConverter) ConvertValue(v interface{}) (Value, error) { } return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind()) } + +type decimalDecompose interface { + // Decompose returns the internal decimal state into parts. + // If the provided buf has sufficient capacity, buf may be returned as the coefficient with + // the value set and length set as appropriate. + Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) +} diff --git a/libgo/go/database/sql/driver/types_test.go b/libgo/go/database/sql/driver/types_test.go index 0379bf8892f..4c2996da85f 100644 --- a/libgo/go/database/sql/driver/types_test.go +++ b/libgo/go/database/sql/driver/types_test.go @@ -57,6 +57,7 @@ var valueConverterTests = []valueConverterTest{ {DefaultParameterConverter, bs{1}, []byte{1}, ""}, {DefaultParameterConverter, s("a"), "a", ""}, {DefaultParameterConverter, is{1}, nil, "unsupported type driver.is, a slice of int"}, + {DefaultParameterConverter, dec{exponent: -6}, dec{exponent: -6}, ""}, } func TestValueConverters(t *testing.T) { @@ -79,3 +80,16 @@ func TestValueConverters(t *testing.T) { } } } + +type dec struct { + form byte + neg bool + coefficient [16]byte + exponent int32 +} + +func (d dec) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) { + coef := make([]byte, 16) + copy(coef, d.coefficient[:]) + return d.form, d.neg, coef, d.exponent +} diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go index dcdd264baa8..c0371f3e784 100644 --- a/libgo/go/database/sql/fakedb_test.go +++ b/libgo/go/database/sql/fakedb_test.go @@ -1141,6 +1141,8 @@ func converterForType(typ string) driver.ValueConverter { return driver.Null{Converter: driver.Bool} case "int32": return driver.Int32 + case "nullint32": + return driver.Null{Converter: driver.DefaultParameterConverter} case "string": return driver.NotNull{Converter: fakeDriverString{}} case "nullstring": @@ -1158,7 +1160,9 @@ func converterForType(typ string) driver.ValueConverter { // TODO(coopernurse): add type-specific converter return driver.Null{Converter: driver.DefaultParameterConverter} case "datetime": - return driver.DefaultParameterConverter + return driver.NotNull{Converter: driver.DefaultParameterConverter} + case "nulldatetime": + return driver.Null{Converter: driver.DefaultParameterConverter} case "any": return anyTypeConverter{} } @@ -1173,6 +1177,8 @@ func colTypeToReflectType(typ string) reflect.Type { return reflect.TypeOf(NullBool{}) case "int32": return reflect.TypeOf(int32(0)) + case "nullint32": + return reflect.TypeOf(NullInt32{}) case "string": return reflect.TypeOf("") case "nullstring": diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go index 8cdc903c680..5c5b7dc7e97 100644 --- a/libgo/go/database/sql/sql.go +++ b/libgo/go/database/sql/sql.go @@ -8,7 +8,7 @@ // The sql package must be used in conjunction with a database driver. // See https://golang.org/s/sqldrivers for a list of drivers. // -// Drivers that do not support context cancelation will not return until +// Drivers that do not support context cancellation will not return until // after the query is completed. // // For usage examples, see the wiki page at @@ -234,6 +234,32 @@ func (n NullInt64) Value() (driver.Value, error) { return n.Int64, nil } +// NullInt32 represents an int32 that may be null. +// NullInt32 implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullInt32 struct { + Int32 int32 + Valid bool // Valid is true if Int32 is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullInt32) Scan(value interface{}) error { + if value == nil { + n.Int32, n.Valid = 0, false + return nil + } + n.Valid = true + return convertAssign(&n.Int32, value) +} + +// Value implements the driver Valuer interface. +func (n NullInt32) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return int64(n.Int32), nil +} + // NullFloat64 represents a float64 that may be null. // NullFloat64 implements the Scanner interface so // it can be used as a scan destination, similar to NullString. @@ -286,6 +312,32 @@ func (n NullBool) Value() (driver.Value, error) { return n.Bool, nil } +// NullTime represents a time.Time that may be null. +// NullTime implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullTime struct { + Time time.Time + Valid bool // Valid is true if Time is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullTime) Scan(value interface{}) error { + if value == nil { + n.Time, n.Valid = time.Time{}, false + return nil + } + n.Valid = true + return convertAssign(&n.Time, value) +} + +// Value implements the driver Valuer interface. +func (n NullTime) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Time, nil +} + // Scanner is an interface used by Scan. type Scanner interface { // Scan assigns a value from a database driver. @@ -1740,6 +1792,8 @@ type Conn struct { done int32 } +// grabConn takes a context to implement stmtConnGrabber +// but the context is not used. func (c *Conn) grabConn(context.Context) (*driverConn, releaseConn, error) { if atomic.LoadInt32(&c.done) != 0 { return nil, nil, ErrConnDone @@ -1804,6 +1858,39 @@ func (c *Conn) PrepareContext(ctx context.Context, query string) (*Stmt, error) return c.db.prepareDC(ctx, dc, release, c, query) } +// Raw executes f exposing the underlying driver connection for the +// duration of f. The driverConn must not be used outside of f. +// +// Once f returns and err is nil, the Conn will continue to be usable +// until Conn.Close is called. +func (c *Conn) Raw(f func(driverConn interface{}) error) (err error) { + var dc *driverConn + var release releaseConn + + // grabConn takes a context to implement stmtConnGrabber, but the context is not used. + dc, release, err = c.grabConn(nil) + if err != nil { + return + } + fPanic := true + dc.Mutex.Lock() + defer func() { + dc.Mutex.Unlock() + + // If f panics fPanic will remain true. + // Ensure an error is passed to release so the connection + // may be discarded. + if fPanic { + err = driver.ErrBadConn + } + release(err) + }() + err = f(dc.ci) + fPanic = false + + return +} + // BeginTx starts a transaction. // // The provided context is used until the transaction is committed or rolled back. diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index 64b9dfea5c2..f68cefe43ae 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -131,6 +131,7 @@ func TestDriverPanic(t *testing.T) { } func exec(t testing.TB, db *DB, query string, args ...interface{}) { + t.Helper() _, err := db.Exec(query, args...) if err != nil { t.Fatalf("Exec of %q: %v", query, err) @@ -1338,6 +1339,54 @@ func TestConnQuery(t *testing.T) { } } +func TestConnRaw(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + conn.dc.ci.(*fakeConn).skipDirtySession = true + defer conn.Close() + + sawFunc := false + err = conn.Raw(func(dc interface{}) error { + sawFunc = true + if _, ok := dc.(*fakeConn); !ok { + return fmt.Errorf("got %T want *fakeConn", dc) + } + return nil + }) + if err != nil { + t.Fatal(err) + } + if !sawFunc { + t.Fatal("Raw func not called") + } + + func() { + defer func() { + x := recover() + if x == nil { + t.Fatal("expected panic") + } + conn.closemu.Lock() + closed := conn.dc == nil + conn.closemu.Unlock() + if !closed { + t.Fatal("expected connection to be closed after panic") + } + }() + err = conn.Raw(func(dc interface{}) error { + panic("Conn.Raw panic should return an error") + }) + t.Fatal("expected panic from Raw func") + }() +} + func TestCursorFake(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -1401,7 +1450,7 @@ func TestInvalidNilValues(t *testing.T) { { name: "int", input: &date2, - expectedError: `sql: Scan error on column index 0, name "bdate": converting driver.Value type <nil> ("<nil>") to a int: invalid syntax`, + expectedError: `sql: Scan error on column index 0, name "bdate": converting NULL to int is unsupported`, }, } @@ -1671,6 +1720,18 @@ func TestNullInt64Param(t *testing.T) { nullTestRun(t, spec) } +func TestNullInt32Param(t *testing.T) { + spec := nullTestSpec{"nullint32", "int32", [6]nullTestRow{ + {NullInt32{31, true}, 1, NullInt32{31, true}}, + {NullInt32{-22, false}, 1, NullInt32{0, false}}, + {22, 1, NullInt32{22, true}}, + {NullInt32{33, true}, 1, NullInt32{33, true}}, + {NullInt32{222, false}, 1, NullInt32{0, false}}, + {0, NullInt32{31, false}, nil}, + }} + nullTestRun(t, spec) +} + func TestNullFloat64Param(t *testing.T) { spec := nullTestSpec{"nullfloat64", "float64", [6]nullTestRow{ {NullFloat64{31.2, true}, 1, NullFloat64{31.2, true}}, @@ -1695,6 +1756,21 @@ func TestNullBoolParam(t *testing.T) { nullTestRun(t, spec) } +func TestNullTimeParam(t *testing.T) { + t0 := time.Time{} + t1 := time.Date(2000, 1, 1, 8, 9, 10, 11, time.UTC) + t2 := time.Date(2010, 1, 1, 8, 9, 10, 11, time.UTC) + spec := nullTestSpec{"nulldatetime", "datetime", [6]nullTestRow{ + {NullTime{t1, true}, t2, NullTime{t1, true}}, + {NullTime{t1, false}, t2, NullTime{t0, false}}, + {t1, t2, NullTime{t1, true}}, + {NullTime{t1, true}, t2, NullTime{t1, true}}, + {NullTime{t1, false}, t2, NullTime{t0, false}}, + {t2, NullTime{t1, false}, nil}, + }} + nullTestRun(t, spec) +} + func nullTestRun(t *testing.T, spec nullTestSpec) { db := newTestDB(t, "") defer closeDB(t, db) @@ -3530,7 +3606,7 @@ type nvcConn struct { skipNamedValueCheck bool } -type decimal struct { +type decimalInt struct { value int } @@ -3554,7 +3630,7 @@ func (c *nvcConn) CheckNamedValue(nv *driver.NamedValue) error { nv.Value = "OUT:*string" } return nil - case decimal, []int64: + case decimalInt, []int64: return nil case doNotInclude: return driver.ErrRemoveArgument @@ -3583,13 +3659,13 @@ func TestNamedValueChecker(t *testing.T) { } o1 := "" - _, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A,str1=?,out1=?O1,array1=?", Named("A", decimal{123}), "hello", Named("O1", Out{Dest: &o1}), []int64{42, 128, 707}, doNotInclude{}) + _, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A,str1=?,out1=?O1,array1=?", Named("A", decimalInt{123}), "hello", Named("O1", Out{Dest: &o1}), []int64{42, 128, 707}, doNotInclude{}) if err != nil { t.Fatal("exec insert", err) } var ( str1 string - dec1 decimal + dec1 decimalInt arr1 []int64 ) err = db.QueryRowContext(ctx, "SELECT|keys|dec1,str1,array1|").Scan(&dec1, &str1, &arr1) @@ -3599,7 +3675,7 @@ func TestNamedValueChecker(t *testing.T) { list := []struct{ got, want interface{} }{ {o1, "from-server"}, - {dec1, decimal{123}}, + {dec1, decimalInt{123}}, {str1, "hello"}, {arr1, []int64{42, 128, 707}}, } @@ -3632,7 +3708,7 @@ func TestNamedValueCheckerSkip(t *testing.T) { t.Fatal("exec create", err) } - _, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A", Named("A", decimal{123})) + _, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A", Named("A", decimalInt{123})) if err == nil { t.Fatalf("expected error with bad argument, got %v", err) } |