Files
go-kv/wal/entry.go
T
dailz cf913b1d52 feat: initialize project structure and error types
- go.mod with github.com/dailz/go-kv, Go 1.26.3, testify
- config/config.go with WalConfig, Validate() with checked arithmetic
- errors.go with sentinel errors (ErrCommitUnknown, ErrWriteStopped, etc.)
- wal/constants.go with all WAL format constants and enums
- wal/header.go with WAL File Header encode/decode (CRC32 IEEE)
- wal/record.go with Physical Record codec, block boundary, SplitIntoRecords
- wal/entry.go with WAL Entry codec (varint keys/values, OpType, ValueKind)
- wal/sequence.go with SequenceManager (atomic, CAS, overflow-safe)
- manifest/manifest.go with MANIFEST stub (Load/Save atomic)
- manifest/current.go with CURRENT file (WriteCurrent/ReadCurrent)
- Comprehensive tests for all modules
- .golangci.yml configuration
2026-06-12 13:23:27 +08:00

135 lines
3.3 KiB
Go

package wal
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
)
// WalEntry represents a single WAL record.
type WalEntry struct {
OpType uint8
ValueKind uint8
Key []byte
Value []byte
}
// Validate checks that the entry fields are consistent with the design rules.
func (e *WalEntry) Validate() error {
keyLen := len(e.Key)
if keyLen == 0 || keyLen > int(MaxWalKeyBytes) {
return fmt.Errorf("wal: invalid key length %d", keyLen)
}
valLen := len(e.Value)
switch e.OpType {
case OpPut:
switch e.ValueKind {
case VKInline:
if valLen > int(MaxWalInlineValueBytes) {
return fmt.Errorf("wal: inline value length %d out of range [0, %d]", valLen, MaxWalInlineValueBytes)
}
case VKValueLogPointer:
if valLen == 0 {
return errors.New("wal: value log pointer requires non-empty value")
}
default:
return fmt.Errorf("wal: put requires valueKind Inline(1) or ValueLogPointer(2), got %d", e.ValueKind)
}
case OpDelete:
if e.ValueKind != VKNone {
return fmt.Errorf("wal: delete requires valueKind None(0), got %d", e.ValueKind)
}
if valLen != 0 {
return fmt.Errorf("wal: delete requires empty value, got length %d", valLen)
}
default:
return fmt.Errorf("wal: invalid opType %d", e.OpType)
}
return nil
}
// EncodeEntry serializes a WalEntry into a byte slice.
func EncodeEntry(e *WalEntry) ([]byte, error) {
if err := e.Validate(); err != nil {
return nil, err
}
keyLen := uint64(len(e.Key))
valLen := uint64(len(e.Value))
// Size: 1 (opType) + 1 (valueKind) + varint(keyLen) + varint(valLen) + key + value
size := 2 + MaxWalVarintBytes + MaxWalVarintBytes + len(e.Key) + len(e.Value)
buf := make([]byte, size)
buf[0] = e.OpType
buf[1] = e.ValueKind
n := 2
n += binary.PutUvarint(buf[n:], keyLen)
n += binary.PutUvarint(buf[n:], valLen)
n += copy(buf[n:], e.Key)
n += copy(buf[n:], e.Value)
return buf[:n], nil
}
// DecodeEntry deserializes a WalEntry from a byte slice.
// Returns the decoded entry and the number of bytes consumed.
func DecodeEntry(data []byte) (entry *WalEntry, consumed int, err error) {
if len(data) < 2 {
return nil, 0, errors.New("wal: data too short for entry header")
}
opType := data[0]
valueKind := data[1]
r := bytes.NewReader(data[2:])
keyLen, err := binary.ReadUvarint(r)
if err != nil {
return nil, 0, fmt.Errorf("wal: reading key length: %w", err)
}
valLen, err := binary.ReadUvarint(r)
if err != nil {
return nil, 0, fmt.Errorf("wal: reading value length: %w", err)
}
// Calculate consumed so far: 2 header bytes + bytes read from reader
consumed = 2 + (len(data) - 2 - r.Len())
// Read key
remaining := len(data) - consumed
if uint64(remaining) < keyLen {
return nil, 0, fmt.Errorf("wal: data truncated: need %d bytes for key, have %d", keyLen, remaining)
}
key := make([]byte, keyLen)
copy(key, data[consumed:consumed+int(keyLen)])
consumed += int(keyLen)
// Read value
remaining = len(data) - consumed
if uint64(remaining) < valLen {
return nil, 0, fmt.Errorf("wal: data truncated: need %d bytes for value, have %d", valLen, remaining)
}
value := make([]byte, valLen)
copy(value, data[consumed:consumed+int(valLen)])
consumed += int(valLen)
e := &WalEntry{
OpType: opType,
ValueKind: valueKind,
Key: key,
Value: value,
}
if err := e.Validate(); err != nil {
return nil, 0, err
}
return e, consumed, nil
}