github.com/scottcagno/storage@v1.8.0/pkg/lsmtree/entry.go (about)

     1  package lsmtree
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"hash/crc32"
     7  	"unsafe"
     8  )
     9  
    10  // Index is a binary entry index
    11  type Index struct {
    12  	Key    []byte
    13  	Offset int64
    14  }
    15  
    16  // String is the stringer method for a *Index
    17  func (i *Index) String() string {
    18  	return fmt.Sprintf("index.key=%q, index.offset=%d", i.Key, i.Offset)
    19  }
    20  
    21  // Tombstone is a marker for an entry that has been deleted
    22  var Tombstone = []byte{0xDE, 0xAD, 0xBE, 0xEF}
    23  
    24  func makeTombstone() []byte {
    25  	data := make([]byte, 4)
    26  	copy(data, Tombstone)
    27  	return data
    28  }
    29  
    30  // EntryHeader is mainly used for serialization
    31  type EntryHeader struct {
    32  	klen uint32
    33  	vlen uint32
    34  	crc  uint32
    35  }
    36  
    37  func (e *Entry) getEntryHeader() *EntryHeader {
    38  	return &EntryHeader{
    39  		klen: uint32(len(e.Key)),
    40  		vlen: uint32(len(e.Value)),
    41  		crc:  e.CRC,
    42  	}
    43  }
    44  
    45  // Entry represents a single entry or record
    46  type Entry struct {
    47  	Key   []byte
    48  	Value []byte
    49  	CRC   uint32
    50  }
    51  
    52  func (e *Entry) hasTombstone() bool {
    53  	return bytes.Equal(e.Value, Tombstone)
    54  }
    55  
    56  // String is the stringer method for an *Entry
    57  func (e *Entry) String() string {
    58  	return fmt.Sprintf("entry.Key=%q, entry.Value=%q, entry.CRC=%d",
    59  		e.Key, e.Value, e.CRC)
    60  }
    61  
    62  // Size returns the size in bytes for an entry
    63  func (e *Entry) Size() int64 {
    64  	ks := int(unsafe.Sizeof(e.Key)) + len(e.Key)
    65  	vs := int(unsafe.Sizeof(e.Value)) + len(e.Value)
    66  	cs := unsafe.Sizeof(e.CRC)
    67  	return int64(ks) + int64(vs) + int64(cs)
    68  }
    69  
    70  // Batch is a set of entries
    71  type Batch struct {
    72  	Entries []*Entry
    73  	size    int64
    74  }
    75  
    76  // NewBatch instantiates a new batch of entries
    77  func NewBatch() *Batch {
    78  	return &Batch{
    79  		Entries: make([]*Entry, 0),
    80  	}
    81  }
    82  
    83  // Size returns the batch size in bytes
    84  func (b *Batch) Size() int64 {
    85  	return b.size
    86  }
    87  
    88  // Write writes a new entry to the batch
    89  func (b *Batch) Write(key, value []byte) error {
    90  	return b.writeEntry(&Entry{Key: key, Value: value})
    91  }
    92  
    93  // writeEntry is the internal write implementation
    94  func (b *Batch) writeEntry(e *Entry) error {
    95  	// make checksum for entry
    96  	e.CRC = checksum(append(e.Key, e.Value...))
    97  	// check entry
    98  	err := checkEntry(e)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	// add size
   103  	b.size += e.Size()
   104  	// write entry to batch
   105  	b.Entries = append(b.Entries, e)
   106  	// check size
   107  	if b.size >= defaultFlushThreshold {
   108  		// if batch has met or exceeded flush threshold
   109  		return ErrFlushThreshold
   110  	}
   111  	return nil
   112  }
   113  
   114  // Discard just vaporizes a batch
   115  func (b *Batch) Discard() {
   116  	for i := range b.Entries {
   117  		b.Entries[i].Key = nil
   118  		b.Entries[i].Value = nil
   119  	}
   120  	b.Entries = nil
   121  	b.size = -1
   122  	b = nil
   123  }
   124  
   125  // Len [implementing sort interface]
   126  func (b *Batch) Len() int {
   127  	return len(b.Entries)
   128  }
   129  
   130  // Less [implementing sort interface]
   131  func (b *Batch) Less(i, j int) bool {
   132  	return bytes.Compare(b.Entries[i].Key, b.Entries[j].Key) == -1
   133  }
   134  
   135  // Swap [implementing sort interface]
   136  func (b *Batch) Swap(i, j int) {
   137  	b.Entries[i], b.Entries[j] = b.Entries[j], b.Entries[i]
   138  }
   139  
   140  // checksum is the checksum calculator used with an entry
   141  func checksum(data []byte) uint32 {
   142  	return crc32.Checksum(data, crc32.MakeTable(crc32.Koopman))
   143  }
   144  
   145  // checkEntry ensures the entry does not violate the max key and value config
   146  func checkEntry(e *Entry) error {
   147  	// init err
   148  	var err error
   149  	// key checks
   150  	err = checkKey(e)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	// value checks
   155  	err = checkValue(e)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	return nil
   160  }
   161  
   162  // checkKey checks the entry key size is okay
   163  func checkKey(e *Entry) error {
   164  	if e.Key == nil || len(e.Key) < minKeySizeAllowed {
   165  		return ErrBadKey
   166  	}
   167  	if int64(len(e.Key)) > maxKeySizeAllowed {
   168  		return ErrKeyTooLarge
   169  	}
   170  	return nil
   171  }
   172  
   173  // checkValue checks the entry value size is okay
   174  func checkValue(e *Entry) error {
   175  	if e.Value == nil || len(e.Value) < minValueSizeAllowed {
   176  		return ErrBadValue
   177  	}
   178  	if int64(len(e.Value)) > maxValueSizeAllowed {
   179  		return ErrValueTooLarge
   180  	}
   181  	return nil
   182  }
   183  
   184  // checkCRC verifies the crc32 checksum is correct
   185  func checkCRC(e *Entry, crc uint32) error {
   186  	if e.CRC != crc {
   187  		return ErrBadChecksum
   188  	}
   189  	return nil
   190  }