diff --git a/array.go b/array.go index 98213063..8f7bf030 100644 --- a/array.go +++ b/array.go @@ -480,7 +480,7 @@ func (a *ArrayDataSlab) Set(storage SlabStorage, address Address, index uint64, oldElem := a.elements[index] oldSize := oldElem.ByteSize() - storable, err := value.Storable(storage, address, MaxInlineArrayElementSize) + storable, err := value.Storable(storage, address, maxInlineArrayElementSize) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") @@ -503,7 +503,7 @@ func (a *ArrayDataSlab) Insert(storage SlabStorage, address Address, index uint6 return NewIndexOutOfBoundsError(index, 0, uint64(len(a.elements))) } - storable, err := value.Storable(storage, address, MaxInlineArrayElementSize) + storable, err := value.Storable(storage, address, maxInlineArrayElementSize) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") @@ -2554,7 +2554,7 @@ func NewArrayFromBatchData(storage SlabStorage, address Address, typeInfo TypeIn } - storable, err := value.Storable(storage, address, MaxInlineArrayElementSize) + storable, err := value.Storable(storage, address, maxInlineArrayElementSize) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") diff --git a/array_benchmark_test.go b/array_benchmark_test.go index ffc150b7..d141425a 100644 --- a/array_benchmark_test.go +++ b/array_benchmark_test.go @@ -90,7 +90,7 @@ func benchmarkArray(b *testing.B, initialArraySize, numberOfElements int) { // setup for i := 0; i < initialArraySize; i++ { v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() err = array.Append(v) @@ -110,7 +110,7 @@ func benchmarkArray(b *testing.B, initialArraySize, numberOfElements int) { for i := 0; i < numberOfElements; i++ { v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() @@ -148,7 +148,7 @@ func benchmarkArray(b *testing.B, initialArraySize, numberOfElements int) { ind := r.Intn(int(array.Count())) v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() @@ -221,7 +221,7 @@ func benchmarkLongTermImpactOnMemory(b *testing.B, initialArraySize, numberOfOps for i := 0; i < initialArraySize; i++ { v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() @@ -243,7 +243,7 @@ func benchmarkLongTermImpactOnMemory(b *testing.B, initialArraySize, numberOfOps case 1: // insert v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() diff --git a/array_debug.go b/array_debug.go index c799e9cb..dfbc5f48 100644 --- a/array_debug.go +++ b/array_debug.go @@ -281,9 +281,9 @@ func validArraySlab( for _, e := range dataSlab.elements { // Verify element size is <= inline size - if e.ByteSize() > uint32(MaxInlineArrayElementSize) { + if e.ByteSize() > uint32(maxInlineArrayElementSize) { return 0, nil, nil, NewFatalError(fmt.Errorf("data slab %d element %s size %d is too large, want < %d", - id, e, e.ByteSize(), MaxInlineArrayElementSize)) + id, e, e.ByteSize(), maxInlineArrayElementSize)) } computedSize += e.ByteSize() diff --git a/array_test.go b/array_test.go index 3bf7b1d4..451fc6e0 100644 --- a/array_test.go +++ b/array_test.go @@ -1092,7 +1092,7 @@ func TestArraySetRandomValues(t *testing.T) { for i := uint64(0); i < arraySize; i++ { oldValue := values[i] - newValue := randomValue(r, int(MaxInlineArrayElementSize)) + newValue := randomValue(r, int(maxInlineArrayElementSize)) values[i] = newValue existingStorable, err := array.Set(i, newValue) @@ -1126,7 +1126,7 @@ func TestArrayInsertRandomValues(t *testing.T) { values := make([]Value, arraySize) for i := uint64(0); i < arraySize; i++ { - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) values[arraySize-i-1] = v err := array.Insert(0, v) @@ -1151,7 +1151,7 @@ func TestArrayInsertRandomValues(t *testing.T) { values := make([]Value, arraySize) for i := uint64(0); i < arraySize; i++ { - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) values[i] = v err := array.Insert(i, v) @@ -1177,7 +1177,7 @@ func TestArrayInsertRandomValues(t *testing.T) { values := make([]Value, arraySize) for i := uint64(0); i < arraySize; i++ { k := r.Intn(int(i) + 1) - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) copy(values[k+1:], values[k:]) values[k] = v @@ -1209,7 +1209,7 @@ func TestArrayRemoveRandomValues(t *testing.T) { values := make([]Value, arraySize) // Insert n random values into array for i := uint64(0); i < arraySize; i++ { - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) values[i] = v err := array.Insert(i, v) @@ -1276,7 +1276,7 @@ func testArrayAppendSetInsertRemoveRandomValues( switch nextOp { case ArrayAppendOp: - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) values = append(values, v) err := array.Append(v) @@ -1284,7 +1284,7 @@ func testArrayAppendSetInsertRemoveRandomValues( case ArraySetOp: k := r.Intn(int(array.Count())) - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) oldV := values[k] @@ -1304,7 +1304,7 @@ func testArrayAppendSetInsertRemoveRandomValues( case ArrayInsertOp: k := r.Intn(int(array.Count() + 1)) - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) if k == int(array.Count()) { values = append(values, v) @@ -1876,7 +1876,7 @@ func TestArrayStringElement(t *testing.T) { r := newRand(t) - stringSize := int(MaxInlineArrayElementSize - 3) + stringSize := int(maxInlineArrayElementSize - 3) values := make([]Value, arraySize) for i := uint64(0); i < arraySize; i++ { @@ -1909,7 +1909,7 @@ func TestArrayStringElement(t *testing.T) { r := newRand(t) - stringSize := int(MaxInlineArrayElementSize + 512) + stringSize := int(maxInlineArrayElementSize + 512) values := make([]Value, arraySize) for i := uint64(0); i < arraySize; i++ { @@ -2206,7 +2206,7 @@ func TestArrayFromBatchData(t *testing.T) { var values []Value var v Value - v = NewStringValue(strings.Repeat("a", int(MaxInlineArrayElementSize-2))) + v = NewStringValue(strings.Repeat("a", int(maxInlineArrayElementSize-2))) values = append(values, v) err = array.Insert(0, v) @@ -2262,7 +2262,7 @@ func TestArrayFromBatchData(t *testing.T) { require.NoError(t, err) } - v = NewStringValue(strings.Repeat("a", int(MaxInlineArrayElementSize-2))) + v = NewStringValue(strings.Repeat("a", int(maxInlineArrayElementSize-2))) values = append(values, nil) copy(values[25+1:], values[25:]) values[25] = v @@ -2309,7 +2309,7 @@ func TestArrayFromBatchData(t *testing.T) { values := make([]Value, arraySize) for i := uint64(0); i < arraySize; i++ { - v := randomValue(r, int(MaxInlineArrayElementSize)) + v := randomValue(r, int(maxInlineArrayElementSize)) values[i] = v err := array.Append(v) @@ -2358,17 +2358,17 @@ func TestArrayFromBatchData(t *testing.T) { var values []Value var v Value - v = NewStringValue(randStr(r, int(MaxInlineArrayElementSize-2))) + v = NewStringValue(randStr(r, int(maxInlineArrayElementSize-2))) values = append(values, v) err = array.Append(v) require.NoError(t, err) - v = NewStringValue(randStr(r, int(MaxInlineArrayElementSize-2))) + v = NewStringValue(randStr(r, int(maxInlineArrayElementSize-2))) values = append(values, v) err = array.Append(v) require.NoError(t, err) - v = NewStringValue(randStr(r, int(MaxInlineArrayElementSize-2))) + v = NewStringValue(randStr(r, int(maxInlineArrayElementSize-2))) values = append(values, v) err = array.Append(v) require.NoError(t, err) @@ -2435,7 +2435,7 @@ func TestArrayMaxInlineElement(t *testing.T) { var values []Value for i := 0; i < 2; i++ { // String length is MaxInlineArrayElementSize - 3 to account for string encoding overhead. - v := NewStringValue(randStr(r, int(MaxInlineArrayElementSize-3))) + v := NewStringValue(randStr(r, int(maxInlineArrayElementSize-3))) values = append(values, v) err = array.Append(v) @@ -2558,7 +2558,7 @@ func TestArraySlabDump(t *testing.T) { array, err := NewArray(storage, address, typeInfo) require.NoError(t, err) - err = array.Append(NewStringValue(strings.Repeat("a", int(MaxInlineArrayElementSize)))) + err = array.Append(NewStringValue(strings.Repeat("a", int(maxInlineArrayElementSize)))) require.NoError(t, err) want := []string{ diff --git a/basicarray.go b/basicarray.go index fbf9a2bf..5aed95a8 100644 --- a/basicarray.go +++ b/basicarray.go @@ -316,7 +316,7 @@ func (a *BasicArray) Get(index uint64) (Value, error) { } func (a *BasicArray) Set(index uint64, v Value) error { - storable, err := v.Storable(a.storage, a.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(a.storage, a.Address(), maxInlineArrayElementSize) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") @@ -332,7 +332,7 @@ func (a *BasicArray) Append(v Value) error { } func (a *BasicArray) Insert(index uint64, v Value) error { - storable, err := v.Storable(a.storage, a.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(a.storage, a.Address(), maxInlineArrayElementSize) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") diff --git a/basicarray_benchmark_test.go b/basicarray_benchmark_test.go index ad238de9..47126467 100644 --- a/basicarray_benchmark_test.go +++ b/basicarray_benchmark_test.go @@ -79,7 +79,7 @@ func benchmarkBasicArray(b *testing.B, initialArraySize, numberOfElements int) { // setup for i := 0; i < initialArraySize; i++ { v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() err = array.Append(v) @@ -98,7 +98,7 @@ func benchmarkBasicArray(b *testing.B, initialArraySize, numberOfElements int) { start = time.Now() for i := 0; i < numberOfElements; i++ { v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() err = array.Append(v) @@ -117,7 +117,7 @@ func benchmarkBasicArray(b *testing.B, initialArraySize, numberOfElements int) { ind := r.Intn(int(array.Count())) s, err := array.Remove(uint64(ind)) require.NoError(b, err) - storable, err := s.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := s.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize -= storable.ByteSize() } @@ -133,7 +133,7 @@ func benchmarkBasicArray(b *testing.B, initialArraySize, numberOfElements int) { for i := 0; i < numberOfElements; i++ { ind := r.Intn(int(array.Count())) v := RandomValue(r) - storable, err := v.Storable(storage, array.Address(), MaxInlineArrayElementSize) + storable, err := v.Storable(storage, array.Address(), maxInlineArrayElementSize) require.NoError(b, err) totalRawDataSize += storable.ByteSize() err = array.Insert(uint64(ind), v) diff --git a/map.go b/map.go index 7bb9e3fc..8a9fae22 100644 --- a/map.go +++ b/map.go @@ -453,13 +453,13 @@ func newElementFromData(cborDec *cbor.StreamDecoder, decodeStorable StorableDeco func newSingleElement(storage SlabStorage, address Address, key Value, value Value) (*singleElement, error) { - ks, err := key.Storable(storage, address, MaxInlineMapKeyOrValueSize) + ks, err := key.Storable(storage, address, maxInlineMapKeySize) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to get key's storable") } - vs, err := value.Storable(storage, address, MaxInlineMapKeyOrValueSize) + vs, err := value.Storable(storage, address, maxInlineMapValueSize(uint64(ks.ByteSize()))) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") @@ -598,7 +598,7 @@ func (e *singleElement) Set( if equal { existingValue := e.value - valueStorable, err := value.Storable(storage, address, MaxInlineMapKeyOrValueSize) + valueStorable, err := value.Storable(storage, address, maxInlineMapValueSize(uint64(e.key.ByteSize()))) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return nil, nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") @@ -1925,7 +1925,7 @@ func (e *singleElements) Set(storage SlabStorage, address Address, b DigesterBui oldSize := elem.Size() - vs, err := value.Storable(storage, address, MaxInlineMapKeyOrValueSize) + vs, err := value.Storable(storage, address, maxInlineMapValueSize(uint64(elem.key.ByteSize()))) if err != nil { // Wrap err as external error (if needed) because err is returned by Value interface. return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to get value's storable") diff --git a/map_test.go b/map_test.go index f983835e..0bb71c21 100644 --- a/map_test.go +++ b/map_test.go @@ -826,8 +826,8 @@ func TestMapRemove(t *testing.T) { collisionKeyValues := make(map[Value]Value) for len(collisionKeyValues) < numOfElementsWithCollision { - k := NewStringValue(randStr(r, int(MaxInlineMapKeyOrValueSize)-2)) - v := NewStringValue(randStr(r, int(MaxInlineMapKeyOrValueSize)-2)) + k := NewStringValue(randStr(r, int(maxInlineMapKeySize)-2)) + v := NewStringValue(randStr(r, int(maxInlineMapKeySize)-2)) collisionKeyValues[k] = v digesterBuilder.On("Digest", k).Return(mockDigester{d: []Digest{nextDigest}}) @@ -907,8 +907,8 @@ func TestMapRemove(t *testing.T) { collisionKeyValues := make(map[Value]Value) for len(collisionKeyValues) < numOfElementsWithCollision { - k := NewStringValue(randStr(r, int(MaxInlineMapKeyOrValueSize)-2)) - v := NewStringValue(randStr(r, int(MaxInlineMapKeyOrValueSize)-2)) + k := NewStringValue(randStr(r, int(maxInlineMapKeySize)-2)) + v := NewStringValue(randStr(r, int(maxInlineMapKeySize)-2)) collisionKeyValues[k] = v digesterBuilder.On("Digest", k).Return(mockDigester{d: []Digest{0}}) @@ -3529,7 +3529,7 @@ func TestMapFromBatchData(t *testing.T) { r := newRand(t) - maxStringSize := int(MaxInlineMapKeyOrValueSize - 2) + maxStringSize := int(maxInlineMapKeySize - 2) typeInfo := testTypeInfo{42} @@ -3677,7 +3677,7 @@ func TestMapMaxInlineElement(t *testing.T) { t.Parallel() r := newRand(t) - maxStringSize := int(MaxInlineMapKeyOrValueSize - 2) + maxStringSize := int(maxInlineMapKeySize - 2) typeInfo := testTypeInfo{42} storage := newTestPersistentStorage(t) address := Address{1, 2, 3, 4, 5, 6, 7, 8} @@ -3687,7 +3687,7 @@ func TestMapMaxInlineElement(t *testing.T) { keyValues := make(map[Value]Value) for len(keyValues) < 2 { - // String length is MaxInlineMapElementSize - 2 to account for string encoding overhead. + // String length is maxInlineMapKeySize - 2 to account for string encoding overhead. k := NewStringValue(randStr(r, maxStringSize)) v := NewStringValue(randStr(r, maxStringSize)) keyValues[k] = v @@ -3889,7 +3889,7 @@ func TestMapSlabDump(t *testing.T) { require.Equal(t, want, dumps) }) - t.Run("overflow", func(t *testing.T) { + t.Run("key overflow", func(t *testing.T) { digesterBuilder := &mockDigesterBuilder{} typeInfo := testTypeInfo{42} @@ -3899,8 +3899,8 @@ func TestMapSlabDump(t *testing.T) { m, err := NewMap(storage, address, digesterBuilder, typeInfo) require.NoError(t, err) - k := NewStringValue(strings.Repeat("a", int(MaxInlineMapKeyOrValueSize))) - v := NewStringValue(strings.Repeat("b", int(MaxInlineMapKeyOrValueSize))) + k := NewStringValue(strings.Repeat("a", int(maxInlineMapKeySize))) + v := NewStringValue(strings.Repeat("b", int(maxInlineMapKeySize))) digesterBuilder.On("Digest", k).Return(mockDigester{d: []Digest{Digest(0)}}) existingStorable, err := m.Set(compare, hashInputProvider, k, v) @@ -3908,9 +3908,35 @@ func TestMapSlabDump(t *testing.T) { require.Nil(t, existingStorable) want := []string{ - "level 1, MapDataSlab id:0x102030405060708.1 size:69 firstkey:0 elements: [0:StorageIDStorable({[1 2 3 4 5 6 7 8] [0 0 0 0 0 0 0 2]}):StorageIDStorable({[1 2 3 4 5 6 7 8] [0 0 0 0 0 0 0 3]})]", + "level 1, MapDataSlab id:0x102030405060708.1 size:102 firstkey:0 elements: [0:StorageIDStorable({[1 2 3 4 5 6 7 8] [0 0 0 0 0 0 0 2]}):bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb]", "overflow: &{0x102030405060708.2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}", - "overflow: &{0x102030405060708.3 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}", + } + dumps, err := DumpMapSlabs(m) + require.NoError(t, err) + require.Equal(t, want, dumps) + }) + + t.Run("value overflow", func(t *testing.T) { + + digesterBuilder := &mockDigesterBuilder{} + typeInfo := testTypeInfo{42} + storage := newTestPersistentStorage(t) + address := Address{1, 2, 3, 4, 5, 6, 7, 8} + + m, err := NewMap(storage, address, digesterBuilder, typeInfo) + require.NoError(t, err) + + k := NewStringValue(strings.Repeat("a", int(maxInlineMapKeySize-2))) + v := NewStringValue(strings.Repeat("b", int(maxInlineMapElementSize))) + digesterBuilder.On("Digest", k).Return(mockDigester{d: []Digest{Digest(0)}}) + + existingStorable, err := m.Set(compare, hashInputProvider, k, v) + require.NoError(t, err) + require.Nil(t, existingStorable) + + want := []string{ + "level 1, MapDataSlab id:0x102030405060708.1 size:100 firstkey:0 elements: [0:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:StorageIDStorable({[1 2 3 4 5 6 7 8] [0 0 0 0 0 0 0 2]})]", + "overflow: &{0x102030405060708.2 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}", } dumps, err := DumpMapSlabs(m) require.NoError(t, err) @@ -4072,3 +4098,122 @@ func TestMaxCollisionLimitPerDigest(t *testing.T) { verifyMap(t, storage, typeInfo, address, m, keyValues, nil, false) }) } + +func TestMaxInlineMapValueSize(t *testing.T) { + + t.Run("small key", func(t *testing.T) { + // Value has larger max inline size when key is less than max map key size. + + SetThreshold(256) + defer SetThreshold(1024) + + mapSize := 2 + keyStringSize := 16 // Key size is less than max map key size. + valueStringSize := maxInlineMapElementSize/2 + 10 // Value size is more than half of max map element size. + + r := newRand(t) + + keyValues := make(map[Value]Value, mapSize) + for len(keyValues) < mapSize { + k := NewStringValue(randStr(r, keyStringSize)) + v := NewStringValue(randStr(r, int(valueStringSize))) + keyValues[k] = v + } + + typeInfo := testTypeInfo{42} + address := Address{1, 2, 3, 4, 5, 6, 7, 8} + storage := newTestPersistentStorage(t) + + m, err := NewMap(storage, address, newBasicDigesterBuilder(), typeInfo) + require.NoError(t, err) + + for k, v := range keyValues { + existingStorable, err := m.Set(compare, hashInputProvider, k, v) + require.NoError(t, err) + require.Nil(t, existingStorable) + } + + // Both key and value are stored in map slab. + require.Equal(t, 1, len(storage.deltas)) + + verifyMap(t, storage, typeInfo, address, m, keyValues, nil, false) + }) + + t.Run("max size key", func(t *testing.T) { + // Value max size is about half of max map element size when key is exactly max map key size. + + SetThreshold(256) + defer SetThreshold(1024) + + mapSize := 1 + keyStringSize := maxInlineMapKeySize - 2 // Key size is exactly max map key size (2 bytes is string encoding overhead). + valueStringSize := maxInlineMapElementSize/2 + 2 // Value size is more than half of max map element size (add 2 bytes to make it more than half). + + r := newRand(t) + + keyValues := make(map[Value]Value, mapSize) + for len(keyValues) < mapSize { + k := NewStringValue(randStr(r, int(keyStringSize))) + v := NewStringValue(randStr(r, int(valueStringSize))) + keyValues[k] = v + } + + typeInfo := testTypeInfo{42} + address := Address{1, 2, 3, 4, 5, 6, 7, 8} + storage := newTestPersistentStorage(t) + + m, err := NewMap(storage, address, newBasicDigesterBuilder(), typeInfo) + require.NoError(t, err) + + for k, v := range keyValues { + existingStorable, err := m.Set(compare, hashInputProvider, k, v) + require.NoError(t, err) + require.Nil(t, existingStorable) + } + + // Key is stored in map slab, while value is stored separately in storable slab. + require.Equal(t, 2, len(storage.deltas)) + + verifyMap(t, storage, typeInfo, address, m, keyValues, nil, false) + }) + + t.Run("large key", func(t *testing.T) { + // Value has larger max inline size when key is more than max map key size because + // when key size exceeds max map key size, it is stored in a separate storable slab, + // and StorageIDStorable is stored as key in the map, which is 19 bytes. + + SetThreshold(256) + defer SetThreshold(1024) + + mapSize := 1 + keyStringSize := maxInlineMapKeySize + 10 // key size is more than max map key size + valueStringSize := maxInlineMapElementSize/2 + 10 // value size is more than half of max map element size + + r := newRand(t) + + keyValues := make(map[Value]Value, mapSize) + for len(keyValues) < mapSize { + k := NewStringValue(randStr(r, int(keyStringSize))) + v := NewStringValue(randStr(r, int(valueStringSize))) + keyValues[k] = v + } + + typeInfo := testTypeInfo{42} + address := Address{1, 2, 3, 4, 5, 6, 7, 8} + storage := newTestPersistentStorage(t) + + m, err := NewMap(storage, address, newBasicDigesterBuilder(), typeInfo) + require.NoError(t, err) + + for k, v := range keyValues { + existingStorable, err := m.Set(compare, hashInputProvider, k, v) + require.NoError(t, err) + require.Nil(t, existingStorable) + } + + // Key is stored in separate storable slabs, while value is stored in map slab. + require.Equal(t, 2, len(storage.deltas)) + + verifyMap(t, storage, typeInfo, address, m, keyValues, nil, false) + }) +} diff --git a/settings.go b/settings.go index bc48dccb..fed9aea0 100644 --- a/settings.go +++ b/settings.go @@ -31,12 +31,12 @@ const ( ) var ( - targetThreshold uint64 - minThreshold uint64 - maxThreshold uint64 - MaxInlineArrayElementSize uint64 - maxInlineMapElementSize uint64 - MaxInlineMapKeyOrValueSize uint64 + targetThreshold uint64 + minThreshold uint64 + maxThreshold uint64 + maxInlineArrayElementSize uint64 + maxInlineMapElementSize uint64 + maxInlineMapKeySize uint64 ) func init() { @@ -54,7 +54,7 @@ func SetThreshold(threshold uint64) (uint64, uint64, uint64, uint64) { // Total slab size available for array elements, excluding slab encoding overhead availableArrayElementsSize := targetThreshold - arrayDataSlabPrefixSize - MaxInlineArrayElementSize = availableArrayElementsSize / minElementCountInSlab + maxInlineArrayElementSize = availableArrayElementsSize / minElementCountInSlab // Total slab size available for map elements, excluding slab encoding overhead availableMapElementsSize := targetThreshold - mapDataSlabPrefixSize - hkeyElementsPrefixSize @@ -65,8 +65,20 @@ func SetThreshold(threshold uint64) (uint64, uint64, uint64, uint64) { // Max inline size for a map's element maxInlineMapElementSize = availableMapElementsSize/minElementCountInSlab - mapElementOverheadSize - // Max inline size for a map's key or value, excluding element encoding overhead - MaxInlineMapKeyOrValueSize = (maxInlineMapElementSize - singleElementPrefixSize) / 2 + // Max inline size for a map's key, excluding element overhead + maxInlineMapKeySize = (maxInlineMapElementSize - singleElementPrefixSize) / 2 - return minThreshold, maxThreshold, MaxInlineArrayElementSize, MaxInlineMapKeyOrValueSize + return minThreshold, maxThreshold, maxInlineArrayElementSize, maxInlineMapKeySize +} + +func MaxInlineArrayElementSize() uint64 { + return maxInlineArrayElementSize +} + +func MaxInlineMapKeySize() uint64 { + return maxInlineMapKeySize +} + +func maxInlineMapValueSize(keySize uint64) uint64 { + return maxInlineMapElementSize - keySize - singleElementPrefixSize }