فهرست منبع

Implemented two's complement encoding and got inspired by the encoding specs from some other CQL drivers

Ben Hood 12 سال پیش
والد
کامیت
4493f46cc3
2فایلهای تغییر یافته به همراه110 افزوده شده و 18 حذف شده
  1. 64 10
      marshal.go
  2. 46 8
      marshal_test.go

+ 64 - 10
marshal.go

@@ -6,6 +6,7 @@ package gocql
 
 import (
 	"bytes"
+	"encoding/binary"
 	"fmt"
 	"math"
 	"math/big"
@@ -14,6 +15,10 @@ import (
 	"time"
 )
 
+var (
+	bigOne = big.NewInt(1)
+)
+
 // Marshaler is the interface implemented by objects that can marshal
 // themselves into values understood by Cassandra.
 type Marshaler interface {
@@ -691,15 +696,10 @@ func marshalDecimal(info *TypeInfo, value interface{}) ([]byte, error) {
 			return nil, nil
 		}
 
-		unscaled := v.UnscaledBig().Bytes()
-		if len(unscaled) == 0 {
-			unscaled = []byte{0}
-		}
-		scale := v.Scale()
-		buf := make([]byte, 4+len(unscaled))
-		copy(buf[0:4], encInt(int32(scale)))
-		copy(buf[4:], unscaled)
-		return buf, nil
+		b := new(bytes.Buffer)
+		binary.Write(b, binary.BigEndian, v.Scale())
+		encBigInt2C(b, v.UnscaledBig())
+		return b.Bytes(), nil
 	}
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
@@ -711,7 +711,7 @@ func unmarshalDecimal(info *TypeInfo, data []byte, value interface{}) error {
 	case **inf.Dec:
 		if len(data) > 4 {
 			scale := decInt(data[0:4])
-			unscaled := new(big.Int).SetBytes(data[4:])
+			unscaled := decBigInt2C(data[4:])
 			*v = inf.NewDecBig(unscaled, inf.Scale(scale))
 		}
 		return nil
@@ -719,6 +719,60 @@ func unmarshalDecimal(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
+func decBigInt2C(bytes []byte) *big.Int {
+	ret := new(big.Int)
+	if len(bytes) > 0 && bytes[0]&0x80 == 0x80 {
+		// This is a negative number.
+		notBytes := make([]byte, len(bytes))
+		for i := range notBytes {
+			notBytes[i] = ^bytes[i]
+		}
+		ret.SetBytes(notBytes)
+		ret.Add(ret, bigOne)
+		ret.Neg(ret)
+		return ret
+	}
+	ret.SetBytes(bytes)
+	return ret
+}
+
+func encBigInt2C(out *bytes.Buffer, n *big.Int) (err error) {
+	if n.Sign() < 0 {
+		// A negative number has to be converted to two's-complement
+		// form. So we'll subtract 1 and invert. If the
+		// most-significant-bit isn't set then we'll need to pad the
+		// beginning with 0xff in order to keep the number negative.
+		nMinus1 := new(big.Int).Neg(n)
+		nMinus1.Sub(nMinus1, bigOne)
+		bytes := nMinus1.Bytes()
+		for i := range bytes {
+			bytes[i] ^= 0xff
+		}
+		if len(bytes) == 0 || bytes[0]&0x80 == 0 {
+			err = out.WriteByte(0xff)
+			if err != nil {
+				return
+			}
+		}
+		_, err = out.Write(bytes)
+	} else if n.Sign() == 0 {
+		// Zero is written as a single 0 zero rather than no bytes.
+		err = out.WriteByte(0x00)
+	} else {
+		bytes := n.Bytes()
+		if len(bytes) > 0 && bytes[0]&0x80 != 0 {
+			// We'll have to pad this with 0x00 in order to stop it
+			// looking like a negative number.
+			err = out.WriteByte(0)
+			if err != nil {
+				return
+			}
+		}
+		_, err = out.Write(bytes)
+	}
+	return
+}
+
 func marshalTimestamp(info *TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:

+ 46 - 8
marshal_test.go

@@ -10,8 +10,6 @@ import (
 	"time"
 )
 
-var quarter, _ = inf.NewDec(0, 0).SetString("0.25")
-
 var marshalTests = []struct {
 	Info  *TypeInfo
 	Data  []byte
@@ -120,11 +118,6 @@ var marshalTests = []struct {
 		[]byte("\x40\x09\x21\xfb\x53\xc8\xd4\xf1"),
 		float64(3.14159265),
 	},
-	{
-		&TypeInfo{Type: TypeDecimal},
-		[]byte("\x00\x01\x86\xa0\xcb\xd7\x12\xbb\x6d"),
-		inf.NewDec(875486690157, 100000),
-	},
 	{
 		&TypeInfo{Type: TypeDecimal},
 		[]byte("\x00\x00\x00\x00\x00"),
@@ -138,7 +131,47 @@ var marshalTests = []struct {
 	{
 		&TypeInfo{Type: TypeDecimal},
 		[]byte("\x00\x00\x00\x02\x19"),
-		quarter,
+		decimalize("0.25"),
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x13\xD5\a;\x20\x14\xA2\x91"),
+		decimalize("-0.0012095473475870063"), // From the iconara/cql-rb test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x13*\xF8\xC4\xDF\xEB]o"),
+		decimalize("0.0012095473475870063"), // From the iconara/cql-rb test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x12\xF2\xD8\x02\xB6R\x7F\x99\xEE\x98#\x99\xA9V"),
+		decimalize("-1042342234234.123423435647768234"), // From the iconara/cql-rb test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\r\nJ\x04\"^\x91\x04\x8a\xb1\x18\xfe"),
+		decimalize("1243878957943.1234124191998"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x06\xe5\xde]\x98Y"),
+		decimalize("-112233.441191"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x14\x00\xfa\xce"),
+		decimalize("0.00000000000000064206"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x14\xff\x052"),
+		decimalize("-0.00000000000000064206"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\xff\xff\xff\x9c\x00\xfa\xce"),
+		inf.NewDec(64206, -100), // From the datastax/python-driver test suite
 	},
 	{
 		&TypeInfo{Type: TypeTimestamp},
@@ -209,6 +242,11 @@ var marshalTests = []struct {
 	},
 }
 
+func decimalize(s string) *inf.Dec {
+	i, _ := new(inf.Dec).SetString(s)
+	return i
+}
+
 func TestMarshal(t *testing.T) {
 	for i, test := range marshalTests {
 		data, err := Marshal(test.Info, test.Value)