Skip to content

Commit 683ffe0

Browse files
committed
database/sql: add support for decimal interface
Add support for scanning decimal types into values. If the dest supports the decimal composer interface and the src supports the decimal decomposer, set the value of the decimal when Scanning. Add support for sending decimal decomposer interface values as parameters. For #30870 Change-Id: Ic5dbf9069df8d56405852b17542a9188d55c2947 Reviewed-on: https://go-review.googlesource.com/c/go/+/174181 Run-TryBot: Daniel Theophanes <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent dc63b59 commit 683ffe0

File tree

5 files changed

+183
-7
lines changed

5 files changed

+183
-7
lines changed

src/database/sql/convert.go

+44
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ func convertAssignRows(dest, src interface{}, rows *Rows) error {
288288
*d = s.AppendFormat((*d)[:0], time.RFC3339Nano)
289289
return nil
290290
}
291+
case decimalDecompose:
292+
switch d := dest.(type) {
293+
case decimalCompose:
294+
return d.Compose(s.Decompose(nil))
295+
}
291296
case nil:
292297
switch d := dest.(type) {
293298
case *interface{}:
@@ -553,3 +558,42 @@ func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
553558
}
554559
return vr.Value()
555560
}
561+
562+
// decimal composes or decomposes a decimal value to and from individual parts.
563+
// There are four parts: a boolean negative flag, a form byte with three possible states
564+
// (finite=0, infinite=1, NaN=2), a base-2 big-endian integer
565+
// coefficient (also known as a significand) as a []byte, and an int32 exponent.
566+
// These are composed into a final value as "decimal = (neg) (form=finite) coefficient * 10 ^ exponent".
567+
// A zero length coefficient is a zero value.
568+
// The big-endian integer coefficent stores the most significant byte first (at coefficent[0]).
569+
// If the form is not finite the coefficient and exponent should be ignored.
570+
// The negative parameter may be set to true for any form, although implementations are not required
571+
// to respect the negative parameter in the non-finite form.
572+
//
573+
// Implementations may choose to set the negative parameter to true on a zero or NaN value,
574+
// but implementations that do not differentiate between negative and positive
575+
// zero or NaN values should ignore the negative parameter without error.
576+
// If an implementation does not support Infinity it may be converted into a NaN without error.
577+
// If a value is set that is larger than what is supported by an implementation,
578+
// an error must be returned.
579+
// Implementations must return an error if a NaN or Infinity is attempted to be set while neither
580+
// are supported.
581+
//
582+
// NOTE(kardianos): This is an experimental interface. See https://golang.org/issue/30870
583+
type decimal interface {
584+
decimalDecompose
585+
decimalCompose
586+
}
587+
588+
type decimalDecompose interface {
589+
// Decompose returns the internal decimal state in parts.
590+
// If the provided buf has sufficient capacity, buf may be returned as the coefficient with
591+
// the value set and length set as appropriate.
592+
Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32)
593+
}
594+
595+
type decimalCompose interface {
596+
// Compose sets the internal decimal value from parts. If the value cannot be
597+
// represented then an error should be returned.
598+
Compose(form byte, negative bool, coefficient []byte, exponent int32) error
599+
}

src/database/sql/convert_test.go

+104
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,107 @@ func TestDriverArgs(t *testing.T) {
494494
}
495495
}
496496
}
497+
498+
type dec struct {
499+
form byte
500+
neg bool
501+
coefficient [16]byte
502+
exponent int32
503+
}
504+
505+
func (d dec) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
506+
coef := make([]byte, 16)
507+
copy(coef, d.coefficient[:])
508+
return d.form, d.neg, coef, d.exponent
509+
}
510+
511+
func (d *dec) Compose(form byte, negative bool, coefficient []byte, exponent int32) error {
512+
switch form {
513+
default:
514+
return fmt.Errorf("unknown form %d", form)
515+
case 1, 2:
516+
d.form = form
517+
d.neg = negative
518+
return nil
519+
case 0:
520+
}
521+
d.form = form
522+
d.neg = negative
523+
d.exponent = exponent
524+
525+
// This isn't strictly correct, as the extra bytes could be all zero,
526+
// ignore this for this test.
527+
if len(coefficient) > 16 {
528+
return fmt.Errorf("coefficent too large")
529+
}
530+
copy(d.coefficient[:], coefficient)
531+
532+
return nil
533+
}
534+
535+
type decFinite struct {
536+
neg bool
537+
coefficient [16]byte
538+
exponent int32
539+
}
540+
541+
func (d decFinite) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
542+
coef := make([]byte, 16)
543+
copy(coef, d.coefficient[:])
544+
return 0, d.neg, coef, d.exponent
545+
}
546+
547+
func (d *decFinite) Compose(form byte, negative bool, coefficient []byte, exponent int32) error {
548+
switch form {
549+
default:
550+
return fmt.Errorf("unknown form %d", form)
551+
case 1, 2:
552+
return fmt.Errorf("unsupported form %d", form)
553+
case 0:
554+
}
555+
d.neg = negative
556+
d.exponent = exponent
557+
558+
// This isn't strictly correct, as the extra bytes could be all zero,
559+
// ignore this for this test.
560+
if len(coefficient) > 16 {
561+
return fmt.Errorf("coefficent too large")
562+
}
563+
copy(d.coefficient[:], coefficient)
564+
565+
return nil
566+
}
567+
568+
func TestDecimal(t *testing.T) {
569+
list := []struct {
570+
name string
571+
in decimalDecompose
572+
out dec
573+
err bool
574+
}{
575+
{name: "same", in: dec{exponent: -6}, out: dec{exponent: -6}},
576+
577+
// Ensure reflection is not used to assign the value by using different types.
578+
{name: "diff", in: decFinite{exponent: -6}, out: dec{exponent: -6}},
579+
580+
{name: "bad-form", in: dec{form: 200}, err: true},
581+
}
582+
for _, item := range list {
583+
t.Run(item.name, func(t *testing.T) {
584+
out := dec{}
585+
err := convertAssign(&out, item.in)
586+
if item.err {
587+
if err == nil {
588+
t.Fatalf("unexpected nil error")
589+
}
590+
return
591+
}
592+
if err != nil {
593+
t.Fatalf("unexpected error: %v", err)
594+
}
595+
if !reflect.DeepEqual(out, item.out) {
596+
t.Fatalf("got %#v want %#v", out, item.out)
597+
}
598+
})
599+
}
600+
}

src/database/sql/driver/types.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ func IsValue(v interface{}) bool {
180180
switch v.(type) {
181181
case []byte, bool, float64, int64, string, time.Time:
182182
return true
183+
case decimalDecompose:
184+
return true
183185
}
184186
return false
185187
}
@@ -236,7 +238,8 @@ func (defaultConverter) ConvertValue(v interface{}) (Value, error) {
236238
return v, nil
237239
}
238240

239-
if vr, ok := v.(Valuer); ok {
241+
switch vr := v.(type) {
242+
case Valuer:
240243
sv, err := callValuerValue(vr)
241244
if err != nil {
242245
return nil, err
@@ -245,6 +248,10 @@ func (defaultConverter) ConvertValue(v interface{}) (Value, error) {
245248
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
246249
}
247250
return sv, nil
251+
252+
// For now, continue to prefer the Valuer interface over the decimal decompose interface.
253+
case decimalDecompose:
254+
return vr, nil
248255
}
249256

250257
rv := reflect.ValueOf(v)
@@ -281,3 +288,10 @@ func (defaultConverter) ConvertValue(v interface{}) (Value, error) {
281288
}
282289
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
283290
}
291+
292+
type decimalDecompose interface {
293+
// Decompose returns the internal decimal state into parts.
294+
// If the provided buf has sufficient capacity, buf may be returned as the coefficient with
295+
// the value set and length set as appropriate.
296+
Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32)
297+
}

src/database/sql/driver/types_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ var valueConverterTests = []valueConverterTest{
5757
{DefaultParameterConverter, bs{1}, []byte{1}, ""},
5858
{DefaultParameterConverter, s("a"), "a", ""},
5959
{DefaultParameterConverter, is{1}, nil, "unsupported type driver.is, a slice of int"},
60+
{DefaultParameterConverter, dec{exponent: -6}, dec{exponent: -6}, ""},
6061
}
6162

6263
func TestValueConverters(t *testing.T) {
@@ -79,3 +80,16 @@ func TestValueConverters(t *testing.T) {
7980
}
8081
}
8182
}
83+
84+
type dec struct {
85+
form byte
86+
neg bool
87+
coefficient [16]byte
88+
exponent int32
89+
}
90+
91+
func (d dec) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
92+
coef := make([]byte, 16)
93+
copy(coef, d.coefficient[:])
94+
return d.form, d.neg, coef, d.exponent
95+
}

src/database/sql/sql_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -3606,7 +3606,7 @@ type nvcConn struct {
36063606
skipNamedValueCheck bool
36073607
}
36083608

3609-
type decimal struct {
3609+
type decimalInt struct {
36103610
value int
36113611
}
36123612

@@ -3630,7 +3630,7 @@ func (c *nvcConn) CheckNamedValue(nv *driver.NamedValue) error {
36303630
nv.Value = "OUT:*string"
36313631
}
36323632
return nil
3633-
case decimal, []int64:
3633+
case decimalInt, []int64:
36343634
return nil
36353635
case doNotInclude:
36363636
return driver.ErrRemoveArgument
@@ -3659,13 +3659,13 @@ func TestNamedValueChecker(t *testing.T) {
36593659
}
36603660

36613661
o1 := ""
3662-
_, 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{})
3662+
_, 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{})
36633663
if err != nil {
36643664
t.Fatal("exec insert", err)
36653665
}
36663666
var (
36673667
str1 string
3668-
dec1 decimal
3668+
dec1 decimalInt
36693669
arr1 []int64
36703670
)
36713671
err = db.QueryRowContext(ctx, "SELECT|keys|dec1,str1,array1|").Scan(&dec1, &str1, &arr1)
@@ -3675,7 +3675,7 @@ func TestNamedValueChecker(t *testing.T) {
36753675

36763676
list := []struct{ got, want interface{} }{
36773677
{o1, "from-server"},
3678-
{dec1, decimal{123}},
3678+
{dec1, decimalInt{123}},
36793679
{str1, "hello"},
36803680
{arr1, []int64{42, 128, 707}},
36813681
}
@@ -3708,7 +3708,7 @@ func TestNamedValueCheckerSkip(t *testing.T) {
37083708
t.Fatal("exec create", err)
37093709
}
37103710

3711-
_, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A", Named("A", decimal{123}))
3711+
_, err = db.ExecContext(ctx, "INSERT|keys|dec1=?A", Named("A", decimalInt{123}))
37123712
if err == nil {
37133713
t.Fatalf("expected error with bad argument, got %v", err)
37143714
}

0 commit comments

Comments
 (0)