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  }