mirror of
https://github.com/luxfi/zapdb.git
synced 2026-10-30 15:12:20 +00:00
The module has been github.com/luxfi/zapdb for a while, but the package inside it still declared itself badger, so every consumer had to write zapdb "github.com/luxfi/zapdb" // package badger and the alias existed only to paper over the mismatch. 56 root files and the 134 qualified references inside the repo now say zapdb. Backward compatible in both directions: an existing consumer with the alias keeps compiling (the alias is now merely redundant), and one without it starts compiling instead of failing on an undefined zapdb. Directory badger/ and its cmd package are untouched — that is the CLI, and its binary is named badger. Build and vet clean; suite green except TestZRestore, which wants a /tmp/zb/src benchmark fixture and fails identically without this change. Co-authored-by: Hanzo Dev <[email protected]>
216 lines
6.1 KiB
Go
216 lines
6.1 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package zapdb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
type valuePointer struct {
|
|
Fid uint32
|
|
Len uint32
|
|
Offset uint32
|
|
}
|
|
|
|
const vptrSize = unsafe.Sizeof(valuePointer{})
|
|
|
|
func (p valuePointer) Less(o valuePointer) bool {
|
|
if p.Fid != o.Fid {
|
|
return p.Fid < o.Fid
|
|
}
|
|
if p.Offset != o.Offset {
|
|
return p.Offset < o.Offset
|
|
}
|
|
return p.Len < o.Len
|
|
}
|
|
|
|
func (p valuePointer) IsZero() bool {
|
|
return p.Fid == 0 && p.Offset == 0 && p.Len == 0
|
|
}
|
|
|
|
// Encode encodes Pointer into byte buffer.
|
|
func (p valuePointer) Encode() []byte {
|
|
b := make([]byte, vptrSize)
|
|
// Copy over the content from p to b.
|
|
*(*valuePointer)(unsafe.Pointer(&b[0])) = p
|
|
return b
|
|
}
|
|
|
|
// Decode decodes the value pointer into the provided byte buffer.
|
|
func (p *valuePointer) Decode(b []byte) {
|
|
// Copy over data from b into p. Using *p=unsafe.pointer(...) leads to
|
|
// pointer alignment issues. See https://github.com/dgraph-io/badger/issues/1096
|
|
// and comment https://github.com/dgraph-io/badger/pull/1097#pullrequestreview-307361714
|
|
copy(((*[vptrSize]byte)(unsafe.Pointer(p))[:]), b[:vptrSize])
|
|
}
|
|
|
|
// header is used in value log as a header before Entry.
|
|
type header struct {
|
|
klen uint32
|
|
vlen uint32
|
|
expiresAt uint64
|
|
meta byte
|
|
userMeta byte
|
|
}
|
|
|
|
const (
|
|
// Maximum possible size of the header. The maximum size of header struct will be 18 but the
|
|
// maximum size of varint encoded header will be 22.
|
|
maxHeaderSize = 22
|
|
)
|
|
|
|
// Encode encodes the header into []byte. The provided []byte should be at least 5 bytes. The
|
|
// function will panic if out []byte isn't large enough to hold all the values.
|
|
// The encoded header looks like
|
|
// +------+----------+------------+--------------+-----------+
|
|
// | Meta | UserMeta | Key Length | Value Length | ExpiresAt |
|
|
// +------+----------+------------+--------------+-----------+
|
|
func (h header) Encode(out []byte) int {
|
|
out[0], out[1] = h.meta, h.userMeta
|
|
index := 2
|
|
index += binary.PutUvarint(out[index:], uint64(h.klen))
|
|
index += binary.PutUvarint(out[index:], uint64(h.vlen))
|
|
index += binary.PutUvarint(out[index:], h.expiresAt)
|
|
return index
|
|
}
|
|
|
|
// Decode decodes the given header from the provided byte slice.
|
|
// Returns the number of bytes read.
|
|
func (h *header) Decode(buf []byte) int {
|
|
h.meta, h.userMeta = buf[0], buf[1]
|
|
index := 2
|
|
klen, count := binary.Uvarint(buf[index:])
|
|
h.klen = uint32(klen)
|
|
index += count
|
|
vlen, count := binary.Uvarint(buf[index:])
|
|
h.vlen = uint32(vlen)
|
|
index += count
|
|
h.expiresAt, count = binary.Uvarint(buf[index:])
|
|
return index + count
|
|
}
|
|
|
|
// DecodeFrom reads the header from the hashReader.
|
|
// Returns the number of bytes read.
|
|
func (h *header) DecodeFrom(reader *hashReader) (int, error) {
|
|
var err error
|
|
h.meta, err = reader.ReadByte()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
h.userMeta, err = reader.ReadByte()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
klen, err := binary.ReadUvarint(reader)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
h.klen = uint32(klen)
|
|
vlen, err := binary.ReadUvarint(reader)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
h.vlen = uint32(vlen)
|
|
h.expiresAt, err = binary.ReadUvarint(reader)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return reader.bytesRead, nil
|
|
}
|
|
|
|
// Entry provides Key, Value, UserMeta and ExpiresAt. This struct can be used by
|
|
// the user to set data.
|
|
type Entry struct {
|
|
Key []byte
|
|
Value []byte
|
|
ExpiresAt uint64 // time.Unix
|
|
version uint64
|
|
offset uint32 // offset is an internal field.
|
|
UserMeta byte
|
|
meta byte
|
|
|
|
// Fields maintained internally.
|
|
hlen int // Length of the header.
|
|
valThreshold int64
|
|
}
|
|
|
|
func (e *Entry) isZero() bool {
|
|
return len(e.Key) == 0
|
|
}
|
|
|
|
func (e *Entry) estimateSizeAndSetThreshold(threshold int64) int64 {
|
|
if e.valThreshold == 0 {
|
|
e.valThreshold = threshold
|
|
}
|
|
k := int64(len(e.Key))
|
|
v := int64(len(e.Value))
|
|
if v < e.valThreshold {
|
|
return k + v + 2 // Meta, UserMeta
|
|
}
|
|
return k + 12 + 2 // 12 for ValuePointer, 2 for metas.
|
|
}
|
|
|
|
func (e *Entry) skipVlogAndSetThreshold(threshold int64) bool {
|
|
if e.valThreshold == 0 {
|
|
e.valThreshold = threshold
|
|
}
|
|
return int64(len(e.Value)) < e.valThreshold
|
|
}
|
|
|
|
//nolint:unused
|
|
func (e Entry) print(prefix string) {
|
|
fmt.Printf("%s Key: %s Meta: %d UserMeta: %d Offset: %d len(val)=%d",
|
|
prefix, e.Key, e.meta, e.UserMeta, e.offset, len(e.Value))
|
|
}
|
|
|
|
// NewEntry creates a new entry with key and value passed in args. This newly created entry can be
|
|
// set in a transaction by calling txn.SetEntry(). All other properties of Entry can be set by
|
|
// calling WithMeta, WithDiscard, WithTTL methods on it.
|
|
// This function uses key and value reference, hence users must
|
|
// not modify key and value until the end of transaction.
|
|
func NewEntry(key, value []byte) *Entry {
|
|
return &Entry{
|
|
Key: key,
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
// WithMeta adds meta data to Entry e. This byte is stored alongside the key
|
|
// and can be used as an aid to interpret the value or store other contextual
|
|
// bits corresponding to the key-value pair of entry.
|
|
func (e *Entry) WithMeta(meta byte) *Entry {
|
|
e.UserMeta = meta
|
|
return e
|
|
}
|
|
|
|
// WithDiscard adds a marker to Entry e. This means all the previous versions of the key (of the
|
|
// Entry) will be eligible for garbage collection.
|
|
// This method is only useful if you have set a higher limit for options.NumVersionsToKeep. The
|
|
// default setting is 1, in which case, this function doesn't add any more benefit. If however, you
|
|
// have a higher setting for NumVersionsToKeep (in Dgraph, we set it to infinity), you can use this
|
|
// method to indicate that all the older versions can be discarded and removed during compactions.
|
|
func (e *Entry) WithDiscard() *Entry {
|
|
e.meta = bitDiscardEarlierVersions
|
|
return e
|
|
}
|
|
|
|
// WithTTL adds time to live duration to Entry e. Entry stored with a TTL would automatically expire
|
|
// after the time has elapsed, and will be eligible for garbage collection.
|
|
func (e *Entry) WithTTL(dur time.Duration) *Entry {
|
|
e.ExpiresAt = uint64(time.Now().Add(dur).Unix())
|
|
return e
|
|
}
|
|
|
|
// withMergeBit sets merge bit in entry's metadata. This
|
|
// function is called by MergeOperator's Add method.
|
|
func (e *Entry) withMergeBit() *Entry {
|
|
e.meta = bitMergeEntry
|
|
return e
|
|
}
|