diff --git a/bitcask.go b/bitcask.go index cae9b02..cb2152a 100644 --- a/bitcask.go +++ b/bitcask.go @@ -16,13 +16,29 @@ import ( ) var ( - ErrKeyNotFound = errors.New("error: key not found") - ErrKeyTooLarge = errors.New("error: key too large") - ErrValueTooLarge = errors.New("error: value too large") + // ErrKeyNotFound is the error returned when a key is not found + ErrKeyNotFound = errors.New("error: key not found") + + // ErrKeyTooLarge is the error returned for a key that exceeds the + // maximum allowed key size (configured with WithMaxKeySize). + ErrKeyTooLarge = errors.New("error: key too large") + + // ErrValueTooLarge is the error returned for a value that exceeds the + // maximum allowed value size (configured with WithMaxValueSize). + ErrValueTooLarge = errors.New("error: value too large") + + // ErrChecksumFailed is the error returned if a key/valie retrieved does + // not match its CRC checksum ErrChecksumFailed = errors.New("error: checksum failed") + + // ErrDatabaseLocked is the error returned if the database is locked + // (typically opened by another process) ErrDatabaseLocked = errors.New("error: database locked") ) +// Bitcask is a struct that represents a on-disk LSM and WAL data structure +// and in-memory hash of key/value pairs as per the Bitcask paper and seen +// in the Riak database. type Bitcask struct { *flock.Flock @@ -32,10 +48,11 @@ type Bitcask struct { keydir *internal.Keydir datafiles []*internal.Datafile trie *trie.Trie - - maxDatafileSize int64 } +// Close closes the database and removes the lock. It is important to call +// Close() as this is the only wat to cleanup the lock held by the open +// database. func (b *Bitcask) Close() error { defer func() { b.Flock.Unlock() @@ -48,10 +65,13 @@ func (b *Bitcask) Close() error { return b.curr.Close() } +// Sync flushes all buffers to disk ensuring all data is written func (b *Bitcask) Sync() error { return b.curr.Sync() } +// Get retrieves the value of the given key. If the key is not found or an/I/O +// error occurs a null byte slice is returend along with the error. func (b *Bitcask) Get(key string) ([]byte, error) { var df *internal.Datafile @@ -79,11 +99,13 @@ func (b *Bitcask) Get(key string) ([]byte, error) { return e.Value, nil } +// Has returns true if the key exists in the database, false otherwise. func (b *Bitcask) Has(key string) bool { _, ok := b.keydir.Get(key) return ok } +// Put stores the key and value in the database. func (b *Bitcask) Put(key string, value []byte) error { if len(key) > b.config.maxKeySize { return ErrKeyTooLarge @@ -103,6 +125,8 @@ func (b *Bitcask) Put(key string, value []byte) error { return nil } +// Delete deletes the named key. If the key doesn't exist or an I/O error +// occurs the error is returned. func (b *Bitcask) Delete(key string) error { _, err := b.put(key, []byte{}) if err != nil { @@ -115,6 +139,9 @@ func (b *Bitcask) Delete(key string) error { return nil } +// Scan performa a prefix scan of keys matching the given prefix and calling +// the function `f` with the keys found. If the function returns an error +// no further keys are processed and the first error returned. func (b *Bitcask) Scan(prefix string, f func(key string) error) error { keys := b.trie.PrefixSearch(prefix) for _, key := range keys { @@ -125,14 +152,19 @@ func (b *Bitcask) Scan(prefix string, f func(key string) error) error { return nil } +// Len returns the total number of keys in the database func (b *Bitcask) Len() int { return b.keydir.Len() } +// Keys returns all keys in the database as a channel of string(s) func (b *Bitcask) Keys() chan string { return b.keydir.Keys() } +// Fold iterates over all keys in the database calling the function `f` for +// each key. If the function returns an error, no further keys are processed +// and the error returned. func (b *Bitcask) Fold(f func(key string) error) error { for key := range b.keydir.Keys() { if err := f(key); err != nil { @@ -148,7 +180,7 @@ func (b *Bitcask) put(key string, value []byte) (int64, error) { return -1, err } - if size >= b.maxDatafileSize { + if size >= int64(b.config.maxDatafileSize) { err := b.curr.Close() if err != nil { return -1, err @@ -173,11 +205,9 @@ func (b *Bitcask) put(key string, value []byte) (int64, error) { return b.curr.Write(e) } -func (b *Bitcask) setMaxDatafileSize(size int64) error { - b.maxDatafileSize = size - return nil -} - +// Merge merges all datafiles in the database creating hint files for faster +// startup. Old keys are squashed and deleted keys removes. Call this function +// periodically to reclaim disk space. func Merge(path string, force bool) error { fns, err := internal.GetDatafiles(path) if err != nil { @@ -284,6 +314,9 @@ func Merge(path string, force bool) error { return nil } +// Open opens the database at the given path with optional options. +// Options can be provided with the `WithXXX` functions that provide +// configuration options as functions. func Open(path string, options ...Option) (*Bitcask, error) { if err := os.MkdirAll(path, 0755); err != nil { return nil, err @@ -373,8 +406,6 @@ func Open(path string, options ...Option) (*Bitcask, error) { keydir: keydir, datafiles: datafiles, trie: trie, - - maxDatafileSize: DefaultMaxDatafileSize, } for _, opt := range options {