Files
litestream/wal.go
2020-11-03 14:50:16 -07:00

128 lines
3.7 KiB
Go

package litestream
import (
"encoding/binary"
"errors"
"io"
)
// TODO: Pages can be written multiple times before 3.11.0 (https://sqlite.org/releaselog/3_11_0.html)
var (
// ErrWALHeaderEmpty is returned when writing an empty header.
ErrWALHeaderEmpty = errors.New("wal header empty")
// ErrWALFileInitialized is returned when writing a header to a
// WAL file that has already has its header written.
ErrWALFileInitialized = errors.New("wal file already initialized")
// ErrChecksumMisaligned is returned when input byte length is not divisible by 8.
ErrChecksumMisaligned = errors.New("checksum input misaligned")
)
// WALHeaderSize is the size of the WAL header, in bytes.
const WALHeaderSize = 32
type WALHeader struct {
Magic uint32
FileFormatVersion uint32
PageSize uint32
CheckpointSeqNo uint32
Salt uint64
Checksum uint64
}
// IsZero returns true if hdr is the zero value.
func (hdr WALHeader) IsZero() bool {
return hdr == (WALHeader{})
}
// ByteOrder returns the byte order based on the hdr magic.
func (hdr WALHeader) ByteOrder() binary.ByteOrder {
switch hdr.Magic {
case MagicLittleEndian:
return binary.LittleEndian
case MagicBigEndian:
return binary.BigEndian
default:
return nil
}
}
// MarshalTo encodes the header to b.
// Returns io.ErrShortWrite if len(b) is less than WALHeaderSize.
func (hdr *WALHeader) MarshalTo(b []byte) error {
if len(b) < WALHeaderSize {
return io.ErrShortWrite
}
binary.BigEndian.PutUint32(b[0:], hdr.Magic)
binary.BigEndian.PutUint32(b[4:], hdr.FileFormatVersion)
binary.BigEndian.PutUint32(b[8:], hdr.PageSize)
binary.BigEndian.PutUint32(b[12:], hdr.CheckpointSeqNo)
binary.BigEndian.PutUint64(b[16:], hdr.Salt)
binary.BigEndian.PutUint64(b[24:], hdr.Checksum)
return nil
}
// Unmarshal decodes the header from b.
// Returns io.ErrUnexpectedEOF if len(b) is less than WALHeaderSize.
func (hdr *WALHeader) Unmarshal(b []byte) error {
if len(b) < WALHeaderSize {
return io.ErrUnexpectedEOF
}
hdr.Magic = binary.BigEndian.Uint32(b[0:])
hdr.FileFormatVersion = binary.BigEndian.Uint32(b[4:])
hdr.PageSize = binary.BigEndian.Uint32(b[8:])
hdr.CheckpointSeqNo = binary.BigEndian.Uint32(b[12:])
hdr.Salt = binary.BigEndian.Uint64(b[16:])
hdr.Checksum = binary.BigEndian.Uint64(b[24:])
return nil
}
// WALFrameHeaderSize is the size of the WAL frame header, in bytes.
const WALFrameHeaderSize = 24
// WALFrameHeader represents a SQLite WAL frame header.
type WALFrameHeader struct {
Pgno uint32
PageN uint32 // only set for commit records
Salt uint64
Checksum uint64
}
// IsZero returns true if hdr is the zero value.
func (hdr WALFrameHeader) IsZero() bool {
return hdr == (WALFrameHeader{})
}
// IsCommit returns true if the frame represents a commit header.
func (hdr *WALFrameHeader) IsCommit() bool {
return hdr.PageN != 0
}
// MarshalTo encodes the frame header to b.
// Returns io.ErrShortWrite if len(b) is less than WALHeaderSize.
func (hdr *WALFrameHeader) MarshalTo(b []byte) error {
if len(b) < WALFrameHeaderSize {
return io.ErrShortWrite
}
binary.BigEndian.PutUint32(b[0:], hdr.Pgno)
binary.BigEndian.PutUint32(b[4:], hdr.PageN)
binary.BigEndian.PutUint64(b[8:], hdr.Salt)
binary.BigEndian.PutUint64(b[16:], hdr.Checksum)
return nil
}
// Unmarshal decodes the frame header from b.
// Returns io.ErrUnexpectedEOF if len(b) is less than WALHeaderSize.
func (hdr *WALFrameHeader) Unmarshal(b []byte) error {
if len(b) < WALFrameHeaderSize {
return io.ErrUnexpectedEOF
}
hdr.Pgno = binary.BigEndian.Uint32(b[0:])
hdr.PageN = binary.BigEndian.Uint32(b[4:])
hdr.Salt = binary.BigEndian.Uint64(b[8:])
hdr.Checksum = binary.BigEndian.Uint64(b[16:])
return nil
}