From fa7f1118a60685b3bd8b6fed56df70813a367549 Mon Sep 17 00:00:00 2001 From: wmin0 Date: Tue, 12 Feb 2019 11:09:34 +0800 Subject: core: vm: sqlvm: add schema define and implement rlp serialization Implement schema struct and handle its rlp serialization. --- core/vm/sqlvm/ast/types.go | 28 ++++---- core/vm/sqlvm/ast/types_test.go | 58 +++++++-------- core/vm/sqlvm/schema/schema.go | 139 ++++++++++++++++++++++++++++++++++++ core/vm/sqlvm/schema/schema_test.go | 127 ++++++++++++++++++++++++++++++++ 4 files changed, 310 insertions(+), 42 deletions(-) create mode 100644 core/vm/sqlvm/schema/schema.go create mode 100644 core/vm/sqlvm/schema/schema_test.go diff --git a/core/vm/sqlvm/ast/types.go b/core/vm/sqlvm/ast/types.go index 80cda796c..48e7b1fb5 100644 --- a/core/vm/sqlvm/ast/types.go +++ b/core/vm/sqlvm/ast/types.go @@ -49,11 +49,13 @@ const ( // DataTypeUnknown for unknown data type. const DataTypeUnknown DataType = 0 -func decomposeDataType(t DataType) (DataTypeMajor, DataTypeMinor) { +// DecomposeDataType to major and minor part with given data type. +func DecomposeDataType(t DataType) (DataTypeMajor, DataTypeMinor) { return DataTypeMajor(t >> 8), DataTypeMinor(t & 0xff) } -func composeDataType(major DataTypeMajor, minor DataTypeMinor) DataType { +// ComposeDataType to concrete type with major and minor part. +func ComposeDataType(major DataTypeMajor, minor DataTypeMinor) DataType { return (DataType(major) << 8) | DataType(minor) } @@ -78,10 +80,10 @@ func DataTypeEncode(n interface{}) (DataType, error) { switch t := n.(type) { case BoolTypeNode: - return composeDataType(DataTypeMajorBool, 0), nil + return ComposeDataType(DataTypeMajorBool, 0), nil case AddressTypeNode: - return composeDataType(DataTypeMajorAddress, 0), nil + return ComposeDataType(DataTypeMajorAddress, 0), nil case IntTypeNode: if t.Size%8 != 0 || t.Size > 256 { @@ -90,9 +92,9 @@ func DataTypeEncode(n interface{}) (DataType, error) { minor := DataTypeMinor((t.Size / 8) - 1) if t.Unsigned { - return composeDataType(DataTypeMajorUint, minor), nil + return ComposeDataType(DataTypeMajorUint, minor), nil } - return composeDataType(DataTypeMajorInt, minor), nil + return ComposeDataType(DataTypeMajorInt, minor), nil case FixedBytesTypeNode: if t.Size%8 != 0 || t.Size > 256 { @@ -100,10 +102,10 @@ func DataTypeEncode(n interface{}) (DataType, error) { } minor := DataTypeMinor((t.Size / 8) - 1) - return composeDataType(DataTypeMajorFixedBytes, minor), nil + return ComposeDataType(DataTypeMajorFixedBytes, minor), nil case DynamicBytesTypeNode: - return composeDataType(DataTypeMajorDynamicBytes, 0), nil + return ComposeDataType(DataTypeMajorDynamicBytes, 0), nil case FixedTypeNode: if t.Size%8 != 0 || t.Size > 256 { @@ -117,9 +119,9 @@ func DataTypeEncode(n interface{}) (DataType, error) { major := DataTypeMajor((t.Size / 8) - 1) minor := DataTypeMinor(t.FractionalDigits) if t.Unsigned { - return composeDataType(DataTypeMajorUfixed+major, minor), nil + return ComposeDataType(DataTypeMajorUfixed+major, minor), nil } - return composeDataType(DataTypeMajorFixed+major, minor), nil + return ComposeDataType(DataTypeMajorFixed+major, minor), nil } return DataTypeUnknown, ErrDataTypeEncode @@ -127,7 +129,7 @@ func DataTypeEncode(n interface{}) (DataType, error) { // DataTypeDecode decodes DataType into data type node. func DataTypeDecode(t DataType) (interface{}, error) { - major, minor := decomposeDataType(t) + major, minor := DecomposeDataType(t) switch major { // TODO(wmin0): define unsupported error for special type. case DataTypeMajorBool: @@ -229,7 +231,7 @@ func decimalDecode(signed bool, bs []byte) decimal.Decimal { // DecimalEncode encodes decimal to bytes depend on data type. func DecimalEncode(dt DataType, d decimal.Decimal) ([]byte, error) { - major, minor := decomposeDataType(dt) + major, minor := DecomposeDataType(dt) switch major { case DataTypeMajorInt, DataTypeMajorUint: @@ -253,7 +255,7 @@ func DecimalEncode(dt DataType, d decimal.Decimal) ([]byte, error) { // DecimalDecode decodes decimal from bytes. func DecimalDecode(dt DataType, b []byte) (decimal.Decimal, error) { - major, minor := decomposeDataType(dt) + major, minor := DecomposeDataType(dt) switch major { case DataTypeMajorInt: return decimalDecode(true, b), nil diff --git a/core/vm/sqlvm/ast/types_test.go b/core/vm/sqlvm/ast/types_test.go index 31ed224fb..aa726c7ba 100644 --- a/core/vm/sqlvm/ast/types_test.go +++ b/core/vm/sqlvm/ast/types_test.go @@ -41,28 +41,28 @@ func (s *TypesTestSuite) requireDecodeError(input DataType) { func (s *TypesTestSuite) TestEncodeAndDecode() { s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorBool, 0), + ComposeDataType(DataTypeMajorBool, 0), BoolTypeNode{}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorAddress, 0), + ComposeDataType(DataTypeMajorAddress, 0), AddressTypeNode{}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorInt, 1), + ComposeDataType(DataTypeMajorInt, 1), IntTypeNode{Size: 16}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorUint, 2), + ComposeDataType(DataTypeMajorUint, 2), IntTypeNode{Unsigned: true, Size: 24}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorFixedBytes, 3), + ComposeDataType(DataTypeMajorFixedBytes, 3), FixedBytesTypeNode{Size: 32}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorDynamicBytes, 0), + ComposeDataType(DataTypeMajorDynamicBytes, 0), DynamicBytesTypeNode{}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorFixed, 1), + ComposeDataType(DataTypeMajorFixed, 1), FixedTypeNode{Size: 8, FractionalDigits: 1}) s.requireEncodeAndDecodeNoError( - composeDataType(DataTypeMajorUfixed+1, 2), + ComposeDataType(DataTypeMajorUfixed+1, 2), FixedTypeNode{Unsigned: true, Size: 16, FractionalDigits: 2}) } @@ -79,15 +79,15 @@ func (s *TypesTestSuite) TestEncodeError() { 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)) + 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() { @@ -96,33 +96,33 @@ func (s *TypesTestSuite) TestEncodeAndDecodeDecimal() { neg := decimal.New(-15, 0) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorInt, 2), + ComposeDataType(DataTypeMajorInt, 2), pos, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorInt, 2), + ComposeDataType(DataTypeMajorInt, 2), zero, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorInt, 2), + ComposeDataType(DataTypeMajorInt, 2), neg, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorUint, 2), + ComposeDataType(DataTypeMajorUint, 2), pos, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorUint, 2), + ComposeDataType(DataTypeMajorUint, 2), zero, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorAddress, 0), + ComposeDataType(DataTypeMajorAddress, 0), pos, 20) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorAddress, 0), + ComposeDataType(DataTypeMajorAddress, 0), zero, 20) @@ -130,24 +130,24 @@ func (s *TypesTestSuite) TestEncodeAndDecodeDecimal() { neg = decimal.New(-15, -2) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorFixed+2, 2), + ComposeDataType(DataTypeMajorFixed+2, 2), pos, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorFixed+2, 2), + ComposeDataType(DataTypeMajorFixed+2, 2), zero, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorFixed+2, 2), + ComposeDataType(DataTypeMajorFixed+2, 2), neg, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorUfixed+2, 2), + ComposeDataType(DataTypeMajorUfixed+2, 2), pos, 3) s.requireEncodeAndDecodeDecimalNoError( - composeDataType(DataTypeMajorUfixed+2, 2), + ComposeDataType(DataTypeMajorUfixed+2, 2), zero, 3) } diff --git a/core/vm/sqlvm/schema/schema.go b/core/vm/sqlvm/schema/schema.go new file mode 100644 index 000000000..4529fd7d5 --- /dev/null +++ b/core/vm/sqlvm/schema/schema.go @@ -0,0 +1,139 @@ +package schema + +import ( + "errors" + "io" + + "github.com/shopspring/decimal" + + "github.com/dexon-foundation/dexon/core/vm/sqlvm/ast" + "github.com/dexon-foundation/dexon/rlp" +) + +// Error defines for encode and decode. +var ( + ErrEncodeUnexpectedType = errors.New("encode unexpected type") + ErrDecodeUnexpectedType = errors.New("decode unexpected type") +) + +// ColumnAttr defines bit flags for describing column attribute. +type ColumnAttr uint16 + +// ColumnAttr enums. +const ( + ColumnAttrHasDefault ColumnAttr = 1 << iota + ColumnAttrNotNull + ColumnAttrHasSequence + ColumnAttrHasForeignKey +) + +// IndexAttr defines bit flags for describing index attribute. +type IndexAttr uint16 + +// IndexAttr enums. +const ( + IndexAttrUnique IndexAttr = 1 << iota +) + +// Schema defines sqlvm schema struct. +type Schema []*Table + +// Table defiens sqlvm table struct. +type Table struct { + Name []byte + Columns []*Column + Indices []*Index +} + +// Index defines sqlvm index struct. +type Index struct { + Name []byte + Attr IndexAttr + Columns []uint8 +} + +type column struct { + Name []byte + Type ast.DataType + Attr ColumnAttr + Sequence uint8 + ForeignTable uint8 + ForeignColumn uint8 + Rest interface{} +} + +// Column defines sqlvm index struct. +type Column struct { + column + Default interface{} // decimal.Decimal, bool, []byte +} + +var _ rlp.Decoder = (*Column)(nil) +var _ rlp.Encoder = Column{} + +// EncodeRLP encodes column with rlp encode. +func (c Column) EncodeRLP(w io.Writer) error { + if c.Default != nil { + switch d := c.Default.(type) { + case bool: + v := byte(0) + if d { + v = byte(1) + } + c.Rest = []byte{v} + case []byte: + c.Rest = d + case decimal.Decimal: + var err error + c.Rest, err = ast.DecimalEncode(c.Type, d) + if err != nil { + return err + } + default: + return ErrEncodeUnexpectedType + } + } else { + c.Rest = nil + } + + return rlp.Encode(w, c.column) +} + +// DecodeRLP decodes column with rlp decode. +func (c *Column) DecodeRLP(s *rlp.Stream) error { + defer func() { c.Rest = nil }() + + err := s.Decode(&c.column) + if err != nil { + return err + } + + switch rest := c.Rest.(type) { + case []interface{}: + // nil is converted to empty list by encoder, while empty list is + // converted to []interface{} by decoder. + // So we view this case as nil and skip it. + case []byte: + major, _ := ast.DecomposeDataType(c.Type) + switch major { + case ast.DataTypeMajorBool: + if rest[0] == 1 { + c.Default = true + } else { + c.Default = false + } + case ast.DataTypeMajorFixedBytes, ast.DataTypeMajorDynamicBytes: + c.Default = rest + default: + d, err := ast.DecimalDecode(c.Type, rest) + if err != nil { + return err + } + c.Default = d + } + default: + return ErrDecodeUnexpectedType + } + + return nil +} diff --git a/core/vm/sqlvm/schema/schema_test.go b/core/vm/sqlvm/schema/schema_test.go new file mode 100644 index 000000000..92bfa6c91 --- /dev/null +++ b/core/vm/sqlvm/schema/schema_test.go @@ -0,0 +1,127 @@ +package schema + +import ( + "bufio" + "bytes" + "io/ioutil" + "testing" + + "github.com/dexon-foundation/dexon/core/vm/sqlvm/ast" + "github.com/dexon-foundation/dexon/rlp" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/suite" +) + +type SchemaTestSuite struct{ suite.Suite } + +func (s *SchemaTestSuite) requireEncodeAndDecodeColumnNoError(c Column) { + buffer := bytes.Buffer{} + w := bufio.NewWriter(&buffer) + s.Require().NoError(rlp.Encode(w, c)) + w.Flush() + + c2 := Column{} + r := ioutil.NopCloser(bufio.NewReader(&buffer)) + s.Require().NoError(rlp.Decode(r, &c2)) + s.Require().Equal(c, c2) +} + +func (s *SchemaTestSuite) TestEncodeAndDecodeColumn() { + s.requireEncodeAndDecodeColumnNoError(Column{ + column: column{ + Name: []byte("a"), + Type: ast.ComposeDataType(ast.DataTypeMajorBool, 0), + Attr: ColumnAttrHasSequence | ColumnAttrHasDefault, + Sequence: 1, + }, + Default: true, + }) + + s.requireEncodeAndDecodeColumnNoError(Column{ + column: column{ + Name: []byte("b"), + Type: ast.ComposeDataType(ast.DataTypeMajorFixedBytes, 0), + }, + }) + + s.requireEncodeAndDecodeColumnNoError(Column{ + column: column{ + Name: []byte("c"), + Type: ast.ComposeDataType(ast.DataTypeMajorDynamicBytes, 0), + Attr: ColumnAttrNotNull | ColumnAttrHasDefault, + }, + Default: []byte{}, + }) + + s.requireEncodeAndDecodeColumnNoError(Column{ + column: column{ + Name: []byte("d"), + Type: ast.ComposeDataType(ast.DataTypeMajorUint, 0), + Attr: ColumnAttrNotNull | ColumnAttrHasDefault, + }, + Default: decimal.New(1, 0), + }) +} + +func (s *SchemaTestSuite) TestEncodeAndDecodeSchema() { + schema := Schema{ + &Table{ + Name: []byte("test"), + Columns: []*Column{ + { + column: column{ + Name: []byte("a"), + Type: ast.ComposeDataType(ast.DataTypeMajorBool, 0), + Attr: ColumnAttrHasSequence | ColumnAttrHasDefault, + Sequence: 1, + }, + Default: true, + }, + }, + Indices: []*Index{ + { + Name: []byte("idx"), + Attr: IndexAttrUnique, + Columns: []uint8{0}, + }, + }, + }, + &Table{ + Name: []byte("test2"), + }, + } + buffer := bytes.Buffer{} + w := bufio.NewWriter(&buffer) + s.Require().NoError(rlp.Encode(w, schema)) + w.Flush() + + schema2 := Schema{} + r := ioutil.NopCloser(bufio.NewReader(&buffer)) + s.Require().NoError(rlp.Decode(r, &schema2)) + + s.Require().Equal(len(schema), len(schema2)) + + for i := 0; i < len(schema); i++ { + table := schema[i] + table2 := schema2[i] + s.Require().Equal(table.Name, table2.Name) + s.Require().Equal(len(table.Columns), len(table2.Columns)) + s.Require().Equal(len(table.Indices), len(table2.Indices)) + + for j := 0; j < len(table.Columns); j++ { + column := table.Columns[j] + column2 := table.Columns[j] + s.Require().Equal(*column, *column2) + } + + for j := 0; j < len(table.Indices); j++ { + index := table.Indices[j] + index2 := table2.Indices[j] + s.Require().Equal(*index, *index2) + } + } +} + +func TestSchema(t *testing.T) { + suite.Run(t, new(SchemaTestSuite)) +} -- cgit v1.2.3