1
0
Fork 0

Add GPT-generated compaction

Not tested.
master
Philip O'Toole 10 months ago
parent 6e30f1476c
commit ae55b98f15

@ -1,6 +1,9 @@
package wal
import "io"
import (
"encoding/binary"
"io"
)
// Frame points to a single WAL frame in a WAL file.
type Frame struct {
@ -23,29 +26,89 @@ func NewCompactor(r io.ReadSeeker) *Compactor {
}
}
// WriteTo compacts the WAL file to the given writer.
// WriteTo writes the compacted WAL file to the given writer.
func (c *Compactor) WriteTo(w io.Writer) (n int64, err error) {
frames, err := c.getFrames()
if err != nil {
return 0, err
}
if len(frames) == 0 {
return 0, nil
}
// Copy the WAL header.
if _, err := c.r.Seek(0, io.SeekStart); err != nil {
if err := c.writeWALHeader(w); err != nil {
return 0, err
}
if _, err := io.CopyN(w, c.r, WALHeaderSize); err != nil {
return 0, err
// Iterate over the frames and write each one to the new WAL file.
for _, frame := range frames {
if err := c.writeFrame(w, frame); err != nil {
return 0, err
}
}
// Write each new WAL Frame header, and the associated page data.
for _, f := range frames {
_ = f
return n, nil
}
func (c *Compactor) writeWALHeader(w io.Writer) error {
header := make([]byte, WALHeaderSize)
c.putUint32(header[0:], SQLITE_WAL_MAGIC)
// File format write version (1 byte) and read version (1 byte).
// Assuming values for SQLite version 3.7.0 or later.
header[4] = 0x02 // Write version
header[5] = 0x02 // Read version
// Database page size (2 bytes).
c.putUint16(header[6:], uint16(c.wr.PageSize()))
// Checkpoint sequence number (4 bytes).
// Incrementing from the original sequence number.
c.putUint32(header[12:], c.wr.seq+1)
// Salt values (4 bytes each), reusing the original salt values.
c.putUint32(header[16:], c.wr.salt1)
c.putUint32(header[20:], c.wr.salt2)
// Write the header to the new WAL file.
if _, err := w.Write(header); err != nil {
return err
}
return 0, nil
return nil
}
func (c *Compactor) writeFrame(w io.Writer, frame *Frame) error {
// Seek to the frame's offset in the original WAL file.
if _, err := c.r.Seek(frame.Offset, io.SeekStart); err != nil {
return err
}
// Read the frame header and data.
header := make([]byte, WALFrameHeaderSize)
if _, err := io.ReadFull(c.r, header); err != nil {
return err
}
data := make([]byte, c.wr.PageSize())
if _, err := io.ReadFull(c.r, data); err != nil {
return err
}
// Recalculate checksums.
chksum1, chksum2 := WALChecksum(c.wr.bo, c.wr.salt1, c.wr.salt2, header)
chksum1, chksum2 = WALChecksum(c.wr.bo, chksum1, chksum2, data)
// Update checksums in the header.
c.putUint32(header[16:], chksum1)
c.putUint32(header[20:], chksum2)
// Write the frame header and data to the new WAL file.
if _, err := w.Write(header); err != nil {
return err
}
if _, err := w.Write(data); err != nil {
return err
}
return nil
}
func (c *Compactor) getFrames() (map[uint32]*Frame, error) {
@ -87,3 +150,23 @@ func (c *Compactor) getFrames() (map[uint32]*Frame, error) {
return frames, nil
}
// putUint16 writes a uint16 to the given byte slice in the same byte order as
// as the source WAL file.
func (c *Compactor) putUint16(b []byte, v uint16) {
if c.wr.bo == binary.LittleEndian {
binary.LittleEndian.PutUint16(b, v)
} else {
binary.BigEndian.PutUint16(b, v)
}
}
// putUint32 writes a uint32 to the given byte slice in the same byte order as
// as the source WAL file.
func (c *Compactor) putUint32(b []byte, v uint32) {
if c.wr.bo == binary.LittleEndian {
binary.LittleEndian.PutUint32(b, v)
} else {
binary.BigEndian.PutUint32(b, v)
}
}

@ -10,6 +10,8 @@ import (
const (
WALHeaderSize = 32
WALFrameHeaderSize = 24
SQLITE_WAL_MAGIC = 0x377f0683
)
// Reader wraps an io.Reader and parses SQLite WAL frames.

Loading…
Cancel
Save