diff --git a/go.sum b/go.sum index a93bb7b33..ead4b48b0 100644 --- a/go.sum +++ b/go.sum @@ -298,6 +298,7 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= @@ -497,6 +498,8 @@ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9 github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= diff --git a/orm/encoding/ormkv/key_codec.go b/orm/encoding/ormkv/key_codec.go new file mode 100644 index 000000000..00e537488 --- /dev/null +++ b/orm/encoding/ormkv/key_codec.go @@ -0,0 +1,271 @@ +package ormkv + +import ( + "bytes" + "io" + + "github.com/cosmos/cosmos-sdk/orm/types/ormerrors" + + "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/cosmos/cosmos-sdk/orm/encoding/ormfield" +) + +type KeyCodec struct { + fixedSize int + variableSizers []struct { + cdc ormfield.Codec + i int + } + + prefix []byte + fieldDescriptors []protoreflect.FieldDescriptor + fieldCodecs []ormfield.Codec +} + +// NewKeyCodec returns a new KeyCodec with the provided prefix and +// codecs for the provided fields. +func NewKeyCodec(prefix []byte, fieldDescriptors []protoreflect.FieldDescriptor) (*KeyCodec, error) { + n := len(fieldDescriptors) + var fieldCodecs []ormfield.Codec + var variableSizers []struct { + cdc ormfield.Codec + i int + } + fixedSize := 0 + names := make([]protoreflect.Name, len(fieldDescriptors)) + for i := 0; i < n; i++ { + nonTerminal := i != n-1 + field := fieldDescriptors[i] + cdc, err := ormfield.GetCodec(field, nonTerminal) + if err != nil { + return nil, err + } + if x := cdc.FixedBufferSize(); x > 0 { + fixedSize += x + } else { + variableSizers = append(variableSizers, struct { + cdc ormfield.Codec + i int + }{cdc, i}) + } + fieldCodecs = append(fieldCodecs, cdc) + names[i] = field.Name() + } + + return &KeyCodec{ + fieldCodecs: fieldCodecs, + fieldDescriptors: fieldDescriptors, + prefix: prefix, + fixedSize: fixedSize, + variableSizers: variableSizers, + }, nil +} + +// Encode encodes the values assuming that they correspond to the fields +// specified for the key. If the array of values is shorter than the +// number of fields in the key, a partial "prefix" key will be encoded +// which can be used for constructing a prefix iterator. +func (cdc *KeyCodec) Encode(values []protoreflect.Value) ([]byte, error) { + sz, err := cdc.ComputeBufferSize(values) + if err != nil { + return nil, err + } + + w := bytes.NewBuffer(make([]byte, 0, sz)) + if _, err = w.Write(cdc.prefix); err != nil { + return nil, err + } + + n := len(values) + if n > len(cdc.fieldCodecs) { + return nil, ormerrors.IndexOutOfBounds.Wrapf("cannot encode %d values into %d fields", n, len(cdc.fieldCodecs)) + } + + for i := 0; i < n; i++ { + if err = cdc.fieldCodecs[i].Encode(values[i], w); err != nil { + return nil, err + } + } + return w.Bytes(), nil +} + +// GetValues extracts the values specified by the key fields from the message. +func (cdc *KeyCodec) GetValues(message protoreflect.Message) []protoreflect.Value { + res := make([]protoreflect.Value, len(cdc.fieldDescriptors)) + for i, f := range cdc.fieldDescriptors { + res[i] = message.Get(f) + } + return res +} + +// Decode decodes the values in the key specified by the reader. If the +// provided key is a prefix key, the values that could be decoded will +// be returned with io.EOF as the error. +func (cdc *KeyCodec) Decode(r *bytes.Reader) ([]protoreflect.Value, error) { + if err := skipPrefix(r, cdc.prefix); err != nil { + return nil, err + } + + n := len(cdc.fieldCodecs) + values := make([]protoreflect.Value, 0, n) + for i := 0; i < n; i++ { + value, err := cdc.fieldCodecs[i].Decode(r) + if err == io.EOF { + return values, err + } else if err != nil { + return nil, err + } + values = append(values, value) + } + return values, nil +} + +// EncodeFromMessage combines GetValues and Encode. +func (cdc *KeyCodec) EncodeFromMessage(message protoreflect.Message) ([]protoreflect.Value, []byte, error) { + values := cdc.GetValues(message) + bz, err := cdc.Encode(values) + return values, bz, err +} + +// IsFullyOrdered returns true if all fields are also ordered. +func (cdc *KeyCodec) IsFullyOrdered() bool { + for _, p := range cdc.fieldCodecs { + if !p.IsOrdered() { + return false + } + } + return true +} + +// CompareValues compares the provided values which must correspond to the +// fields in this key. Prefix keys of different lengths are supported but the +// function will panic if either array is too long. A negative value is returned +// if values1 is less than values2, 0 is returned if the two arrays are equal, +// and a positive value is returned if values2 is greater. +func (cdc *KeyCodec) CompareValues(values1, values2 []protoreflect.Value) int { + j := len(values1) + k := len(values2) + n := j + if k < j { + n = k + } + + if n > len(cdc.fieldCodecs) { + panic("array is too long") + } + + var cmp int + for i := 0; i < n; i++ { + cmp = cdc.fieldCodecs[i].Compare(values1[i], values2[i]) + // any non-equal parts determine our ordering + if cmp != 0 { + return cmp + } + } + + // values are equal but arrays of different length + if j == k { + return 0 + } else if j < k { + return -1 + } else { + return 1 + } +} + +// ComputeBufferSize computes the required buffer size for the provided values +// which can represent a full or prefix key. +func (cdc KeyCodec) ComputeBufferSize(values []protoreflect.Value) (int, error) { + size := cdc.fixedSize + n := len(values) + for _, sz := range cdc.variableSizers { + // handle prefix key encoding case where don't need all the sizers + if sz.i >= n { + return size, nil + } + + x, err := sz.cdc.ComputeBufferSize(values[sz.i]) + if err != nil { + return 0, err + } + size += x + } + return size, nil +} + +// SetValues sets the provided values on the message which must correspond +// exactly to the field descriptors for this key. Prefix keys aren't +// supported. +func (cdc *KeyCodec) SetValues(message protoreflect.Message, values []protoreflect.Value) { + for i, f := range cdc.fieldDescriptors { + message.Set(f, values[i]) + } +} + +// CheckValidRangeIterationKeys checks if the start and end key prefixes are valid +// for range iteration meaning that for each non-equal field in the prefixes +// those field types support ordered iteration. If start or end is longer than +// the other, the omitted values will function as the minimum and maximum +// values of that type respectively. +func (cdc KeyCodec) CheckValidRangeIterationKeys(start, end []protoreflect.Value) error { + lenStart := len(start) + shortest := lenStart + longest := lenStart + lenEnd := len(end) + if lenEnd < shortest { + shortest = lenEnd + } else { + longest = lenEnd + } + + if longest > len(cdc.fieldCodecs) { + return ormerrors.IndexOutOfBounds + } + + i := 0 + var cmp int + + for ; i < shortest; i++ { + fieldCdc := cdc.fieldCodecs[i] + x := start[i] + y := end[i] + + cmp = fieldCdc.Compare(x, y) + if cmp > 0 { + return ormerrors.InvalidRangeIterationKeys.Wrapf( + "start must be before end for field %s", + cdc.fieldDescriptors[i].FullName(), + ) + } else if !fieldCdc.IsOrdered() && cmp != 0 { + descriptor := cdc.fieldDescriptors[i] + return ormerrors.InvalidRangeIterationKeys.Wrapf( + "field %s of kind %s doesn't support ordered range iteration", + descriptor.FullName(), + descriptor.Kind(), + ) + } else if cmp < 0 { + break + } + } + + // the last prefix value must not be equal if the key lengths are the same + if lenStart == lenEnd { + if cmp == 0 { + return ormerrors.InvalidRangeIterationKeys + } + } else { + // check any remaining values in start or end + for j := i; j < longest; j++ { + if !cdc.fieldCodecs[j].IsOrdered() { + return ormerrors.InvalidRangeIterationKeys.Wrapf( + "field %s of kind %s doesn't support ordered range iteration", + cdc.fieldDescriptors[j].FullName(), + cdc.fieldDescriptors[j].Kind(), + ) + } + } + } + + return nil +} diff --git a/orm/encoding/ormkv/key_codec_test.go b/orm/encoding/ormkv/key_codec_test.go new file mode 100644 index 000000000..f920c97cc --- /dev/null +++ b/orm/encoding/ormkv/key_codec_test.go @@ -0,0 +1,337 @@ +package ormkv_test + +import ( + "bytes" + "io" + "testing" + + "github.com/cosmos/cosmos-sdk/orm/internal/testpb" + + "github.com/cosmos/cosmos-sdk/orm/encoding/ormkv" + + "google.golang.org/protobuf/reflect/protoreflect" + "gotest.tools/v3/assert" + "pgregory.net/rapid" + + "github.com/cosmos/cosmos-sdk/orm/internal/testutil" +) + +func TestKeyCodec(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + key := testutil.TestKeyCodecGen.Draw(t, "key").(testutil.TestKeyCodec) + for i := 0; i < 100; i++ { + keyValues := key.Draw(t, "values") + + bz1 := assertEncDecKey(t, key, keyValues) + + if key.Codec.IsFullyOrdered() { + // check if ordered keys have ordered encodings + keyValues2 := key.Draw(t, "values2") + bz2 := assertEncDecKey(t, key, keyValues2) + // bytes comparison should equal comparison of values + assert.Equal(t, key.Codec.CompareValues(keyValues, keyValues2), bytes.Compare(bz1, bz2)) + } + } + }) +} + +func assertEncDecKey(t *rapid.T, key testutil.TestKeyCodec, keyValues []protoreflect.Value) []byte { + bz, err := key.Codec.Encode(keyValues) + assert.NilError(t, err) + keyValues2, err := key.Codec.Decode(bytes.NewReader(bz)) + assert.NilError(t, err) + assert.Equal(t, 0, key.Codec.CompareValues(keyValues, keyValues2)) + return bz +} + +func TestCompareValues(t *testing.T) { + cdc, err := ormkv.NewKeyCodec(nil, []protoreflect.FieldDescriptor{ + testutil.GetTestField("u32"), + testutil.GetTestField("str"), + testutil.GetTestField("i32"), + }) + assert.NilError(t, err) + + tests := []struct { + name string + values1 []protoreflect.Value + values2 []protoreflect.Value + expect int + validRange bool + }{ + { + "eq", + ValuesOf(uint32(0), "abc", int32(-3)), + ValuesOf(uint32(0), "abc", int32(-3)), + 0, + false, + }, + { + "eq prefix 0", + ValuesOf(), + ValuesOf(), + 0, + false, + }, + { + "eq prefix 1", + ValuesOf(uint32(0)), + ValuesOf(uint32(0)), + 0, + false, + }, + { + "eq prefix 2", + ValuesOf(uint32(0), "abc"), + ValuesOf(uint32(0), "abc"), + 0, + false, + }, + { + "lt1", + ValuesOf(uint32(0), "abc", int32(-3)), + ValuesOf(uint32(1), "abc", int32(-3)), + -1, + true, + }, + { + "lt2", + ValuesOf(uint32(1), "abb", int32(-3)), + ValuesOf(uint32(1), "abc", int32(-3)), + -1, + true, + }, + { + "lt3", + ValuesOf(uint32(1), "abb", int32(-4)), + ValuesOf(uint32(1), "abb", int32(-3)), + -1, + true, + }, + { + "less prefix 0", + ValuesOf(), + ValuesOf(uint32(1), "abb", int32(-4)), + -1, + true, + }, + { + "less prefix 1", + ValuesOf(uint32(1)), + ValuesOf(uint32(1), "abb", int32(-4)), + -1, + true, + }, + { + "less prefix 2", + ValuesOf(uint32(1), "abb"), + ValuesOf(uint32(1), "abb", int32(-4)), + -1, + true, + }, + { + "gt1", + ValuesOf(uint32(2), "abb", int32(-4)), + ValuesOf(uint32(1), "abb", int32(-4)), + 1, + false, + }, + { + "gt2", + ValuesOf(uint32(2), "abc", int32(-4)), + ValuesOf(uint32(2), "abb", int32(-4)), + 1, + false, + }, + { + "gt3", + ValuesOf(uint32(2), "abc", int32(1)), + ValuesOf(uint32(2), "abc", int32(-3)), + 1, + false, + }, + { + "gt prefix 0", + ValuesOf(uint32(2), "abc", int32(-3)), + ValuesOf(), + 1, + true, + }, + { + "gt prefix 1", + ValuesOf(uint32(2), "abc", int32(-3)), + ValuesOf(uint32(2)), + 1, + true, + }, + { + "gt prefix 2", + ValuesOf(uint32(2), "abc", int32(-3)), + ValuesOf(uint32(2), "abc"), + 1, + true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal( + t, test.expect, + cdc.CompareValues(test.values1, test.values2), + ) + // CheckValidRangeIterationKeys should give comparable results + err := cdc.CheckValidRangeIterationKeys(test.values1, test.values2) + if test.validRange { + assert.NilError(t, err) + } else { + assert.ErrorContains(t, err, "") + } + }) + } +} + +func ValuesOf(values ...interface{}) []protoreflect.Value { + n := len(values) + res := make([]protoreflect.Value, n) + for i := 0; i < n; i++ { + res[i] = protoreflect.ValueOf(values[i]) + } + return res +} + +func TestDecodePrefixKey(t *testing.T) { + cdc, err := ormkv.NewKeyCodec(nil, []protoreflect.FieldDescriptor{ + testutil.GetTestField("u32"), + testutil.GetTestField("str"), + testutil.GetTestField("bz"), + testutil.GetTestField("i32"), + }) + + assert.NilError(t, err) + tests := []struct { + name string + values []protoreflect.Value + }{ + { + "1", + ValuesOf(uint32(5), "abc"), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + bz, err := cdc.Encode(test.values) + assert.NilError(t, err) + values, err := cdc.Decode(bytes.NewReader(bz)) + assert.ErrorType(t, err, io.EOF) + assert.Equal(t, 0, cdc.CompareValues(test.values, values)) + }) + } +} + +func TestValidRangeIterationKeys(t *testing.T) { + cdc, err := ormkv.NewKeyCodec(nil, []protoreflect.FieldDescriptor{ + testutil.GetTestField("u32"), + testutil.GetTestField("str"), + testutil.GetTestField("bz"), + testutil.GetTestField("i32"), + }) + assert.NilError(t, err) + + tests := []struct { + name string + values1 []protoreflect.Value + values2 []protoreflect.Value + expectErr bool + }{ + { + "1 eq", + ValuesOf(uint32(0)), + ValuesOf(uint32(0)), + true, + }, + { + "1 lt", + ValuesOf(uint32(0)), + ValuesOf(uint32(1)), + false, + }, + { + "1 gt", + ValuesOf(uint32(1)), + ValuesOf(uint32(0)), + true, + }, + { + "1,2 lt", + ValuesOf(uint32(0)), + ValuesOf(uint32(0), "abc"), + false, + }, + { + "1,2 gt", + ValuesOf(uint32(0), "abc"), + ValuesOf(uint32(0)), + false, + }, + { + "1,2,3", + ValuesOf(uint32(0)), + ValuesOf(uint32(0), "abc", []byte{1, 2}), + true, + }, + { + "1,2,3,4 lt", + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)), + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)), + false, + }, + { + "too long", + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)), + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1), int32(1)), + true, + }, + { + "1,2,3,4 eq", + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)), + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)), + true, + }, + { + "1,2,3,4 bz err", + ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)), + ValuesOf(uint32(0), "abc", []byte{1, 2, 3}, int32(1)), + true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := cdc.CheckValidRangeIterationKeys(test.values1, test.values2) + if test.expectErr { + assert.ErrorContains(t, err, "") + } else { + assert.NilError(t, err) + } + }) + } +} + +func TestGetSet(t *testing.T) { + cdc, err := ormkv.NewKeyCodec(nil, []protoreflect.FieldDescriptor{ + testutil.GetTestField("u32"), + testutil.GetTestField("str"), + testutil.GetTestField("i32"), + }) + assert.NilError(t, err) + + var a testpb.A + values := ValuesOf(uint32(4), "abc", int32(1)) + cdc.SetValues(a.ProtoReflect(), values) + values2 := cdc.GetValues(a.ProtoReflect()) + assert.Equal(t, 0, cdc.CompareValues(values, values2)) + bz, err := cdc.Encode(values) + assert.NilError(t, err) + values3, bz2, err := cdc.EncodeFromMessage(a.ProtoReflect()) + assert.NilError(t, err) + assert.Equal(t, 0, cdc.CompareValues(values, values3)) + assert.Assert(t, bytes.Equal(bz, bz2)) +} diff --git a/orm/encoding/ormkv/util.go b/orm/encoding/ormkv/util.go new file mode 100644 index 000000000..499b9f5c4 --- /dev/null +++ b/orm/encoding/ormkv/util.go @@ -0,0 +1,17 @@ +package ormkv + +import ( + "bytes" + "io" +) + +func skipPrefix(r *bytes.Reader, prefix []byte) error { + n := len(prefix) + if n > 0 { + // we skip checking the prefix for performance reasons because we assume + // that it was checked by the caller + _, err := r.Seek(int64(n), io.SeekCurrent); + return err + } + return nil +} diff --git a/orm/internal/testutil/testutil.go b/orm/internal/testutil/testutil.go index 7d5a07398..103e19b75 100644 --- a/orm/internal/testutil/testutil.go +++ b/orm/internal/testutil/testutil.go @@ -4,13 +4,13 @@ import ( "fmt" "strings" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "pgregory.net/rapid" "github.com/cosmos/cosmos-sdk/orm/encoding/ormfield" + "github.com/cosmos/cosmos-sdk/orm/encoding/ormkv" "github.com/cosmos/cosmos-sdk/orm/internal/testpb" ) @@ -114,3 +114,43 @@ func GetTestField(fname protoreflect.Name) protoreflect.FieldDescriptor { a := &testpb.A{} return a.ProtoReflect().Descriptor().Fields().ByName(fname) } + +type TestKeyCodec struct { + KeySpecs []TestFieldSpec + Codec *ormkv.KeyCodec +} + +var TestKeyCodecGen = rapid.Custom(func(t *rapid.T) TestKeyCodec { + xs := rapid.SliceOfNDistinct(rapid.IntRange(0, len(TestFieldSpecs)-1), 0, 5, func(i int) int { return i }). + Draw(t, "fieldSpecs").([]int) + + var specs []TestFieldSpec + var fields []protoreflect.FieldDescriptor + + for _, x := range xs { + spec := TestFieldSpecs[x] + specs = append(specs, spec) + fields = append(fields, GetTestField(spec.FieldName)) + } + + prefix := rapid.SliceOfN(rapid.Byte(), 0, 5).Draw(t, "prefix").([]byte) + + cdc, err := ormkv.NewKeyCodec(prefix, fields) + if err != nil { + panic(err) + } + + return TestKeyCodec{ + Codec: cdc, + KeySpecs: specs, + } +}) + +func (k TestKeyCodec) Draw(t *rapid.T, id string) []protoreflect.Value { + n := len(k.KeySpecs) + keyValues := make([]protoreflect.Value, n) + for i, k := range k.KeySpecs { + keyValues[i] = protoreflect.ValueOf(k.Gen.Draw(t, fmt.Sprintf("%s[%d]", id, i))) + } + return keyValues +} diff --git a/orm/types/ormerrors/errors.go b/orm/types/ormerrors/errors.go index c4b900012..e44ce81ff 100644 --- a/orm/types/ormerrors/errors.go +++ b/orm/types/ormerrors/errors.go @@ -5,6 +5,27 @@ import "github.com/cosmos/cosmos-sdk/types/errors" var codespace = "orm" var ( - UnsupportedKeyField = errors.New(codespace, 1, "unsupported key field") - BytesFieldTooLong = errors.New(codespace, 2, "bytes field is longer than 255 bytes") + InvalidTableId = errors.New(codespace, 1, "invalid or missing table or single id, need a non-zero value") + MissingPrimaryKey = errors.New(codespace, 2, "table is missing primary key") + InvalidKeyFieldsDefinition = errors.New(codespace, 3, "invalid field definition for key") + DuplicateKeyField = errors.New(codespace, 4, "duplicate field in key") + FieldNotFound = errors.New(codespace, 5, "field not found") + InvalidAutoIncrementKey = errors.New(codespace, 6, "an auto-increment primary key must specify a single uint64 field") + InvalidIndexId = errors.New(codespace, 7, "invalid or missing index id, need a non-zero value") + DuplicateIndexId = errors.New(codespace, 8, "duplicate index id") + PrimaryKeyConstraintViolation = errors.New(codespace, 9, "object with primary key already exists") + NotFoundOnUpdate = errors.New(codespace, 10, "can't update object which doesn't exist") + PrimaryKeyInvalidOnUpdate = errors.New(codespace, 11, "can't update object with missing or invalid primary key") + AutoIncrementKeyAlreadySet = errors.New(codespace, 12, "can't create with auto-increment primary key already set") + CantFindIndexer = errors.New(codespace, 13, "can't find indexer") + UnexpectedDecodePrefix = errors.New(codespace, 14, "unexpected prefix while trying to decode an entry") + BytesFieldTooLong = errors.New(codespace, 15, "bytes field is longer than 255 bytes") + UnsupportedOperation = errors.New(codespace, 16, "unsupported operation") + BadDecodeEntry = errors.New(codespace, 17, "bad decode entry") + IndexOutOfBounds = errors.New(codespace, 18, "index out of bounds") + InvalidListOptions = errors.New(codespace, 19, "invalid list options") + UnsupportedKeyField = errors.New(codespace, 20, "unsupported key field") + UnexpectedError = errors.New(codespace, 21, "unexpected error") + InvalidRangeIterationKeys = errors.New(codespace, 22, "invalid range iteration keys") + JSONImportError = errors.New(codespace, 23, "json import error") )