From e64684dad8b773318edfabe5d7a2ca7e77f4330a Mon Sep 17 00:00:00 2001 From: wmin0 Date: Wed, 30 Jan 2019 17:54:18 +0800 Subject: core: vm: sqlvm: ast: add encoder/decoder for decimal to bytes Add encoder/decoder to convert between decimal and bytes. Also handle issues below. * Signed & Unsigned * Padding * Floating point of fixed --- core/vm/sqlvm/ast/type.go | 160 ----------------------- core/vm/sqlvm/ast/type_test.go | 84 ------------- core/vm/sqlvm/ast/types.go | 272 ++++++++++++++++++++++++++++++++++++++++ core/vm/sqlvm/ast/types_test.go | 157 +++++++++++++++++++++++ 4 files changed, 429 insertions(+), 244 deletions(-) delete mode 100644 core/vm/sqlvm/ast/type.go delete mode 100644 core/vm/sqlvm/ast/type_test.go create mode 100644 core/vm/sqlvm/ast/types.go create mode 100644 core/vm/sqlvm/ast/types_test.go (limited to 'core') diff --git a/core/vm/sqlvm/ast/type.go b/core/vm/sqlvm/ast/type.go deleted file mode 100644 index 06f0c0207..000000000 --- a/core/vm/sqlvm/ast/type.go +++ /dev/null @@ -1,160 +0,0 @@ -package ast - -import ( - "errors" - "reflect" -) - -// Error defines. -var ( - ErrDataTypeEncode = errors.New("data type encode failed") - ErrDataTypeDecode = errors.New("data type decode failed") -) - -// DataTypeMajor defines type for high byte of DataType. -type DataTypeMajor uint8 - -// DataTypeMinor defines type for low byte of DataType. -type DataTypeMinor uint8 - -// DataType defines type for data type encoded. -type DataType uint16 - -// DataTypeMajor enums. -const ( - DataTypeMajorUnknown DataTypeMajor = iota - DataTypeMajorSpecial - DataTypeMajorBool - DataTypeMajorAddress - DataTypeMajorInt - DataTypeMajorUint - DataTypeMajorFixedBytes - DataTypeMajorDynamicBytes - DataTypeMajorFixed DataTypeMajor = 0x10 - DataTypeMajorUfixed DataTypeMajor = 0x30 -) - -// DataTypeUnknown for unknown data type. -const DataTypeUnknown DataType = 0 - -func decomposeDataType(t DataType) (DataTypeMajor, DataTypeMinor) { - return DataTypeMajor(t >> 8), DataTypeMinor(t & 0xff) -} - -func composeDataType(major DataTypeMajor, minor DataTypeMinor) DataType { - return (DataType(major) << 8) | DataType(minor) -} - -// DataTypeEncode encodes data type node into DataType. -func DataTypeEncode(n interface{}) (DataType, error) { - if n == nil { - return DataTypeUnknown, ErrDataTypeEncode - } - if reflect.TypeOf(n).Kind() == reflect.Ptr { - return DataTypeEncode(reflect.ValueOf(n).Elem()) - } - - switch t := n.(type) { - case BoolTypeNode: - return composeDataType(DataTypeMajorBool, 0), nil - - case AddressTypeNode: - return composeDataType(DataTypeMajorAddress, 0), nil - - case IntTypeNode: - if t.Size%8 != 0 || t.Size > 256 { - return DataTypeUnknown, ErrDataTypeEncode - } - - minor := DataTypeMinor((t.Size / 8) - 1) - if t.Unsigned { - return composeDataType(DataTypeMajorUint, minor), nil - } - return composeDataType(DataTypeMajorInt, minor), nil - - case FixedBytesTypeNode: - if t.Size%8 != 0 || t.Size > 256 { - return DataTypeUnknown, ErrDataTypeEncode - } - - minor := DataTypeMinor((t.Size / 8) - 1) - return composeDataType(DataTypeMajorFixedBytes, minor), nil - - case DynamicBytesTypeNode: - return composeDataType(DataTypeMajorDynamicBytes, 0), nil - - case FixedTypeNode: - if t.Size%8 != 0 || t.Size > 256 { - return DataTypeUnknown, ErrDataTypeEncode - } - - if t.FractionalDigits > 80 { - return DataTypeUnknown, ErrDataTypeEncode - } - - major := DataTypeMajor((t.Size / 8) - 1) - minor := DataTypeMinor(t.FractionalDigits) - if t.Unsigned { - return composeDataType(DataTypeMajorUfixed+major, minor), nil - } - return composeDataType(DataTypeMajorFixed+major, minor), nil - } - - return DataTypeUnknown, ErrDataTypeEncode -} - -// DataTypeDecode decodes DataType into data type node. -func DataTypeDecode(t DataType) (interface{}, error) { - major, minor := decomposeDataType(t) - switch major { - // TODO(wmin0): define unsupported error for special type. - case DataTypeMajorBool: - if minor == 0 { - return BoolTypeNode{}, nil - } - case DataTypeMajorAddress: - if minor == 0 { - return AddressTypeNode{}, nil - } - case DataTypeMajorInt: - if minor <= 0x1f { - size := (uint32(minor) + 1) * 8 - return IntTypeNode{Unsigned: false, Size: size}, nil - } - case DataTypeMajorUint: - if minor <= 0x1f { - size := (uint32(minor) + 1) * 8 - return IntTypeNode{Unsigned: true, Size: size}, nil - } - case DataTypeMajorFixedBytes: - if minor <= 0x1f { - size := (uint32(minor) + 1) * 8 - return FixedBytesTypeNode{Size: size}, nil - } - case DataTypeMajorDynamicBytes: - if minor == 0 { - return DynamicBytesTypeNode{}, nil - } - } - switch { - case major >= DataTypeMajorFixed && major-DataTypeMajorFixed <= 0x1f: - if minor <= 80 { - size := (uint32(major-DataTypeMajorFixed) + 1) * 8 - return FixedTypeNode{ - Unsigned: false, - Size: size, - FractionalDigits: uint32(minor), - }, nil - } - case major >= DataTypeMajorUfixed && major-DataTypeMajorUfixed <= 0x1f: - if minor <= 80 { - size := (uint32(major-DataTypeMajorUfixed) + 1) * 8 - return FixedTypeNode{ - Unsigned: true, - Size: size, - FractionalDigits: uint32(minor), - }, nil - } - } - return nil, ErrDataTypeDecode -} diff --git a/core/vm/sqlvm/ast/type_test.go b/core/vm/sqlvm/ast/type_test.go deleted file mode 100644 index 41c5d3a20..000000000 --- a/core/vm/sqlvm/ast/type_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package ast - -import ( - "testing" - - "github.com/stretchr/testify/suite" -) - -type TypeTestSuite struct{ suite.Suite } - -func (s *TypeTestSuite) requireEncodeAndDecodeNoError( - d DataType, t interface{}) { - encode, err := DataTypeEncode(t) - s.Require().NoError(err) - s.Require().Equal(d, encode) - decode, err := DataTypeDecode(d) - s.Require().NoError(err) - s.Require().Equal(t, decode) -} - -func (s *TypeTestSuite) requireEncodeError(input interface{}) { - _, err := DataTypeEncode(input) - s.Require().Error(err) -} - -func (s *TypeTestSuite) requireDecodeError(input DataType) { - _, err := DataTypeDecode(input) - s.Require().Error(err) -} - -func (s *TypeTestSuite) TestEncodeAndDecode() { - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorBool, 0), - BoolTypeNode{}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorAddress, 0), - AddressTypeNode{}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorInt, 1), - IntTypeNode{Size: 16}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorUint, 2), - IntTypeNode{Unsigned: true, Size: 24}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorFixedBytes, 3), - FixedBytesTypeNode{Size: 32}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorDynamicBytes, 0), - DynamicBytesTypeNode{}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorFixed, 1), - FixedTypeNode{Size: 8, FractionalDigits: 1}) - s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorUfixed+1, 2), - FixedTypeNode{Unsigned: true, Size: 16, FractionalDigits: 2}) -} - -func (s *TypeTestSuite) TestEncodeError() { - s.requireEncodeError(struct{}{}) - s.requireEncodeError(IntTypeNode{Size: 1}) - s.requireEncodeError(IntTypeNode{Size: 257}) - s.requireEncodeError(FixedBytesTypeNode{Size: 1}) - s.requireEncodeError(FixedBytesTypeNode{Size: 257}) - s.requireEncodeError(FixedTypeNode{Size: 1, FractionalDigits: 0}) - s.requireEncodeError(FixedTypeNode{Size: 257, FractionalDigits: 0}) - s.requireEncodeError(FixedTypeNode{Size: 8, FractionalDigits: 81}) -} - -func (s *TypeTestSuite) TestDecodeError() { - s.requireDecodeError(DataTypeUnknown) - s.requireDecodeError(composeDataType(DataTypeMajorBool, 1)) - s.requireDecodeError(composeDataType(DataTypeMajorAddress, 1)) - s.requireDecodeError(composeDataType(DataTypeMajorInt, 0x20)) - s.requireDecodeError(composeDataType(DataTypeMajorUint, 0x20)) - s.requireDecodeError(composeDataType(DataTypeMajorFixedBytes, 0x20)) - s.requireDecodeError(composeDataType(DataTypeMajorDynamicBytes, 1)) - s.requireDecodeError(composeDataType(DataTypeMajorFixed, 81)) - s.requireDecodeError(composeDataType(DataTypeMajorUfixed, 81)) - s.requireDecodeError(composeDataType(DataTypeMajorUfixed+0x20, 80)) -} - -func TestType(t *testing.T) { - suite.Run(t, new(TypeTestSuite)) -} diff --git a/core/vm/sqlvm/ast/types.go b/core/vm/sqlvm/ast/types.go new file mode 100644 index 000000000..80cda796c --- /dev/null +++ b/core/vm/sqlvm/ast/types.go @@ -0,0 +1,272 @@ +package ast + +import ( + "errors" + "math/big" + "reflect" + + "github.com/shopspring/decimal" + + "github.com/dexon-foundation/dexon/common" +) + +var ( + bigIntOne = big.NewInt(1) + bigIntTen = big.NewInt(10) +) + +// Error defines. +var ( + ErrDataTypeEncode = errors.New("data type encode failed") + ErrDataTypeDecode = errors.New("data type decode failed") + ErrDecimalEncode = errors.New("decimal encode failed") + ErrDecimalDecode = errors.New("decimal decode failed") +) + +// DataTypeMajor defines type for high byte of DataType. +type DataTypeMajor uint8 + +// DataTypeMinor defines type for low byte of DataType. +type DataTypeMinor uint8 + +// DataType defines type for data type encoded. +type DataType uint16 + +// DataTypeMajor enums. +const ( + DataTypeMajorUnknown DataTypeMajor = iota + DataTypeMajorSpecial + DataTypeMajorBool + DataTypeMajorAddress + DataTypeMajorInt + DataTypeMajorUint + DataTypeMajorFixedBytes + DataTypeMajorDynamicBytes + DataTypeMajorFixed DataTypeMajor = 0x10 + DataTypeMajorUfixed DataTypeMajor = 0x30 +) + +// DataTypeUnknown for unknown data type. +const DataTypeUnknown DataType = 0 + +func decomposeDataType(t DataType) (DataTypeMajor, DataTypeMinor) { + return DataTypeMajor(t >> 8), DataTypeMinor(t & 0xff) +} + +func composeDataType(major DataTypeMajor, minor DataTypeMinor) DataType { + return (DataType(major) << 8) | DataType(minor) +} + +// IsFixedRange checks if major is in range of DataTypeMajorFixed. +func (d DataTypeMajor) IsFixedRange() bool { + return d >= DataTypeMajorFixed && d-DataTypeMajorFixed <= 0x1f +} + +// IsUfixedRange checks if major is in range of DataTypeMajorUfixed. +func (d DataTypeMajor) IsUfixedRange() bool { + return d >= DataTypeMajorUfixed && d-DataTypeMajorUfixed <= 0x1f +} + +// DataTypeEncode encodes data type node into DataType. +func DataTypeEncode(n interface{}) (DataType, error) { + if n == nil { + return DataTypeUnknown, ErrDataTypeEncode + } + if reflect.TypeOf(n).Kind() == reflect.Ptr { + return DataTypeEncode(reflect.ValueOf(n).Elem()) + } + + switch t := n.(type) { + case BoolTypeNode: + return composeDataType(DataTypeMajorBool, 0), nil + + case AddressTypeNode: + return composeDataType(DataTypeMajorAddress, 0), nil + + case IntTypeNode: + if t.Size%8 != 0 || t.Size > 256 { + return DataTypeUnknown, ErrDataTypeEncode + } + + minor := DataTypeMinor((t.Size / 8) - 1) + if t.Unsigned { + return composeDataType(DataTypeMajorUint, minor), nil + } + return composeDataType(DataTypeMajorInt, minor), nil + + case FixedBytesTypeNode: + if t.Size%8 != 0 || t.Size > 256 { + return DataTypeUnknown, ErrDataTypeEncode + } + + minor := DataTypeMinor((t.Size / 8) - 1) + return composeDataType(DataTypeMajorFixedBytes, minor), nil + + case DynamicBytesTypeNode: + return composeDataType(DataTypeMajorDynamicBytes, 0), nil + + case FixedTypeNode: + if t.Size%8 != 0 || t.Size > 256 { + return DataTypeUnknown, ErrDataTypeEncode + } + + if t.FractionalDigits > 80 { + return DataTypeUnknown, ErrDataTypeEncode + } + + major := DataTypeMajor((t.Size / 8) - 1) + minor := DataTypeMinor(t.FractionalDigits) + if t.Unsigned { + return composeDataType(DataTypeMajorUfixed+major, minor), nil + } + return composeDataType(DataTypeMajorFixed+major, minor), nil + } + + return DataTypeUnknown, ErrDataTypeEncode +} + +// DataTypeDecode decodes DataType into data type node. +func DataTypeDecode(t DataType) (interface{}, error) { + major, minor := decomposeDataType(t) + switch major { + // TODO(wmin0): define unsupported error for special type. + case DataTypeMajorBool: + if minor == 0 { + return BoolTypeNode{}, nil + } + case DataTypeMajorAddress: + if minor == 0 { + return AddressTypeNode{}, nil + } + case DataTypeMajorInt: + if minor <= 0x1f { + size := (uint32(minor) + 1) * 8 + return IntTypeNode{Unsigned: false, Size: size}, nil + } + case DataTypeMajorUint: + if minor <= 0x1f { + size := (uint32(minor) + 1) * 8 + return IntTypeNode{Unsigned: true, Size: size}, nil + } + case DataTypeMajorFixedBytes: + if minor <= 0x1f { + size := (uint32(minor) + 1) * 8 + return FixedBytesTypeNode{Size: size}, nil + } + case DataTypeMajorDynamicBytes: + if minor == 0 { + return DynamicBytesTypeNode{}, nil + } + } + switch { + case major.IsFixedRange(): + if minor <= 80 { + size := (uint32(major-DataTypeMajorFixed) + 1) * 8 + return FixedTypeNode{ + Unsigned: false, + Size: size, + FractionalDigits: uint32(minor), + }, nil + } + case major.IsUfixedRange(): + if minor <= 80 { + size := (uint32(major-DataTypeMajorUfixed) + 1) * 8 + return FixedTypeNode{ + Unsigned: true, + Size: size, + FractionalDigits: uint32(minor), + }, nil + } + } + return nil, ErrDataTypeDecode +} + +// Don't handle overflow here. +func decimalEncode(size int, d decimal.Decimal) []byte { + ret := make([]byte, size) + s := d.Sign() + if s == 0 { + return ret + } + + exp := new(big.Int).Exp(bigIntTen, big.NewInt(int64(d.Exponent())), nil) + b := new(big.Int).Mul(d.Coefficient(), exp) + + if s > 0 { + bs := b.Bytes() + copy(ret[size-len(bs):], bs) + return ret + } + + b.Add(b, bigIntOne) + bs := b.Bytes() + copy(ret[size-len(bs):], bs) + for idx := range ret { + ret[idx] = ^ret[idx] + } + return ret +} + +// Don't handle overflow here. +func decimalDecode(signed bool, bs []byte) decimal.Decimal { + neg := false + if signed && (bs[0]&0x80 != 0) { + neg = true + for idx := range bs { + bs[idx] = ^bs[idx] + } + } + + b := new(big.Int).SetBytes(bs) + + if neg { + b.Add(b, bigIntOne) + b.Neg(b) + } + + return decimal.NewFromBigInt(b, 0) +} + +// DecimalEncode encodes decimal to bytes depend on data type. +func DecimalEncode(dt DataType, d decimal.Decimal) ([]byte, error) { + major, minor := decomposeDataType(dt) + switch major { + case DataTypeMajorInt, + DataTypeMajorUint: + return decimalEncode(int(minor)+1, d), nil + case DataTypeMajorAddress: + return decimalEncode(common.AddressLength, d), nil + } + switch { + case major.IsFixedRange(): + return decimalEncode( + int(major-DataTypeMajorFixed)+1, + d.Shift(int32(minor))), nil + case major.IsUfixedRange(): + return decimalEncode( + int(major-DataTypeMajorUfixed)+1, + d.Shift(int32(minor))), nil + } + + return nil, ErrDecimalEncode +} + +// DecimalDecode decodes decimal from bytes. +func DecimalDecode(dt DataType, b []byte) (decimal.Decimal, error) { + major, minor := decomposeDataType(dt) + switch major { + case DataTypeMajorInt: + return decimalDecode(true, b), nil + case DataTypeMajorUint, + DataTypeMajorAddress: + return decimalDecode(false, b), nil + } + switch { + case major.IsFixedRange(): + return decimalDecode(true, b).Shift(-int32(minor)), nil + case major.IsUfixedRange(): + return decimalDecode(false, b).Shift(-int32(minor)), nil + } + + return decimal.Zero, ErrDecimalDecode +} diff --git a/core/vm/sqlvm/ast/types_test.go b/core/vm/sqlvm/ast/types_test.go new file mode 100644 index 000000000..31ed224fb --- /dev/null +++ b/core/vm/sqlvm/ast/types_test.go @@ -0,0 +1,157 @@ +package ast + +import ( + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/suite" +) + +type TypesTestSuite struct{ suite.Suite } + +func (s *TypesTestSuite) requireEncodeAndDecodeDecimalNoError( + d DataType, t decimal.Decimal, bs int) { + encode, err := DecimalEncode(d, t) + s.Require().NoError(err) + s.Require().Len(encode, bs) + decode, err := DecimalDecode(d, encode) + s.Require().NoError(err) + s.Require().Equal(t.String(), decode.String()) +} + +func (s *TypesTestSuite) requireEncodeAndDecodeNoError( + d DataType, t interface{}) { + encode, err := DataTypeEncode(t) + s.Require().NoError(err) + s.Require().Equal(d, encode) + decode, err := DataTypeDecode(d) + s.Require().NoError(err) + s.Require().Equal(t, decode) +} + +func (s *TypesTestSuite) requireEncodeError(input interface{}) { + _, err := DataTypeEncode(input) + s.Require().Error(err) +} + +func (s *TypesTestSuite) requireDecodeError(input DataType) { + _, err := DataTypeDecode(input) + s.Require().Error(err) +} + +func (s *TypesTestSuite) TestEncodeAndDecode() { + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorBool, 0), + BoolTypeNode{}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorAddress, 0), + AddressTypeNode{}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorInt, 1), + IntTypeNode{Size: 16}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorUint, 2), + IntTypeNode{Unsigned: true, Size: 24}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorFixedBytes, 3), + FixedBytesTypeNode{Size: 32}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorDynamicBytes, 0), + DynamicBytesTypeNode{}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorFixed, 1), + FixedTypeNode{Size: 8, FractionalDigits: 1}) + s.requireEncodeAndDecodeNoError( + composeDataType(DataTypeMajorUfixed+1, 2), + FixedTypeNode{Unsigned: true, Size: 16, FractionalDigits: 2}) +} + +func (s *TypesTestSuite) TestEncodeError() { + s.requireEncodeError(struct{}{}) + s.requireEncodeError(IntTypeNode{Size: 1}) + s.requireEncodeError(IntTypeNode{Size: 257}) + s.requireEncodeError(FixedBytesTypeNode{Size: 1}) + s.requireEncodeError(FixedBytesTypeNode{Size: 257}) + s.requireEncodeError(FixedTypeNode{Size: 1, FractionalDigits: 0}) + s.requireEncodeError(FixedTypeNode{Size: 257, FractionalDigits: 0}) + s.requireEncodeError(FixedTypeNode{Size: 8, FractionalDigits: 81}) +} + +func (s *TypesTestSuite) TestDecodeError() { + s.requireDecodeError(DataTypeUnknown) + s.requireDecodeError(composeDataType(DataTypeMajorBool, 1)) + s.requireDecodeError(composeDataType(DataTypeMajorAddress, 1)) + s.requireDecodeError(composeDataType(DataTypeMajorInt, 0x20)) + s.requireDecodeError(composeDataType(DataTypeMajorUint, 0x20)) + s.requireDecodeError(composeDataType(DataTypeMajorFixedBytes, 0x20)) + s.requireDecodeError(composeDataType(DataTypeMajorDynamicBytes, 1)) + s.requireDecodeError(composeDataType(DataTypeMajorFixed, 81)) + s.requireDecodeError(composeDataType(DataTypeMajorUfixed, 81)) + s.requireDecodeError(composeDataType(DataTypeMajorUfixed+0x20, 80)) +} + +func (s *TypesTestSuite) TestEncodeAndDecodeDecimal() { + pos := decimal.New(15, 0) + zero := decimal.Zero + neg := decimal.New(-15, 0) + + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorInt, 2), + pos, + 3) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorInt, 2), + zero, + 3) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorInt, 2), + neg, + 3) + + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorUint, 2), + pos, + 3) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorUint, 2), + zero, + 3) + + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorAddress, 0), + pos, + 20) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorAddress, 0), + zero, + 20) + + pos = decimal.New(15, -2) + neg = decimal.New(-15, -2) + + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorFixed+2, 2), + pos, + 3) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorFixed+2, 2), + zero, + 3) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorFixed+2, 2), + neg, + 3) + + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorUfixed+2, 2), + pos, + 3) + s.requireEncodeAndDecodeDecimalNoError( + composeDataType(DataTypeMajorUfixed+2, 2), + zero, + 3) +} + +func TestTypes(t *testing.T) { + suite.Run(t, new(TypesTestSuite)) +} -- cgit v1.2.3