table.go
table.go - Overview
This file defines the Table
struct and related methods for managing SSTable (Sorted String Table) files in BadgerDB. It includes functionalities for opening, reading, and accessing data within these tables, along with support for encryption, compression, and checksum verification.
Detailed Documentation
Options
type Options struct {
ReadOnly bool
MetricsEnabled bool
TableSize uint64
tableCapacity uint64
ChkMode options.ChecksumVerificationMode
BloomFalsePositive float64
BlockSize int
DataKey *pb.DataKey
Compression options.CompressionType
BlockCache *ristretto.Cache[[]byte, *Block]
IndexCache *ristretto.Cache[uint64, *fb.TableIndex]
AllocPool *z.AllocatorPool
ZSTDCompressionLevel int
}
- Purpose: Configuration options for opening and building tables.
ReadOnly
: Opens tables in read-only mode.MetricsEnabled
: Enables metrics for the table.TableSize
: Maximum size of the table.tableCapacity
: 0.9x TableSize.ChkMode
: Checksum verification mode.BloomFalsePositive
: False positive probability of bloom filter.BlockSize
: Size of each block inside SSTable in bytes.DataKey
: Key used to decrypt the encrypted text.Compression
: Compression algorithm used for block compression.BlockCache
: Cache for decompressed and decrypted blocks.IndexCache
: Cache for table indexes.AllocPool
: Allocator pool.ZSTDCompressionLevel
: ZSTD compression level.
TableInterface
type TableInterface interface {
Smallest() []byte
Biggest() []byte
DoesNotHave(hash uint32) bool
MaxVersion() uint64
}
- Purpose: Interface for testing
Table
struct.Smallest()
: Returns the smallest key in the table.Biggest()
: Returns the biggest key in the table.DoesNotHave(hash uint32)
: Checks if the table does not have the key hash.MaxVersion()
: Returns the maximum version across all keys stored in this table.
Table
type Table struct {
sync.Mutex
*z.MmapFile
tableSize int
_index *fb.TableIndex
_cheap *cheapIndex
ref atomic.Int32
smallest, biggest []byte
id uint64
Checksum []byte
CreatedAt time.Time
indexStart int
indexLen int
hasBloomFilter bool
IsInmemory bool
opt *Options
}
- Purpose: Represents a loaded table file.
MmapFile
: Mapped file.tableSize
: Size of the table._index
: Table index._cheap
: Cheap index for quick access.ref
: Reference count for garbage collection.smallest
: Smallest key in the table.biggest
: Biggest key in the table.id
: File ID.Checksum
: Checksum of the table.CreatedAt
: Time the table was created.indexStart
: Start position of the index in the file.indexLen
: Length of the index.hasBloomFilter
: Indicates if the table has a bloom filter.IsInmemory
: Indicates if the table is in memory.opt
: Options for the table.
cheapIndex
type cheapIndex struct {
MaxVersion uint64
KeyCount uint32
UncompressedSize uint32
OnDiskSize uint32
BloomFilterLength int
OffsetsLength int
}
- Purpose: Provides quick access to index metadata.
MaxVersion
: Maximum version across all keys.KeyCount
: Total number of keys.UncompressedSize
: Uncompressed size of data.OnDiskSize
: On-disk size of key-values.BloomFilterLength
: Length of the bloom filter.OffsetsLength
: Number of offsets.
(t *Table) cheapIndex() *cheapIndex
func (t *Table) cheapIndex() *cheapIndex {
return t._cheap
}
- Purpose: Returns the cheap index of the table.
- Returns: A pointer to the
cheapIndex
struct.
(t *Table) offsetsLength() int
func (t *Table) offsetsLength() int { return t.cheapIndex().OffsetsLength }
- Purpose: Returns the length of the offsets in the cheap index.
- Returns: The
OffsetsLength
from thecheapIndex
.
(t *Table) MaxVersion() uint64
func (t *Table) MaxVersion() uint64 { return t.cheapIndex().MaxVersion }
- Purpose: Returns the maximum version of keys in the table using the cheap index.
- Returns: The
MaxVersion
from thecheapIndex
.
(t *Table) BloomFilterSize() int
func (t *Table) BloomFilterSize() int { return t.cheapIndex().BloomFilterLength }
- Purpose: Returns the size of the bloom filter using the cheap index.
- Returns: The
BloomFilterLength
from thecheapIndex
.
(t *Table) UncompressedSize() uint32
func (t *Table) UncompressedSize() uint32 { return t.cheapIndex().UncompressedSize }
- Purpose: Returns the uncompressed size of the data using the cheap index.
- Returns: The
UncompressedSize
from thecheapIndex
.
(t *Table) KeyCount() uint32
func (t *Table) KeyCount() uint32 { return t.cheapIndex().KeyCount }
- Purpose: Returns the number of keys in the table using the cheap index.
- Returns: The
KeyCount
from thecheapIndex
.
(t *Table) OnDiskSize() uint32
func (t *Table) OnDiskSize() uint32 { return t.cheapIndex().OnDiskSize }
- Purpose: Returns the on-disk size of the key-values using the cheap index.
- Returns: The
OnDiskSize
from thecheapIndex
.
(t *Table) CompressionType() options.CompressionType
func (t *Table) CompressionType() options.CompressionType {
return t.opt.Compression
}
- Purpose: Returns the compression type used for the table.
- Returns: The
Compression
type from the table's options.
(t *Table) IncrRef()
func (t *Table) IncrRef() {
t.ref.Add(1)
}
- Purpose: Increments the reference count of the table.
(t *Table) DecrRef() error
func (t *Table) DecrRef() error {
newRef := t.ref.Add(-1)
if newRef == 0 {
for i := 0; i < t.offsetsLength(); i++ {
t.opt.BlockCache.Del(t.blockCacheKey(i))
}
if err := t.Delete(); err != nil {
return err
}
}
return nil
}
- Purpose: Decrements the reference count of the table and deletes it if the count reaches zero.
- Returns: An error if deletion fails, nil otherwise.
BlockEvictHandler(b *Block)
func BlockEvictHandler(b *Block) {
b.decrRef()
}
- Purpose: Handler function called when a block is evicted from the cache. Decrements the reference count of the block.
- Parameters:
b
: The block being evicted.
Block
type Block struct {
offset int
data []byte
checksum []byte
entriesIndexStart int
entryOffsets []uint32
chkLen int
freeMe bool
ref atomic.Int32
}
- Purpose: Represents a block of data within a table.
offset
: Offset of the block in the file.data
: Data within the block.checksum
: Checksum of the block.entriesIndexStart
: Start index of entryOffsets list.entryOffsets
: Offsets of entries within the block.chkLen
: Checksum length.freeMe
: Indicates if the data needs to be freed.ref
: Reference count.
(b *Block) incrRef() bool
func (b *Block) incrRef() bool {
for {
ref := b.ref.Load()
if ref == 0 {
return false
}
if b.ref.CompareAndSwap(ref, ref+1) {
return true
}
}
}
- Purpose: Atomically increments the reference count of the block.
- Returns:
true
if the increment was successful,false
otherwise.
(b *Block) decrRef()
func (b *Block) decrRef() {
if b == nil {
return
}
if b.ref.Add(-1) == 0 {
if b.freeMe {
z.Free(b.data)
}
NumBlocks.Add(-1)
}
y.AssertTrue(b.ref.Load() >= 0)
}
- Purpose: Decrements the reference count of the block and frees the data if the count reaches zero and the
freeMe
flag is set.
(b *Block) size() int64
func (b *Block) size() int64 {
return int64(3*intSize /* Size of the offset, entriesIndexStart and chkLen */ +
cap(b.data) + cap(b.checksum) + cap(b.entryOffsets)*4)
}
- Purpose: Calculates the size of the block in bytes.
- Returns: The calculated size as an
int64
.
(b *Block) verifyCheckSum() error
func (b *Block) verifyCheckSum() error {
cs := &pb.Checksum{}
if err := proto.Unmarshal(b.checksum, cs); err != nil {
return y.Wrapf(err, "unable to unmarshal checksum for block")
}
return y.VerifyChecksum(b.data, cs)
}
- Purpose: Verifies the checksum of the block's data.
- Returns: An error if the checksum verification fails, nil otherwise.
CreateTable(fname string, builder *Builder) (*Table, error)
func CreateTable(fname string, builder *Builder) (*Table, error) {
bd := builder.Done()
mf, err := z.OpenMmapFile(fname, os.O_CREATE|os.O_RDWR|os.O_EXCL, bd.Size)
if err == z.NewFile {
} else if err != nil {
return nil, y.Wrapf(err, "while creating table: %s", fname)
} else {
return nil, fmt.Errorf("file already exists: %s", fname)
}
written := bd.Copy(mf.Data)
y.AssertTrue(written == len(mf.Data))
if err := z.Msync(mf.Data); err != nil {
return nil, y.Wrapf(err, "while calling msync on %s", fname)
}
return OpenTable(mf, *builder.opts)
}
- Purpose: Creates a new table file from a builder.
- Parameters:
fname
: The filename for the new table.builder
: The builder containing the table data.
- Returns: A pointer to the created
Table
and an error, if any.
OpenTable(mf *z.MmapFile, opts Options) (*Table, error)
func OpenTable(mf *z.MmapFile, opts Options) (*Table, error) {
if opts.BlockSize == 0 && opts.Compression != options.None {
return nil, errors.New("Block size cannot be zero")
}
fileInfo, err := mf.Fd.Stat()
if err != nil {
mf.Close(-1)
return nil, y.Wrap(err, "")
}
filename := fileInfo.Name()
id, ok := ParseFileID(filename)
if !ok {
mf.Close(-1)
return nil, fmt.Errorf("Invalid filename: %s", filename)
}
t := &Table{
MmapFile: mf,
id: id,
opt: &opts,
IsInmemory: false,
tableSize: int(fileInfo.Size()),
CreatedAt: fileInfo.ModTime(),
}
t.ref.Store(1)
if err := t.initBiggestAndSmallest(); err != nil {
return nil, y.Wrapf(err, "failed to initialize table")
}
if opts.ChkMode == options.OnTableRead || opts.ChkMode == options.OnTableAndBlockRead {
if err := t.VerifyChecksum(); err != nil {
mf.Close(-1)
return nil, y.Wrapf(err, "failed to verify checksum")
}
}
return t, nil
}
- Purpose: Opens an existing table file.
- Parameters:
mf
: The mmapped file representing the table.opts
: The options for opening the table.
- Returns: A pointer to the opened
Table
and an error, if any.
OpenInMemoryTable(data []byte, id uint64, opt *Options) (*Table, error)
func OpenInMemoryTable(data []byte, id uint64, opt *Options) (*Table, error) {
mf := &z.MmapFile{
Data: data,
Fd: nil,
}
t := &Table{
MmapFile: mf,
opt: opt,
tableSize: len(data),
IsInmemory: true,
id: id,
}
t.ref.Store(1)
if err := t.initBiggestAndSmallest(); err != nil {
return nil, err
}
return t, nil
}
- Purpose: Opens a table from in-memory data.
- Parameters:
data
: The byte slice containing the table data.id
: The ID of the table.opt
: The options for the table.
- Returns: A pointer to the opened
Table
and an error, if any.
(t *Table) initBiggestAndSmallest() error
func (t *Table) initBiggestAndSmallest() error {
defer func() {
if r := recover(); r != nil {
var debugBuf bytes.Buffer
defer func() {
panic(fmt.Sprintf("%s\n== Recovered ==\n", debugBuf.String()))
}()
count := 0
for i := len(t.Data) - 1; i >= 0; i-- {
if t.Data[i] != 0 {
break
}
count++
}
fmt.Fprintf(&debugBuf, "\n== Recovering from initIndex crash ==\n")
fmt.Fprintf(&debugBuf, "File Info: [ID: %d, Size: %d, Zeros: %d]\n",
t.id, t.tableSize, count)
fmt.Fprintf(&debugBuf, "isEnrypted: %v ", t.shouldDecrypt())
readPos := t.tableSize
readPos -= 4
buf := t.readNoFail(readPos, 4)
checksumLen := int(y.BytesToU32(buf))
fmt.Fprintf(&debugBuf, "checksumLen: %d ", checksumLen)
checksum := &pb.Checksum{}
readPos -= checksumLen
buf = t.readNoFail(readPos, checksumLen)
_ = proto.Unmarshal(buf, checksum)
fmt.Fprintf(&debugBuf, "checksum: %+v ", checksum)
readPos -= 4
buf = t.readNoFail(readPos, 4)
indexLen := int(y.BytesToU32(buf))
fmt.Fprintf(&debugBuf, "indexLen: %d ", indexLen)
readPos -= t.indexLen
t.indexStart = readPos
indexData := t.readNoFail(readPos, t.indexLen)
fmt.Fprintf(&debugBuf, "index: %v ", indexData)
}
}()
var err error
var ko *fb.BlockOffset
if ko, err = t.initIndex(); err != nil {
return y.Wrapf(err, "failed to read index.")
}
t.smallest = y.Copy(ko.KeyBytes())
it2 := t.NewIterator(REVERSED | NOCACHE)
defer it2.Close()
it2.Rewind()
if !it2.Valid() {
return y.Wrapf(it2.err, "failed to initialize biggest for table %s", t.Filename())
}
t.biggest = y.Copy(it2.Key())
return nil
}
- Purpose: Initializes the smallest and biggest keys of the table by reading the index.
- Returns: An error if initialization fails, nil otherwise.
(t *Table) read(off, sz int) ([]byte, error)
func (t *Table) read(off, sz int) ([]byte, error) {
return t.Bytes(off, sz)
}
- Purpose: Reads a slice of bytes from the table's data.
- Parameters:
off
: The offset to start reading from.sz
: The number of bytes to read.
- Returns: A byte slice containing the read data and an error, if any.
(t *Table) readNoFail(off, sz int) []byte
func (t *Table) readNoFail(off, sz int) []byte {
res, err := t.read(off, sz)
y.Check(err)
return res
}
- Purpose: Reads a slice of bytes from the table's data, panicking on error.
- Parameters:
off
: The offset to start reading from.sz
: The number of bytes to read.
- Returns: A byte slice containing the read data.
(t *Table) initIndex() (*fb.BlockOffset, error)
func (t *Table) initIndex() (*fb.BlockOffset, error) {
readPos := t.tableSize
readPos -= 4
buf := t.readNoFail(readPos, 4)
checksumLen := int(y.BytesToU32(buf))
if checksumLen < 0 {
return nil, errors.New("checksum length less than zero. Data corrupted")
}
expectedChk := &pb.Checksum{}
readPos -= checksumLen
buf = t.readNoFail(readPos, checksumLen)
if err := proto.Unmarshal(buf, expectedChk); err != nil {
return nil, err
}
readPos -= 4
buf = t.readNoFail(readPos, 4)
t.indexLen = int(y.BytesToU32(buf))
readPos -= t.indexLen
t.indexStart = readPos
data := t.readNoFail(readPos, t.indexLen)
if err := y.VerifyChecksum(data, expectedChk); err != nil {
return nil, y.Wrapf(err, "failed to verify checksum for table: %s", t.Filename())
}
index, err := t.readTableIndex()
if err != nil {
return nil, err
}
if !t.shouldDecrypt() {
t._index = index
}
t._cheap = &cheapIndex{
MaxVersion: index.MaxVersion(),
KeyCount: index.KeyCount(),
UncompressedSize: index.UncompressedSize(),
OnDiskSize: index.OnDiskSize(),
OffsetsLength: index.OffsetsLength(),
BloomFilterLength: index.BloomFilterLength(),
}
t.hasBloomFilter = len(index.BloomFilterBytes()) > 0
var bo fb.BlockOffset
y.AssertTrue(index.Offsets(&bo, 0))
return &bo, nil
}
- Purpose: Initializes the table index by reading it from the file.
- Returns: A pointer to the first
BlockOffset
and an error, if any.
(t *Table) KeySplits(n int, prefix []byte) []string
func (t *Table) KeySplits(n int, prefix []byte) []string {
if n == 0 {
return nil
}
oLen := t.offsetsLength()
jump := oLen / n
if jump == 0 {
jump = 1
}
var bo fb.BlockOffset
var res []string
for i := 0; i < oLen; i += jump {
if i >= oLen {
i = oLen - 1
}
y.AssertTrue(t.offsets(&bo, i))
if bytes.HasPrefix(bo.KeyBytes(), prefix) {
res = append(res, string(bo.KeyBytes()))
}
}
return res
}
- Purpose: Splits the table into at least
n
ranges based on the block offsets, considering a given prefix. - Parameters:
n
: The minimum number of ranges to split into.prefix
: Only include keys with this prefix.
- Returns: A slice of strings representing the split keys.
(t *Table) fetchIndex() *fb.TableIndex
func (t *Table) fetchIndex() *fb.TableIndex {
if !t.shouldDecrypt() {
return t._index
}
if t.opt.IndexCache == nil {
panic("Index Cache must be set for encrypted workloads")
}
if val, ok := t.opt.IndexCache.Get(t.indexKey()); ok && val != nil {
return val
}
index, err := t.readTableIndex()
y.Check(err)
t.opt.IndexCache.Set(t.indexKey(), index, int64(t.indexLen))
return index
}
- Purpose: Fetches the table index, either from the cache or by reading it from the file.
- Returns: A pointer to the
TableIndex
.
(t *Table) offsets(ko *fb.BlockOffset, i int) bool
func (t *Table) offsets(ko *fb.BlockOffset, i int) bool {
return t.fetchIndex().Offsets(ko, i)
}
- Purpose: Retrieves the block offset at the given index from the table index.
- Parameters:
ko
: A pointer to aBlockOffset
to store the result.i
: The index of the desired block offset.
- Returns: A boolean indicating success or failure.
(t *Table) block(idx int, useCache bool) (*Block, error)
func (t *Table) block(idx int, useCache bool) (*Block, error) {
y.AssertTruef(idx >= 0, "idx=%d", idx)
if idx >= t.offsetsLength() {
return nil, errors.New("block out of index")
}
if t.opt.BlockCache != nil {
key := t.blockCacheKey(idx)
blk, ok := t.opt.BlockCache.Get(key)
if ok && blk != nil {
if blk.incrRef() {
return blk, nil
}
}
}
var ko fb.BlockOffset
y.AssertTrue(t.offsets(&ko, idx))
blk := &Block{offset: int(ko.Offset())}
blk.ref.Store(1)
defer blk.decrRef()
NumBlocks.Add(1)
var err error
if blk.data, err = t.read(blk.offset, int(ko.Len())); err != nil {
return nil, y.Wrapf(err,
"failed to read from file: %s at offset: %d, len: %d",
t.Fd.Name(), blk.offset, ko.Len())
}
if t.shouldDecrypt() {
if blk.data, err = t.decrypt(blk.data, true); err != nil {
return nil, err
}
blk.freeMe = true
}
if err = t.decompress(blk); err != nil {
return nil, y.Wrapf(err,
"failed to decode compressed data in file: %s at offset: %d, len: %d",
t.Fd.Name(), blk.offset, ko.Len())
}
readPos := len(blk.data) - 4
blk.chkLen = int(y.BytesToU32(blk.data[readPos : readPos+4]))
if blk.chkLen > len(blk.data) {
return nil, errors.New("invalid checksum length. Either the data is " +
"corrupted or the table options are incorrectly set")
}
readPos -= blk.chkLen
blk.checksum = blk.data[readPos : readPos+blk.chkLen]
readPos -= 4
numEntries := int(y.BytesToU32(blk.data[readPos : readPos+4]))
entriesIndexStart := readPos - (numEntries * 4)
entriesIndexEnd := entriesIndexStart + numEntries*4
blk.entryOffsets = y.BytesToU32Slice(blk.data[entriesIndexStart:entriesIndexEnd])
blk.entriesIndexStart = entriesIndexStart
blk.data = blk.data[:readPos+4]
if t.opt.ChkMode == options.OnBlockRead || t.opt.ChkMode == options.OnTableAndBlockRead {
if err = blk.verifyCheckSum(); err != nil {
return nil, err
}
}
blk.incrRef()
if useCache && t.opt.BlockCache != nil {
y.AssertTrue(blk.incrRef())
if !t.opt.BlockCache.Set(key, blk, blk.size()) {
blk.decrRef()
}
}
return blk, nil
}
- Purpose: Retrieves a block from the table, using the block cache if enabled.
- Parameters:
idx
: The index of the block to retrieve.useCache
: Whether to use the block cache.
- Returns: A pointer to the retrieved
Block
and an error, if any.
(t *Table) blockCacheKey(idx int) []byte
func (t *Table) blockCacheKey(idx int) []byte {
y.AssertTrue(t.id < math.MaxUint32)
y.AssertTrue(uint32(idx) < math.MaxUint32)
buf := make([]byte, 8)
binary.BigEndian.PutUint32(buf[:4], uint32(t.ID()))
binary.BigEndian.PutUint32(buf[4:], uint32(idx))
return buf
}
- Purpose: Generates a cache key for a block.
- Parameters:
idx
: The index of the block.
- Returns: A byte slice representing the cache key.
(t *Table) indexKey() uint64
func (t *Table) indexKey() uint64 {
return t.id
}
- Purpose: Returns the cache key for the table index.
- Returns: The ID of the table as the cache key.
(t *Table) IndexSize() int
func (t *Table) IndexSize() int {
return t.indexLen
}
- Purpose: Returns the size of the table index.
- Returns: The
indexLen
field.
(t *Table) Size() int64
func (t *Table) Size() int64 { return int64(t.tableSize) }
- Purpose: Returns the size of the table file.
- Returns: The
tableSize
field as anint64
.
(t *Table) StaleDataSize() uint32
func (t *Table) StaleDataSize() uint32 { return t.fetchIndex().StaleDataSize() }
- Purpose: Returns the amount of stale data in the table.
- Returns: The
StaleDataSize
from the table index.
(t *Table) Smallest() []byte
func (t *Table) Smallest() []byte { return t.smallest }
- Purpose: Returns the smallest key in the table.
- Returns: The
smallest
field.
(t *Table) Biggest() []byte
func (t *Table) Biggest() []byte { return t.biggest }
- Purpose: Returns the biggest key in the table.
- Returns: The
biggest
field.
(t *Table) Filename() string
func (t *Table) Filename() string { return t.Fd.Name() }
- Purpose: Returns the filename of the table.
- Returns: The name of the file descriptor.
(t *Table) ID() uint64
func (t *Table) ID() uint64 { return t.id }
- Purpose: Returns the ID of the table.
- Returns: The
id
field.
(t *Table) DoesNotHave(hash uint32) bool
func (t *Table) DoesNotHave(hash uint32) bool {
if !t.hasBloomFilter {
return false
}
y.NumLSMBloomHitsAdd(t.opt.MetricsEnabled, "DoesNotHave_ALL", 1)
index := t.fetchIndex()
bf := index.BloomFilterBytes()
mayContain := y.Filter(bf).MayContain(hash)
if !mayContain {
y.NumLSMBloomHitsAdd(t.opt.MetricsEnabled, "DoesNotHave_HIT", 1)
}
return !mayContain
}
- Purpose: Checks if the table potentially does not have a key based on the bloom filter.
- Parameters:
hash
: The hash of the key.
- Returns:
true
if the bloom filter indicates the key is not present,false
otherwise.
(t *Table) readTableIndex() (*fb.TableIndex, error)
func (t *Table) readTableIndex() (*fb.TableIndex, error) {
data := t.readNoFail(t.indexStart, t.indexLen)
var err error
if t.shouldDecrypt() {
if data, err = t.decrypt(data, false); err != nil {
return nil, y.Wrapf(err,
"Error while decrypting table index for the table %d in readTableIndex", t.id)
}
}
return fb.GetRootAsTableIndex(data, 0), nil
}
- Purpose: Reads the table index from the file, decrypting if necessary.
- Returns: A pointer to the
TableIndex
and an error, if any.
(t *Table) VerifyChecksum() error
func (t *Table) VerifyChecksum() error {
ti := t.fetchIndex()
for i := 0; i < ti.OffsetsLength(); i++ {
b, err := t.block(i, true)
if err != nil {
return y.Wrapf(err, "checksum validation failed for table: %s, block: %d, offset:%d",
t.Filename(), i, b.offset)
}
defer b.decrRef()
if !(t.opt.ChkMode == options.OnBlockRead || t.opt.ChkMode == options.OnTableAndBlockRead) {
if err = b.verifyCheckSum(); err != nil {
return y.Wrapf(err,
"checksum validation failed for table: %s, block: %d, offset:%d",
t.Filename(), i, b.offset)
}
}
}
return nil
}
- Purpose: Verifies the checksum of all blocks in the table.
- Returns: An error if any checksum verification fails, nil otherwise.
(t *Table) shouldDecrypt() bool
func (t *Table) shouldDecrypt() bool {
return t.opt.DataKey != nil
}
- Purpose: Determines whether the table should be decrypted based on the presence of a data key.
- Returns:
true
if a data key is present,false
otherwise.
(t *Table) KeyID() uint64
func (t *Table) KeyID() uint64 {
if t.opt.DataKey != nil {
return t.opt.DataKey.KeyId
}
return 0
}
- Purpose: Returns the ID of the data key used for encryption.
- Returns: The key ID if a data key is present, 0 otherwise.
(t *Table) decrypt(data []byte, viaCalloc bool) ([]byte, error)
func (t *Table) decrypt(data []byte, viaCalloc bool) ([]byte, error) {
iv := data[len(data)-aes.BlockSize:]