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
This commit is contained in:
+134
@@ -0,0 +1,134 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user