All checks were successful
Go Tests / Run Tests (1.24.2) (push) Successful in 9m48s
239 lines
5.4 KiB
Go
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
|
|
}
|