From dd4e5be47b34cb5a00a883928e2ab9be9db2b19b Mon Sep 17 00:00:00 2001
From: wmin0 <wmin0@cobinhood.com>
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