kevo/pkg/engine/transaction/buffer.go
Jeremy Tregunna 7e226825df
All checks were successful
Go Tests / Run Tests (1.24.2) (push) Successful in 9m48s
fix: engine refactor bugfix fest, go fmt
2025-04-25 23:36:08 -06:00

239 lines
5.4 KiB
Go

package transaction
import (
"bytes"
"encoding/base64"
"sort"
"sync"
)
// Operation represents a single operation in the transaction buffer
type Operation struct {
Key []byte
Value []byte
IsDelete bool
}
// Buffer stores pending changes for a transaction
type Buffer struct {
operations map[string]*Operation // Key string -> Operation (using base64 encoding for binary safety)
mu sync.RWMutex
}
// NewBuffer creates a new transaction buffer
func NewBuffer() *Buffer {
return &Buffer{
operations: make(map[string]*Operation),
}
}
// Put adds or updates a key-value pair in the buffer
func (b *Buffer) Put(key, value []byte) {
b.mu.Lock()
defer b.mu.Unlock()
// Copy the key and value to avoid external modification
keyCopy := make([]byte, len(key))
valueCopy := make([]byte, len(value))
copy(keyCopy, key)
copy(valueCopy, value)
// Create or update the operation - use base64 encoding for map key to avoid binary encoding issues
b.operations[base64.StdEncoding.EncodeToString(key)] = &Operation{
Key: keyCopy,
Value: valueCopy,
IsDelete: false,
}
}
// Delete marks a key for deletion in the buffer
func (b *Buffer) Delete(key []byte) {
b.mu.Lock()
defer b.mu.Unlock()
// Copy the key to avoid external modification
keyCopy := make([]byte, len(key))
copy(keyCopy, key)
// Create or update the operation - use base64 encoding for map key to avoid binary encoding issues
b.operations[base64.StdEncoding.EncodeToString(key)] = &Operation{
Key: keyCopy,
Value: nil,
IsDelete: true,
}
}
// Get retrieves a value for the given key from the buffer
// Returns the value and a boolean indicating if the key was found
func (b *Buffer) Get(key []byte) ([]byte, bool) {
b.mu.RLock()
defer b.mu.RUnlock()
encodedKey := base64.StdEncoding.EncodeToString(key)
op, ok := b.operations[encodedKey]
// Debug info for key lookup
if !ok && len(key) < 100 {
strKey := string(key)
println("Buffer key miss:", strKey, ", base64:", encodedKey)
// Print all keys in map for debugging
if len(b.operations) < 10 {
println("Available keys in buffer:")
for k := range b.operations {
keyData, _ := base64.StdEncoding.DecodeString(k)
println(" -", string(keyData), "(base64:", k, ")")
}
}
}
if !ok {
return nil, false
}
// If this is a deletion marker, return nil
if op.IsDelete {
return nil, true
}
// Return a copy of the value to prevent modification
valueCopy := make([]byte, len(op.Value))
copy(valueCopy, op.Value)
return valueCopy, true
}
// Clear removes all operations from the buffer
func (b *Buffer) Clear() {
b.mu.Lock()
defer b.mu.Unlock()
// Create a new operations map
b.operations = make(map[string]*Operation)
}
// Size returns the number of operations in the buffer
func (b *Buffer) Size() int {
b.mu.RLock()
defer b.mu.RUnlock()
return len(b.operations)
}
// Operations returns a sorted list of operations
// This is used for applying the changes in order
func (b *Buffer) Operations() []*Operation {
b.mu.RLock()
defer b.mu.RUnlock()
// Create a list of operations
ops := make([]*Operation, 0, len(b.operations))
for _, op := range b.operations {
ops = append(ops, op)
}
// Sort by key for consistent application order
sort.Slice(ops, func(i, j int) bool {
return bytes.Compare(ops[i].Key, ops[j].Key) < 0
})
return ops
}
// Iterator returns a new iterator over the buffer
func (b *Buffer) NewIterator() *BufferIterator {
// Get all operations
ops := b.Operations()
return &BufferIterator{
operations: ops,
position: -1,
}
}
// BufferIterator is an iterator over the transaction buffer
type BufferIterator struct {
operations []*Operation
position int
}
// SeekToFirst positions the iterator at the first key
func (it *BufferIterator) SeekToFirst() {
if len(it.operations) > 0 {
it.position = 0
} else {
it.position = -1
}
}
// SeekToLast positions the iterator at the last key
func (it *BufferIterator) SeekToLast() {
if len(it.operations) > 0 {
it.position = len(it.operations) - 1
} else {
it.position = -1
}
}
// Seek positions the iterator at the first key >= target
func (it *BufferIterator) Seek(target []byte) bool {
if len(it.operations) == 0 {
return false
}
// Binary search to find the first key >= target
i := sort.Search(len(it.operations), func(i int) bool {
return bytes.Compare(it.operations[i].Key, target) >= 0
})
if i >= len(it.operations) {
it.position = -1
return false
}
it.position = i
return true
}
// Next advances to the next key
func (it *BufferIterator) Next() bool {
if it.position < 0 || it.position >= len(it.operations)-1 {
it.position = -1
return false
}
it.position++
return true
}
// Key returns the current key
func (it *BufferIterator) Key() []byte {
if it.position < 0 || it.position >= len(it.operations) {
return nil
}
return it.operations[it.position].Key
}
// Value returns the current value
func (it *BufferIterator) Value() []byte {
if it.position < 0 || it.position >= len(it.operations) {
return nil
}
return it.operations[it.position].Value
}
// Valid returns true if the iterator is valid
func (it *BufferIterator) Valid() bool {
return it.position >= 0 && it.position < len(it.operations)
}
// IsTombstone returns true if the current entry is a deletion marker
func (it *BufferIterator) IsTombstone() bool {
if it.position < 0 || it.position >= len(it.operations) {
return false
}
return it.operations[it.position].IsDelete
}