Documentation
¶
Overview ¶
Package format implements pure byte-level encoding and decoding of every on-disk artefact in Tarantool's journal envelope: meta header, fixheader, xrow records, tx blocks, the EOF marker, and per-body schemas (DML / RAFT / SYNCHRO / VYLOG / vinyl RUN and INDEX) and the operations of UPDATE and UPSERT. It is the foundation for the `reader`, `writer`, `dir`, `rotate`, and utility packages.
Design references in the Tarantool 3.x tree:
- meta: src/box/xlog.c:115-122,155
- fixheader: src/box/iproto_constants.h:51, src/box/xlog.c:1095-1105
- magic bytes: src/box/xlog.c:73-75
- tx encoding: src/box/xlog.c:1080-1120 (uncompressed), :1128-1200 (zstd)
- xrow header: src/box/xrow.c:331-444
- tx semantics:src/box/xrow.c:259-263, 402-410
- CRC32C: src/box/xlog.c:1086, src/lib/digest/crc32_impl.c:196-205
- vy_log keys: src/box/vy_log.c:71-89
- vinyl runs: src/box/vy_run.c, src/box/vy_stmt.c
- vclock: src/lib/vclock/vclock.h:54-80, src/lib/vclock/vclock.c:55-72
- update ops: src/box/xrow_update_field.c (xrow_update_op_decode)
No I/O. No global mutable state (sync.Pool instances are construction caches, not state).
Aliasing ¶
The decoders do not copy. Every []byte a decoder returns, and every []byte field of a struct it fills, is a sub-slice of its input buffer. This is the contract of the whole package, not a detail of one function, and it is what keeps the read path allocation-free: a caller that only needs the SpaceID of a row never pays for its tuple.
The flip side is that a decoded value is only valid as long as its input is. Retaining an aliased slice past the next reuse of the buffer is a use-after-free in all but name: nothing fails, the bytes just change under it, and it reads as data corruption. A file reader hands the decoders a fresh region per block, so the window is wide and the bug hides; a wire consumer that reads every packet into one receive buffer sees the next row overwrite the tuple it kept from the last one.
The slices that alias the input are, per type:
- XRow: BodyRaw (DecodeXRow, DecodeXRowInto). A body decoded from BodyRaw aliases the same buffer in turn.
- DMLBody: Tuple, Key, Ops, OldTuple, NewTuple and Arrow.
- VyStmtBody: Tuple, Key and Ops.
- VyRunInfo: MinKey, MaxKey and Bloom.
- VyPageInfo: MinKey.
- VyLogBody: the values of Keys. The map itself is new on every decode.
- Operation: FieldName and Value (DecodeOps, DecodeOpsInto).
- DecodeOptions.OnUnknown: its raw argument, and so the values of the map CollectUnknown fills.
- DecodeTxBlock: the row slices, which alias the plain payload, and the plain payload itself for a block that is not compressed.
Nothing else aliases: RaftBody and SynchroBody hold numbers and a VClock map built on every decode, DecodeVyRowIndexBody returns new offsets, and a VClockArray and a Fixheader are values.
To keep a value past its buffer, copy it rather than its fields one by one. XRow, Operation and every body struct - RaftBody and SynchroBody too, for their maps - have Clone, which returns a deep copy sharing no memory with the input, and CloneInto, which deep-copies into a caller-owned value and reuses the capacity of its slices, so a loop that retains rows allocates nothing once warm. CloneOps and CloneOpsInto do the same for a slice of operations; a raw value of OnUnknown or a row slice is a plain []byte for bytes.Clone. A nil slice or map stays nil and an empty one stays empty, so a clone is reflect.DeepEqual to its source.
CloneInto writes into the slices dst holds, so they must belong to dst: dst must be the zero value or hold what Clone or CloneInto last put in it. A dst that was decoded into aliases its buffer, and CloneInto would copy into that buffer; zero it first. A slice of dst that shares its array with the same field of the source - dst is the source, or a shallow copy of it - is replaced rather than reused, so CloneInto never leaves dst sharing memory with the source. A map is never reused, as the decoders never reuse one: the vclock of a RaftBody or a SynchroBody and the keys of a VyLogBody are new maps.
Index ¶
- Constants
- Variables
- func AppendFramedBlock(dst, plain []byte, c Compression) ([]byte, error)
- func AppendTxBlock(dst []byte, rows []XRow, opts TxOptions) ([]byte, error)
- func AppendTxBlockPayload(dst []byte, rows []XRow) ([]byte, error)
- func CRC32C(data []byte) uint32
- func CompressTx(payload []byte) ([]byte, error)
- func CompressTxInto(dst, payload []byte, level int) ([]byte, error)
- func DecodeDMLBodyInto(b []byte, dst *DMLBody, opts *DecodeOptions) error
- func DecodeFixheaderInto(b [FixheaderSize]byte, h *Fixheader) error
- func DecodeRaftBodyInto(b []byte, dst *RaftBody, opts *DecodeOptions) error
- func DecodeSynchroBodyInto(b []byte, dst *SynchroBody, opts *DecodeOptions) error
- func DecodeTxBlock(b []byte) ([][]byte, []byte, int, error)
- func DecodeVClockArrayInto(b []byte, dst *VClockArray) (int, error)
- func DecodeVyLogBodyInto(b []byte, dst *VyLogBody, opts *DecodeOptions) error
- func DecodeVyPageInfoBodyInto(b []byte, dst *VyPageInfo, opts *DecodeOptions) error
- func DecodeVyRowIndexBody(b []byte) ([]uint32, error)
- func DecodeVyRunInfoBodyInto(b []byte, dst *VyRunInfo, opts *DecodeOptions) error
- func DecodeVyStmtBodyInto(b []byte, dst *VyStmtBody, opts *DecodeOptions) error
- func DecodeXRowInto(b []byte, dst *XRow) (int, error)
- func DecompressTx(payload, scratch []byte) ([]byte, error)
- func EncodeFixheader(buf *[FixheaderSize]byte, h *Fixheader)
- func EncodeMeta(w io.Writer, m *Meta) error
- func EncodeTxBlock(rows []XRow, opts TxOptions) ([]byte, error)
- func EncodeXRow(buf []byte, r *XRow) ([]byte, error)
- func TypeName(t iproto.Type) string
- type Compression
- type DMLBody
- type DecodeOptions
- type Filetype
- type Fixheader
- type Meta
- type MetaOptions
- type OpCode
- type Operation
- type Present
- type RaftBody
- type SynchroBody
- type TxOptions
- type VClock
- type VClockArray
- func (v *VClockArray) Compare(o *VClockArray) (int, bool)
- func (v *VClockArray) Get(id uint32) (int64, bool)
- func (v *VClockArray) Has(id uint32) bool
- func (v *VClockArray) IsZero() bool
- func (v *VClockArray) Len() int
- func (v *VClockArray) Reset()
- func (v *VClockArray) Set(id uint32, lsn int64) error
- func (v *VClockArray) Signature() int64
- func (v *VClockArray) String() string
- func (v *VClockArray) VClock() VClock
- type VyKey
- type VyLogBody
- type VyLogType
- type VyPageInfo
- type VyRunInfo
- type VyStmtBody
- type VyStmtFlags
- type VyStmtStat
- type XRow
- func (r *XRow) Clone() *XRow
- func (r *XRow) CloneInto(dst *XRow)
- func (r *XRow) HasFlags() bool
- func (r *XRow) HasGroupID() bool
- func (r *XRow) HasLSN() bool
- func (r *XRow) HasReplicaID() bool
- func (r *XRow) HasSchemaVersion() bool
- func (r *XRow) HasStreamID() bool
- func (r *XRow) HasSync() bool
- func (r *XRow) HasTSN() bool
- func (r *XRow) HasTimestamp() bool
- func (r *XRow) HasType() bool
- func (r *XRow) IsCommit() bool
Examples ¶
Constants ¶
const ( // MarkerSize is the byte length of every magic marker (RowMarker, // ZRowMarker, EOFMarker) — the fixed prefix a reader peeks to classify // what comes next. MarkerSize = 4 // FixheaderSize is the exact on-disk size of every tx-block fixheader. // XLOG_FIXHEADER_SIZE in Tarantool. The fixheader is zero-padded as an // mp_str so the total always reaches this value. FixheaderSize = 19 // CompressThreshold is the minimum payload size (XLOG_TX_COMPRESS_THRESHOLD) // above which a tx is zstd-compressed. CompressThreshold = 2 * 1024 // AutocommitThreshold is the in-memory buffer size after which the writer // auto-flushes the current tx on the next row boundary // (XLOG_TX_AUTOCOMMIT_THRESHOLD, src/box/xlog.c:85). AutocommitThreshold = 128 * 1024 // ZstdLevel is the compression level Tarantool uses // (ZSTD_compressBegin(ctx, 3), src/box/xlog.c:1135). ZstdLevel = 3 // FormatVersion is the literal "0.13" string written in the meta header // for current files. The writer always emits this. FormatVersion = "0.13" // LegacyFormatVersion is the older "0.12" string the reader accepts when // AcceptV012 is set on MetaOptions. LegacyFormatVersion = "0.12" )
Layout constants. See src/box/iproto_constants.h:51 and src/box/xlog.c:85,93.
const MaxDecompressedTxSize = 64 << 20 // 64 MiB
MaxDecompressedTxSize caps the number of bytes a single zstd-compressed tx block (ZRowMarker) is allowed to decompress to. It is the decompression-bomb guard: a malicious or corrupt file can carry a tiny ZRowMarker payload whose zstd frame *declares* an enormous content size; klauspost's decoder defaults to a 64 GiB ceiling, so without this cap a few on-disk bytes can drive a multi-gigabyte allocation and OOM the process before a single row is read.
The cap is deliberately generous relative to anything Tarantool writes: a tx block is auto-flushed at AutocommitThreshold (128 KiB), so a legitimate plain payload is at most that plus one trailing row, and a single row is bounded by the tuple-size limit (1 MiB by default, raisable). 64 MiB leaves ~500x headroom over the autocommit threshold and comfortably covers even large-tuple deployments, while bounding a hostile block to an allocation the process can survive. The decoder rejects an over-cap frame (returning zstd.ErrDecoderSizeExceeded) from its *declared* size, before allocating.
const VClockMax = 32
VClockMax is the number of components of a vclock — Tarantool's VCLOCK_MAX (src/lib/vclock/vclock.h:54). Replica ids of a VClockArray are 0..VClockMax-1.
Variables ¶
var ( ErrIsLeaderSeenLen = errors.New("is_leader_seen bad length") ErrIsLeaderSeenTag = errors.New("is_leader_seen unexpected tag") )
Sentinel errors for malformed RAFT bodies.
var ( // ErrVyMissingKey is returned for a run info or a page info row without // a key Tarantool requires. ErrVyMissingKey = errors.New("missing mandatory key") // ErrVyRowIndex is returned for a row index whose data is not a whole // number of offsets. ErrVyRowIndex = errors.New("bad row index") )
Sentinel errors of the vinyl body decoders.
var ( // RowMarker prefixes an uncompressed tx block. RowMarker = [4]byte{0xD5, 0xBA, 0x0B, 0xAB} // ZRowMarker prefixes a zstd-compressed tx block. ZRowMarker = [4]byte{0xD5, 0xBA, 0x0B, 0xBA} // EOFMarker terminates a complete xlog/snap/vylog file. EOFMarker = [4]byte{0xD5, 0x10, 0xAD, 0xED} )
Magic byte sequences — the first 4 bytes of every tx block and the EOF marker. Stored big-endian on disk; we treat them as opaque [4]byte to avoid host-endianness ambiguity. See src/box/xlog.c:73-75.
var ( ErrShortFixheader = errors.New("format: fixheader: truncated") ErrUnknownMagic = errors.New("format: fixheader: unknown magic") ErrFixheaderShape = errors.New("format: fixheader: malformed shape") ErrPaddingTooLarge = errors.New("format: fixheader: padding exceeds fixheader size") )
ErrShortFixheader / ErrUnknownMagic / ErrFixheaderShape — surfaced when DecodeFixheader rejects a malformed block.
var ( ErrMetaTruncated = errors.New("format: meta header truncated before blank-line terminator") ErrMetaBadFormat = errors.New("format: malformed meta header") ErrMetaBadVersion = errors.New("format: unsupported format version") ErrNilMeta = errors.New("nil meta") ErrEmptyFiletype = errors.New("empty Filetype") ErrNilMetaReader = errors.New("nil reader") )
Sentinel errors returned by DecodeMeta. The reader package wraps them with file context.
var ( ErrEmptyInput = errors.New("empty input") ErrShortInput = errors.New("short input") ErrTruncatedInput = errors.New("truncated input") ErrUnexpectedTag = errors.New("unexpected type tag") ErrUnsupportedTag = errors.New("unsupported type tag") ErrShortHeader = errors.New("header") ErrShortPayload = errors.New("payload") ErrShortFixedWidth = errors.New("short fixed-width value") ErrShortFixstr = errors.New("short fixstr") )
Sentinel errors for the msgpack peek/skip helpers. Each call site wraps one of these with its specific prefix so the rendered message stays byte-for-byte identical to the historical hand-written strings while satisfying err113 (static, comparable error values).
var ( // ErrOpNotArray is returned when the operations, or one of them, are not // a msgpack array. ErrOpNotArray = errors.New("not an array") // ErrOpArity is returned when an operation has the wrong number of // elements for its opcode. ErrOpArity = errors.New("wrong number of arguments") // ErrOpCodeType is returned when the opcode of an operation is not a // string. ErrOpCodeType = errors.New("opcode is not a string") // ErrUnknownOpCode is returned when the opcode of an operation is not // one of Tarantool's. ErrUnknownOpCode = errors.New("unknown opcode") // ErrOpFieldType is returned when the field of an operation is neither // an integer nor a string. ErrOpFieldType = errors.New("field is neither an integer nor a string") // ErrOpArgType is returned when the position or offset of a splice is // not an integer. ErrOpArgType = errors.New("splice argument is not an integer") // ErrOpIntRange is returned when a field number or a splice argument does // not fit an int32, as Tarantool requires (mp_read_int32). ErrOpIntRange = errors.New("integer out of int32 range") )
Sentinel errors of DecodeOps and DecodeOpsInto, wrapped with the position of the operation at fault.
var ( ErrEOFMarkerBlock = errors.New("EOF marker, not a tx block") ErrMissingType = errors.New("header missing IPROTO_REQUEST_TYPE") )
Sentinel errors for tx-block decoding.
var ( ErrVClockBraces = errors.New("vclock: missing surrounding braces") ErrVClockColon = errors.New("vclock: missing ':' in entry") ErrVClockDupID = errors.New("vclock: duplicate replica id") )
Sentinel errors for ParseVClock.
var ( ErrNilRow = errors.New("nil row") ErrEmptyXRowInput = errors.New("empty input") )
Sentinel errors for xrow encode/decode.
var ErrBadVyLogRecord = errors.New("bad vylog record")
ErrBadVyLogRecord is returned by DecodeVyLogBody for a VYLOG row body that does not hold a vinyl log record.
var ErrCorruptCRC = errors.New("format: tx block: crc32c mismatch")
ErrCorruptCRC is returned by DecodeTxBlock when the computed CRC32C does not match the value in the fixheader.
var ErrEmptyBody = errors.New("empty body")
ErrEmptyBody is returned by the body decoders when handed a zero-length body slice.
var ErrUnknownFiletype = errors.New("format: unknown filetype")
ErrUnknownFiletype is returned by Filetype.Ext for a Filetype that does not correspond to a Tarantool on-disk artefact.
var ErrVClockReplicaID = errors.New("vclock: replica id out of range")
ErrVClockReplicaID reports a replica id that does not fit a VClockArray (id >= VClockMax). The VClock map accepts any id; a VClockArray, like Tarantool's struct vclock, does not.
Functions ¶
func AppendFramedBlock ¶
func AppendFramedBlock(dst, plain []byte, c Compression) ([]byte, error)
AppendFramedBlock frames an already-encoded plain payload into a complete on-disk tx block (fixheader + payload) appended to dst, returning the extended buffer. It compresses the payload (into a pooled scratch) when the Compression policy says to, computes the CRC32C over the on-disk bytes, and writes the fixheader. Callers holding their own reusable scratch should prefer CompressTxInto + EncodeFixheader directly.
func AppendTxBlock ¶
AppendTxBlock appends a complete on-disk tx block (fixheader + payload) for rows to dst and returns the extended buffer. Compression and CRC follow the opts.Compression policy (zstd at its level iff the payload meets the policy's threshold and it is not Disabled). EncodeTxBlock is AppendTxBlock(nil, …); writers reuse a dst across txs to avoid the per-block allocation.
Note: the payload is encoded into a transient buffer internally, so this path still allocates that scratch. The fully reusable hot path lives in the writer package (AppendTxBlockPayload into owned scratch + framing), which is what high-throughput writers should use.
func AppendTxBlockPayload ¶
AppendTxBlockPayload appends the concatenated EncodeXRow output of rows — the *uncompressed* tx payload, with no fixheader — to dst and returns the extended buffer. Passing a reused dst[:0] makes the per-tx payload encode allocation-free in steady state (the buffer grows to the largest tx, then reuses its capacity). It is the building block BatchWriter accumulates into, and the first half of AppendTxBlock.
func CRC32C ¶
CRC32C computes the Tarantool variant CRC-32C over data:
- polynomial: Castagnoli (0x82F63B78 reflected)
- initial value: 0
- final XOR: none
Used for the `crc32c` field of every tx-block fixheader. The CRC is computed over the on-disk payload bytes — post-compression for ZRowMarker tx blocks, plain bytes for RowMarker tx blocks.
Tarantool's variant runs init=0 with NO final XOR, whereas the stdlib's crc32.Checksum / crc32.New apply the standard CRC frame (init=^0, final ^0), incompatible with `crc32_calc` (src/lib/digest/crc32_impl.c:196-205). We recover the unframed variant from Go's *hardware-accelerated* Castagnoli path with a length-independent identity: seeding crc32.Update with ^0 cancels its entry inversion, and the trailing ^0 cancels its exit inversion. This is ~20x faster than a byte-at-a-time loop on block-sized payloads (≈0.5 GB/s → ≈11 GB/s on arm64); crc32c_test.go pins it against a software reference over fixed and random inputs.
Example ¶
ExampleCRC32C shows the Castagnoli CRC used to protect every tx block: it is a pure function of the input, so identical bytes hash equally and any change changes the checksum.
package main
import (
"fmt"
"github.com/tarantool/go-xlog/format"
)
func main() {
a := format.CRC32C([]byte("tarantool"))
b := format.CRC32C([]byte("tarantool"))
c := format.CRC32C([]byte("Tarantool"))
fmt.Println(a == b)
fmt.Println(a == c)
}
Output: true false
func CompressTx ¶
CompressTx zstd-encodes payload at ZstdLevel (3). The output is a self-contained zstd frame; Tarantool's libzstd decoder accepts it. Note that the output bytes are NOT byte-identical to libzstd's output — only the decompressed content is guaranteed to round-trip.
func CompressTxInto ¶
CompressTxInto zstd-encodes payload at the given level, appending the frame to dst[:0] and returning the result (which may alias dst if it had enough capacity, or a fresh allocation otherwise). Pass a reused dst to amortise the output allocation across calls — the writer's hot path does exactly this.
func DecodeDMLBodyInto ¶ added in v0.2.0
func DecodeDMLBodyInto(b []byte, dst *DMLBody, opts *DecodeOptions) error
DecodeDMLBodyInto parses a DML body map at b[0:] into dst. It is the zero-allocation core of DecodeDMLBody: a hot loop can decode every body into one caller-owned DMLBody. It first resets dst to the zero DMLBody, so a reused dst carries no field of a prior row, then sets the fields of the keys b holds. The raw fields of dst (Tuple, Key, Ops, OldTuple, NewTuple, Arrow) alias b; see Aliasing in the package doc. A key with no field goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. On error dst holds what was decoded before it.
Example ¶
ExampleDecodeDMLBodyInto decodes the bodies of a stream of rows into one DMLBody, with no allocation per row. A body key DMLBody has no field for is skipped unless the options ask for it: here CollectUnknown gathers them, as a path that writes the body again must.
package main
import (
"fmt"
"github.com/tarantool/go-xlog/format"
)
func main() {
bodies := [][]byte{
// {IPROTO_SPACE_ID: 512, IPROTO_INDEX_BASE: 1, IPROTO_KEY: [1]}
{0x83, 0x10, 0xcd, 0x02, 0x00, 0x15, 0x01, 0x20, 0x91, 0x01},
// {IPROTO_SPACE_ID: 513, 0x7e: 5}
{0x82, 0x10, 0xcd, 0x02, 0x01, 0x7e, 0x05},
}
var body format.DMLBody
for _, raw := range bodies {
var unknown map[uint64][]byte
if err := format.DecodeDMLBodyInto(raw, &body, format.CollectUnknown(&unknown)); err != nil {
panic(err)
}
fmt.Printf("space=%d index_base=%d key=%x unknown=%x\n", body.SpaceID, body.IndexBase, body.Key, unknown)
}
}
Output: space=512 index_base=1 key=9101 unknown=map[] space=513 index_base=0 key= unknown=map[7e:05]
func DecodeFixheaderInto ¶
func DecodeFixheaderInto(b [FixheaderSize]byte, h *Fixheader) error
DecodeFixheaderInto parses the 19 bytes into h, fully overwriting it. It is the zero-allocation core of DecodeFixheader: a reader driving a tight block loop reuses a single Fixheader instead of allocating one per tx block.
func DecodeRaftBodyInto ¶ added in v0.2.0
func DecodeRaftBodyInto(b []byte, dst *RaftBody, opts *DecodeOptions) error
DecodeRaftBodyInto parses a RAFT body map into dst, the core of DecodeRaftBody. It first resets dst to the zero RaftBody, so a dst reused across rows carries no field of a prior row, then sets the fields of the keys b holds. A key with no field goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. The VClock map of a prior row is never reused: a body without a vclock decodes with no allocation, but a vclock allocates a new map. VClockArray is the allocation-free alternative to VClock; the body decoders do not fill one. On error dst holds what was decoded before it.
func DecodeSynchroBodyInto ¶ added in v0.2.0
func DecodeSynchroBodyInto(b []byte, dst *SynchroBody, opts *DecodeOptions) error
DecodeSynchroBodyInto parses a synchro body map at b[0:] into dst, the core of DecodeSynchroBody. It first resets dst to the zero SynchroBody, so a dst reused across rows carries no field of a prior row, then sets the fields of the keys b holds. A key with no field goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. The ConfirmedVClock map of a prior row is never reused: a CONFIRM or ROLLBACK decodes with no allocation, but a PROMOTE or DEMOTE with a vclock allocates a new map. VClockArray is the allocation-free alternative to VClock; the body decoders do not fill one. On error dst holds what was decoded before it.
func DecodeTxBlock ¶
DecodeTxBlock parses a complete tx block at b[0:] and returns:
- rowSlices: byte slices, one per xrow record, into the *plain* payload
- plain: the decompressed (or as-is) payload bytes
- n: total bytes consumed from b (fixheader + on-disk payload)
- err: on CRC mismatch, malformed header, or truncated input
rowSlices aliases plain, and plain is a fresh allocation for a zrow block and aliases b for a row block; see Aliasing in the package doc.
func DecodeVClockArrayInto ¶ added in v0.2.0
func DecodeVClockArrayInto(b []byte, dst *VClockArray) (int, error)
DecodeVClockArrayInto decodes a msgpack map of {uint replica_id: uint lsn} — the encoding of IPROTO_RAFT_VCLOCK and IPROTO_VCLOCK — into dst and returns the number of bytes consumed. It resets dst first, so nothing of a previous value survives, and allocates nothing on success.
A replica id >= VClockMax fails with ErrVClockReplicaID. A repeated id keeps its last LSN, as the VClock decoder does. On error dst is left reset.
func DecodeVyLogBodyInto ¶ added in v0.2.0
func DecodeVyLogBodyInto(b []byte, dst *VyLogBody, opts *DecodeOptions) error
DecodeVyLogBodyInto parses a VYLOG row body into dst, the core of DecodeVyLogBody. It first resets dst to the zero VyLogBody, so a dst reused across rows carries no field of a prior row. The values of Keys alias b; see Aliasing in the package doc. A body key other than IPROTO_TUPLE goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. The Keys map of a prior row is never reused: every record allocates a new one. On error dst holds what was decoded before it.
func DecodeVyPageInfoBodyInto ¶ added in v0.2.0
func DecodeVyPageInfoBodyInto(b []byte, dst *VyPageInfo, opts *DecodeOptions) error
DecodeVyPageInfoBodyInto parses the body of a VY_INDEX_PAGE_INFO row into dst, the zero-allocation core of DecodeVyPageInfoBody. It first resets dst to the zero VyPageInfo, so a reused dst carries no field of a prior row, then sets the fields of the keys b holds. MinKey aliases b; see Aliasing in the package doc. A key with no field goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. On error dst holds what was decoded before it.
func DecodeVyRowIndexBody ¶ added in v0.2.0
DecodeVyRowIndexBody parses the body of a VY_RUN_ROW_INDEX row: the offset of each statement of the page in the rows of the page, uncompressed (vy_row_index_encode, src/box/vy_run.c).
func DecodeVyRunInfoBodyInto ¶ added in v0.2.0
func DecodeVyRunInfoBodyInto(b []byte, dst *VyRunInfo, opts *DecodeOptions) error
DecodeVyRunInfoBodyInto parses the body of a VY_INDEX_RUN_INFO row into dst, the zero-allocation core of DecodeVyRunInfoBody. It first resets dst to the zero VyRunInfo, so a reused dst carries no field of a prior row, then sets the fields of the keys b holds. The raw fields of dst (MinKey, MaxKey, Bloom) alias b; see Aliasing in the package doc. A key with no field goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. On error dst holds what was decoded before it.
func DecodeVyStmtBodyInto ¶ added in v0.2.0
func DecodeVyStmtBodyInto(b []byte, dst *VyStmtBody, opts *DecodeOptions) error
DecodeVyStmtBodyInto parses the body of a statement of a .run file into dst, the zero-allocation core of DecodeVyStmtBody. It first resets dst to the zero VyStmtBody, so a dst reused across statements carries no field of a prior one, then sets the fields of the keys b holds. The raw fields of dst (Tuple, Key, Ops) alias b; see Aliasing in the package doc. A body key with no field goes to opts.OnUnknown, or is skipped when opts or its OnUnknown is nil. On error dst holds what was decoded before it.
func DecodeXRowInto ¶
DecodeXRowInto parses one xrow record at b[0:] into dst, fully overwriting every field (including zeroing BodyRaw / StreamID / Sync / SchemaVersion / Flags / Present so a reused dst carries no state from a prior row), and returns the number of bytes consumed. It is the zero-allocation core of DecodeXRow: a hot read loop can reuse a single *XRow — or an arena slot — across rows with no per-row heap allocation. As with DecodeXRow, dst.BodyRaw aliases b; see Aliasing in the package doc.
func DecompressTx ¶
DecompressTx zstd-decodes payload. The caller may pass a non-nil scratch to amortise allocations across calls; the returned slice is the decompressed bytes (may alias scratch if it was large enough).
func EncodeFixheader ¶
func EncodeFixheader(buf *[FixheaderSize]byte, h *Fixheader)
EncodeFixheader writes a complete 19-byte fixheader into buf.
The three uints are msgpack-encoded with the smallest width that fits (matching `mp_encode_uint` in mp.h). The remaining tail bytes are filled as an mp_str header with N-1 zero bytes of content, where N is the remaining byte count — exactly the recipe in src/box/xlog.c:1100-1105.
EncodeFixheader panics if the encoded uints overflow 15 bytes, which cannot happen for uint32 fields (max 5+5+5 = 15 bytes after the 4-byte magic, leaving exactly 0 padding bytes — and `mp_encode_strl(0)` for a zero-length fixstr is one byte 0xa0, but since `padding == 0` the writer skips the str entirely, matching Tarantool's `if (padding > 0)`).
func EncodeMeta ¶
EncodeMeta writes the meta header. The format-version line is m.FormatVer when set (it must be "0.13" or the legacy "0.12", else ErrMetaBadVersion), and defaults to FormatVersion ("0.13") when m.FormatVer is empty — so the writer's freshly-built metas still emit 0.13, while a rewrite can preserve or retarget the source's version. The VClock line is always emitted (even "VClock: {}", required by Tarantool's signature check); an empty PrevVClock line is omitted. The header ends with a blank line.
Example ¶
ExampleEncodeMeta writes a journal meta header to a byte buffer. The header is plain text terminated by a blank line; the format version defaults to "0.13" and an empty PrevVClock is omitted.
package main
import (
"bytes"
"os"
"github.com/google/uuid"
"github.com/tarantool/go-xlog/format"
)
func main() {
meta := &format.Meta{
Filetype: format.FiletypeXLOG,
Version: "example",
InstanceUUID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
VClock: format.VClock{1: 2},
}
var buf bytes.Buffer
if err := format.EncodeMeta(&buf, meta); err != nil {
panic(err)
}
_, _ = os.Stdout.Write(buf.Bytes())
}
Output: XLOG 0.13 Version: example Instance: 11111111-2222-3333-4444-555555555555 VClock: {1: 2}
func EncodeTxBlock ¶
EncodeTxBlock encodes rows into a complete on-disk tx block: fixheader + payload. The payload is each row's EncodeXRow output concatenated in order. If opts.Compression says to compress (the payload meets its threshold and it is not Disabled), the payload is zstd-compressed and the fixheader magic is ZRowMarker; otherwise RowMarker is used. The CRC32C in the fixheader is computed over the on-disk payload bytes (post-compression for zrow, plain for row) per src/box/xlog.c:1086,1165.
func EncodeXRow ¶
EncodeXRow appends a complete xrow record to buf and returns the new buf. The record consists of:
- a header map (per src/box/xrow.c:331-444)
- the BodyRaw bytes appended verbatim
Encoding rules (src/box/xrow.c:402-410):
- IPROTO_REQUEST_TYPE is always present.
- IPROTO_SYNC / IPROTO_SCHEMA_VERSION / IPROTO_REPLICA_ID / IPROTO_LSN / IPROTO_GROUP_ID / IPROTO_TIMESTAMP / IPROTO_STREAM_ID present iff nonzero.
- Single-statement tx (TSN == LSN && IsCommit && TSN != 0): omit IPROTO_TSN AND clear IPROTO_FLAG_COMMIT in the encoded flags.
- Multi-statement tx (TSN != LSN || !IsCommit): emit IPROTO_TSN as the diff (LSN - TSN). If IsCommit && TSN != LSN (last row of multi-stmt), OR IPROTO_FLAG_COMMIT back into the encoded flags.
- IPROTO_FLAGS is emitted iff the resulting flags value is non-zero.
The caller is responsible for BodyRaw being a valid msgpack map (or nil for NOP rows).
EncodeXRow never reads r.Present: a key is emitted by the rules above, from the value of its field alone. Present is decode-only, and every XRow built by hand has Present == 0; gating a key on it would encode such rows with an empty header (see XRow.Present).
Caveat: this function mirrors Tarantool's `if (header->tsn != 0)` guard (src/box/xrow.c:402). A row with LSN == TSN == 0 will skip the TSN/COMMIT emission block entirely. For a single-statement tx that is harmless — the decoder also infers `is_commit = true` when IPROTO_TSN is absent. For a multi-row tx where some rows legitimately have LSN == 0, the encoded output would be ambiguous (Tarantool would treat each as an independent single-stmt tx). The writer package's assignTxIDs avoids this by setting TSN = rows[0].LSN before encoding; callers using EncodeXRow directly must ensure rows in a multi-row tx have non-zero LSN.
The keys are emitted in the order of xrow_header_encode (src/box/xrow.c:338-452): REQUEST_TYPE, SYNC, REPLICA_ID, GROUP_ID, LSN, TIMESTAMP, TSN, STREAM_ID, FLAGS, so a header Tarantool would write encodes to the same bytes. xrow_header_encode never writes IPROTO_SCHEMA_VERSION; only iproto_header_encode (src/box/xrow.c:497-515) does, in a response header, right after IPROTO_SYNC, so EncodeXRow emits it there too.
Example ¶
ExampleEncodeXRow round-trips a single-statement row through the byte codec. EncodeXRow appends the header map plus the verbatim body bytes; DecodeXRow parses them back and, because no IPROTO_TSN was emitted, infers the row is a committed single-statement transaction.
package main
import (
"fmt"
"github.com/tarantool/go-iproto"
"github.com/tarantool/go-xlog/format"
)
func main() {
// A minimal valid msgpack body: the one-element array [1].
body := []byte{0x91, 0x01}
row := &format.XRow{Type: iproto.IPROTO_INSERT, LSN: 1, BodyRaw: body}
encoded, err := format.EncodeXRow(nil, row)
if err != nil {
panic(err)
}
decoded, n, err := format.DecodeXRow(encoded)
if err != nil {
panic(err)
}
fmt.Printf("type=%s lsn=%d commit=%t body=%d bytes (consumed %d of %d)\n",
format.TypeName(decoded.Type), decoded.LSN, decoded.IsCommit(),
len(decoded.BodyRaw), n, len(encoded))
}
Output: type=INSERT lsn=1 commit=true body=2 bytes (consumed 7 of 7)
func TypeName ¶
TypeName returns the short Tarantool name of a durable request type ("INSERT", "REPLACE", "RAFT", …). It is iproto.Type.String() with the "IPROTO_" prefix trimmed; the vinyl row types are named as Tarantool's iproto_type_name names them ("RUNINFO", "PAGEINFO", "ROWINDEX"); unknown types render as iproto's Type(n) form.
Types ¶
type Compression ¶
type Compression struct {
// Disabled turns compression off entirely: every block is written plain
// (RowMarker) regardless of size.
Disabled bool
// Level is the zstd compression level. Zero means ZstdLevel (Tarantool's
// 3). Passed to klauspost's EncoderLevelFromZstd.
Level int
// Threshold is the minimum plain-payload size in bytes at which a block is
// compressed; smaller blocks are written plain. Zero means
// CompressThreshold (2 KiB). Ignored when Disabled.
Threshold int
}
Compression is a tx-block compression policy. Compression is applied at block granularity (a whole tx block is zstd-compressed or not — there is no per-row compression). The zero value means "default": zstd at ZstdLevel over any payload of at least CompressThreshold bytes — i.e. exactly Tarantool's behaviour. A writer holds one policy; EncodeTxBlock / AppendTxBlock take one.
func (Compression) Compresses ¶
func (c Compression) Compresses(plainLen int) bool
Compresses reports whether a plain payload of plainLen bytes is compressed under this policy (not Disabled and at or above the effective threshold).
func (Compression) ResolvedLevel ¶
func (c Compression) ResolvedLevel() int
ResolvedLevel returns the effective zstd level, substituting ZstdLevel for a zero Level.
type DMLBody ¶
type DMLBody struct {
// SpaceID is IPROTO_SPACE_ID, the space the request applies to.
SpaceID uint32
// IndexID is IPROTO_INDEX_ID, the index a DELETE or UPDATE looks its key
// up in; 0 (the primary index) when the body carries none. Tarantool
// rebinds a request on a secondary index to the primary key before it
// writes the WAL, so a journal row normally has none.
IndexID uint32
// IndexBase is IPROTO_INDEX_BASE of an UPDATE or UPSERT: the number of
// the first field in the field numbers of Ops (or of Tuple, which holds
// the operations of an UPDATE). 0 when the body carries none; a request
// from Lua carries 1.
IndexBase uint32
// Tuple is IPROTO_TUPLE, raw msgpack: the tuple of an INSERT, REPLACE or
// UPSERT, the operations of an UPDATE. It aliases the input.
Tuple []byte
// Key is IPROTO_KEY, raw msgpack: the key of an UPDATE or DELETE. It
// aliases the input.
Key []byte
// Ops is IPROTO_OPS, raw msgpack: the operations of an UPSERT. It
// aliases the input.
Ops []byte
// OldTuple is IPROTO_OLD_TUPLE, raw msgpack: the tuple before the change,
// written for replica-applied and audited rows. It aliases the input.
OldTuple []byte
// NewTuple is IPROTO_NEW_TUPLE, raw msgpack: the tuple after the change,
// written for replica-applied and audited rows. It aliases the input.
NewTuple []byte
// Arrow is IPROTO_ARROW of an INSERT_ARROW, raw msgpack: the whole
// MP_EXT value, ext header included, holding the Arrow IPC stream. It
// aliases the input.
Arrow []byte
}
DMLBody is the typed body of an INSERT/REPLACE/UPDATE/DELETE/UPSERT row (and of INSERT_ARROW). Keys come from src/box/iproto_constants.h and are written by Tarantool's xrow_encode_dml (src/box/xrow.c). Each variable- length field (Tuple, Key, Ops, OldTuple, NewTuple, Arrow) is held as raw msgpack that aliases the input, so callers who only need the SpaceID pay no decode cost; see Aliasing in the package doc.
A body key with no field (IPROTO_TUPLE_META, IPROTO_BEGIN_KEY, ...) is skipped, unless DecodeOptions.OnUnknown of DecodeDMLBodyInto receives it.
func DecodeDMLBody ¶
DecodeDMLBody parses a DML body map at b[0:]. It returns an error if b is not a msgpack map header. It is DecodeDMLBodyInto into a new DMLBody, with no options: a key with no field is skipped.
func (*DMLBody) Clone ¶ added in v0.2.0
Clone returns a deep copy of b: its Tuple, Key, Ops, OldTuple, NewTuple and Arrow are copied into one new allocation. A nil receiver returns nil.
type DecodeOptions ¶ added in v0.2.0
type DecodeOptions struct {
// OnUnknown, if non-nil, is called for each body key with no typed
// field, in the order of the body. raw is the msgpack value of the key
// and aliases the input; see Aliasing in the package doc. Nil means
// unknown keys are skipped, and the decode stays allocation-free.
//
// A path that decodes a body and encodes it again must set OnUnknown and
// write the keys it receives back, or the encoded body loses them.
OnUnknown func(key uint64, raw []byte)
}
DecodeOptions configure the body decoders that fill a caller-owned struct (DecodeDMLBodyInto, DecodeRaftBodyInto, DecodeSynchroBodyInto, DecodeVyLogBodyInto, DecodeVyStmtBodyInto, DecodeVyRunInfoBodyInto and DecodeVyPageInfoBodyInto). A nil *DecodeOptions is the zero value: unknown keys are skipped.
func CollectUnknown ¶ added in v0.2.0
func CollectUnknown(dst *map[uint64][]byte) *DecodeOptions
CollectUnknown returns DecodeOptions whose OnUnknown stores every unknown body key and its raw msgpack value into *dst, making the map on the first one: *dst stays nil for a body with no unknown key. The values alias the input; see Aliasing in the package doc. A later key replaces an earlier duplicate. *dst is not cleared between decodes; set it to nil to start over.
type Filetype ¶
type Filetype string
Filetype is the leading text token in the meta header (`XLOG`, `SNAP`, `VYLOG`, `RUN`, `INDEX`). It is the discriminator the reader uses to pick which body decoders are valid for the rows inside.
type Fixheader ¶
Fixheader is the 19-byte preface to every tx block. The wire shape (src/box/xlog.c:1095-1105) is:
bytes 0-3 : Magic (4-byte raw)
mp_uint : Len — payload byte count after the fixheader
mp_uint : CRC32P — always 0 (legacy, not validated on read)
mp_uint : CRC32C — checksum over the on-disk payload bytes
mp_str(N) : padding — N=(remaining-bytes-1) zero bytes, sized so the
whole fixheader is exactly FixheaderSize
func DecodeFixheader ¶
func DecodeFixheader(b [FixheaderSize]byte) (*Fixheader, error)
DecodeFixheader parses the 19 bytes and returns the Fixheader. It validates that the magic is one of {RowMarker, ZRowMarker, EOFMarker} and that the three uints and the trailing padding consume exactly FixheaderSize bytes. The padding bytes themselves are not inspected — Tarantool's writer always zeroes them but readers tolerate any content since it is mp_str-typed data (strict on shape, but the content is not inspected).
type Meta ¶
type Meta struct {
Filetype Filetype
FormatVer string
Version string
InstanceUUID uuid.UUID
VClock VClock
PrevVClock VClock
// Extras captures any unknown `Key: value` line we did not recognise,
// preserving it across rewrite round-trips (forward-compat catch-all).
Extras map[string]string
}
Meta is the parsed text header that every xlog/snap/vylog/run/index file begins with. The on-disk layout is:
<Filetype>\n # XLOG | SNAP | VYLOG | RUN | INDEX
<FormatVer>\n # "0.13" (or "0.12" with AcceptV012)
Version: <ver>\n # Tarantool's PACKAGE_VERSION string
Instance: <uuid>\n # 8-4-4-4-12 hex UUID (alias: "Server: ...")
VClock: {id: lsn, ...}\n # ALWAYS present; empty renders "VClock: {}"
PrevVClock: {id: lsn, ...}\n# OMITTED when empty / for SNAP files
\n # blank line terminates the header
Source: src/box/xlog.c:115-122, 155 (writer) and the meta_parse path.
func DecodeMeta ¶
func DecodeMeta(r *bufio.Reader, opts MetaOptions) (*Meta, error)
DecodeMeta reads a meta header from r up to and including the blank-line terminator. The reader is left positioned at the first byte after the terminator (i.e. the magic byte of tx block 1 or the EOF marker).
Strictness defaults: unrecognised filetypes / format versions return ErrMetaBadFormat / ErrMetaBadVersion.
func (*Meta) Clone ¶
Clone returns a deep copy of m. The returned *Meta shares no maps with the receiver: VClock, PrevVClock, and Extras are independently allocated. A nil receiver returns nil.
Used by tools.RewriteMeta to hand a mutation-safe copy to user transform functions without aliasing the parsed source meta.
type MetaOptions ¶
type MetaOptions struct {
// AcceptV012 accepts the legacy "0.12" format version in addition to
// the current "0.13". Default false: strict on FormatVersion.
AcceptV012 bool
}
MetaOptions controls strictness of DecodeMeta.
type OpCode ¶ added in v0.2.0
type OpCode byte
OpCode is the opcode of an update operation: the one-character string an operation array starts with.
const ( // OpSet assigns Value to the field: [=, field, value]. OpSet OpCode = '=' // OpInsert inserts Value before the field: [!, field, value]. OpInsert OpCode = '!' // OpAdd adds the number Value to the field: [+, field, value]. OpAdd OpCode = '+' // OpSubtract subtracts the number Value from the field: [-, field, value]. OpSubtract OpCode = '-' // OpAnd is the bitwise AND of the field and the unsigned Value: // [&, field, value]. OpAnd OpCode = '&' // OpOr is the bitwise OR of the field and the unsigned Value: // [|, field, value]. OpOr OpCode = '|' // OpXor is the bitwise XOR of the field and the unsigned Value: // [^, field, value]. OpXor OpCode = '^' // OpSplice replaces Offset bytes of the string field at Position with the // string Value: [:, field, position, offset, value]. OpSplice OpCode = ':' // OpDelete deletes Value fields starting with the field, Value being a // positive integer: [#, field, count]. OpDelete OpCode = '#' )
The opcodes Tarantool knows (xrow_update_op_by).
type Operation ¶ added in v0.2.0
type Operation struct {
// Op is the opcode.
Op OpCode
// Field is the number of the field the operation addresses when
// FieldName is nil, and 0 otherwise.
Field int32
// FieldName is the name or JSON path of the field the operation
// addresses, nil when it addresses it by number. It aliases the input.
// Tarantool writes a string; a bin is accepted too.
FieldName []byte
// HasSplice reports that Position and Offset are set: the operation is
// a splice.
HasSplice bool
// Position is the position in the string of a splice where Offset bytes
// are cut and Value pasted (Tarantool's offset); a negative one counts
// from the end of the string.
Position int32
// Offset is the number of bytes a splice cuts (Tarantool's cut_length).
Offset int32
// Value is the raw msgpack of the last element of the operation: the
// value of =, !, +, -, &, |, ^, the number of fields of # and the string
// : pastes. It aliases the input.
Value []byte
}
Operation is a decoded update operation. FieldName and Value alias the input; see Aliasing in the package doc.
Field numbers and the splice position are as written in the request. Tarantool subtracts the index base of the request from a non-negative one: IPROTO_INDEX_BASE (DMLBody.IndexBase), which box.space:update and :upsert set to 1 and which is 0 when absent. A negative one counts from the end, -1 being the last field. The operations of a vinyl run (VyStmtBody.Ops) are 0-based.
func CloneOps ¶ added in v0.2.0
CloneOps returns a deep copy of ops: a new slice whose operations share no memory with ops, their FieldName and Value copied into one allocation. nil returns nil, and an empty non-nil ops an empty non-nil slice.
func CloneOpsInto ¶ added in v0.2.0
CloneOpsInto is CloneOps into dst, as DecodeOpsInto is DecodeOps: it deep-copies the operations of src into dst[:len(src)], reusing the capacity of dst and, by Operation.CloneInto, that of the operations it holds up to its capacity, and returns the result. It does not allocate when dst holds as many operations as src, each with the capacity for its bytes. The operations of dst must own their slices, as for CloneInto. A nil src returns nil.
func DecodeOps ¶ added in v0.2.0
DecodeOps decodes the operations of an UPDATE or UPSERT: the IPROTO_TUPLE of an UPDATE (DMLBody.Tuple - Tarantool keeps the UPDATE operations under IPROTO_TUPLE for legacy reasons, src/box/iproto_constants.h) or the IPROTO_OPS of an UPSERT (DMLBody.Ops). The Ops of a vinyl UPSERT (VyStmtBody.Ops) is an array of such operation lists; DecodeOps decodes one list.
It checks the shape of each operation - its number of elements and the types of its opcode, field and splice arguments - not the type of its value, which Tarantool checks when it applies the operation. Bytes after the array are ignored. The FieldName and Value of every operation alias b; see Aliasing in the package doc.
func DecodeOpsInto ¶ added in v0.2.0
DecodeOpsInto is DecodeOps decoding into dst[:0], reusing its capacity: it does not allocate when cap(dst) holds every operation. It returns the operations, or dst[:0] and an error. The FieldName and Value of every operation alias b; see Aliasing in the package doc.
func (*Operation) Clone ¶ added in v0.2.0
Clone returns a deep copy of op: its FieldName and Value are copied into one new allocation. It returns a value, as DecodeOps does.
type Present ¶ added in v0.2.0
type Present uint32
Present is a set of the header keys of a row, one bit per key the decoder recognises. The bit of a key is 1 << its IPROTO code, so PresentLSN is 1 << IPROTO_LSN.
const ( PresentType Present = 1 << iproto.IPROTO_REQUEST_TYPE PresentSync Present = 1 << iproto.IPROTO_SYNC PresentReplicaID Present = 1 << iproto.IPROTO_REPLICA_ID PresentLSN Present = 1 << iproto.IPROTO_LSN PresentTimestamp Present = 1 << iproto.IPROTO_TIMESTAMP PresentSchemaVersion Present = 1 << iproto.IPROTO_SCHEMA_VERSION PresentGroupID Present = 1 << iproto.IPROTO_GROUP_ID // PresentTSN is set only when IPROTO_TSN is on the wire. The TSN the // decoder infers from the LSN of a single-statement row, which carries no // IPROTO_TSN, does not set it. PresentTSN Present = 1 << iproto.IPROTO_TSN // PresentFlags is set only when IPROTO_FLAGS is on the wire. The // IPROTO_FLAG_COMMIT the decoder infers for a single-statement row does // not set it. PresentFlags Present = 1 << iproto.IPROTO_FLAGS PresentStreamID Present = 1 << iproto.IPROTO_STREAM_ID )
The header keys of a row recorded in XRow.Present.
type RaftBody ¶
type RaftBody struct {
// Term is IPROTO_RAFT_TERM, the Raft term of the instance.
Term uint64
// Vote is IPROTO_RAFT_VOTE, the instance voted for in Term, 0 for none.
Vote uint64
// State is IPROTO_RAFT_STATE, the Raft state (follower, candidate,
// leader).
State uint32
// VClock is IPROTO_RAFT_VCLOCK, the vclock of the instance, nil when the
// body carries none.
VClock VClock
// LeaderID is IPROTO_RAFT_LEADER_ID, the leader the instance knows of.
LeaderID uint32
// IsLeaderSeen is IPROTO_RAFT_IS_LEADER_SEEN, whether the instance sees
// the leader directly.
IsLeaderSeen bool
}
RaftBody is the typed body of a IPROTO_RAFT row. Keys come from src/box/iproto_constants.h:465-471. A body key with no field is skipped, unless DecodeOptions.OnUnknown of DecodeRaftBodyInto receives it.
func DecodeRaftBody ¶
DecodeRaftBody parses a RAFT body map. It is DecodeRaftBodyInto into a new RaftBody, with no options: a key with no field is skipped.
type SynchroBody ¶
type SynchroBody struct {
// ReplicaID is the owner of the synchronous queue (queue_owner_id): the
// instance whose transactions the row confirms or rolls back, and for a
// PROMOTE/DEMOTE the previous owner. It is not the issuer of the row,
// which is the replica id of the row header.
ReplicaID uint32
// LSN is the LSN of the owner the row applies to: a CONFIRM confirms the
// transactions up to it, a ROLLBACK rolls back those from it on, and a
// PROMOTE/DEMOTE confirms up to it and rolls back the rest.
LSN int64
// Term is the Raft term a PROMOTE/DEMOTE is issued in (IPROTO_TERM).
// CONFIRM and ROLLBACK carry none.
Term uint64
// ConfirmedVClock is the confirmed LSN of every previous queue owner
// (IPROTO_VCLOCK) of a PROMOTE/DEMOTE, nil when the row carries none.
ConfirmedVClock VClock
}
SynchroBody is the typed body of the synchronous replication rows: IPROTO_RAFT_CONFIRM, IPROTO_RAFT_ROLLBACK, IPROTO_RAFT_PROMOTE and IPROTO_RAFT_DEMOTE. Tarantool's xrow_encode_synchro writes a map of ordinary IPROTO keys (src/box/xrow.c):
- CONFIRM and ROLLBACK: IPROTO_REPLICA_ID and IPROTO_LSN.
- PROMOTE and DEMOTE: IPROTO_REPLICA_ID, IPROTO_LSN, IPROTO_TERM and, when it is set, IPROTO_VCLOCK. The PROMOTE that checkpoints the limbo in a snapshot carries the vclock.
Any other key is skipped, unless DecodeOptions.OnUnknown of DecodeSynchroBodyInto receives it.
func DecodeSynchroBody ¶
func DecodeSynchroBody(b []byte) (*SynchroBody, error)
DecodeSynchroBody parses a synchro body map at b[0:]. It is DecodeSynchroBodyInto into a new SynchroBody, with no options: a key with no field is skipped.
func (*SynchroBody) Clone ¶ added in v0.2.0
func (b *SynchroBody) Clone() *SynchroBody
Clone returns a deep copy of b. A SynchroBody aliases nothing, but its ConfirmedVClock is a map, which the copy gets its own of. A nil receiver returns nil.
func (*SynchroBody) CloneInto ¶ added in v0.2.0
func (b *SynchroBody) CloneInto(dst *SynchroBody)
CloneInto deep-copies b into dst, overwriting every field. The ConfirmedVClock of dst is a new map, or nil when that of b is. dst may be b.
type TxOptions ¶
type TxOptions struct {
// Compression is the block compression policy (zstd level + threshold, or
// disabled). The zero value compresses at ZstdLevel over payloads of at
// least CompressThreshold bytes (Tarantool's behaviour); set Disabled for
// byte-inspectable plain output, or Level/Threshold to tune.
Compression Compression
}
TxOptions controls EncodeTxBlock / AppendTxBlock behaviour.
type VClock ¶
VClock is a Lamport vector keyed by replica id. Empty map means an "empty" vclock — Tarantool's `vclock_is_set` returns false in that case. The meta header omits the `PrevVClock:` line when empty, but always emits the `VClock:` line (as `VClock: {}` when empty), because Tarantool's recovery validates the filename signature against it.
Tarantool stores vclocks as a fixed-size array internally but the textual ("{id: lsn, id: lsn}") and msgpack-map encodings are sparse, so a Go map is the natural representation here.
Example ¶
ExampleVClock shows the two everyday VClock operations: Signature (the arithmetic sum of per-replica LSNs, which names the file on disk) and the Tarantool textual form via String.
package main
import (
"fmt"
"github.com/tarantool/go-xlog/format"
)
func main() {
v := format.VClock{1: 10, 2: 5}
fmt.Println(v) // Tarantool "{id: lsn, ...}" form, ids ascending.
fmt.Println(v.Signature()) // 10 + 5 — the filename signature.
}
Output: {1: 10, 2: 5} 15
func ParseVClock ¶
ParseVClock decodes a Tarantool vclock literal (`{}`, `{1: 42}`, or `{0: 0, 1: 42, 2: 7}`). Whitespace around braces, commas, and colons is tolerated. Returns an empty (non-nil) VClock for `{}`.
Example ¶
ExampleParseVClock parses a Tarantool vclock literal back into a VClock and recomputes its signature — the inverse of VClock.String.
package main
import (
"fmt"
"github.com/tarantool/go-xlog/format"
)
func main() {
v, err := format.ParseVClock("{0: 0, 1: 42}")
if err != nil {
panic(err)
}
fmt.Println(v)
fmt.Println(v.Signature())
}
Output: {0: 0, 1: 42} 42
func (VClock) Compare ¶
Compare implements the standard vector-clock partial order.
Returns:
(-1, true) — v < o on every replica (and strictly less on at least one)
( 0, true) — v == o on every replica
( 1, true) — v > o on every replica (and strictly greater on at least one)
( 0, false) — incomparable (e.g. {1:5,2:3} vs {1:3,2:5})
Missing replicas in either side are treated as LSN 0.
Example ¶
ExampleVClock_Compare demonstrates the vector-clock partial order. Two vclocks are comparable only when one dominates the other on every replica; mixed advances (one ahead on replica 1, the other on replica 2) are incomparable.
package main
import (
"fmt"
"github.com/tarantool/go-xlog/format"
)
func main() {
a := format.VClock{1: 5, 2: 3}
b := format.VClock{1: 6, 2: 3}
c := format.VClock{1: 3, 2: 5}
ord, ok := a.Compare(b)
fmt.Printf("a vs b: ord=%d ok=%t\n", ord, ok) // a < b
ord, ok = a.Compare(c)
fmt.Printf("a vs c: ord=%d ok=%t\n", ord, ok) // incomparable
}
Output: a vs b: ord=-1 ok=true a vs c: ord=0 ok=false
func (VClock) Signature ¶
Signature returns the arithmetic sum of all per-replica LSNs — Tarantool's `vclock_sum` (src/lib/vclock/vclock.h:230). Used for the `<signature>.<ext>` filename naming.
func (VClock) String ¶
String formats the vclock in Tarantool's `vclock_to_string` form: `{id: lsn, id: lsn}` with ids sorted ascending. An empty vclock renders as `{}` — which matches Tarantool's output for the empty case (e.g. `simple.xlog`'s meta has `VClock: {}` only because the file was pre-bootstrap; in normal operation `vclock_is_set` would gate the line out entirely, see meta.go).
Src/lib/vclock/vclock.c:55-72.
type VClockArray ¶ added in v0.2.0
type VClockArray struct {
// contains filtered or unexported fields
}
VClockArray is a dense vclock: Tarantool's struct vclock (src/lib/vclock/vclock.h:71-80) without the cached signature. It holds one LSN per replica id 0..VClockMax-1 and a bitmap of the ids that are set, so it copies by value and allocates nothing — the hot-path counterpart of VClock, which is a map. Convert at the boundary with VClock and VClockArrayFrom.
The zero value is an empty vclock. The LSN of an id that is not set is always 0, so two VClockArrays are == exactly when they set the same ids to the same LSNs.
The methods take a pointer receiver so that a call does not copy the 264-byte value; print a VClockArray through a pointer (fmt.Print(&v)) for String to apply.
func VClockArrayFrom ¶ added in v0.2.0
func VClockArrayFrom(v VClock) (VClockArray, error)
VClockArrayFrom converts v to a VClockArray. It returns ErrVClockReplicaID if v holds an id >= VClockMax. A nil or empty v gives the zero VClockArray.
func (*VClockArray) Compare ¶ added in v0.2.0
func (v *VClockArray) Compare(o *VClockArray) (int, bool)
Compare orders v against o with the semantics of VClock.Compare: (-1, true) if v is before o, (0, true) if they are equal, (1, true) if v is after o, and (0, false) if they are incomparable. An id that is not set counts as LSN 0. This is Tarantool's vclock_compare (src/lib/vclock/vclock.h:310-344).
func (*VClockArray) Get ¶ added in v0.2.0
func (v *VClockArray) Get(id uint32) (int64, bool)
Get returns the LSN of replica id and whether id is set. An id >= VClockMax is never set.
func (*VClockArray) Has ¶ added in v0.2.0
func (v *VClockArray) Has(id uint32) bool
Has reports whether replica id is set.
func (*VClockArray) IsZero ¶ added in v0.2.0
func (v *VClockArray) IsZero() bool
IsZero reports whether no replica id is set — the zero VClockArray.
func (*VClockArray) Len ¶ added in v0.2.0
func (v *VClockArray) Len() int
Len returns the number of replica ids that are set.
func (*VClockArray) Reset ¶ added in v0.2.0
func (v *VClockArray) Reset()
Reset clears every replica id, making v the zero VClockArray.
func (*VClockArray) Set ¶ added in v0.2.0
func (v *VClockArray) Set(id uint32, lsn int64) error
Set sets the LSN of replica id, marking it set even when lsn is 0. It returns ErrVClockReplicaID, leaving v unchanged, if id >= VClockMax.
func (*VClockArray) Signature ¶ added in v0.2.0
func (v *VClockArray) Signature() int64
Signature returns the sum of the LSNs — Tarantool's vclock_sum (src/lib/vclock/vclock.h:236), the same value as VClock.Signature.
func (*VClockArray) String ¶ added in v0.2.0
func (v *VClockArray) String() string
String formats v as VClock.String does — Tarantool's vclock_to_string form `{id: lsn, id: lsn}` with ids ascending, `{}` when empty (src/lib/vclock/vclock.c:55-72).
func (*VClockArray) VClock ¶ added in v0.2.0
func (v *VClockArray) VClock() VClock
VClock converts v to a VClock map. An empty v gives an empty, non-nil VClock, as decoding an empty msgpack vclock map does.
type VyKey ¶
type VyKey uint64
VyKey is a vy_log body-map key. The VYLOG body is a msgpack map keyed by these integers (Tarantool's vy_log_key enum). Go-iproto does not cover these, so they are defined here. See src/box/vy_log.c:71-89.
const ( VyKeyLSMID VyKey = 0 VyKeyRangeID VyKey = 1 VyKeyRunID VyKey = 2 VyKeyBegin VyKey = 3 VyKeyEnd VyKey = 4 VyKeyIndexID VyKey = 5 VyKeySpaceID VyKey = 6 VyKeyDef VyKey = 7 VyKeySliceID VyKey = 8 VyKeyDumpLSN VyKey = 9 VyKeyGCLSN VyKey = 10 VyKeyTruncateCount VyKey = 11 VyKeyCreateLSN VyKey = 12 VyKeyModifyLSN VyKey = 13 VyKeyDropLSN VyKey = 14 VyKeyGroupID VyKey = 15 VyKeyDumpCount VyKey = 16 )
vy_log body keys (Tarantool's vy_log_key enum, src/box/vy_log.c:71-89).
type VyLogBody ¶
type VyLogBody struct {
// Type is the record type.
Type VyLogType
// Keys holds every field of the record as raw msgpack, keyed by
// vy_log_key. The caller decodes the fields it needs lazily. Its values
// alias the input.
Keys map[VyKey][]byte
}
VyLogBody is the typed body of a VYLOG row: one vinyl log record. Tarantool's vy_log_record_encode (src/box/vy_log.c) stores a record as an IPROTO_INSERT of no space whose tuple is a pair of the record type and a map of its fields keyed by vy_log_key:
{IPROTO_TUPLE: [type, {VyKey: value, ...}]}
A field equal to zero is not written: an absent key reads as zero (the first LSM tree, for instance, has id 0 and no VyKeyLSMID).
func DecodeVyLogBody ¶
DecodeVyLogBody parses a VYLOG row body. Body keys other than IPROTO_TUPLE are ignored, as Tarantool's vy_log_record_decode does. It is DecodeVyLogBodyInto into a new VyLogBody, with no options.
type VyLogType ¶ added in v0.2.0
type VyLogType uint32
VyLogType is the type of a vinyl log record (Tarantool's vy_log_record_type enum, src/box/vy_log.h).
const ( VyLogCreateLSM VyLogType = 0 VyLogDropLSM VyLogType = 1 VyLogInsertRange VyLogType = 2 VyLogDeleteRange VyLogType = 3 VyLogPrepareRun VyLogType = 4 VyLogCreateRun VyLogType = 5 VyLogDropRun VyLogType = 6 VyLogForgetRun VyLogType = 7 VyLogInsertSlice VyLogType = 8 VyLogDeleteSlice VyLogType = 9 VyLogDumpLSM VyLogType = 10 VyLogSnapshot VyLogType = 11 VyLogTruncateLSM VyLogType = 12 VyLogModifyLSM VyLogType = 13 VyLogForgetLSM VyLogType = 14 VyLogPrepareLSM VyLogType = 15 VyLogRebootstrap VyLogType = 16 VyLogAbortRebootstrap VyLogType = 17 )
Vinyl log record types (src/box/vy_log.h).
type VyPageInfo ¶ added in v0.2.0
type VyPageInfo struct {
// Offset is the offset of the page in the .run file: where its tx block
// starts.
Offset uint64
// Size is the size of the tx block of the page in the file.
Size uint32
// UnpackedSize is the size of the rows of the page, uncompressed.
UnpackedSize uint32
// RowCount is the number of statements of the page.
RowCount uint32
// MinKey is the smallest key of the page, a raw msgpack array. It
// aliases the input.
MinKey []byte
// RowIndexOffset is the offset of the VY_RUN_ROW_INDEX row in the rows of
// the page, uncompressed.
RowIndexOffset uint32
}
VyPageInfo is the typed body of a VY_INDEX_PAGE_INFO row of a .index file (vy_page_info_encode, src/box/vy_run.c).
func DecodeVyPageInfoBody ¶ added in v0.2.0
func DecodeVyPageInfoBody(b []byte) (*VyPageInfo, error)
DecodeVyPageInfoBody parses the body of a VY_INDEX_PAGE_INFO row. Like Tarantool's vy_page_info_decode it requires every key. It is DecodeVyPageInfoBodyInto into a new VyPageInfo, with no options: a key with no field is skipped.
func (*VyPageInfo) Clone ¶ added in v0.2.0
func (page *VyPageInfo) Clone() *VyPageInfo
Clone returns a deep copy of page: its MinKey is a new slice. A nil receiver returns nil.
func (*VyPageInfo) CloneInto ¶ added in v0.2.0
func (page *VyPageInfo) CloneInto(dst *VyPageInfo)
CloneInto deep-copies page into dst, overwriting every field, and copies page.MinKey into the array of dst.MinKey when it has the capacity. dst must own its slices; see Aliasing in the package doc. dst may be page.
type VyRunInfo ¶ added in v0.2.0
type VyRunInfo struct {
// MinKey and MaxKey are the smallest and the greatest key of the run,
// raw msgpack arrays. They alias the input.
MinKey []byte
MaxKey []byte
// MinLSN and MaxLSN bound the LSNs of the statements of the run.
MinLSN int64
MaxLSN int64
// PageCount is the number of pages of the run, and of VY_INDEX_PAGE_INFO
// rows after this one.
PageCount uint32
// BloomVersion is the version of the bloom filter: 3 for the current
// one, 1 or 2 for the legacy ones, 0 when the run has none.
BloomVersion uint8
// Bloom is the bloom filter of the keys of the run, raw msgpack. It
// aliases the input.
Bloom []byte
// StmtStat counts the statements of the run by type.
StmtStat VyStmtStat
}
VyRunInfo is the typed body of a VY_INDEX_RUN_INFO row, the first row of a .index file (vy_run_info_encode, src/box/vy_run.c).
func DecodeVyRunInfoBody ¶ added in v0.2.0
DecodeVyRunInfoBody parses the body of a VY_INDEX_RUN_INFO row. Like Tarantool's vy_run_info_decode it requires the keys, the LSNs and the page count. It is DecodeVyRunInfoBodyInto into a new VyRunInfo, with no options: a key with no field is skipped.
func (*VyRunInfo) Clone ¶ added in v0.2.0
Clone returns a deep copy of info: its MinKey, MaxKey and Bloom are copied into one new allocation. A nil receiver returns nil.
type VyStmtBody ¶ added in v0.2.0
type VyStmtBody struct {
// SpaceID is the IPROTO_SPACE_ID of the row. Tarantool does not write it
// to a run, so it is normally 0; the space and index of a run are in its
// path, <vinyl_dir>/<space_id>/<index_id>/<run_id>.run.
SpaceID uint32
// Tuple is the IPROTO_TUPLE of an INSERT, REPLACE or UPSERT, raw msgpack.
// In a primary index run it is the whole tuple; in a secondary index run
// it is the key of the tuple: the parts of the secondary index followed
// by those of the primary one. It aliases the input.
Tuple []byte
// Key is the IPROTO_KEY of a DELETE, raw msgpack: the key the DELETE
// removes, in the parts of the index (as Tuple of a secondary index). It
// aliases the input.
Key []byte
// Ops is the IPROTO_OPS of an UPSERT, raw msgpack. Unlike the operations
// of an UPSERT request it is an array of operation lists, one per UPSERT
// squashed into the statement, oldest first: when the key does not exist,
// Tuple is inserted and the first list is not applied, and each other list
// is applied in turn. Field numbers are 0-based. It aliases the input.
Ops []byte
// Flags are the statement flags of IPROTO_TUPLE_META.
Flags VyStmtFlags
}
VyStmtBody is the typed body of a statement of a .run file: an INSERT, REPLACE, DELETE or UPSERT row written by Tarantool's vy_stmt_encode_primary or vy_stmt_encode_secondary (src/box/vy_stmt.c). It is a DML body without a space id, plus the statement flags.
func DecodeVyStmtBody ¶ added in v0.2.0
func DecodeVyStmtBody(b []byte) (*VyStmtBody, error)
DecodeVyStmtBody parses the body of a statement of a .run file. It is DecodeVyStmtBodyInto into a new VyStmtBody, with no options: a key with no field is skipped.
Example ¶
ExampleDecodeVyStmtBody decodes a DELETE statement of a vinyl run: the key it deletes and the flag that it did not delete the old tuple from the secondary indexes, as Tarantool writes it for a space with defer_deletes.
package main
import (
"fmt"
"github.com/tarantool/go-xlog/format"
)
func main() {
// {IPROTO_KEY: [2], IPROTO_TUPLE_META: {VY_STMT_FLAGS: 1}}
raw := []byte{0x82, 0x20, 0x91, 0x02, 0x2a, 0x81, 0x01, 0x01}
stmt, err := format.DecodeVyStmtBody(raw)
if err != nil {
panic(err)
}
fmt.Printf("key=%x deferred_delete=%t\n", stmt.Key, stmt.Flags&format.VyStmtDeferredDelete != 0)
}
Output: key=9102 deferred_delete=true
func (*VyStmtBody) Clone ¶ added in v0.2.0
func (b *VyStmtBody) Clone() *VyStmtBody
Clone returns a deep copy of b: its Tuple, Key and Ops are copied into one new allocation. A nil receiver returns nil.
func (*VyStmtBody) CloneInto ¶ added in v0.2.0
func (b *VyStmtBody) CloneInto(dst *VyStmtBody)
CloneInto deep-copies b into dst, overwriting every field, and copies each raw field of b into the array of the same field of dst when it has the capacity. dst must own its slices; see Aliasing in the package doc. dst may be b.
type VyStmtFlags ¶ added in v0.2.0
type VyStmtFlags uint64
VyStmtFlags are the persistent flags of a vinyl statement (vy_stmt_flags, src/box/vy_stmt.h).
const ( // VyStmtDeferredDelete marks a REPLACE or DELETE of a primary index that // skipped deleting the old tuple from the secondary indexes: compaction // of the primary index generates those DELETEs. Only primary index runs // carry it. VyStmtDeferredDelete VyStmtFlags = 1 << 0 // VyStmtSkipRead marks a statement reads ignore: the deferred DELETEs // compaction of the primary index writes to a secondary index. VyStmtSkipRead VyStmtFlags = 1 << 1 )
type VyStmtStat ¶ added in v0.2.0
VyStmtStat counts the statements of a run by type.
type XRow ¶
type XRow struct {
Type iproto.Type
ReplicaID uint32
GroupID uint32
LSN int64
TSN int64
Timestamp float64
// Flags carries every flag bit *except* IPROTO_FLAG_COMMIT, plus
// IPROTO_FLAG_COMMIT when the row is a transaction-terminating row. The
// encoder is responsible for the single-stmt vs multi-stmt logic of
// whether to actually emit IPROTO_FLAGS / IPROTO_FLAG_COMMIT on the wire.
Flags iproto.Flag
BodyRaw []byte
StreamID uint64 // Optional, wire-only; usually zero in xlog.
// Sync is IPROTO_SYNC, the id that correlates an IPROTO request with its
// response. Wire-only: a row written to an xlog carries none.
Sync uint64
// SchemaVersion is IPROTO_SCHEMA_VERSION, the schema version a response
// (or a request that checks it) carries. Wire-only, like Sync.
SchemaVersion uint64
// Present records which header keys the decoded row carried on the wire,
// so a key that is absent can be told from a key present with the value
// 0: a replication heartbeat, for one, is a row without IPROTO_LSN
// (!HasLSN()), not a row whose LSN is 0. Only DecodeXRowInto (and
// DecodeXRow) sets it, and it describes the row as decoded: changing a
// field afterwards does not update it.
//
// DECODE-ONLY AND ADVISORY: EncodeXRow never consults Present. It infers
// which keys to emit from the zero-ness of the fields, as it always has,
// because every XRow built by hand - in a test, a writer, a filter that
// rewrites rows - has Present == 0, and an encoder that honoured it would
// emit an empty header for all of them. The asymmetry is deliberate; do
// not "fix" it. To drop a key from an encoded row, zero its field.
Present Present
}
XRow is one row in a tx-block payload.
On disk an xrow is a *header* msgpack map followed by an optional *body* msgpack map. NOP rows (IPROTO_NOP) have no body — the row ends after the header map (src/box/xrow.c:272-282). All other types carry exactly one body map immediately after the header.
BodyRaw holds the body map bytes verbatim (decoded on demand). It aliases the input. For NOP rows it is nil.
func DecodeXRow ¶
DecodeXRow parses one xrow record at b[0:] and returns the row plus the number of bytes consumed (header + body). Unknown header keys are skipped via skipMP, preserving forward compatibility with future IPROTO keys (strict on shape, forgiving on unknown keys).
The "is there a body?" question is answered as: NOP rows (IPROTO_NOP) have no body; everything else has a single trailing msgpack value (the body map). This matches src/box/xrow.c:272-282.
Single-statement tx detection: if IPROTO_TSN was absent, set TSN = LSN and Flags |= IPROTO_FLAG_COMMIT.
The row's Present records the header keys found on the wire; the TSN and IPROTO_FLAG_COMMIT inferred for a single-statement row set no bit. Its BodyRaw aliases b; see Aliasing in the package doc.
func (*XRow) Clone ¶ added in v0.2.0
Clone returns a deep copy of r: its BodyRaw is a new slice. A nil receiver returns nil.
Cloning a row does not detach a body decoded from r.BodyRaw before: that body aliases the buffer r.BodyRaw did. Decode the body from the BodyRaw of the clone, or clone the body too.
func (*XRow) CloneInto ¶ added in v0.2.0
CloneInto deep-copies r into dst, overwriting every field, and copies r.BodyRaw into the array of dst.BodyRaw when it has the capacity. dst must own its slices; see Aliasing in the package doc. dst may be r.
func (*XRow) HasFlags ¶ added in v0.2.0
HasFlags reports whether the decoded row carried IPROTO_FLAGS. It is false for a single-statement row with no other flag, whose IPROTO_FLAG_COMMIT the decoder infers.
func (*XRow) HasGroupID ¶ added in v0.2.0
HasGroupID reports whether the decoded row carried IPROTO_GROUP_ID.
func (*XRow) HasLSN ¶ added in v0.2.0
HasLSN reports whether the decoded row carried IPROTO_LSN. A heartbeat of the replication stream carries none.
func (*XRow) HasReplicaID ¶ added in v0.2.0
HasReplicaID reports whether the decoded row carried IPROTO_REPLICA_ID.
func (*XRow) HasSchemaVersion ¶ added in v0.2.0
HasSchemaVersion reports whether the decoded row carried IPROTO_SCHEMA_VERSION.
func (*XRow) HasStreamID ¶ added in v0.2.0
HasStreamID reports whether the decoded row carried IPROTO_STREAM_ID.
func (*XRow) HasTSN ¶ added in v0.2.0
HasTSN reports whether the decoded row carried IPROTO_TSN, that is whether it belongs to a multi-statement transaction. It is false for a single-statement row, whose TSN the decoder infers from its LSN.
func (*XRow) HasTimestamp ¶ added in v0.2.0
HasTimestamp reports whether the decoded row carried IPROTO_TIMESTAMP.