Documentation
¶
Overview ¶
Package qdf is a compact, streaming-friendly binary serialization format.
Quick start ¶
One encode entry point: Marshal(v, opts). The Options bit-mask picks which codecs run for that call. Convenience bundles cover the common tradeoffs:
OptSpeed — Fast mode, no codecs (matches encoding/json shape)
OptBalanced — Dense + QPack + shape interning + Markov-1 + MTF
OptCompression — OptBalanced + heavier float codecs: Gorilla XOR for
smooth time-series and ALP for quantized/decimal
float64 (large wire reduction at higher encode CPU)
A single decoder handles every variant; the wire header self- describes the dialect.
b, err := qdf.Marshal(v, qdf.OptSpeed) // hot path b, err := qdf.Marshal(v, qdf.OptBalanced) // telemetry / logs b, err := qdf.Marshal(v, qdf.OptCompression) // backup / archive err := qdf.Unmarshal(b, &v) enc := qdf.NewStreamEncoder(w, qdf.OptBalanced) dec := qdf.NewStreamDecoder(r)
Marshal returns a freshly-allocated slice owned by the caller. AppendMarshal is the zero-extra-copy variant.
Data shapes and codecs ¶
Under OptBalanced the encoder picks a codec per slice from the data itself: integer slices use Frame-of-Reference, delta, run-length or dictionary coding; bool slices bit-pack; repeated strings are interned and back-referenced. A slice of homogeneous flat structs is transposed and compressed column-by-column automatically — numeric columns get the integer codecs, an enum-like string column is dictionary-coded (distinct table + a bit-packed index per row) when that beats per-value interning, an optional (*T, T a scalar or string) field becomes a presence bitmap plus a dense present-only column instead of forcing the struct back to row-major, other repeated string columns collapse — with no flag, and a per-array probe falls back to the plain row encoding when columnar would not help. The float codecs that trade encode CPU for size live behind OptCompression: Gorilla XOR on smooth series, ALP on quantized/decimal float64 grids (quantized telemetry, prices, latencies).
Concurrency ¶
Marshal, Unmarshal and AppendMarshal are safe for concurrent use: each call leases its own encoder/decoder from a pool. A single Encoder, Decoder or StreamEncoder value is not safe to share across goroutines — use one per goroutine. A Dense document is a sequential stream, so one document cannot be encoded or decoded in parallel; to parallelize a large dataset, split it into independent shards and Marshal each separately.
Selective decode ¶
Under OptColumnIndex a columnar []struct payload carries a fixed-width uint32 column-length index, written after the shape declaration and before the column bodies (~4 bytes per column). A reader wanting only some columns then advances past the rest using the index instead of decoding them, so the cost becomes O(columns you read), not O(all columns). Two entry points: decode into a subset struct whose fields are a subset of the wire (matched by qdf tag / field name) with plain Unmarshal — wire columns absent from the target are skipped, target fields absent from the wire are left zero — or name the columns explicitly with UnmarshalColumns (also drives the dynamic *[]map[string]any form). The option is a true no-op on non-columnar payloads: the flag is backpatched only when the index is actually emitted, so the default columnar wire stays byte-identical when it is off. Without the index a subset decode still returns correct results by decoding and discarding unwanted columns. The index is a single-message feature and is not emitted in streaming mode.
Predicate pushdown ¶
Unmarshal accepts trailing QueryOption arguments that filter and project a columnar []struct payload before it is materialized. Where(field, pred) keeps only the rows for which a typed predicate holds — func(T) bool over the column's element type, AND-ed across multiple Where clauses, with zero per-value boxing — and Select(fields...) restricts the output to a named subset of columns. The filter field need not appear in the output type, and the result can be either *[]Struct or *[]map[string]any. The decoder reads each predicate column whole, evaluates it into a row bitmask, ANDs the masks, then compacts and materializes only the surviving rows of the projected columns; OptColumnIndex lets it seek past skipped columns instead of decode-and-discard. A nullable column's nil rows never match. Pushdown fires only on columnar payloads — a non-columnar payload (a single struct, or a batch the columnar probe declined) returns an error wrapping ErrUnsupported; a missing field yields ErrFieldNotFound and a predicate whose argument type mismatches the column yields ErrTypeMismatch, both as a *QueryError (errors.Is / errors.As). Versus a full decode followed by a manual loop, pushdown moves materially fewer bytes (about 4x less on a wide batch with 1% selectivity) and runs roughly 2x faster. No other Go serializer reads back a filtered, projected subset of a batch without decoding the whole thing — see docs/PREDICATE-PUSHDOWN.md for the full guide, rationale, and tutorial.
See docs/USAGE.md in the repository for a fuller guide.
Public API surface ¶
The package contract is split across four layers; everything else under internal/ is implementation detail and may change between releases without notice.
Top-level entry points (file: qdf.go):
func Marshal(v any, opts Options) ([]byte, error) func AppendMarshal(dst []byte, v any, opts Options) ([]byte, error) func Unmarshal(data []byte, out any) error type Options uint32 // bit-mask of OptDense, OptQPack, OptMTF, …
Selective columnar decode (file: columnar_select.go):
func UnmarshalColumns(data []byte, out any, fields ...string) error
Predicate pushdown — filter rows and project columns on a columnar []struct, AND-ed typed predicates with zero per-value boxing (file: query.go):
func Unmarshal(data []byte, out any, opts ...QueryOption) error func Where[T Queryable](field string, pred func(T) bool) QueryOption func Select(fields ...string) QueryOption
Typed convenience wrappers — generic, zero-extra-reflection (file: qdf_generic.go):
func MarshalT[T any](v T, opts Options) ([]byte, error) func AppendMarshalT[T any](dst []byte, v T, opts Options) ([]byte, error) func UnmarshalT[T any](data []byte) (T, error)
Low-level encoder / decoder for callers driving the wire directly or interoperating with the qdfgen code generator (files: encoder.go, decoder.go, stream.go):
type Encoder struct{ … }
NewEncoder(mode Mode) *Encoder
NewEncoderWith(opts Options) *Encoder
NewEncoderOnBuf(buf []byte, mode Mode) *Encoder
(e *Encoder) WriteString / WriteBytes / WriteInt / WriteUint /
WriteFloat32 / WriteFloat64 / WriteBool / WriteNil /
WriteArrayHeader / WriteMapHeader /
WriteTimestamp / WriteStringInline
(e *Encoder) AppendBytes / EnsureHeader / Bytes / Take /
Reset / SetBuffer / AdoptBuffer / SetIntern /
SetMaxDepth / SetQPack / QPack / ApplyOpts /
EncodeValue / PreIntern
type Decoder struct{ … }
NewDecoder() *Decoder
NewDecoderOnBuf(buf []byte) *Decoder
(d *Decoder) ReadString / ReadStringBytes / ReadBytes /
ReadBool / ReadInt / ReadUint / ReadFloat32 /
ReadFloat64 / ReadNil / ReadArrayHeader /
ReadMapHeader / ReadTimestamp / Skip /
PeekTag / IsNil / Pos / Remaining / RemainingBytes /
Advance / SetInput / SetNoCopy / MarkHeaderRead /
CheckLength / InternKey
type StreamEncoder, type StreamDecoder // io.Writer / io.Reader
User-side hook points (file: marshaler.go):
type Marshaler interface { MarshalQDF(dst []byte) ([]byte, error) }
type Unmarshaler interface { UnmarshalQDF(src []byte) (int, error) }
The qdfgen code generator (cmd/qdfgen) emits MarshalQDF / UnmarshalQDF methods for user struct types; the codegen path uses the same Encoder / Decoder primitives listed above and requires no reflection at runtime.
Example ¶
Example demonstrates the simplest possible round-trip: Marshal a value into a freshly-allocated byte slice and Unmarshal it back into a typed receiver.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
ID int `qdf:"id"`
Source string `qdf:"source"`
}
in := Event{ID: 42, Source: "ingest"}
buf, err := qdf.Marshal(in, qdf.OptSpeed)
if err != nil {
panic(err)
}
var out Event
if err := qdf.Unmarshal(buf, &out); err != nil {
panic(err)
}
fmt.Printf("%+v\n", out)
}
Output: {ID:42 Source:ingest}
Example (RoundTripSlice) ¶
Example_roundTripSlice demonstrates encoding a slice of structs. QDF's reflect path resolves the element type once per typeDesc; every element after the first emits its tag stream straight from the cached descriptor.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Row struct {
Service string `qdf:"service"`
Status int `qdf:"status"`
}
rows := []Row{
{"auth", 200},
{"billing", 503},
{"auth", 200},
}
buf, _ := qdf.Marshal(rows, qdf.OptBalanced)
var got []Row
_ = qdf.Unmarshal(buf, &got)
for _, r := range got {
fmt.Printf("%s=%d\n", r.Service, r.Status)
}
}
Output: auth=200 billing=503 auth=200
Index ¶
- Constants
- Variables
- func AppendMarshal(dst []byte, v any, opts Options) ([]byte, error)
- func AppendMarshalDirect[T Marshaler](dst []byte, v T) ([]byte, error)
- func AppendMarshalT[T any](dst []byte, v T, opts Options) ([]byte, error)
- func Marshal(v any, opts Options) ([]byte, error)
- func MarshalDirect[T Marshaler](v T) ([]byte, error)
- func MarshalT[T any](v T, opts Options) ([]byte, error)
- func Unmarshal(data []byte, out any, opts ...QueryOption) error
- func UnmarshalColumns(data []byte, out any, fields ...string) error
- func UnmarshalDirect[T Unmarshaler](data []byte, out T) error
- func UnmarshalNested(u Unmarshaler, src []byte, noCopy bool) (int, error)
- func UnmarshalNestedArena(u Unmarshaler, src []byte, noCopy bool, a *Arena) (int, error)
- func UnmarshalT[T any](data []byte, out *T) error
- type Arena
- type Decoder
- func (d *Decoder) Advance(n int)
- func (d *Decoder) CheckLength(n int, perElem int) error
- func (d *Decoder) InternKey(b []byte) string
- func (d *Decoder) IsNil() (bool, error)
- func (d *Decoder) MarkHeaderRead()
- func (d *Decoder) PeekTag() (byte, error)
- func (d *Decoder) Pos() int
- func (d *Decoder) ReadArrayHeader() (int, error)
- func (d *Decoder) ReadBool() (bool, error)
- func (d *Decoder) ReadBytes() ([]byte, error)
- func (d *Decoder) ReadFloat32() (float32, error)
- func (d *Decoder) ReadFloat64() (float64, error)
- func (d *Decoder) ReadInt() (int64, error)
- func (d *Decoder) ReadMapHeader() (int, error)
- func (d *Decoder) ReadNil() error
- func (d *Decoder) ReadString() (string, error)
- func (d *Decoder) ReadStringBytes() ([]byte, error)
- func (d *Decoder) ReadTimestamp() (sec int64, nsec uint32, err error)
- func (d *Decoder) ReadUint() (uint64, error)
- func (d *Decoder) Remaining() int
- func (d *Decoder) RemainingBytes() []byte
- func (d *Decoder) SetArena(a *Arena)
- func (d *Decoder) SetInput(buf []byte)
- func (d *Decoder) SetNoCopy(v bool)
- func (d *Decoder) Skip() error
- type Encoder
- func (e *Encoder) AdoptBuffer(b []byte)
- func (e *Encoder) AppendBytes(p []byte)
- func (e *Encoder) ApplyOpts(opts Options)
- func (e *Encoder) Bytes() []byte
- func (e *Encoder) EncodeValue(v any) error
- func (e *Encoder) EnsureHeader()
- func (e *Encoder) MarkHeaderWritten()
- func (e *Encoder) PreIntern(strs ...string)
- func (e *Encoder) QPack() bool
- func (e *Encoder) Reset()
- func (e *Encoder) SetBuffer(b []byte)
- func (e *Encoder) SetIntern(min int, cap int)
- func (e *Encoder) SetMaxDepth(d int)
- func (e *Encoder) SetQPack(v bool)
- func (e *Encoder) Take() []byte
- func (e *Encoder) WriteArrayHeader(n int)
- func (e *Encoder) WriteBool(v bool)
- func (e *Encoder) WriteBytes(b []byte)
- func (e *Encoder) WriteFloat32(v float32)
- func (e *Encoder) WriteFloat64(v float64)
- func (e *Encoder) WriteInt(v int64)
- func (e *Encoder) WriteMapHeader(n int)
- func (e *Encoder) WriteNil()
- func (e *Encoder) WriteString(s string)
- func (e *Encoder) WriteStringInline(s string)
- func (e *Encoder) WriteTimestamp(sec int64, nsec uint32)
- func (e *Encoder) WriteUint(v uint64)
- type FSSTDict
- type Marshaler
- type Mode
- type Options
- type QueryError
- type QueryOption
- type Queryable
- type StreamDecoder
- type StreamEncoder
- type Unmarshaler
- type UnmarshalerArena
- type UnmarshalerOpts
Examples ¶
- Package
- Package (RoundTripSlice)
- AppendMarshal
- Arena
- Arena (Reset)
- Decoder.IsNil
- Decoder.PeekTag
- Decoder.SetNoCopy
- Encoder
- Encoder.EncodeValue
- Encoder.PreIntern
- MarshalT
- Marshaler
- Options
- Options (ColumnarStringDict)
- Options (StreamingDictShared)
- Or
- StreamDecoder.SetNoCopy
- StreamEncoder
- Unmarshal (PredicatePushdown)
- UnmarshalColumns
- WithNoCopy
Constants ¶
const ( Magic0 byte = 'Q' Magic1 byte = 'D' Magic2 byte = 'F' Version1 byte = 0x01 )
const ( FlagDense byte = 1 << 0 // FlagQPack signals that the encoder may emit QPack codec tags // (0xE3..0xEF). A decoder that does not recognize the tags will fail // with ErrBadTag on the first packed slice; the flag is an early hint // so callers can refuse the buffer up front. FlagQPack byte = 1 << 1 // FlagRANS signals that the body (everything after the 5-byte header) is // rANS-compressed: varuint(origLen) + 256-entry frequency table + // rANS stream. The decoder reverses it before reading tags. Set only // when the rANS form is strictly smaller than the plain body. FlagRANS byte = 1 << 2 // FlagColIndex marks a tagColStruct (columnar []struct) payload that // carries a fixed-width column-length table right after the shape // declaration and before the column bodies. The table is K little-endian // uint32 entries (one per column, K = number of columns), each the byte // length of the corresponding column body. It lets a reader skip columns // it does not need without decoding them. Opt-in (~4 bytes per column); // only meaningful when the value is columnar — for any other top-level // shape the bit is set but no index block exists. FlagColIndex byte = 1 << 3 )
Flag bits in the 5th header byte.
const DefaultMaxDepth = 10_000
DefaultMaxDepth caps reflect-path pointer/struct recursion. Set large enough for any legitimate payload (10 000) while still rejecting genuine cycles before the goroutine stack overflows.
const TagNil = tagNil
TagNil exposes the nil-value tag for comparison with PeekTag.
Variables ¶
var ( ErrShortBuffer = errors.New("qdf: short buffer") ErrBadMagic = errors.New("qdf: bad magic / not a qdf stream") ErrBadVersion = errors.New("qdf: unsupported wire version") ErrBadTag = errors.New("qdf: unknown tag") ErrTypeMismatch = errors.New("qdf: type mismatch on decode") ErrInvalidLength = errors.New("qdf: invalid length prefix") ErrUnknownStateID = errors.New("qdf: unknown state-table id") ErrUnsupported = errors.New("qdf: unsupported type") ErrCycleDetected = errors.New("qdf: pointer cycle detected (max depth exceeded)") ErrFieldNotFound = errors.New("qdf: query predicate field not found") // ErrStreamBadFlags is returned by StreamDecoder.Decode when the stream // header carries whole-payload flags that do not apply to a frame stream // (FlagRANS / FlagColIndex). A conforming StreamEncoder never sets them; a // hostile stream claiming them would, if honored, swap the shared window // buffer mid-stream and desync framing. The decoder is latched broken. ErrStreamBadFlags = errors.New("qdf: stream header has unsupported whole-payload flags") )
var ErrStreamBroken = errors.New("qdf: stream encoder broken by a prior mid-message error")
ErrStreamBroken is returned by StreamEncoder.Encode after a previous Encode failed mid-message: the cross-message encoder state can no longer be trusted, so the stream must be abandoned (the valid prefix may still be flushed).
var ErrStreamDecoderBroken = errors.New("qdf: stream decoder broken by a prior mid-message error")
ErrStreamDecoderBroken is returned by StreamDecoder.Decode after a previous Decode failed mid-message: the read cursor and the shared dense state can no longer be trusted (subsequent frames would misparse), so the stream must be abandoned.
Functions ¶
func AppendMarshal ¶
AppendMarshal encodes v and appends the result to dst. Reuse the returned slice as dst on the next call to avoid per-message allocations.
Example ¶
ExampleAppendMarshal shows the zero-extra-copy form. The caller keeps a reusable byte buffer and asks Marshal to append into it, returning the extended slice. Combined with a sync.Pool of buffers this drives encode allocations to zero on the hot path.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Tick struct {
Ts int64 `qdf:"ts"`
Price float64 `qdf:"price"`
}
buf := make([]byte, 0, 64) // caller-owned, reusable
t := Tick{Ts: 1_700_000_000, Price: 99.5}
var err error
buf, err = qdf.AppendMarshal(buf, t, qdf.OptSpeed)
if err != nil {
panic(err)
}
var out Tick
if err := qdf.Unmarshal(buf, &out); err != nil {
panic(err)
}
fmt.Printf("%+v\n", out)
}
Output: {Ts:1700000000 Price:99.5}
func AppendMarshalDirect ¶
AppendMarshalDirect appends the serialisation of v to dst.
func AppendMarshalT ¶
AppendMarshalT is the generic equivalent of AppendMarshal.
func Marshal ¶
Marshal encodes v with the given option bit-mask. Combine bits with | to opt into individual codecs, or use one of the OptSpeed / OptBalanced / OptCompression bundles.
b, _ := qdf.Marshal(event, qdf.OptSpeed) // hot path
b, _ := qdf.Marshal(batch, qdf.OptBalanced) // telemetry
b, _ := qdf.Marshal(snapshot, qdf.OptCompression) // backup
b, _ := qdf.Marshal(payload, // tuned
qdf.OptDense|qdf.OptQPack|qdf.OptShapeIntern)
Big-output detach (in marshalDict): cloning a multi-megabyte buffer to hand the caller their own copy used to dominate Large-payload profiles (slices.Clone + runtime.memmove). Above marshalDetachThreshold the pool would drop the buffer in putEnc anyway (cap exceeds maxPooledBuf), so handing the original to the caller and leaving the pool encoder with a nil buf is strictly cheaper. Small payloads stay on the clone path so the warm 4 KiB pool buffer survives.
func MarshalDirect ¶
MarshalDirect serialises a value through its MarshalQDF method. Skips the public Marshal entry point's any-boxing, reflect.New, and descriptor lookup. Roughly 2-4× faster than Marshal for generated types on small payloads, with one fewer allocation per call.
v must be non-nil: MarshalDirect calls v.MarshalQDF directly, so a nil pointer-receiver value panics. Use Marshal, which emits an explicit nil, if the value may be nil.
func MarshalT ¶
MarshalT is the generic equivalent of Marshal. T is fixed at the call site; opts copies by value, so the call adds zero heap allocations over MarshalT itself.
Example ¶
ExampleMarshalT shows the generic typed wrapper. T is fixed at the call site, so the compiler skips the reflect.TypeOf step that Marshal(v any, opts) pays inside; encode/decode of value types stays on the stack.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Point struct {
X int32 `qdf:"x"`
Y int32 `qdf:"y"`
}
buf, err := qdf.MarshalT(Point{X: 3, Y: 4}, qdf.OptSpeed)
if err != nil {
panic(err)
}
var p Point
if err := qdf.UnmarshalT(buf, &p); err != nil {
panic(err)
}
fmt.Printf("%+v\n", p)
}
Output: {X:3 Y:4}
func Unmarshal ¶
func Unmarshal(data []byte, out any, opts ...QueryOption) error
Unmarshal decodes data into out, which must be a non-nil pointer. The wire dialect is detected from the header — the same Unmarshal reads any output produced by Marshal regardless of the encode-side option bits.
Slice-backing reuse: when out is a *[]T whose slice already has enough capacity for the decoded element count, the decoder reuses that backing array instead of allocating a new one — eliminating the result allocation (the dominant decode cost) on a decode into a pooled / pre-sized slice. The reused backing is overwritten in place (its elements are zeroed first, so no stale data leaks), so do not share it with other live slices across the call. A nil or too-small destination allocates fresh, so default usage is unaffected.
The optional QueryOptions (Where / Select) turn the call into a filtering/projecting columnar decode: predicates are AND-ed and the named columns projected. They apply only to a columnar []struct, []map[string]any or []any payload; on any other shape a query call returns a *QueryError wrapping ErrUnsupported. With no options the behavior is exactly the plain decode above.
Round-trip fidelity: decoding into the same concrete Go type reproduces the value exactly, including the nil-vs-empty distinction for slices, maps and pointers (a nil []T decodes as nil, an empty []T{} as empty — like encoding/json's null vs []). Two deliberate normalizations are NOT bit-exact: decoding into an interface (any / map[string]any) canonicalizes integers to int64 / uint64 and structs to map[string]any (the schemaless type contract), and a time.Time loses any monotonic-clock reading on encode (as the standard library strips it for transport). Values nested deeper than the configured max depth, and maps with both int and uint keys of the same small value, are the documented exceptions.
Example (PredicatePushdown) ¶
ExampleUnmarshal_predicatePushdown keeps only the rows matching typed predicates (AND-ed), decoding just the projected columns of those rows.
Predicate pushdown only fires when the payload is columnar, which the encoder picks for a sufficiently large []struct batch, so this example builds a 20-row batch rather than a handful of literals.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
TS int64 `qdf:"ts"`
Level string `qdf:"level"`
Code int32 `qdf:"code"`
}
in := make([]Event, 0, 20)
for i := range 20 {
lvl, code := "INFO", int32(200)
switch i % 5 {
case 0:
lvl, code = "ERROR", 500
case 1:
lvl, code = "ERROR", 404
case 2:
lvl, code = "WARN", 503
}
in = append(in, Event{TS: int64(i + 1), Level: lvl, Code: code})
}
buf, _ := qdf.Marshal(in, qdf.OptBalanced|qdf.OptColumnIndex)
type Out struct {
TS int64 `qdf:"ts"`
Code int32 `qdf:"code"`
}
var hot []Out // filter on level (not in Out), return ts+code
_ = qdf.Unmarshal(buf, &hot,
qdf.Where("level", func(s string) bool { return s == "ERROR" }),
qdf.Where("code", func(c int32) bool { return c >= 500 }))
for _, e := range hot {
fmt.Printf("ts=%d code=%d\n", e.TS, e.Code)
}
}
Output: ts=1 code=500 ts=6 code=500 ts=11 code=500 ts=16 code=500
func UnmarshalColumns ¶ added in v0.0.2
UnmarshalColumns decodes only the named columns of a columnar []struct payload into out, leaving the rest undecoded. out may point at a typed subset slice (e.g. *[]Subset whose fields are the wanted columns, matched by name like Unmarshal) or at the dynamic *[]map[string]any form, where only the named columns are stored. When the payload carries the column-length index (OptColumnIndex), unrequested columns are skipped without decoding; otherwise they are decoded but dropped.
fields names the wire columns to keep. With no fields it behaves like Unmarshal.
Example ¶
ExampleUnmarshalColumns decodes only a named subset of columns from a columnar []struct payload. The producer opts in to OptColumnIndex so the payload carries a column-length index; the decoder then advances past the columns it was not asked for instead of decoding them. Here a 4-field struct batch is decoded into a 2-field typed subset, naming the wanted columns explicitly.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
TS int64 `qdf:"ts"`
Level string `qdf:"level"`
Service string `qdf:"service"`
Code int32 `qdf:"code"`
}
in := make([]Event, 4)
levels := []string{"INFO", "WARN", "ERROR", "INFO"}
for i := range in {
in[i] = Event{
TS: int64(1700000000 + i),
Level: levels[i],
Service: "billing",
Code: int32(200 + i),
}
}
// OptColumnIndex writes the column-length index; the default columnar
// wire is byte-identical without it.
buf, _ := qdf.Marshal(in, qdf.OptBalanced|qdf.OptColumnIndex)
// Decode only the "level" and "service" columns into a typed subset.
// The "ts" and "code" columns are skipped via the index, not decoded.
type Subset struct {
Level string `qdf:"level"`
Service string `qdf:"service"`
}
var out []Subset
if err := qdf.UnmarshalColumns(buf, &out, "level", "service"); err != nil {
fmt.Println("error:", err)
return
}
for _, e := range out {
fmt.Printf("level=%-5s service=%s\n", e.Level, e.Service)
}
}
Output: level=INFO service=billing level=WARN service=billing level=ERROR service=billing level=INFO service=billing
func UnmarshalDirect ¶
func UnmarshalDirect[T Unmarshaler](data []byte, out T) error
UnmarshalDirect dispatches to out.UnmarshalQDF after validating the 5-byte header. It is the inverse of MarshalDirect; the same wire constraints apply (Fast-mode only).
If the input carries the FlagDense bit, UnmarshalDirect falls back to the full Unmarshal path because generated code cannot resolve state-ref tags without the decoder's intern table.
Decode-side performance is bounded by the user's UnmarshalQDF implementation: the reflect path is heavily pooled (Decoder pool + per-decoder key intern cache) and tends to win against naive hand-rolled receivers. Generated code from cmd/qdfgen uses Decoder.InternKey for map / struct keys and matches or beats the reflect path; ad-hoc UnmarshalQDF methods that call NewDecoderOnBuf + ReadString in a tight loop will not.
func UnmarshalNested ¶ added in v0.0.2
func UnmarshalNested(u Unmarshaler, src []byte, noCopy bool) (int, error)
UnmarshalNested decodes one nested Unmarshaler value from src, honoring noCopy when u also implements UnmarshalerOpts. External Unmarshalers without the Opts method fall back to a copying decode. Used by decodeUnmarshaler and by cmd/qdfgen-generated code; exported for the latter.
func UnmarshalNestedArena ¶ added in v0.0.6
UnmarshalNestedArena is UnmarshalNested threading a decode arena: when a is non-nil and u implements UnmarshalerArena, the nested decode packs its strings into a. Otherwise it falls back to the plain (copying) nested decode. Exported for cmd/qdfgen-generated code.
func UnmarshalT ¶
UnmarshalT is the generic equivalent of Unmarshal. The destination pointer must not be nil.
Types ¶
type Arena ¶ added in v0.0.6
type Arena struct {
// contains filtered or unexported fields
}
Arena is a bump allocator for decoded string (and []byte-as-string) bodies. Pass it to a decode via WithArena (or Decoder.SetArena) and every copied string is packed into the arena's dense, contiguous blocks instead of one heap allocation per string. Across an epoch of many decodes this amortizes the block allocation to near zero and keeps the strings cache-local; the GC scans each block as a single object instead of one object per string.
Lifetime / safety: strings returned by an arena decode ALIAS the arena's memory. They stay valid as long as the Arena (or any string from it) is reachable — the GC keeps a block alive from an interior pointer, so you need not track the buffer manually. The SAFE pattern is one Arena per epoch (request / batch / stream window), then drop it:
a := qdf.NewArena()
for _, msg := range batch {
var v Event
_ = qdf.Unmarshal(msg, &v, qdf.WithArena(a))
use(v) // v's strings live in a
}
// drop a; the GC frees every block once the decoded values die.
Reset reuses the arena across epochs for zero allocation, but is UNSAFE while any string from the previous epoch is still live (see Reset).
An Arena is NOT safe for concurrent use; give each goroutine its own.
Example ¶
ExampleArena shows the safe default pattern: one arena per epoch (here, one batch of messages). Every decoded string is bump-packed into the arena instead of a separate heap allocation. No Reset and no manual lifetime management are needed — the arena and its blocks are reclaimed by the GC once the decoded values are dropped.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
Service string `qdf:"service"`
Level string `qdf:"level"`
Msg string `qdf:"msg"`
}
// Pretend these arrived over the wire.
msgs := make([][]byte, 3)
for i, m := range []Event{
{"api", "info", "request handled"},
{"worker", "warn", "queue backlog"},
{"edge", "error", "upstream timeout"},
} {
b, err := qdf.Marshal(m, qdf.OptSpeed)
if err != nil {
panic(err)
}
msgs[i] = b
}
a := qdf.NewArena() // one arena for this batch
out := make([]Event, len(msgs))
for i, msg := range msgs {
if err := qdf.Unmarshal(msg, &out[i], qdf.WithArena(a)); err != nil {
panic(err)
}
}
// out's strings live in `a`; when `out` is dropped the GC frees the arena.
for _, e := range out {
fmt.Printf("%s/%s: %s\n", e.Service, e.Level, e.Msg)
}
}
Output: api/info: request handled worker/warn: queue backlog edge/error: upstream timeout
Example (Reset) ¶
ExampleArena_reset shows the max-performance pattern: reuse one arena across an unbounded stream, calling Reset at each epoch boundary for zero steady-state allocation.
SAFETY: Reset rewinds the arena and the next decode overwrites its memory, so every value decoded before a Reset must be done being used before that Reset runs. Here each event is processed inside the loop body, before the next iteration's Reset.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
Service string `qdf:"service"`
Msg string `qdf:"msg"`
}
stream := []Event{
{"api", "ok"},
{"worker", "retry"},
}
encoded := make([][]byte, len(stream))
for i, e := range stream {
b, _ := qdf.Marshal(e, qdf.OptSpeed)
encoded[i] = b
}
a := qdf.NewArena()
for _, msg := range encoded {
a.Reset() // safe: the previous iteration's value is no longer used
var ev Event
if err := qdf.Unmarshal(msg, &ev, qdf.WithArena(a)); err != nil {
panic(err)
}
fmt.Printf("%s: %s\n", ev.Service, ev.Msg)
}
}
Output: api: ok worker: retry
func NewArena ¶ added in v0.0.6
func NewArena() *Arena
NewArena returns an empty arena. The first decode allocates the first block.
func (*Arena) Reset ¶ added in v0.0.6
func (a *Arena) Reset()
Reset rewinds the arena to reuse its current block, so the next epoch of decodes allocates nothing.
UNSAFE: Reset invalidates every string previously returned from this arena — the next decode overwrites the same memory. Call it ONLY once every value decoded into the arena since the last Reset (or since NewArena) is dead. This is a manual use-after-free contract the race detector cannot catch; if unsure, drop the Arena and make a new one instead (the GC then frees it safely).
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
Decoder reads QDF wire data from a single input buffer. Call SetInput to bind a buffer and the typed Read* methods to walk it. Behaviour is undefined if the input is mutated while the decoder holds it.
func NewDecoder ¶
func NewDecoder() *Decoder
NewDecoder constructs a Decoder. Bind input via SetInput.
func NewDecoderOnBuf ¶
NewDecoderOnBuf binds a Decoder to buf. Equivalent to NewDecoder followed by SetInput(buf), without the SetInput branch checks.
func (*Decoder) CheckLength ¶
CheckLength returns ErrShortBuffer when n claimed elements cannot fit in the remaining input. Use before allocating per-element storage.
func (*Decoder) InternKey ¶
InternKey returns a string equal to b, sharing storage with prior identical keys when the cache has them.
func (*Decoder) IsNil ¶
IsNil reports whether the next value is the nil tag, consuming it on true. Returns (false, nil) for any other tag.
Example ¶
ExampleDecoder_IsNil consumes a nil tag if present, returning false (without advancing) for any other tag. Useful when a field is optional on the wire and the caller wants to keep the read cursor stable on a non-nil branch.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
buf, _ := qdf.Marshal([]any{nil, "hello"}, qdf.OptSpeed)
dec := qdf.NewDecoderOnBuf(buf)
n, _ := dec.ReadArrayHeader()
for range n {
isNil, _ := dec.IsNil()
if isNil {
fmt.Println("<nil>")
continue
}
s, _ := dec.ReadString()
fmt.Println(s)
}
}
Output: <nil> hello
func (*Decoder) MarkHeaderRead ¶
func (d *Decoder) MarkHeaderRead()
MarkHeaderRead tells the decoder the magic+version header is already consumed (e.g. the buffer is a tail slice from a parent decoder). The next read will skip the header check.
func (*Decoder) PeekTag ¶
PeekTag returns the next tag byte without advancing the cursor.
Example ¶
ExampleDecoder_PeekTag inspects the next wire tag without consuming it. Handy when implementing a dispatch loop over a schemaless payload, or when an Unmarshaler needs to branch on the upcoming type.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
buf, _ := qdf.Marshal(int64(-42), qdf.OptSpeed)
dec := qdf.NewDecoderOnBuf(buf)
tag, _ := dec.PeekTag()
if tag == qdf.TagNil {
fmt.Println("nil value")
return
}
v, _ := dec.ReadInt()
fmt.Printf("int=%d (tag=0x%02x)\n", v, tag)
}
Output: int=-42 (tag=0xc7)
func (*Decoder) ReadArrayHeader ¶
ReadArrayHeader returns the element count.
func (*Decoder) ReadBytes ¶
ReadBytes returns a []byte value. With noCopy = true the result aliases the input.
func (*Decoder) ReadFloat32 ¶
func (*Decoder) ReadFloat64 ¶
func (*Decoder) ReadMapHeader ¶
ReadMapHeader returns the key/value pair count.
func (*Decoder) ReadString ¶
ReadString returns the next string. If the decoder is in noCopy mode the returned string aliases the input buffer; otherwise a copy is made.
When the tag at the cursor resolves through the intern table — tagInternStr, tagStateRef, tagStateMTF, tagStatePair, tagStateRepeat — readStringBytes leaves d.state.lastID set to the entry it just touched. We return the pre-materialised d.state.stringValues[id] from that path instead of allocating a fresh `string(b)` copy on every state-ref hit. Inline reads (fixstr, str8/16/32) set lastID = lruInvalidID, fall through, and pay the copy as before.
func (*Decoder) ReadStringBytes ¶
ReadStringBytes returns the next string or []byte value without copying. The returned slice aliases either the input buffer or the decoder's state-table storage. Callers that retain it beyond the input's lifetime must copy.
func (*Decoder) ReadTimestamp ¶ added in v0.0.2
ReadTimestamp reads a full-range timestamp and returns (sec, nsec, err). sec is seconds since Unix epoch (may be negative for pre-1970 instants). nsec is nanoseconds in [0, 999_999_999]. This replaces the old fixed-8-byte UnixNano encoding (clean break).
func (*Decoder) ReadUint ¶
ReadUint reads a value that was encoded with WriteUint or WriteInt with a non-negative argument. Returns an error if the next value is negative.
func (*Decoder) RemainingBytes ¶
RemainingBytes returns the unread portion of the input buffer. The result aliases the buffer and is invalidated by the next SetInput.
func (*Decoder) SetArena ¶ added in v0.0.6
SetArena directs the decoder to pack copied inline string bodies into a, bump-packed, instead of allocating one string per field. Pass nil to disable. The decoded strings alias a's memory; see Arena for the lifetime contract. Ignored while noCopy is set. Not concurrent-safe: one Arena per goroutine.
func (*Decoder) SetNoCopy ¶
SetNoCopy switches the decoder into aliasing mode: string and []byte reads return slices that share storage with the input buffer. Faster (~2x on string-heavy payloads) with near-zero allocations.
The aliases are valid only while the input is alive and unmodified. This is safe — and free — when the input is caller-owned and not reused: an mmap, a file fully read into memory, or a buffer allocated fresh per message and never pooled (the aliasing headers keep it alive via the GC, so you need not track its lifetime). The hazard is buffer REUSE or mutation (a sync.Pool buffer, an overwritten scratch slice), which silently corrupts already-decoded values — a manual use-after-free the race detector will not catch. See WithNoCopy for the full contract.
Example ¶
ExampleDecoder_SetNoCopy returns string / []byte values that alias the input buffer instead of allocating per-value copies. Use this for read-heavy hot paths where the input buffer outlives every value the decoder hands back; do not retain the values past the buffer's lifetime.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
in := []string{"alpha", "beta", "gamma"}
buf, _ := qdf.Marshal(in, qdf.OptSpeed)
dec := qdf.NewDecoderOnBuf(buf)
dec.SetNoCopy(true)
n, _ := dec.ReadArrayHeader()
for range n {
s, _ := dec.ReadString() // alias into buf
fmt.Println(s)
}
}
Output: alpha beta gamma
func (*Decoder) Skip ¶
Skip advances past one value without materializing it.
Skip recurses through nested arrays and maps, so it bounds nesting depth via descend/ascend exactly like the reflect decode path: an unknown struct field whose value is a deeply-nested array is skipped here, and without this guard a hostile payload of N nested arrays would overflow the goroutine stack (an unrecoverable fatal error). The recursive d.Skip() calls below re-enter this guard, so every level is counted.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder writes a single QDF value into a growing internal buffer. Reset drops the buffer contents and (in Dense mode) the intern table so the encoder can be reused.
Example ¶
ExampleEncoder drives the encoder by hand instead of via the top-level Marshal pool. Use this when you need to:
- keep a reusable buffer alive across many encodes;
- call PreIntern with a known hot string pool;
- mix WriteX primitives with reflect-driven EncodeValue.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
enc := qdf.NewEncoderWith(qdf.OptSpeed)
// Primitives + struct fields can be mixed by hand.
enc.WriteMapHeader(2)
enc.WriteString("name")
enc.WriteString("alice")
enc.WriteString("age")
enc.WriteInt(33)
dec := qdf.NewDecoderOnBuf(enc.Bytes())
n, _ := dec.ReadMapHeader()
for range n {
k, _ := dec.ReadString()
switch k {
case "name":
v, _ := dec.ReadString()
fmt.Printf("%s=%s\n", k, v)
case "age":
v, _ := dec.ReadInt()
fmt.Printf("%s=%d\n", k, v)
}
}
}
Output: name=alice age=33
func NewEncoder ¶
NewEncoder returns an Encoder. The internal buffer is allocated lazily on first write.
func NewEncoderOnBuf ¶
NewEncoderOnBuf returns an Encoder that appends to buf at its current length. The buffer is NOT truncated; pass an empty slice for a fresh encoding. Call SetBuffer afterwards to truncate.
func NewEncoderWith ¶
NewEncoderWith returns an Encoder configured by the option bit-mask directly. Dense state machinery is allocated only when OptDense is set. Defaults for intern threshold / depth match NewEncoder.
func (*Encoder) AdoptBuffer ¶
AdoptBuffer installs b as the working buffer, preserving its current length. Used to continue writing into a buffer that already carries data — for example, after a nested type returned its extended slice. headerOut is left unchanged.
func (*Encoder) AppendBytes ¶
AppendBytes appends raw, already-valid wire bytes. Bypasses tag dispatch; used by generated code to emit pre-encoded field-name prefixes.
func (*Encoder) ApplyOpts ¶
ApplyOpts reconfigures the encoder via the options bit-mask. Equivalent to recreating the encoder with NewEncoderWith but preserves the existing pool buffer and (Dense-mode) intern state. The pool-backed Marshal* entry points call this on every acquire; callers driving an Encoder directly use it to switch between OptSpeed and OptBalanced without re-allocating.
func (*Encoder) Bytes ¶
Bytes returns the encoded payload. It aliases the encoder's buffer and is only valid until the next write or Reset.
func (*Encoder) EncodeValue ¶
EncodeValue runs the reflect-driven Marshal pipeline on v against this Encoder. Convenience for callers driving an Encoder directly (e.g. with PreIntern) — equivalent to what the pool-backed Marshal does internally.
Example ¶
ExampleEncoder_EncodeValue drives the reflect-based encode path against any Go value directly through the encoder, bypassing the pool used by qdf.Marshal. Combine with PreIntern, custom SetBuffer, or SetIntern tuning for a long-lived encoder that shapes its state for a specific workload.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Config struct {
Name string `qdf:"name"`
Limit int `qdf:"limit"`
}
enc := qdf.NewEncoderWith(qdf.OptBalanced)
if err := enc.EncodeValue(Config{Name: "service.cpu", Limit: 1000}); err != nil {
panic(err)
}
var out Config
_ = qdf.Unmarshal(enc.Bytes(), &out)
fmt.Printf("%+v\n", out)
}
Output: {Name:service.cpu Limit:1000}
func (*Encoder) EnsureHeader ¶
func (e *Encoder) EnsureHeader()
EnsureHeader forces a header write if one has not been emitted yet.
func (*Encoder) MarkHeaderWritten ¶
func (e *Encoder) MarkHeaderWritten()
MarkHeaderWritten tells the encoder the QDF header is already present in its buffer (e.g. left there by a parent encoder). The next write will skip the header emission.
func (*Encoder) PreIntern ¶
PreIntern registers the given strings against the encoder's intern table up front. Subsequent WriteString / WriteBytes calls that pass the SAME backing string header (i.e. the same underlying byte pointer and length) skip the hash + slot probe and emit a state-ref directly against the cached intern id.
Intended for power users who know their hot string pool ahead of time — service names, region codes, enum-like values drawn from a fixed slice. Real telemetry payloads draw 90 %+ of their dense intern hits from such pools. Skipping the hash/probe on those calls is the main per-emit cost left on the encode hot path.
Requires OptDense to be applied first (otherwise the call is a no-op). The registered identities are dropped on Reset so a pooled encoder does not carry caller-supplied pointers across recycles.
Safety: the caller must keep the backing memory of every PreIntern'd string alive for the lifetime of the next encode call. For literals embedded in a slice / global / struct field this is automatic; for short-lived stack strings the caller is responsible.
Example ¶
ExampleEncoder_PreIntern registers a known string pool against the encoder so subsequent WriteString calls with the same backing pointer skip the intern table's hash + slot probe and emit a state-ref directly. Use this when the caller knows the hot vocabulary up front (service names, region codes, enum- like fields).
The caller must keep the registered strings' backing memory alive for the lifetime of the next encode call. String literals embedded in a slice, global, or struct field stay alive automatically; short-lived stack strings do not.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Row struct {
Service string `qdf:"service"`
Region string `qdf:"region"`
Status int `qdf:"status"`
}
// Stable hot pool — backing slices live for the program's
// lifetime, so the registered pointers stay valid across
// many encode calls on the same encoder.
services := []string{"billing", "auth", "ingest"}
regions := []string{"eu-west-1", "us-east-1"}
enc := qdf.NewEncoderWith(qdf.OptBalanced)
enc.PreIntern(services...)
enc.PreIntern(regions...)
rows := []Row{
{services[0], regions[0], 200},
{services[1], regions[1], 500},
{services[2], regions[0], 200},
}
if err := enc.EncodeValue(rows); err != nil {
panic(err)
}
var out []Row
_ = qdf.Unmarshal(enc.Bytes(), &out)
for _, r := range out {
fmt.Printf("%s/%s=%d\n", r.Service, r.Region, r.Status)
}
}
Output: billing/eu-west-1=200 auth/us-east-1=500 ingest/eu-west-1=200
func (*Encoder) Reset ¶
func (e *Encoder) Reset()
Reset truncates the buffer and resets the intern table. Capacities are preserved. opts / mode / qpack are also reset to OptSpeed so a pooled encoder does not leak its previous configuration into the next caller — apply the desired options explicitly via applyOpts / SetQPack after Reset.
func (*Encoder) SetIntern ¶
SetIntern overrides the Dense-mode tuning knobs. Zero values keep the current setting.
func (*Encoder) SetMaxDepth ¶
SetMaxDepth caps reflect-path pointer/struct recursion. The default (DefaultMaxDepth = 10000) is sufficient for any normal payload and rejects pointer cycles before they stack-overflow the goroutine. Set to 0 to disable the check entirely — only safe when the caller can prove the input graph is acyclic.
func (*Encoder) SetQPack ¶
SetQPack toggles QPack codec emission. When true, slice fast paths produce packed/encoded forms (bitpacked bools, FOR-packed integers, Gorilla-encoded floats, raw-LE bulk) instead of per-element tag streams. Setting must happen before the first write of the value (the header is emitted lazily and carries the FlagQPack hint when this is on).
func (*Encoder) Take ¶
Take returns the encoded payload and detaches it from the encoder. The caller takes ownership.
func (*Encoder) WriteArrayHeader ¶
WriteArrayHeader writes the header for an array of n elements. The caller must follow with exactly n element writes.
func (*Encoder) WriteBytes ¶
WriteBytes writes a []byte. In Dense mode, eligible payloads are intern-encoded. The intern table is keyed on the byte sequence, so a string and a []byte with identical content share an ID.
func (*Encoder) WriteFloat32 ¶
func (*Encoder) WriteFloat64 ¶
func (*Encoder) WriteMapHeader ¶
WriteMapHeader writes the header for a map of n entries. The caller must follow with exactly n key/value pairs.
func (*Encoder) WriteString ¶
WriteString writes s. In Dense mode (OptDense set), eligible strings are intern-encoded.
func (*Encoder) WriteStringInline ¶
WriteStringInline forces an in-line encoding even when Dense intern would be eligible. Use for fields known to be unique per message.
func (*Encoder) WriteTimestamp ¶ added in v0.0.2
WriteTimestamp writes a full-range timestamp as two uvarints: sec (zigzag-encoded signed int64 seconds since Unix epoch) and nsec (unsigned uint32 nanoseconds in [0, 999_999_999]). This replaces the old fixed-8-byte UnixNano encoding (clean break).
type FSSTDict ¶ added in v0.0.2
type FSSTDict struct {
// contains filtered or unexported fields
}
FSSTDict is a pre-trained, immutable FSST symbol table.
Training the per-column symbol table is the dominant cost of FSST encoding. Train one FSSTDict over representative samples and reuse it across many FSSTDict.Marshal calls: each encode then skips training and pays only compression — the train-once, encode-many pattern (the "Static" in FSST).
An FSSTDict is bounded (≤255 symbols, a few KB) and never mutated after training, so it is safe for concurrent use and holds a fixed, small amount of memory. It cannot grow, so reusing one across the life of a process does not leak. The wire stays self-describing: each FSST column still carries its table, so output produced with a dictionary decodes with a plain Unmarshal.
func TrainFSSTDict ¶ added in v0.0.2
TrainFSSTDict learns an FSST symbol table from representative byte samples (typically a slice of the string column you will encode). Deterministic: the same samples always yield the same dictionary.
func TrainFSSTDictStrings ¶ added in v0.0.2
TrainFSSTDictStrings is TrainFSSTDict over []string, viewing each string's bytes without copying (the strings are only read during training).
func (*FSSTDict) AppendMarshal ¶ added in v0.0.2
AppendMarshal encodes v with this dictionary and appends to dst (see AppendMarshal). Enables FSST and its columnar prerequisites.
func (*FSSTDict) Marshal ¶ added in v0.0.2
Marshal encodes v using this pre-trained dictionary for FSST string columns. It enables the FSST codec and its columnar prerequisites (Dense, QPack, ShapeIntern), so even FSSTDict.Marshal(v, OptSpeed) compresses; combine with OptCompression to add the float codecs and rANS.
type Marshaler ¶
Marshaler is implemented by types that know how to serialize themselves into the QDF wire format. Implementations should append exactly one value to dst and return the extended slice.
Example ¶
ExampleMarshaler shows a custom MarshalQDF / UnmarshalQDF pair. Use the interface when you want a compact wire form for a type — e.g. packing a small struct into a single primitive, versioning a payload, or pre-validating bytes before they hit the wire.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
// Color is a 24-bit RGB value stored as a single uint32 on the
// wire. It implements Marshaler / Unmarshaler so qdf encodes it
// as a primitive integer instead of recursing into reflect.
type Color struct{ R, G, B uint8 }
// MarshalQDF packs the three bytes into a uint32 and writes it
// using the encoder primitives from a temporary qdf.Encoder
// over the caller's buffer. This shape — temporary encoder over
// the caller's dst slice — is what qdfgen produces by default.
func (c Color) MarshalQDF(dst []byte) ([]byte, error) {
enc := qdf.NewEncoderOnBuf(dst, qdf.Fast)
enc.MarkHeaderWritten() // dst already contains the parent header
enc.WriteUint(uint64(c.R)<<16 | uint64(c.G)<<8 | uint64(c.B))
return enc.Bytes(), nil
}
// UnmarshalQDF reverses the pack and returns the number of bytes
// consumed from src.
func (c *Color) UnmarshalQDF(src []byte) (int, error) {
dec := qdf.NewDecoderOnBuf(src)
dec.MarkHeaderRead()
v, err := dec.ReadUint()
if err != nil {
return 0, err
}
c.R = uint8(v >> 16)
c.G = uint8(v >> 8)
c.B = uint8(v)
return dec.Pos(), nil
}
// ExampleMarshaler shows a custom MarshalQDF / UnmarshalQDF pair.
// Use the interface when you want a compact wire form for a
// type — e.g. packing a small struct into a single primitive,
// versioning a payload, or pre-validating bytes before they hit
// the wire.
func main() {
in := Color{R: 0x12, G: 0x34, B: 0x56}
buf, _ := qdf.MarshalDirect(&in)
var out Color
_ = qdf.UnmarshalDirect(buf, &out)
fmt.Printf("R=%02x G=%02x B=%02x\n", out.R, out.G, out.B)
}
Output: R=12 G=34 B=56
type Options ¶
type Options uint32
Options is a bit-mask of per-call encoder feature toggles. A zero value (OptSpeed) gives the fastest path: Fast mode, no codecs, no predictors. Or-combine bits with | to opt in to individual codecs. Pre-built bundles cover the common "max speed", "balanced", and "max compression" use cases without having to remember the layout.
Options carry by value (uint32) so Marshal / AppendMarshal add zero allocations over the pool / output-clone overhead. The encoder checks each bit with a single AND on the hot path.
Example ¶
ExampleOptions shows how the Options bit-mask picks which codec layers run for a Marshal call. The same input encodes to a progressively smaller wire as more codecs opt in, with a small CPU penalty per added codec. The decoder reads any dialect off the wire header so changing Options is a producer-side decision.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Row struct {
Service string `qdf:"service"`
Region string `qdf:"region"`
Status int `qdf:"status"`
}
// Repetitive batch — every row shares the same service and region.
in := make([]Row, 50)
for i := range in {
in[i] = Row{Service: "billing", Region: "eu-west-1", Status: 200}
}
for _, p := range []struct {
name string
opts qdf.Options
}{
{"OptSpeed", qdf.OptSpeed},
{"OptBalanced", qdf.OptBalanced},
} {
buf, _ := qdf.Marshal(in, p.opts)
fmt.Printf("%-12s wire=%d bytes\n", p.name, len(buf))
}
}
Output: OptSpeed wire=2208 bytes OptBalanced wire=161 bytes
Example (ColumnarStringDict) ¶
ExampleOptions_columnarStringDict shows the per-column string dictionary that fires automatically under OptBalanced for a slice of structs whose string fields are enum-like — a small set of distinct values scattered across rows. The distinct strings are stored once and each row keeps a few-bit index, so the wire is far smaller than one interned reference per value. It is gated never-worse: run-heavy or high-cardinality columns keep the per-value path, and the choice is invisible to the decoder.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type LogRow struct {
TS int64 `qdf:"ts"` // sequential — Delta+FOR makes columnar win
Level string `qdf:"level"` // enum — string dictionary
Service string `qdf:"service"` // enum — string dictionary
}
levels := []string{"INFO", "WARN", "ERROR"}
services := []string{"api", "auth", "billing"}
in := make([]LogRow, 300)
for i := range in {
in[i] = LogRow{
TS: int64(1700000000 + i),
Level: levels[i%len(levels)],
Service: services[(i*7)%len(services)],
}
}
buf, _ := qdf.Marshal(in, qdf.OptBalanced)
var out []LogRow
_ = qdf.Unmarshal(buf, &out)
fmt.Printf("rows=%d wire=%d bytes roundtrip=%v\n", len(in), len(buf), out[0] == in[0] && out[299] == in[299])
}
Output: rows=300 wire=234 bytes roundtrip=true
const ( // OptDense activates the inline intern table. First occurrences of // strings and []byte payloads are stored once and back-referenced // by ID (1-3 bytes per reuse). Required for any of the state-ref // codecs (Repeat / MTF / Pair) and for tagMapShape. OptDense Options = 1 << iota // OptQPack enables the numeric / boolean slice codecs. Bools // bit-pack; 32- and 64-bit integer slices ([]int/int32/int64, // []uint/uint32/uint64) run the codec picker — Frame-of-Reference, // Delta+FOR, RLE, dictionary, and Patched FOR — widening 32-bit // values to 64-bit for selection and narrowing back on decode; // float slices stay on raw-LE. Auto-selected per slice; the // encoder falls back to raw-LE when nothing wins. Gorilla XOR // for floats lives behind OptGorillaFloat (bit 5) — see // OptCompression for the bundle that turns it on. OptQPack // OptShapeIntern routes struct emissions through tagMapShape: each // distinct struct type declares its field ordering on first emit; // subsequent emits write only the shape ID + values. Requires // OptDense (the shape table lives on the intern table side). OptShapeIntern // OptPairPred enables the Markov-1 successor predictor // (tagStatePair, 0xEA). Per previous state-ref ID the encoder stores // its most-recent successor (top-1, K=1); a hit emits the transition // rank in a single byte (always rank 0). Requires OptDense. OptPairPred // OptMTF enables Move-to-Front rank coding (tagStateMTF, 0xE9). // When the LRU rank of a state-ref ID needs fewer varuint bytes // than the raw id, the rank is emitted instead. Requires OptDense. OptMTF // OptGorillaFloat opts in to the Gorilla XOR codec for []float64 // (and []float32) slices. Gorilla collapses smooth time-series // data dramatically (~75 % wire reduction on quantised metric // streams) but trades that for ~10× more CPU on the encode/decode // path because the body is bit-level. Off by default in // OptBalanced for that reason; included in OptCompression for // archive-style workloads where wire size dominates. Requires // OptQPack. OptGorillaFloat // OptRANS adds a final static order-0 rANS entropy pass over the whole // encoded body. Opt-in (bundled into OptCompression) because it trades // encode/decode CPU for wire size. The encoder applies it only when the // rANS form is strictly smaller than the plain body, so it never makes a // buffer larger. Whole-buffer, so the streaming encoder ignores it. OptRANS // OptColumnIndex makes a columnar []struct payload carry a column-length // index so readers can decode a subset of columns without decoding the // rest. Opt-in, ~4 bytes per column. Only affects payloads the encoder // transposes into the columnar container (tagColStruct); has no effect on // other shapes. Sets FlagColIndex on the header. OptColumnIndex // OptFSST opts in to the FSST string codec for columnar string columns // (high-cardinality, substring-sharing text: log lines, URLs, paths). A // CPU-for-size trade (trains a per-column symbol table), so it is excluded // from OptBalanced and bundled into OptCompression. Never-larger: emitted // only when strictly smaller than the dictionary / per-value forms. OptFSST // OptMapShape interns map key-sets the way OptShapeIntern interns struct // field-names: the first map with a given set of (string) keys declares the // key ordering via tagMapShape; subsequent maps with the same key-set emit // only the shape ID + values in canonical (sorted) key order. Cuts encode // CPU and wire on maps with recurring keys (telemetry tags, log labels). // Requires OptDense (the shape table lives on the intern side). Opt-in in // v1; never-worse fallback to a plain map header when a key-set does not // recur. OptMapShape // OptSpeed is the zero-bit preset: Fast mode, no codecs, no // predictors. Maximum throughput, smallest CPU footprint. OptSpeed Options = 0 // OptBalanced bundles every codec that does not trade CPU for // compression beyond its sweet spot. The right default for // telemetry, log batches, and any payload with repetitive // strings or numeric slices. Notably excludes OptGorillaFloat — // reach for OptCompression when the float slices in the payload // are smooth time-series and wire size matters more than encode // latency. OptBalanced Options = OptDense | OptQPack | OptShapeIntern | OptPairPred | OptMTF // OptCompression bundles every codec that the encoder will spend // CPU on for wire-size gains. On top of OptBalanced it adds // OptGorillaFloat (Gorilla XOR for float slices), which trades // encode/decode CPU for wire size, so it stays out of the // OptBalanced default; future heavy codecs (rANS, dictionary // preloading) will land in this bundle without breaking the name. OptCompression Options = OptBalanced | OptGorillaFloat | OptRANS | OptFSST )
type QueryError ¶ added in v0.0.2
type QueryError struct {
Op string // e.g. "predicate pushdown"
Field string // filter/projection field involved, if any
Want colKind // kind implied by the predicate's T (kind mismatches)
Got colKind // kind found on the wire (kind mismatches)
Err error // wrapped sentinel
}
QueryError describes why a filtering/projecting decode (Unmarshal with QueryOptions) could not proceed. It wraps one of ErrUnsupported, ErrTypeMismatch, or ErrFieldNotFound, so callers can categorise the failure with errors.Is and read the specifics with errors.As.
func (*QueryError) Error ¶ added in v0.0.2
func (e *QueryError) Error() string
func (*QueryError) Unwrap ¶ added in v0.0.2
func (e *QueryError) Unwrap() error
type QueryOption ¶ added in v0.0.2
type QueryOption struct {
// contains filtered or unexported fields
}
QueryOption configures a filtering/projecting Unmarshal. Construct with Where, And, Or, Not (predicate tree) or Select (projection). Exactly one of node / selectFields is set.
func And ¶ added in v0.0.2
func And(opts ...QueryOption) QueryOption
And keeps rows where every sub-predicate is true. Multiple top-level Where options are an implicit And; And is the explicit, nestable form.
func Not ¶ added in v0.0.2
func Not(opt QueryOption) QueryOption
Not keeps rows where the sub-predicate is not true. Under three-valued NULL semantics a nil (absent) nullable row is UNKNOWN, so Not(pred) excludes nil rows just as the bare predicate does.
func Or ¶ added in v0.0.2
func Or(opts ...QueryOption) QueryOption
Or keeps rows where at least one sub-predicate is true.
Example ¶
ExampleOr demonstrates OR/NOT predicate combinators: keep events that are either level=="ERROR" or code>=500, while excluding level=="DEBUG" rows.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
Level string `qdf:"level"`
Code int32 `qdf:"code"`
}
events := make([]Event, 30)
for i := range events {
if i%2 == 0 {
events[i] = Event{Level: "ERROR", Code: int32(500 + i)}
} else {
events[i] = Event{Level: "INFO", Code: int32(i)}
}
}
buf, _ := qdf.Marshal(events, qdf.OptBalanced|qdf.OptColumnIndex)
var hot []Event
_ = qdf.Unmarshal(buf, &hot,
qdf.Or(
qdf.Where("level", func(s string) bool { return s == "ERROR" }),
qdf.Where("code", func(c int32) bool { return c >= 500 }),
),
qdf.Not(qdf.Where("level", func(s string) bool { return s == "DEBUG" })),
)
fmt.Println(len(hot))
}
Output: 15
func Select ¶ added in v0.0.2
func Select(fields ...string) QueryOption
Select restricts decoding to the named columns. Top-level only; nesting a Select inside And/Or/Not is reported as ErrUnsupported.
func Where ¶ added in v0.0.2
func Where[T Queryable](field string, pred func(T) bool) QueryOption
Where keeps only rows whose field column satisfies pred. T must match the column's native kind (any integer width, float32/64, string, or bool); a mismatch is reported as ErrTypeMismatch at decode time. If field is absent from the wire the call returns ErrFieldNotFound. Multiple Where options are AND-ed. The predicate is called once per row with the native value — no interface boxing per value.
func WithArena ¶ added in v0.0.6
func WithArena(a *Arena) QueryOption
WithArena makes the decode pack copied inline string values into a, instead of allocating one string per field — near-zero allocations across an epoch of decodes (see Arena). The decoded strings alias a's memory and follow Arena's lifetime contract. Ignored together with WithNoCopy (aliasing already skips the copy). Composes with predicates / Select / WithNoCopy.
func WithNoCopy ¶ added in v0.0.2
func WithNoCopy() QueryOption
WithNoCopy makes the decode return string and []byte values that ALIAS the input buffer instead of copying them. On string-heavy payloads this is ~2x faster with near-zero allocations.
DANGER — lifetime contract: the returned values are valid ONLY while the input buffer passed to Unmarshal stays alive and is never modified or reused. Do NOT use WithNoCopy when the input may be recycled, mutated, or freed before you finish reading the decoded values — e.g. a pooled HTTP request body. The decoded strings would silently become garbage. This is a manual-memory use-after-free, not a data race: the race detector will NOT catch it.
Safe for caller-owned, immutable input such as an mmap or a file read fully into memory.
Also safe — and the common zero-copy high-throughput pattern — when the input buffer is allocated FRESH per message and never pooled or overwritten: e.g. io.ReadAll(body), or a make([]byte, n) you read one message into and do not reuse. You do NOT have to track the buffer's lifetime manually: the aliasing string/[]byte headers in the decoded value keep the backing buffer alive through the garbage collector (Go marks the whole allocation from an interior pointer), so the buffer lives exactly as long as the values that point into it. The hazard is exclusively buffer REUSE (a sync.Pool buffer returned after the handler, a scratch slice you overwrite on the next read) or mutation — not lifetime. If every decode reads into its own fresh slice, WithNoCopy is safe and gives the full ~2x / near-zero-alloc win for free.
Scope: WithNoCopy affects the reflect decode path. A type that implements Unmarshaler (including codegen-generated UnmarshalQDF) decodes through its own decoder via the byte-only UnmarshalQDF(data) interface, which cannot inherit this flag, so such types still copy. Use SetNoCopy on a Decoder/StreamDecoder you drive directly if you need zero-copy there.
Example ¶
ExampleWithNoCopy decodes with zero-copy: the returned string / []byte fields alias the input buffer instead of being copied, cutting allocations to near zero (~1.7x faster on string-heavy batches).
Lifetime contract: the decoded values are valid only while data stays alive and is never modified or reused. Never use WithNoCopy when data is a pooled or recycled buffer (e.g. an HTTP request body) — the values would silently corrupt. It is opt-in for this reason; the default Unmarshal copies.
package main
import (
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
Service string `qdf:"service"`
Message string `qdf:"message"`
}
// data is owned here and outlives `out`, so noCopy is safe.
data, _ := qdf.Marshal(&Event{Service: "api", Message: "ok"}, qdf.OptSpeed)
var out Event
if err := qdf.Unmarshal(data, &out, qdf.WithNoCopy()); err != nil {
panic(err)
}
fmt.Println(out.Service, out.Message)
}
Output: api ok
type Queryable ¶ added in v0.0.2
type Queryable interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 | uintptr |
float32 | float64 | string | bool
}
Queryable is the set of column element types a predicate can match. Exact base types only: Where dispatches on the concrete func(T) bool type once at construction, which matches base types but not user-defined named types.
type StreamDecoder ¶
type StreamDecoder struct {
// contains filtered or unexported fields
}
StreamDecoder reads a sequence of values from an io.Reader. The intern table is preserved across Decode calls to match StreamEncoder.
The decoder grows its window buffer as needed. State-table entries alias the buffer, so the window is never compacted during a stream; total memory tracks the stream length. For unbounded streams partition into envelopes and create a fresh StreamDecoder per envelope.
func NewStreamDecoder ¶
func NewStreamDecoder(r io.Reader) *StreamDecoder
NewStreamDecoder returns a stream decoder reading from r.
func (*StreamDecoder) Close ¶
func (s *StreamDecoder) Close() error
Close releases the scratch buffer to the pool. The underlying reader is not closed. Idempotent: subsequent calls are a safe no-op.
func (*StreamDecoder) Decode ¶
func (s *StreamDecoder) Decode(out any) error
Decode reads the next value into out. out must be a pointer. Returns io.EOF at a clean end of stream. Each message is length-framed, so a message of any size is buffered in full before decoding — no 4 KiB window limit.
func (*StreamDecoder) SetNoCopy ¶
func (s *StreamDecoder) SetNoCopy(v bool)
SetNoCopy makes Decode return string / []byte values that alias the decoder's internal window buffer instead of copying them — eliminating the per-value copy that dominates decode allocation.
Unlike the one-shot Decoder.SetNoCopy (which aliases the caller's input buffer and is unsafe the moment that buffer is reused — the typical server recycles its read buffer right after the handler returns), the stream owns its window buffer, so aliasing is safe for the lifetime of the stream:
- A decoded value stays valid until Close. Close returns the window to a pool, after which any retained aliased value is undefined — copy (strings.Clone / append) anything you need past Close.
- The window grows but is never compacted mid-stream (the Dense intern table already aliases it for cross-message back-references), so a growth does not invalidate earlier values, and noCopy adds no extra memory: the bytes are retained either way.
Because the window is retained for the whole stream regardless, this is the safe home for zero-copy decode — process or copy each value before Close and you pay zero per-value allocation across every message. No-op after Close.
Example ¶
ExampleStreamDecoder_SetNoCopy decodes a multi-message stream without copying the decoded strings out of the input buffer. The decoded values alias the io.Reader's payload; the caller must not retain the values past the next Decode call.
package main
import (
"bytes"
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
Service string `qdf:"service"`
}
// Build a 2-message stream.
var sink bytes.Buffer
enc := qdf.NewStreamEncoderWith(&sink, qdf.OptBalanced)
_ = enc.Encode(Event{Service: "auth"})
_ = enc.Encode(Event{Service: "auth"})
_ = enc.Flush()
_ = enc.Close()
dec := qdf.NewStreamDecoder(&sink)
dec.SetNoCopy(true) // aliased reads — zero allocations
defer dec.Close() //nolint:errcheck // example
var ev Event
for dec.Decode(&ev) == nil {
fmt.Println(ev.Service)
}
}
Output: auth auth
type StreamEncoder ¶
type StreamEncoder struct {
// contains filtered or unexported fields
}
StreamEncoder writes a sequence of values into an io.Writer. The header is emitted once before the first value; in Dense mode the intern table survives across Encode calls so back-references span the whole stream.
Example ¶
ExampleStreamEncoder writes a batch of messages into a single io.Writer where the Dense-mode intern table, shape table, and predictors survive across calls. The first message of every shape pays for the key intern records; every subsequent message of the same shape emits values only.
This is the form to reach for when piping events to a file or a network socket — per-message Marshal would reset the state table each call and lose the cross-message compression.
package main
import (
"bytes"
"fmt"
"github.com/alex60217101990/qdf"
)
func main() {
type Event struct {
Service string `qdf:"service"`
Status int `qdf:"status"`
}
var sink bytes.Buffer
enc := qdf.NewStreamEncoderWith(&sink, qdf.OptBalanced)
defer enc.Close() //nolint:errcheck // example
events := []Event{
{"billing", 200},
{"billing", 200},
{"billing", 500},
}
for _, e := range events {
if err := enc.Encode(e); err != nil {
panic(err)
}
}
if err := enc.Flush(); err != nil {
panic(err)
}
dec := qdf.NewStreamDecoder(&sink)
for {
var out Event
if err := dec.Decode(&out); err != nil {
break
}
fmt.Printf("%s=%d\n", out.Service, out.Status)
}
}
Output: billing=200 billing=200 billing=500
func NewStreamEncoder ¶
func NewStreamEncoder(w io.Writer, mode Mode) *StreamEncoder
NewStreamEncoder returns a stream encoder backed by w. Dense mode activates the full balanced codec set (OptBalanced); Fast mode emits raw tagged bytes (OptSpeed). For finer per-stream tuning, build the StreamEncoder via NewStreamEncoderWith.
func NewStreamEncoderWith ¶
func NewStreamEncoderWith(w io.Writer, opts Options) *StreamEncoder
NewStreamEncoderWith returns a stream encoder with the given Options bit-mask. The intern table (when OptDense is set) survives across Encode calls so back-references span the whole stream.
func (*StreamEncoder) Close ¶
func (s *StreamEncoder) Close() error
Close flushes pending data and releases the scratch buffer to the pool. The underlying writer is not closed. Idempotent: subsequent calls are a safe no-op.
func (*StreamEncoder) Encode ¶
func (s *StreamEncoder) Encode(v any) error
Encode writes v as the next value in the stream. Each message is framed with a uvarint byte-length prefix so the decoder can buffer a whole message — of any size — before decoding it. The 5-byte stream header is written once, before the first frame, and is not itself framed. The encoder flushes internally when its buffer crosses 16 KiB; call Flush to push earlier.
func (*StreamEncoder) Flush ¶
func (s *StreamEncoder) Flush() error
Flush writes any buffered bytes to the underlying writer.
type Unmarshaler ¶
Unmarshaler is implemented by types that know how to deserialize themselves from a QDF wire-format slice. Implementations should consume exactly one value from src and return the number of bytes consumed.
A custom UnmarshalQDF reads the Fast wire format. Marshal honours this: a type implementing Marshaler always emits its Fast-format body and is framed as Fast regardless of the Options passed, so Marshaler+Unmarshaler types round-trip under any Options. A type that implements Unmarshaler WITHOUT also implementing Marshaler is encoded structurally; under a Dense/QPack tier that produces a Dense body its Fast-only UnmarshalQDF cannot read. Implement both interfaces (or neither) to avoid this — generated code from cmd/qdfgen always implements both.
type UnmarshalerArena ¶ added in v0.0.6
type UnmarshalerArena interface {
UnmarshalerOpts
UnmarshalQDFArena(src []byte, noCopy bool, a *Arena) (n int, err error)
}
UnmarshalerArena is an optional extension that also accepts a decode Arena, so the implementation packs copied string/[]byte fields into it (see WithArena) instead of one allocation per field. Generated code from cmd/qdfgen implements this; UnmarshalQDFOpts delegates to it with a nil arena. Decoders honor it only when the caller passed an arena.
type UnmarshalerOpts ¶ added in v0.0.2
type UnmarshalerOpts interface {
Unmarshaler
UnmarshalQDFOpts(src []byte, noCopy bool) (n int, err error)
}
UnmarshalerOpts is an optional extension of Unmarshaler that accepts the noCopy flag. When noCopy is true, the implementation should decode string and []byte fields as aliases of src (see WithNoCopy) instead of copying. Generated code from cmd/qdfgen implements this; the plain UnmarshalQDF delegates to it with noCopy=false. Decoders honor it only when the caller opted into noCopy.
Source Files
¶
- arena.go
- columnar.go
- columnar_decode_scratch.go
- columnar_select.go
- decoder.go
- decoder_read.go
- decoder_skip.go
- encoder.go
- errors.go
- floats_default.go
- fsst_dict.go
- map_shape.go
- maps_fast.go
- maps_fast_generated.go
- marshaler.go
- nullable_col.go
- qdf.go
- qdf_direct.go
- qdf_generic.go
- qpack.go
- qpack_alp.go
- qpack_delta.go
- qpack_dict.go
- qpack_for.go
- qpack_fsst.go
- qpack_gorilla.go
- qpack_pfor.go
- qpack_rle.go
- qpack_strdict.go
- qpack_strraw.go
- query.go
- race_norace.go
- reflect_desc.go
- reflect_encode.go
- reuse.go
- slices_fast.go
- state.go
- stream.go
- wire.go
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
bitpack
Package bitpack implements the pure bit-packing and SIMD kernel layer shared by qdf's integer codecs (FOR, Delta+FOR, dict, PFOR, ALP).
|
Package bitpack implements the pure bit-packing and SIMD kernel layer shared by qdf's integer codecs (FOR, Delta+FOR, dict, PFOR, ALP). |
|
bufpool
Package bufpool implements size-classed, sharded byte-slice pools.
|
Package bufpool implements size-classed, sharded byte-slice pools. |
|
bumparena
Package bumparena is a monotonic bump allocator for decoded string bodies.
|
Package bumparena is a monotonic bump allocator for decoded string bodies. |
|
endian
Package endian exposes a build-time constant for native byte order.
|
Package endian exposes a build-time constant for native byte order. |
|
fsst
Package fsst implements FSST (Fast Static Symbol Table) string compression: a learned table of up to 255 substrings (each 1..8 bytes) replaced by 1-byte codes, with code 255 reserved as an escape for a following literal byte.
|
Package fsst implements FSST (Fast Static Symbol Table) string compression: a learned table of up to 255 substrings (each 1..8 bytes) replaced by 1-byte codes, with code 255 reserved as an escape for a following literal byte. |
|
intern
Package intern provides a small fixed-size string cache used by the decoder to deduplicate repeated map keys.
|
Package intern provides a small fixed-size string cache used by the decoder to deduplicate repeated map keys. |
|
internarena
Package internarena is a bytes-only bump-pointer allocator for the Dense-mode intern table.
|
Package internarena is a bytes-only bump-pointer allocator for the Dense-mode intern table. |
|
mapsgen
command
internal/mapsgen — code generator for typed map encode/decode fast paths.
|
internal/mapsgen — code generator for typed map encode/decode fast paths. |
|
rans
Package rans is a static order-0 byte range Asymmetric Numeral System coder (the canonical rans_byte: 32-bit state, byte renormalization, a 12-bit normalized frequency table).
|
Package rans is a static order-0 byte range Asymmetric Numeral System coder (the canonical rans_byte: 32-bit state, byte renormalization, a 12-bit normalized frequency table). |
|
reflectutil
Package reflectutil holds the reflect-based helpers the qdf codec uses to allocate slices and maps without going through reflect.Value.
|
Package reflectutil holds the reflect-based helpers the qdf codec uses to allocate slices and maps without going through reflect.Value. |
|
unsafestr
Package unsafestr provides a zero-copy conversion from []byte to string.
|
Package unsafestr provides a zero-copy conversion from []byte to string. |