From bf103451425a0f7a986ff6dd1e2b7d1e478bd948 Mon Sep 17 00:00:00 2001 From: Jhih-Ming Huang Date: Mon, 22 Apr 2019 17:56:20 +0800 Subject: core: vm: sqlvm: runtime: implement fillAutoInc and fillDefault This commit implements fillAutoInc and fillDefault, and modifies Storage.IncSequence such that it can handle the numbers larger than maximum of uint64. --- core/vm/sqlvm/common/storage.go | 20 +- core/vm/sqlvm/common/storage_test.go | 18 +- core/vm/sqlvm/runtime/instructions.go | 62 ++++++ core/vm/sqlvm/runtime/instructions_test.go | 293 ++++++++++++++++++++++++++++- core/vm/sqlvm/schema/schema.go | 5 +- 5 files changed, 372 insertions(+), 26 deletions(-) diff --git a/core/vm/sqlvm/common/storage.go b/core/vm/sqlvm/common/storage.go index f56e7984d..7babc04dd 100644 --- a/core/vm/sqlvm/common/storage.go +++ b/core/vm/sqlvm/common/storage.go @@ -5,6 +5,7 @@ import ( "golang.org/x/crypto/sha3" + "github.com/dexon-foundation/decimal" "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/core/vm" "github.com/dexon-foundation/dexon/core/vm/sqlvm/schema" @@ -114,8 +115,8 @@ func (s *Storage) GetPrimaryPathHash(tableRef schema.TableRef) (h common.Hash) { return s.hashPathKey(key) } -// getSequencePathHash return the hash address of a sequence. -func (s *Storage) getSequencePathHash( +// GetSequencePathHash return the hash address of a sequence. +func (s *Storage) GetSequencePathHash( tableRef schema.TableRef, seqIdx uint8, ) common.Hash { // PathKey(["tables", "{table_name}", "sequence", uint8(sequence_idx)]) @@ -225,11 +226,14 @@ func (s *Storage) IncSequence( tableRef schema.TableRef, seqIdx uint8, inc uint64, -) uint64 { - seqPath := s.getSequencePathHash(tableRef, seqIdx) +) decimal.Decimal { + seqPath := s.GetSequencePathHash(tableRef, seqIdx) slot := s.GetState(contract, seqPath) - val := bytesToUint64(slot.Bytes()) - // TODO(yenlin): Check overflow? - s.SetState(contract, seqPath, common.BytesToHash(uint64ToBytes(val+inc))) - return val + b := new(big.Int).SetBytes(slot.Bytes()) + b.Add(b, new(big.Int).SetUint64(inc)) + newHash := make([]byte, common.HashLength) + bs := b.Bytes() + copy(newHash[common.HashLength-len(bs):], bs) + s.SetState(contract, seqPath, common.BytesToHash(newHash)) + return decimal.NewFromBigInt(b, 0) } diff --git a/core/vm/sqlvm/common/storage_test.go b/core/vm/sqlvm/common/storage_test.go index b02705c11..2bc1354e1 100644 --- a/core/vm/sqlvm/common/storage_test.go +++ b/core/vm/sqlvm/common/storage_test.go @@ -192,17 +192,17 @@ func (s *StorageTestSuite) TestSequence() { table2 := schema.TableRef(1) contract := common.BytesToAddress([]byte("A")) - s.Require().Equal(uint64(0), s.storage.IncSequence(contract, table1, 0, 2)) - s.Require().Equal(uint64(2), s.storage.IncSequence(contract, table1, 0, 1)) - s.Require().Equal(uint64(3), s.storage.IncSequence(contract, table1, 0, 1)) + s.Require().Equal(uint64(2), s.storage.IncSequence(contract, table1, 0, 2).Rescale(0).Coefficient().Uint64()) + s.Require().Equal(uint64(3), s.storage.IncSequence(contract, table1, 0, 1).Rescale(0).Coefficient().Uint64()) + s.Require().Equal(uint64(4), s.storage.IncSequence(contract, table1, 0, 1).Rescale(0).Coefficient().Uint64()) // Repeat on another sequence. - s.Require().Equal(uint64(0), s.storage.IncSequence(contract, table1, 1, 1)) - s.Require().Equal(uint64(1), s.storage.IncSequence(contract, table1, 1, 2)) - s.Require().Equal(uint64(3), s.storage.IncSequence(contract, table1, 1, 3)) + s.Require().Equal(uint64(1), s.storage.IncSequence(contract, table1, 1, 1).Rescale(0).Coefficient().Uint64()) + s.Require().Equal(uint64(3), s.storage.IncSequence(contract, table1, 1, 2).Rescale(0).Coefficient().Uint64()) + s.Require().Equal(uint64(6), s.storage.IncSequence(contract, table1, 1, 3).Rescale(0).Coefficient().Uint64()) // Repeat on another table. - s.Require().Equal(uint64(0), s.storage.IncSequence(contract, table2, 0, 3)) - s.Require().Equal(uint64(3), s.storage.IncSequence(contract, table2, 0, 4)) - s.Require().Equal(uint64(7), s.storage.IncSequence(contract, table2, 0, 5)) + s.Require().Equal(uint64(3), s.storage.IncSequence(contract, table2, 0, 3).Rescale(0).Coefficient().Uint64()) + s.Require().Equal(uint64(7), s.storage.IncSequence(contract, table2, 0, 4).Rescale(0).Coefficient().Uint64()) + s.Require().Equal(uint64(12), s.storage.IncSequence(contract, table2, 0, 5).Rescale(0).Coefficient().Uint64()) } func (s *StorageTestSuite) TestPKHeaderEncodeDecode() { diff --git a/core/vm/sqlvm/runtime/instructions.go b/core/vm/sqlvm/runtime/instructions.go index 35d3135b2..033ad3080 100644 --- a/core/vm/sqlvm/runtime/instructions.go +++ b/core/vm/sqlvm/runtime/instructions.go @@ -1921,3 +1921,65 @@ func opRepeatPK(ctx *common.Context, input []*Operand, registers []*Operand, out registers[output], err = uint64ToOperands(IDs) return } + +// fillAutoInc returns the operand reference with incremented value. +func fillAutoInc( + ctx *common.Context, + col schema.Column, + tableRef schema.TableRef, +) (*Operand, error) { + dVal := ctx.Storage.IncSequence(ctx.Contract.Address(), + tableRef, uint8(col.Sequence), 1) + _, max, ok := col.Type.GetMinMax() + if !ok { + return nil, se.ErrorCodeInvalidDataType + } + if dVal.Cmp(max) > 0 { + return nil, se.ErrorCodeOverflow + } + op := &Operand{ + Meta: []ast.DataType{col.Type}, + Data: []Tuple{ + { + &Raw{ + Value: dVal, + Bytes: nil, + }, + }, + }, + } + return op, nil +} + +// fillDefault returns the operand reference with default value. +func fillDefault( + ctx *common.Context, + col schema.Column, +) (*Operand, error) { + var r Raw + major, _ := ast.DecomposeDataType(col.Type) + switch major { + case ast.DataTypeMajorDynamicBytes, ast.DataTypeMajorAddress: + r = Raw{ + Bytes: col.Default.([]byte), + } + case ast.DataTypeMajorBool: + b := col.Default.(bool) + if b { + r = Raw{Value: dec.True} + } else { + r = Raw{Value: dec.False} + } + default: + r = Raw{Value: col.Default.(decimal.Decimal)} + } + op := &Operand{ + Meta: []ast.DataType{col.Type}, + Data: []Tuple{ + { + &r, + }, + }, + } + return op, nil +} diff --git a/core/vm/sqlvm/runtime/instructions_test.go b/core/vm/sqlvm/runtime/instructions_test.go index a00ac12dd..55dbab18d 100644 --- a/core/vm/sqlvm/runtime/instructions_test.go +++ b/core/vm/sqlvm/runtime/instructions_test.go @@ -55,7 +55,7 @@ func createSchema(storage *common.Storage, raws []*raw) { storage.Schema[1].Columns[i] = schema.NewColumn( []byte{byte(i)}, ast.ComposeDataType(raws[i].major, raws[i].minor), - 0, nil, 0, + 0, nil, 0, nil, ) } storage.Schema.SetupColumnOffset() @@ -481,12 +481,6 @@ func (s opRepeatPKSuite) TestRepeatPK() { } } -func TestInstructions(t *testing.T) { - suite.Run(t, new(opLoadSuite)) - suite.Run(t, new(opRepeatPKSuite)) - suite.Run(t, new(instructionSuite)) -} - func makeOperand(im bool, meta []ast.DataType, pTuple []Tuple) (op *Operand) { op = &Operand{IsImmediate: im, Meta: meta, Data: pTuple} return @@ -541,3 +535,288 @@ func (s *instructionSuite) run(testcases []opTestcase, opfunc OpFunction) { ) } } + +type autoIncSuite struct { + suite.Suite + ctx *common.Context +} + +func (s *autoIncSuite) SetupTest() { + s.ctx = &common.Context{} + s.ctx.Storage = newStorage() + address := dexCommon.HexToAddress("0x6655") + s.ctx.Storage.CreateAccount(address) + s.ctx.Contract = vm.NewContract(vm.AccountRef(address), + vm.AccountRef(address), new(big.Int), 0) + s.ctx.Storage.Schema = schema.Schema{ + schema.Table{ + Name: []byte("normal_case"), + Columns: []schema.Column{ + schema.NewColumn( + []byte("c1"), + ast.ComposeDataType(ast.DataTypeMajorInt, 0), + schema.ColumnAttrHasSequence, + nil, + 0, + nil, + ), + schema.NewColumn( + []byte("c2"), + ast.ComposeDataType(ast.DataTypeMajorUint, 0), + schema.ColumnAttrHasSequence, + nil, + 1, + nil, + ), + }, + }, + schema.Table{ + Name: []byte("overflow_int_case"), + Columns: []schema.Column{ + schema.NewColumn( + []byte("c1"), + ast.ComposeDataType(ast.DataTypeMajorInt, 0), + schema.ColumnAttrHasSequence, + nil, + 0, + nil, + ), + }, + }, + schema.Table{ + Name: []byte("overflow_uint_case"), + Columns: []schema.Column{ + schema.NewColumn( + []byte("c1"), + ast.ComposeDataType(ast.DataTypeMajorUint, 0), + schema.ColumnAttrHasSequence, + nil, + 0, + nil, + ), + }, + }, + } + s.SetOverflow(1, 0, ast.ComposeDataType(ast.DataTypeMajorInt, 0)) + s.SetOverflow(2, 0, ast.ComposeDataType(ast.DataTypeMajorUint, 0)) + s.ctx.Storage.Schema.SetupColumnOffset() +} + +func (s *autoIncSuite) SetOverflow(tableRef schema.TableRef, seqIdx uint8, dt ast.DataType) { + storage := s.ctx.Storage + seqPath := storage.GetSequencePathHash(tableRef, seqIdx) + newHash := make([]byte, dexCommon.HashLength) + _, max, _ := dt.GetMinMax() + bs, _ := ast.DecimalEncode(dt, max) + copy(newHash[len(newHash)-len(bs):], bs) + storage.SetState(s.ctx.Contract.Address(), seqPath, dexCommon.BytesToHash(newHash)) +} + +func (s *autoIncSuite) TestFillAutoInc() { + type testcase struct { + name string + col schema.Column + tableRef schema.TableRef + result *Operand + err error + } + tt := []testcase{ + { + name: "normal case 1", + col: s.ctx.Storage.Schema[0].Columns[0], + tableRef: schema.TableRef(0), + result: &Operand{ + Meta: []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorInt, 0)}, + Data: []Tuple{ + { + &Raw{ + Value: decimal.New(1, 0), + }, + }, + }, + }, + err: nil, + }, + { + name: "normal case 2", + col: s.ctx.Storage.Schema[0].Columns[1], + tableRef: schema.TableRef(0), + result: &Operand{ + Meta: []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorUint, 0)}, + Data: []Tuple{ + { + &Raw{ + Value: decimal.New(1, 0), + }, + }, + }, + }, + err: nil, + }, + { + name: "int overflow", + col: s.ctx.Storage.Schema[1].Columns[0], + tableRef: schema.TableRef(1), + result: nil, + err: errors.ErrorCodeOverflow, + }, + { + name: "unt overflow", + col: s.ctx.Storage.Schema[2].Columns[0], + tableRef: schema.TableRef(2), + result: nil, + err: errors.ErrorCodeOverflow, + }, + } + + for _, t := range tt { + r, err := fillAutoInc(s.ctx, t.col, t.tableRef) + s.Require().Equalf(t.err, err, "testcase %v\n", t.name) + if t.err == nil { + s.Require().Truef(r.Equal(t.result), + "testcase: %v", t.name) + } + } +} + +type setDefaultSuite struct { + suite.Suite + ctx *common.Context +} + +func (s *setDefaultSuite) SetupTest() { + s.ctx = &common.Context{} + s.ctx.Storage = newStorage() + address := dexCommon.HexToAddress("0x6655") + s.ctx.Storage.CreateAccount(address) + s.ctx.Contract = vm.NewContract(vm.AccountRef(address), + vm.AccountRef(address), new(big.Int), 0) + s.ctx.Storage.Schema = schema.Schema{ + schema.Table{ + Name: []byte("table1"), + Columns: []schema.Column{ + schema.NewColumn( + []byte("c1"), + ast.ComposeDataType(ast.DataTypeMajorInt, 0), + schema.ColumnAttrHasDefault, + nil, + 0, + decimal.New(127, 0), + ), + schema.NewColumn( + []byte("c2"), + ast.ComposeDataType(ast.DataTypeMajorDynamicBytes, 0), + schema.ColumnAttrHasDefault, + nil, + 0, + []byte{1, 2, 3, 4}, + ), + schema.NewColumn( + []byte("c3"), + ast.ComposeDataType(ast.DataTypeMajorUint, 0), + schema.ColumnAttrHasDefault, + nil, + 1, + decimal.New(255, 0), + ), + schema.NewColumn( + []byte("c4"), + ast.ComposeDataType(ast.DataTypeMajorAddress, 0), + schema.ColumnAttrHasDefault, + nil, + 1, + address[:], + ), + }, + }, + } + s.ctx.Storage.Schema.SetupColumnOffset() +} + +func (s *setDefaultSuite) TestFillDefault() { + type testcase struct { + name string + col schema.Column + result *Operand + err error + } + tt := []testcase{ + { + name: "int8", + col: s.ctx.Storage.Schema[0].Columns[0], + result: &Operand{ + Meta: []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorInt, 0)}, + Data: []Tuple{ + { + &Raw{ + Value: decimal.New(127, 0), + }, + }, + }, + }, + err: nil, + }, + { + name: "dynamic byes", + col: s.ctx.Storage.Schema[0].Columns[1], + result: &Operand{ + Meta: []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorDynamicBytes, 0)}, + Data: []Tuple{ + { + &Raw{ + Bytes: []byte{1, 2, 3, 4}, + }, + }, + }, + }, + err: nil, + }, + { + name: "uint8", + col: s.ctx.Storage.Schema[0].Columns[2], + result: &Operand{ + Meta: []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorUint, 0)}, + Data: []Tuple{ + { + &Raw{ + Value: decimal.New(255, 0), + }, + }, + }, + }, + err: nil, + }, + { + name: "address", + col: s.ctx.Storage.Schema[0].Columns[3], + result: &Operand{ + Meta: []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorAddress, 0)}, + Data: []Tuple{ + { + &Raw{ + Bytes: dexCommon.HexToAddress("0x6655").Bytes(), + }, + }, + }, + }, + err: nil, + }, + } + + for _, t := range tt { + r, err := fillDefault(s.ctx, t.col) + s.Require().Equalf(t.err, err, "testcase %v\n", t.name) + if t.err == nil { + s.Require().Truef(r.Equal(t.result), + "testcase: %v", t.name) + } + } +} + +func TestInstructions(t *testing.T) { + suite.Run(t, new(opLoadSuite)) + suite.Run(t, new(opRepeatPKSuite)) + suite.Run(t, new(instructionSuite)) + suite.Run(t, new(autoIncSuite)) + suite.Run(t, new(setDefaultSuite)) +} diff --git a/core/vm/sqlvm/schema/schema.go b/core/vm/sqlvm/schema/schema.go index 1e87d88cf..71122cc26 100644 --- a/core/vm/sqlvm/schema/schema.go +++ b/core/vm/sqlvm/schema/schema.go @@ -180,7 +180,7 @@ type Column struct { // NewColumn return a Column instance. func NewColumn(Name []byte, Type ast.DataType, Attr ColumnAttr, - ForeignKeys []ColumnDescriptor, Sequence SequenceRef) Column { + ForeignKeys []ColumnDescriptor, Sequence SequenceRef, def interface{}) Column { c := column{ Name: Name, Type: Type, @@ -190,7 +190,8 @@ func NewColumn(Name []byte, Type ast.DataType, Attr ColumnAttr, } return Column{ - column: c, + column: c, + Default: def, } } -- cgit v1.2.3