Repository: denisenkom/go-mssqldb
Pull request #516 (2019-09-06 00:13:42)

Refactor out decimal APIs into a subpackage
Part of issue #357 

codecov[bot]: # [Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=h1) Report
> Merging [#516](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=desc) into [master](https://codecov.io/gh/denisenkom/go-mssqldb/commit/7920e8ef889885076655d694e366b621d2ce950d?src=pr&el=desc) will **decrease** coverage by `0.6%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516/graphs/tree.svg?width=650&token=MoMB2u5avp&height=150&src=pr)](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #516      +/-   ##
==========================================
- Coverage   67.95%   67.34%   -0.61%     
==========================================
  Files          21       21              
  Lines        5108     5019      -89     
==========================================
- Hits         3471     3380      -91     
- Misses       1421     1423       +2     
  Partials      216      216
```


| [Impacted Files](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [types.go](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516/diff?src=pr&el=tree#diff-dHlwZXMuZ28=) | `77.29% <100%> (-0.02%)` | :arrow_down: |
| [bulkcopy.go](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516/diff?src=pr&el=tree#diff-YnVsa2NvcHkuZ28=) | `56.3% <100%> (ø)` | :arrow_up: |
| [mssql\_go19pre.go](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516/diff?src=pr&el=tree#diff-bXNzcWxfZ28xOXByZS5nbw==) | `50% <0%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=footer). Last update [7920e8e...876b22b](https://codecov.io/gh/denisenkom/go-mssqldb/pull/516?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/bulkcopy.go b/bulkcopy.go
index 3b319af..c6dcf6a 100644
--- a/bulkcopy.go
+++ b/bulkcopy.go
@@ -10,6 +10,8 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/denisenkom/go-mssqldb/internal/decimal"
 )
 
 type Bulk struct {
@@ -481,12 +483,12 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
 
 		perc := col.ti.Prec
 		scale := col.ti.Scale
-		var dec Decimal
-		dec, err = Float64ToDecimalScale(value, scale)
+		var dec decimal.Decimal
+		dec, err = decimal.Float64ToDecimalScale(value, scale)
 		if err != nil {
 			return res, err
 		}
-		dec.prec = perc
+		dec.SetPrec(perc)
 
 		var length byte
 		switch {
diff --git a/decimal.go b/internal/decimal/decimal.go
similarity index 68%
rename from decimal.go
rename to internal/decimal/decimal.go
index 02b2ead..68f790a 100644
--- a/decimal.go
+++ b/internal/decimal/decimal.go
@@ -1,4 +1,4 @@
-package mssql
+package decimal
 
 import (
 	"encoding/binary"
@@ -7,7 +7,7 @@ import (
 	"math/big"
 )
 
-// http://msdn.microsoft.com/en-us/library/ee780893.aspx
+// Decimal represents decimal type in the Microsoft Open Specifications: http://msdn.microsoft.com/en-us/library/ee780893.aspx
 type Decimal struct {
 	integer  [4]uint32
 	positive bool
@@ -17,6 +17,37 @@ type Decimal struct {
 
 var scaletblflt64 [39]float64
 
+func init() {
+	var acc float64 = 1
+	for i := 0; i <= 38; i++ {
+		scaletblflt64[i] = acc
+		acc *= 10
+	}
+}
+
+const autoScale = 100
+
+// SetInteger sets the ind'th element in the integer array
+func (d *Decimal) SetInteger(integer uint32, ind uint8) {
+	d.integer[ind] = integer
+}
+
+// SetPositive sets the positive member
+func (d *Decimal) SetPositive(positive bool) {
+	d.positive = positive
+}
+
+// SetPrec sets the prec member
+func (d *Decimal) SetPrec(prec uint8) {
+	d.prec = prec
+}
+
+// SetScale sets the scale member
+func (d *Decimal) SetScale(scale uint8) {
+	d.scale = scale
+}
+
+// ToFloat64 converts decimal to a float64
 func (d Decimal) ToFloat64() float64 {
 	val := float64(0)
 	for i := 3; i >= 0; i-- {
@@ -32,12 +63,44 @@ func (d Decimal) ToFloat64() float64 {
 	return val
 }
 
-const autoScale = 100
+// BigInt converts decimal to a bigint
+func (d Decimal) BigInt() big.Int {
+	bytes := make([]byte, 16)
+	binary.BigEndian.PutUint32(bytes[0:4], d.integer[3])
+	binary.BigEndian.PutUint32(bytes[4:8], d.integer[2])
+	binary.BigEndian.PutUint32(bytes[8:12], d.integer[1])
+	binary.BigEndian.PutUint32(bytes[12:16], d.integer[0])
+	var x big.Int
+	x.SetBytes(bytes)
+	if !d.positive {
+		x.Neg(&x)
+	}
+	return x
+}
+
+// Bytes converts decimal to a scaled byte slice
+func (d Decimal) Bytes() []byte {
+	x := d.BigInt()
+	return ScaleBytes(x.String(), d.scale)
+}
+
+// UnscaledBytes converts decimal to a unscaled byte slice
+func (d Decimal) UnscaledBytes() []byte {
+	x := d.BigInt()
+	return x.Bytes()
+}
+
+// String converts decimal to a string
+func (d Decimal) String() string {
+	return string(d.Bytes())
+}
 
+// Float64ToDecimal converts float64 to decimal
 func Float64ToDecimal(f float64) (Decimal, error) {
 	return Float64ToDecimalScale(f, autoScale)
 }
 
+// Float64ToDecimalScale converts float64 to decimal; user can specify the scale
 func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) {
 	var dec Decimal
 	if math.IsNaN(f) {
@@ -74,39 +137,8 @@ func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) {
 	return dec, nil
 }
 
-func init() {
-	var acc float64 = 1
-	for i := 0; i <= 38; i++ {
-		scaletblflt64[i] = acc
-		acc *= 10
-	}
-}
-
-func (d Decimal) BigInt() big.Int {
-	bytes := make([]byte, 16)
-	binary.BigEndian.PutUint32(bytes[0:4], d.integer[3])
-	binary.BigEndian.PutUint32(bytes[4:8], d.integer[2])
-	binary.BigEndian.PutUint32(bytes[8:12], d.integer[1])
-	binary.BigEndian.PutUint32(bytes[12:16], d.integer[0])
-	var x big.Int
-	x.SetBytes(bytes)
-	if !d.positive {
-		x.Neg(&x)
-	}
-	return x
-}
-
-func (d Decimal) Bytes() []byte {
-	x := d.BigInt()
-	return scaleBytes(x.String(), d.scale)
-}
-
-func (d Decimal) UnscaledBytes() []byte {
-	x := d.BigInt()
-	return x.Bytes()
-}
-
-func scaleBytes(s string, scale uint8) []byte {
+// ScaleBytes converts a stringified decimal to a scaled byte slice
+func ScaleBytes(s string, scale uint8) []byte {
 	z := make([]byte, 0, len(s)+1)
 	if s[0] == '-' || s[0] == '+' {
 		z = append(z, byte(s[0]))
@@ -128,7 +160,3 @@ func scaleBytes(s string, scale uint8) []byte {
 	}
 	return z
 }
-
-func (d Decimal) String() string {
-	return string(d.Bytes())
-}
diff --git a/types.go b/types.go
index 19c20bf..b6e7fb2 100644
--- a/types.go
+++ b/types.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"github.com/denisenkom/go-mssqldb/internal/cp"
+	"github.com/denisenkom/go-mssqldb/internal/decimal"
 )
 
 // fixed-length data types
@@ -818,12 +819,12 @@ func decodeMoney(buf []byte) []byte {
 		uint64(buf[1])<<40 |
 		uint64(buf[2])<<48 |
 		uint64(buf[3])<<56)
-	return scaleBytes(strconv.FormatInt(money, 10), 4)
+	return decimal.ScaleBytes(strconv.FormatInt(money, 10), 4)
 }
 
 func decodeMoney4(buf []byte) []byte {
 	money := int32(binary.LittleEndian.Uint32(buf[0:4]))
-	return scaleBytes(strconv.FormatInt(int64(money), 10), 4)
+	return decimal.ScaleBytes(strconv.FormatInt(int64(money), 10), 4)
 }
 
 func decodeGuid(buf []byte) []byte {
@@ -835,15 +836,14 @@ func decodeGuid(buf []byte) []byte {
 func decodeDecimal(prec uint8, scale uint8, buf []byte) []byte {
 	var sign uint8
 	sign = buf[0]
-	dec := Decimal{
-		positive: sign != 0,
-		prec:     prec,
-		scale:    scale,
-	}
+	var dec decimal.Decimal
+	dec.SetPositive(sign != 0)
+	dec.SetPrec(prec)
+	dec.SetScale(scale)
 	buf = buf[1:]
 	l := len(buf) / 4
 	for i := 0; i < l; i++ {
-		dec.integer[i] = binary.LittleEndian.Uint32(buf[0:4])
+		dec.SetInteger(binary.LittleEndian.Uint32(buf[0:4]), uint8(i))
 		buf = buf[4:]
 	}
 	return dec.Bytes()


Test patch:
diff --git a/decimal_test.go b/internal/decimal/decimal_test.go
similarity index 99%
rename from decimal_test.go
rename to internal/decimal/decimal_test.go
index bf38d20..f59b06c 100644
--- a/decimal_test.go
+++ b/internal/decimal/decimal_test.go
@@ -1,4 +1,4 @@
-package mssql
+package decimal
 
 import (
 	"math"