github.com/iotexproject/iotex-core@v1.14.1-rc1/db/counting_index.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package db 7 8 import ( 9 "fmt" 10 "sync/atomic" 11 12 "github.com/pkg/errors" 13 14 "github.com/iotexproject/iotex-core/db/batch" 15 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 16 ) 17 18 var ( 19 // CountKey is the special key to store the size of index, it satisfies bytes.Compare(CountKey, MinUint64) = -1 20 CountKey = []byte{0, 0, 0, 0, 0, 0, 0} 21 // ZeroIndex is 8-byte of 0 22 ZeroIndex = byteutil.Uint64ToBytesBigEndian(0) 23 // ErrInvalid indicates an invalid input 24 ErrInvalid = errors.New("invalid input") 25 ) 26 27 type ( 28 // CountingIndex is a bucket of <k, v> where 29 // k consists of 8-byte whose value increments (0, 1, 2 ... n) upon each insertion 30 // the special key CountKey stores the total number of items in bucket so far 31 CountingIndex interface { 32 // Size returns the total number of keys so far 33 Size() uint64 34 // Add inserts a value into the index 35 Add([]byte, bool) error 36 // Get return value of key[slot] 37 Get(uint64) ([]byte, error) 38 // Range return value of keys [start, start+count) 39 Range(uint64, uint64) ([][]byte, error) 40 // Revert removes entries from end 41 Revert(uint64) error 42 // Close makes the index not usable 43 Close() 44 // Commit commits the batch 45 Commit() error 46 // UseBatch 47 UseBatch(batch.KVStoreBatch) error 48 // Finalize 49 Finalize() error 50 } 51 52 // countingIndex is CountingIndex implementation based on KVStore 53 countingIndex struct { 54 kvStore KVStoreWithRange 55 bucket string 56 size uint64 // total number of keys 57 batch batch.KVStoreBatch 58 } 59 ) 60 61 // NewCountingIndexNX creates a new counting index if it does not exist, otherwise return existing index 62 func NewCountingIndexNX(kv KVStore, name []byte) (CountingIndex, error) { 63 if kv == nil { 64 return nil, errors.Wrap(ErrInvalid, "KVStore object is nil") 65 } 66 kvRange, ok := kv.(KVStoreWithRange) 67 if !ok { 68 return nil, errors.New("counting index can only be created from KVStoreWithRange") 69 } 70 if len(name) == 0 { 71 return nil, errors.Wrap(ErrInvalid, "bucket name is nil") 72 } 73 bucket := string(name) 74 // check if the index exist or not 75 total, err := kv.Get(bucket, CountKey) 76 if errors.Cause(err) == ErrNotExist || total == nil { 77 // put 0 as total number of keys 78 if err := kv.Put(bucket, CountKey, ZeroIndex); err != nil { 79 return nil, errors.Wrapf(err, "failed to create counting index %x", name) 80 } 81 total = ZeroIndex 82 } 83 84 return &countingIndex{ 85 kvStore: kvRange, 86 bucket: bucket, 87 size: byteutil.BytesToUint64BigEndian(total), 88 }, nil 89 } 90 91 // GetCountingIndex return an existing counting index 92 func GetCountingIndex(kv KVStore, name []byte) (CountingIndex, error) { 93 kvRange, ok := kv.(KVStoreWithRange) 94 if !ok { 95 return nil, errors.New("counting index can only be created from KVStoreWithRange") 96 } 97 bucket := string(name) 98 // check if the index exist or not 99 total, err := kv.Get(bucket, CountKey) 100 if errors.Cause(err) == ErrNotExist || total == nil { 101 return nil, errors.Wrapf(err, "counting index 0x%x doesn't exist", name) 102 } 103 return &countingIndex{ 104 kvStore: kvRange, 105 bucket: bucket, 106 size: byteutil.BytesToUint64BigEndian(total), 107 }, nil 108 } 109 110 // Size returns the total number of keys so far 111 func (c *countingIndex) Size() uint64 { 112 return atomic.LoadUint64(&c.size) 113 } 114 115 // Add inserts a value into the index 116 func (c *countingIndex) Add(value []byte, inBatch bool) error { 117 if inBatch { 118 return c.addBatch(value) 119 } 120 if c.batch != nil { 121 return errors.Wrap(ErrInvalid, "cannot call Add in batch mode, call Commit() first to exit batch mode") 122 } 123 b := batch.NewBatch() 124 size := c.Size() 125 b.Put(c.bucket, byteutil.Uint64ToBytesBigEndian(size), value, fmt.Sprintf("failed to add %d-th item", size+1)) 126 b.Put(c.bucket, CountKey, byteutil.Uint64ToBytesBigEndian(size+1), fmt.Sprintf("failed to update size = %d", size+1)) 127 b.AddFillPercent(c.bucket, 1.0) 128 if err := c.kvStore.WriteBatch(b); err != nil { 129 return err 130 } 131 atomic.AddUint64(&c.size, 1) 132 return nil 133 } 134 135 // addBatch inserts a value into the index in batch mode 136 func (c *countingIndex) addBatch(value []byte) error { 137 if c.batch == nil { 138 c.batch = batch.NewBatch() 139 } 140 size := c.Size() 141 c.batch.Put(c.bucket, byteutil.Uint64ToBytesBigEndian(size), value, fmt.Sprintf("failed to add %d-th item", size+1)) 142 atomic.AddUint64(&c.size, 1) 143 return nil 144 } 145 146 // Get return value of key[slot] 147 func (c *countingIndex) Get(slot uint64) ([]byte, error) { 148 if slot >= c.Size() { 149 return nil, errors.Wrapf(ErrNotExist, "slot: %d", slot) 150 } 151 return c.kvStore.Get(c.bucket, byteutil.Uint64ToBytesBigEndian(slot)) 152 } 153 154 // Range return value of keys [start, start+count) 155 func (c *countingIndex) Range(start, count uint64) ([][]byte, error) { 156 if start+count > c.Size() || count == 0 { 157 return nil, errors.Wrapf(ErrInvalid, "start: %d, count: %d", start, count) 158 } 159 return c.kvStore.Range(c.bucket, byteutil.Uint64ToBytesBigEndian(start), count) 160 } 161 162 // Revert removes entries from end 163 func (c *countingIndex) Revert(count uint64) error { 164 if c.batch != nil { 165 return errors.Wrap(ErrInvalid, "cannot call Revert in batch mode, call Commit() first to exit batch mode") 166 } 167 size := c.Size() 168 if count == 0 || count > size { 169 return errors.Wrapf(ErrInvalid, "count: %d", count) 170 } 171 b := batch.NewBatch() 172 start := size - count 173 for i := uint64(0); i < count; i++ { 174 b.Delete(c.bucket, byteutil.Uint64ToBytesBigEndian(start+i), fmt.Sprintf("failed to delete %d-th item", start+i)) 175 } 176 b.Put(c.bucket, CountKey, byteutil.Uint64ToBytesBigEndian(start), fmt.Sprintf("failed to update size = %d", start)) 177 b.AddFillPercent(c.bucket, 1.0) 178 if err := c.kvStore.WriteBatch(b); err != nil { 179 return err 180 } 181 atomic.StoreUint64(&c.size, start) 182 return nil 183 } 184 185 // Close makes the index not usable 186 func (c *countingIndex) Close() { 187 // frees reference to db, the db object itself will be closed/freed by its owner, not here 188 c.kvStore = nil 189 c.batch = nil 190 } 191 192 // Commit commits a batch 193 func (c *countingIndex) Commit() error { 194 if c.batch == nil { 195 return nil 196 } 197 size := c.Size() 198 c.batch.Put(c.bucket, CountKey, byteutil.Uint64ToBytesBigEndian(size), fmt.Sprintf("failed to update size = %d", size)) 199 c.batch.AddFillPercent(c.bucket, 1.0) 200 if err := c.kvStore.WriteBatch(c.batch); err != nil { 201 return err 202 } 203 c.batch = nil 204 return nil 205 } 206 207 // UseBatch sets a (usually common) batch for the counting index to use 208 func (c *countingIndex) UseBatch(b batch.KVStoreBatch) error { 209 if b == nil { 210 return ErrInvalid 211 } 212 c.batch = b 213 return nil 214 } 215 216 // Finalize updates the total size before committing the (usually common) batch 217 func (c *countingIndex) Finalize() error { 218 if c.batch == nil { 219 return ErrInvalid 220 } 221 size := c.Size() 222 c.batch.Put(c.bucket, CountKey, byteutil.Uint64ToBytesBigEndian(size), fmt.Sprintf("failed to update size = %d", size)) 223 c.batch.AddFillPercent(c.bucket, 1.0) 224 c.batch = nil 225 return nil 226 }