Documentation ¶
Overview ¶
Package record reads and writes sequences of records. Each record is a stream of bytes that completes before the next record starts.
When reading, call Next to obtain an io.Reader for the next record. Next will return io.EOF when there are no more records. It is valid to call Next without reading the current record to exhaustion.
When writing, call Next to obtain an io.Writer for the next record. Calling Next finishes the current record. Call Close to finish the final record.
Optionally, call Flush to finish the current record and flush the underlying writer without starting a new record. To start a new record after flushing, call Next.
Neither Readers or Writers are safe to use concurrently.
Example code:
func read(r io.Reader) ([]string, error) { var ss []string records := record.NewReader(r) for { rec, err := records.Next() if err == io.EOF { break } if err != nil { log.Printf("recovering from %v", err) r.Recover() continue } s, err := ioutil.ReadAll(rec) if err != nil { log.Printf("recovering from %v", err) r.Recover() continue } ss = append(ss, string(s)) } return ss, nil } func write(w io.Writer, ss []string) error { records := record.NewWriter(w) for _, s := range ss { rec, err := records.Next() if err != nil { return err } if _, err := rec.Write([]byte(s)), err != nil { return err } } return records.Close() }
The wire format is that the stream is divided into 32KiB blocks, and each block contains a number of tightly packed chunks. Chunks cannot cross block boundaries. The last block may be shorter than 32 KiB. Any unused bytes in a block must be zero.
A record maps to one or more chunks. Each chunk has a 7 byte header (a 4 byte checksum, a 2 byte little-endian uint16 length, and a 1 byte chunk type) followed by a payload. The checksum is over the chunk type and the payload.
There are four chunk types: whether the chunk is the full record, or the first, middle or last chunk of a multi-chunk record. A multi-chunk record has one first chunk, zero or more middle chunks, and one last chunk.
The wire format allows for limited recovery in the face of data corruption: on a format error (such as a checksum mismatch), the reader moves to the next block and looks for the next full or first chunk.
Index ¶
Constants ¶
const ( BlockSize = 64 * 1024 BlockSizeMask = BlockSize - 1 HeaderSize = 7 )
Variables ¶
var ( // ErrNotAnIOSeeker is returned if the io.Reader underlying a Reader does not implement io.Seeker. ErrNotAnIOSeeker = errors.New("pebble/record: reader does not implement io.Seeker") // ErrNoLastRecord is returned if LastRecordOffset is called and there is no previous record. ErrNoLastRecord = errors.New("pebble/record: no last record exists") )
Functions ¶
func ComputeEnd ¶
ComputeEnd caculate the absolute end of appeding some "length" bytes
Types ¶
type LogWriter ¶
type LogWriter struct {
// contains filtered or unexported fields
}
LogWriter writes records to an underlying io.Writer.
func NewLogWriter ¶
NewLogWriter returns a new LogWriter.
func (*LogWriter) Flush ¶
Flush flushes unwritten data. May be called concurrently with Write, Sync and itself.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader reads records from an underlying io.Reader.
func (*Reader) Next ¶
Next returns a reader for the next record. It returns io.EOF if there are no more records. The reader returned becomes stale after the next Next call, and should no longer be used.
func (*Reader) Recover ¶
func (r *Reader) Recover()
Recover clears any errors read so far, so that calling Next will start reading from the next good 32KiB block. If there are no such blocks, Next will return io.EOF. Recover also marks the current reader, the one most recently returned by Next, as stale. If Recover is called without any prior error, then Recover is a no-op.
func (*Reader) SeekRecord ¶
SeekRecord seeks in the underlying io.Reader such that calling r.Next returns the record whose first chunk header starts at the provided offset. Its behavior is undefined if the argument given is not such an offset, as the bytes at that offset may coincidentally appear to be a valid header.
It returns ErrNotAnIOSeeker if the underlying io.Reader does not implement io.Seeker.
SeekRecord will fail and return an error if the Reader previously encountered an error, including io.EOF. Such errors can be cleared by calling Recover. Calling SeekRecord after Recover will make calling Next return the record at the given offset, instead of the record at the next good 32KiB block as Recover normally would. Calling SeekRecord before Recover has no effect on Recover's semantics other than changing the starting point for determining the next good 32KiB block.
The offset is always relative to the start of the underlying io.Reader, so negative values will result in an error as per io.Seeker.