aggregate

package
v2.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultGroupLimit      = 50_000
	DefaultAccumArrayLimit = 10_000
	DefaultMemoryLimit     = 256 << 20
)

Variables

View Source
var (
	// ErrGroupLimitExceeded is returned when $group exceeds the configured
	// maximum number of unique group keys.
	ErrGroupLimitExceeded = errors.New("any-store/aggregate: group limit exceeded")

	// ErrAccumArrayLimitExceeded is returned when a $push or $addToSet
	// accumulator exceeds the configured maximum array length.
	ErrAccumArrayLimitExceeded = errors.New("any-store/aggregate: accumulator array limit exceeded")

	// ErrMemoryLimitExceeded is returned when the blocking stages of a
	// pipeline retain more than the configured memory budget.
	ErrMemoryLimitExceeded = errors.New("any-store/aggregate: memory limit exceeded")
)

Functions

func Accumulators

func Accumulators() []string

Accumulators returns the $group accumulator vocabulary, sorted and with the leading '$'. The slice is a fresh copy: callers may keep or mutate it.

func CutSink

func CutSink(p Pipeline) (StageSpec, Pipeline)

CutSink splits a trailing sink spec off the pipeline. Returns (nil, p) when the pipeline does not end in one. ParsePipeline guarantees a sink can appear only in the last position.

func ExplainStages

func ExplainStages(p Pipeline) string

ExplainStages renders the in-pipeline stage list for Explain output, including the top-K annotation a following $skip/$limit folds into $sort.

func HasLookup

func HasLookup(p Pipeline) bool

HasLookup reports whether the pipeline contains a $lookup spec, including inside $facet sub-pipelines (the root package injects Env.Lookup — and keeps a read tx for it — only then).

func SinkName

func SinkName(s StageSpec) string

SinkName returns the stage name of a sink spec ("$merge"/"$out"), or "" for any other spec.

func SplitPrefix

func SplitPrefix(p Pipeline) (Prefix, Pipeline)

SplitPrefix consumes the longest pushable pipeline prefix in canonical order — $match* → $sort? → $skip? → $limit? — and returns it together with the remaining stages (which run as in-pipeline operators). Pushdown stops at the first stage that synthesizes documents ($group/$project/$addFields/ $unwind/$count) and at any stage out of canonical order: a $match after $skip/$limit does not commute, and out-of-order combinations are rare enough that running them in-pipeline (correct, just unoptimized) beats rewrite complexity.

$expr predicates are never pushed into the access plan: a leading $match's ordinary filter part enters the prefix (index-eligible) while its expressions come back as a synthesized residual MatchSpec at the head of the remaining stages. A following $sort may still be pushed — filtering a sorted stream preserves its order — but $skip/$limit may not: they would truncate before the residual predicate runs.

func Stages

func Stages() []string

Stages returns the stage vocabulary accepted by the pipeline parser — every stage key ParsePipeline recognizes ($set is $addFields' alias), sorted and with the leading '$'. The slice is a fresh copy: callers may keep or mutate it. Use it to advertise the grammar (docs, error payloads) instead of hand-copying the list.

Types

type AbsExpr

type AbsExpr struct{ Arg Expr }

AbsExpr evaluates $abs; missing/null/non-numeric operand → null.

func (*AbsExpr) Eval

func (e *AbsExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*AbsExpr) String

func (e *AbsExpr) String() string

type AccumOp

type AccumOp uint8

AccumOp identifies a $group accumulator operator.

const (
	AccumSum AccumOp = iota
	AccumAvg
	AccumMin
	AccumMax
	AccumCount
	AccumFirst
	AccumLast
	AccumPush
	AccumAddToSet
)

func (AccumOp) String

func (op AccumOp) String() string

type AccumSpec

type AccumSpec struct {
	Name string
	Op   AccumOp
	Arg  Expr
}

AccumSpec is one accumulator of a $group stage. Arg is nil for $count.

type Accumulator

type Accumulator interface {
	Step(val *anyenc.Value) error
	Finalize(out *anyenc.Arena) (*anyenc.Value, error)
}

Accumulator folds the per-row values of one $group output field.

Step receives the already-evaluated argument expression (nil = missing); the value is transient — valid only during the call — so implementations retain data by marshaling into self-owned buffers. Finalize is called once, after the upstream drain; results may be allocated on out or on an accumulator-owned parser (the stage consumes the result before the next emit either way).

type AddFieldsSpec

type AddFieldsSpec struct{ Fields []ProjectField }

func (AddFieldsSpec) String

func (s AddFieldsSpec) String() string

type AddFieldsStage

type AddFieldsStage struct {
	Src    Stage
	Fields []ProjectField
	// contains filtered or unexported fields
}

AddFieldsStage overlays computed fields onto each upstream document by mutating it in place (the same pattern FtsIter uses to inject _score): zero-copy, no output object rebuild. Mutation is safe because rows are transient — the underlying parse buffers are rewritten on the next row.

When an upstream $unwind may emit the same underlying document for several consecutive rows (undoOverlay, set by Build), the original value of every overlaid field is captured and rolled back before the next row is pulled: otherwise a later row of the same document would evaluate its expressions against the previous row's overlays instead of the stored fields.

func (*AddFieldsStage) Close

func (s *AddFieldsStage) Close()

func (*AddFieldsStage) Next

func (s *AddFieldsStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*AddFieldsStage) String

func (s *AddFieldsStage) String() string

type ArithExpr

type ArithExpr struct {
	Op   ArithOp
	Args []Expr
}

ArithExpr evaluates $add/$subtract/$multiply/$divide over numeric operands, plus Mongo's date arithmetic: $add with exactly one dateTime operand shifts it by the numeric sum of millis, and $subtract handles [date, date] → millis and [date, number] → date. Any other mix, a missing/null/non-numeric operand, division by zero, overflow and a non-finite result make the result null (Mongo errors for these; see evalNumber). An empty operand list yields the operator's identity, Mongo semantics ($add 0, $multiply 1).

func (*ArithExpr) Eval

func (e *ArithExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*ArithExpr) String

func (e *ArithExpr) String() string

type ArithOp

type ArithOp uint8

ArithOp identifies an arithmetic expression operator.

const (
	OpAdd ArithOp = iota
	OpSubtract
	OpMultiply
	OpDivide
)

func (ArithOp) String

func (op ArithOp) String() string

type ArrayExpr

type ArrayExpr struct {
	Exprs []Expr
}

ArrayExpr is an array expression: every element is an expression. Missing elements evaluate to null so the array length is preserved.

func (*ArrayExpr) Eval

func (e *ArrayExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*ArrayExpr) String

func (e *ArrayExpr) String() string

type CompareExpr

type CompareExpr struct {
	Op   CompareOp
	A, B Expr
	// contains filtered or unexported fields
}

CompareExpr evaluates $eq/$ne/$gt/$gte/$lt/$lte ($cmp: -1/0/1) over any two values in the engine's canonical cross-type order: bytes.Compare of the marshaled anyenc encoding — the exact order $sort and $min/$max use. The type tag leads the encoding, so types order by tag (null < number < string < false < true < array < object < ... < dateTime; differs from BSON's canonical order, see docs/aggregation.md); within a type the encoding is order-preserving: numbers via the sortable float encoding (-0 normalized to 0), strings bytewise, arrays elementwise, objects by marshaled bytes (field-order-sensitive, consistent with $group key equality). $eq is c == 0, so all seven operators agree by construction. A missing operand marshals as the null tag: missing == null. Operands marshal into two reusable per-expr scratch buffers (expressions are per-pipeline, single-goroutine): alloc-free after warm-up.

func (*CompareExpr) Eval

func (e *CompareExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*CompareExpr) String

func (e *CompareExpr) String() string

type CompareOp

type CompareOp uint8

CompareOp identifies a comparison expression operator.

const (
	CmpEq CompareOp = iota
	CmpNe
	CmpGt
	CmpGte
	CmpLt
	CmpLte
	CmpCmp // three-way: -1/0/1
)

func (CompareOp) String

func (op CompareOp) String() string

type ConcatExpr

type ConcatExpr struct {
	Args []Expr
	// contains filtered or unexported fields
}

ConcatExpr evaluates $concat over string operands into a reusable scratch buffer (expressions are per-pipeline, single-goroutine), then creates one arena string value. A missing, null, or non-string operand makes the result null (Mongo errors on non-strings; same rationale as evalNumber). An empty operand list yields "", Mongo semantics.

func (*ConcatExpr) Eval

func (e *ConcatExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*ConcatExpr) String

func (e *ConcatExpr) String() string

type CondExpr

type CondExpr struct {
	If, Then, Else Expr
}

CondExpr evaluates $cond in both spellings: [if, then, else] and {"if":..., "then":..., "else":...} (all three required). Only the taken branch is evaluated (lazy, Mongo semantics).

func (*CondExpr) Eval

func (e *CondExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*CondExpr) String

func (e *CondExpr) String() string

String renders the canonical array spelling for both parsed forms.

type CountSpec

type CountSpec struct{ Field string }

func (CountSpec) String

func (s CountSpec) String() string

type CountStage

type CountStage struct {
	Src   Stage
	Field string
	// contains filtered or unexported fields
}

CountStage drains the upstream stream and emits a single {"<field>": N} document.

func (*CountStage) Close

func (s *CountStage) Close()

func (*CountStage) Next

func (s *CountStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*CountStage) String

func (s *CountStage) String() string

type Ctx

type Ctx struct {
	Context  context.Context
	RowArena *anyenc.Arena
	Buf      *syncpool.DocBuffer
	Mem      *MemAccount
}

Ctx carries per-pipeline shared state through the stage chain.

RowArena ownership: the arena is reset only by the current "row owner" — the source stage (once per source row), a blocking stage on its output side (once per emitted row), and $project/$addFields when Build proves nothing below them keeps arena-allocated data alive across pulls. Non-owning stages allocate on RowArena freely and never reset it, so synthesized values and zero-copy aliases of the source document stay valid for the whole life of the row.

type DateAddExpr

type DateAddExpr struct {
	Start  Expr
	Amount Expr
	Unit   dateUnit
	Loc    *time.Location
	TZ     string // original timezone spelling, "" when defaulted to UTC
}

DateAddExpr evaluates $dateAdd. year/quarter/month are calendar-aware in the operative timezone with Mongo's day-of-month clamping (Jan 31 + 1 month = Feb 28; time.AddDate would normalize to Mar 3); week/day add calendar days in the operative timezone, preserving the local clock across DST (Mongo semantics — a repeated wall time resolves to its earlier occurrence); hour/minute/second/millisecond are fixed millis spans. A missing/null/non-dateTime startDate, a non-integral amount and an out-of-range result → null (Mongo errors; see evalNumber).

func (*DateAddExpr) Eval

func (e *DateAddExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*DateAddExpr) String

func (e *DateAddExpr) String() string

type DateDiffExpr

type DateDiffExpr struct {
	Start, End Expr
	Unit       dateUnit
	Loc        *time.Location
	TZ         string
	WeekStart  time.Weekday
	WeekName   string // original startOfWeek spelling, "" when defaulted
}

DateDiffExpr evaluates $dateDiff: the signed count of unit boundaries crossed from startDate to endDate (Mongo semantics, not elapsed time — a day diff of 23:59 → 00:01 is 1). year/quarter/month/week/day boundaries are local calendar boundaries in the operative timezone; hour/minute/second/ millisecond are absolute-millis boundaries (timezone-independent). startOfWeek (default sunday) applies to unit "week" only.

func (*DateDiffExpr) Eval

func (e *DateDiffExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*DateDiffExpr) String

func (e *DateDiffExpr) String() string

type DatePartExpr

type DatePartExpr struct {
	Part datePart
	Date Expr
	Loc  *time.Location
	TZ   string
}

DatePartExpr evaluates $year and $week in the operative timezone. $week is Mongo's Sunday-based 0-53 week of year (days before the year's first Sunday are week 0) — not the ISO week. Both spellings parse: a plain date expression and the {date, timezone?} object form.

func (*DatePartExpr) Eval

func (e *DatePartExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*DatePartExpr) String

func (e *DatePartExpr) String() string

type DateTruncExpr

type DateTruncExpr struct {
	Date      Expr
	Unit      dateUnit
	Bin       int64 // effective binSize, >= 1
	Loc       *time.Location
	TZ        string
	WeekStart time.Weekday
	WeekName  string
	// contains filtered or unexported fields
}

DateTruncExpr evaluates $dateTrunc: truncate down to the containing binSize×unit bin. Bins anchor at Mongo's documented reference point — 2000-01-01T00:00:00 (in the operative timezone) for every unit except week, which anchors at the first startOfWeek on or after 2000-01-01 (a Saturday). year/quarter/month/week/day truncate local calendar components (a fall-back-repeated local midnight resolves to its earlier occurrence); hour/minute/second/millisecond subtract the local wall-clock residue on the absolute timeline, so truncation stays monotone and idempotent across DST transitions.

func (*DateTruncExpr) Eval

func (e *DateTruncExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*DateTruncExpr) String

func (e *DateTruncExpr) String() string

type Env

type Env struct {
	// Lookup resolves $lookup point reads. Required when the pipeline
	// contains a LookupSpec; Build rejects the spec without it.
	Lookup LookupFunc
}

Env carries execution-environment hooks injected at Build time.

type Expr

type Expr interface {
	// Eval returns the expression value for doc; nil means "missing".
	// Results may be allocated on a; field refs alias doc (zero-copy),
	// except that implicit array traversal collects doc-aliasing elements
	// into an a-allocated array container. Either way the result is valid
	// only while both doc and a are.
	Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)
	fmt.Stringer
}

Expr is an aggregation expression evaluated against one document.

Supported: field references ("$a.b.c"), literals ({"$literal": x} or any plain value), document/array expressions (objects and arrays whose members are themselves expressions, Mongo semantics), and the compute operators in exprOpParsers — arithmetic/string ($add, $round, $concat, ...), conditional ($cond, $switch, $ifNull), comparison ($eq ... $lte, $cmp) and date ($dateAdd, $dateDiff, $dateTrunc, $year, $week). Remaining compute operators (string transforms, ...) are additive future work: composite Expr implementations dispatched in ParseExpr.

func ParseExpr

func ParseExpr(v *anyenc.Value) (Expr, error)

ParseExpr parses an aggregation expression value (Mongo expression-context rules): "$path" strings are field references, {"$literal": x} is an escaped literal, single-key $-objects in exprOpParsers are compute operators, non-operator objects are document expressions, arrays are arrays of expressions, everything else is a literal. Unknown $-operators are rejected.

type FacetSpec

type FacetSpec struct {
	Names     []string
	Pipelines []Pipeline
}

FacetSpec is a parsed $facet stage: named sub-pipelines fanned out over one shared input stream. Names and Pipelines are parallel, in spec order.

func (FacetSpec) String

func (s FacetSpec) String() string

type FacetStage

type FacetStage struct {
	Src  Stage
	Spec FacetSpec
	// contains filtered or unexported fields
}

FacetStage implements $facet: it consumes its entire input stream, feeding each row to every sub-pipeline incrementally — streaming sub-stages process per-row, blocking sub-stages buffer per their own logic — then emits exactly one document {name: [results], ...}.

The fan-out is synchronous pull inversion: each row is pushed by parking it in the facet's feed and driving the sub-chain until the feed pauses it (errFacetPause). The drive-until-pause protocol guarantees the sub-chain quiesces every round — a pause only surfaces through an upstream pull, and every in-place mutator undoes its overlay strictly before pulling — so non-copying facets always hand the shared row back unmodified. Emitted sub-pipeline documents are marshaled into the facet's result buffer immediately (they are only valid until the sub-chain is driven again); buffered result bytes count against the pipeline's shared memory budget.

A $match at the head of a facet filters the shared stream in-flight; per-facet predicate pushdown (one union scan tagging rows per facet) is a future optimization.

func (*FacetStage) Close

func (s *FacetStage) Close()

func (*FacetStage) Next

func (s *FacetStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*FacetStage) String

func (s *FacetStage) String() string

type FieldRefExpr

type FieldRefExpr struct {
	Field string // original spelling without "$", for String()
	Path  []string
}

FieldRefExpr resolves a pre-split document path ("$a.b.c") with Mongo's aggregation field-path semantics, including implicit array traversal; see resolveFieldPath.

func (*FieldRefExpr) Eval

func (e *FieldRefExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*FieldRefExpr) String

func (e *FieldRefExpr) String() string

type GroupSpec

type GroupSpec struct {
	Key    Expr
	Accums []AccumSpec
}

func (GroupSpec) String

func (s GroupSpec) String() string

type GroupStage

type GroupStage struct {
	Src    Stage
	Spec   GroupSpec
	Limits Limits
	// contains filtered or unexported fields
}

GroupStage implements $group: a blocking hash aggregation.

Group keys are the marshaled anyenc bytes of the key expression (missing → null, Mongo semantics); the byte encoding is canonical, so byte equality is value equality. Lookups are alloc-free (map[string]X with a string(bytes) conversion the compiler elides); new keys are interned into a grow-only buffer with unsafe.String map keys (the query.In pattern). Group states live in one slab slice in first-seen order, which is also the emit order (unspecified for callers).

func (*GroupStage) Close

func (s *GroupStage) Close()

func (*GroupStage) Next

func (s *GroupStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*GroupStage) String

func (s *GroupStage) String() string

type IfNullExpr

type IfNullExpr struct {
	Args []Expr
}

IfNullExpr evaluates $ifNull (variadic, Mongo 4.4 form, at least 2 operands): the first operand that is neither null nor missing, else the last operand's value verbatim. Evaluation is lazy left-to-right.

func (*IfNullExpr) Eval

func (e *IfNullExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*IfNullExpr) String

func (e *IfNullExpr) String() string

type LimitSpec

type LimitSpec struct{ N int }

func (LimitSpec) String

func (s LimitSpec) String() string

type LimitStage

type LimitStage struct {
	Src Stage
	N   int
	// contains filtered or unexported fields
}

LimitStage emits at most N rows and stops pulling upstream afterwards.

func (*LimitStage) Close

func (s *LimitStage) Close()

func (*LimitStage) Next

func (s *LimitStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*LimitStage) String

func (s *LimitStage) String() string

type Limits

type Limits struct {
	// MaxGroups caps the number of unique $group keys.
	MaxGroups int
	// MaxAccumArrayLen caps $push / $addToSet result arrays.
	MaxAccumArrayLen int
	// MaxMemoryBytes caps retained bytes across all blocking stages.
	MaxMemoryBytes int
}

Limits bounds the memory-unbounded parts of a pipeline. Zero values mean "use default"; negative values mean "unlimited".

func (Limits) WithDefaults

func (l Limits) WithDefaults() Limits

type LiteralExpr

type LiteralExpr struct {
	// contains filtered or unexported fields
}

LiteralExpr yields the same constant value for every document. The value is detached from the input at parse time (owned bytes + dedicated parser), so it stays valid for the lifetime of the pipeline regardless of caller arenas.

func NewLiteralExpr

func NewLiteralExpr(v *anyenc.Value) (*LiteralExpr, error)

func (*LiteralExpr) Eval

func (e *LiteralExpr) Eval(_ *anyenc.Arena, _ *anyenc.Value) (*anyenc.Value, error)

func (*LiteralExpr) String

func (e *LiteralExpr) String() string

type LookupFunc

type LookupFunc func(key []byte, buf *syncpool.DocBuffer) (*anyenc.Value, error)

LookupFunc serves one $lookup point read: key is the anyenc-marshaled primary-key value, buf is stage-owned scratch (DocBuf + Parser) the implementation fetches and parses the stored document with — the returned value stays valid until the same buf is reused. (nil, nil) means no document has that key. Injected by the root package at Build time: the reads must run against the same snapshot the pipeline streams from, and internal/aggregate cannot import the root package to reach it.

type LookupSpec

type LookupSpec struct {
	From       string // "" = self-join implied
	LocalField string
	LocalPath  []string
	As         string
}

LookupSpec is a parsed $lookup stage, scoped to a self-join point lookup on the primary key: From (optional) must name the aggregated collection itself — the parser doesn't know that name, so the root package validates it at execution setup — and the foreign field is always the primary key "id" (enforced at parse time).

func (LookupSpec) String

func (s LookupSpec) String() string

type LookupStage

type LookupStage struct {
	Src    Stage
	Spec   LookupSpec
	Lookup LookupFunc
	// contains filtered or unexported fields
}

LookupStage implements the self-join $lookup: for each row it resolves the localField value(s) as primary keys of the aggregated collection and overlays the matched full documents onto the row as the "as" field — always an array (Mongo semantics), empty when nothing matches, replacing any existing field.

A missing or null local value yields an empty array: the primary key is never null, so Mongo's null-matching join cannot apply here. An array local value is set membership: elements are deduplicated by first occurrence and the output keeps first-occurrence order (Mongo leaves the order unspecified). An element of a type no stored key has simply doesn't match.

Fetched documents live in stage-owned per-slot buffers reused every row — zero steady-state allocations once the slot count reaches the row's match count high-water mark.

func (*LookupStage) Close

func (s *LookupStage) Close()

func (*LookupStage) Next

func (s *LookupStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*LookupStage) String

func (s *LookupStage) String() string

type MatchSpec

type MatchSpec struct {
	Filter query.Filter // nil when the spec is pure $expr (or empty)
	Exprs  []Expr       // nil when the spec has no $expr
}

MatchSpec is a parsed $match stage: the ordinary query-filter part and the $expr predicates, AND-ed together. Filter is index-eligible when the stage sits in the pushdown prefix; Exprs are always residual per-document predicates (Mongo semantics) — they never become index bounds.

func (MatchSpec) String

func (s MatchSpec) String() string

type MatchStage

type MatchStage struct {
	Src    Stage
	Filter query.Filter // nil: no ordinary-filter part
	Exprs  []Expr       // nil: no $expr part
	// contains filtered or unexported fields
}

MatchStage filters the upstream stream with a query filter and/or $expr predicates (AND-ed, $cond truthiness: false/0/null/missing are false). Matched rows are passed through untouched (1:1); discarded rows are freed by the row owner's per-row arena reset on the following pull. Expression temporaries are allocated on RowArena and die with the row.

func (*MatchStage) Close

func (s *MatchStage) Close()

func (*MatchStage) Next

func (s *MatchStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*MatchStage) String

func (s *MatchStage) String() string

type MemAccount

type MemAccount struct {
	// contains filtered or unexported fields
}

MemAccount is a coarse retained-bytes budget shared by the blocking stages of one pipeline ($group state, $sort arena). Streaming stages retain nothing and don't report.

func NewMemAccount

func NewMemAccount(limit int) *MemAccount

func (*MemAccount) Add

func (m *MemAccount) Add(n int) error

Add records n retained bytes and fails with ErrMemoryLimitExceeded once the budget is exhausted. n may be negative when a stage shrinks/releases.

func (*MemAccount) Used

func (m *MemAccount) Used() int

Used returns the currently accounted retained bytes.

type MergeSpec

type MergeSpec struct {
	Into           string
	WhenMatched    MergeWhenMatched
	WhenNotMatched MergeWhenNotMatched
}

MergeSpec is a parsed $merge stage: upsert pipeline results into Into, keyed by the primary key "id" (the only supported "on"; enforced at parse time, mirroring $lookup's pk-only scope).

func (MergeSpec) String

func (s MergeSpec) String() string

type MergeWhenMatched

type MergeWhenMatched uint8

MergeWhenMatched selects $merge's action for a result document whose id already exists in the target. The zero value is the default ("merge").

const (
	// MergeMatchedMerge overlays the result's top-level fields onto the
	// existing document (missing fields keep their existing values).
	MergeMatchedMerge MergeWhenMatched = iota
	// MergeMatchedReplace replaces the existing document entirely.
	MergeMatchedReplace
	// MergeMatchedKeepExisting leaves the existing document untouched.
	MergeMatchedKeepExisting
	// MergeMatchedFail aborts the whole write, persisting nothing.
	MergeMatchedFail
)

func (MergeWhenMatched) String

func (m MergeWhenMatched) String() string

type MergeWhenNotMatched

type MergeWhenNotMatched uint8

MergeWhenNotMatched selects $merge's action for a result document whose id does not exist in the target. The zero value is the default ("insert").

const (
	// MergeNotMatchedInsert inserts the result document.
	MergeNotMatchedInsert MergeWhenNotMatched = iota
	// MergeNotMatchedDiscard drops the result document.
	MergeNotMatchedDiscard
	// MergeNotMatchedFail aborts the whole write, persisting nothing.
	MergeNotMatchedFail
)

func (MergeWhenNotMatched) String

func (m MergeWhenNotMatched) String() string

type ObjectExpr

type ObjectExpr struct {
	Names []string
	Exprs []Expr
}

ObjectExpr is a document expression: an object whose values are expressions (Mongo semantics for non-operator objects in expression context, e.g. compound $group keys {"a":"$x","b":"$y"}). Missing member values are omitted from the result, like Mongo.

func (*ObjectExpr) Eval

func (e *ObjectExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*ObjectExpr) String

func (e *ObjectExpr) String() string

type OutSpec

type OutSpec struct {
	Coll string
}

OutSpec is a parsed $out stage: replace the contents of collection Coll with the pipeline results.

func (OutSpec) String

func (s OutSpec) String() string

type Pipeline

type Pipeline []StageSpec

Pipeline is a parsed aggregation pipeline: an ordered list of stage specs.

func MustParsePipeline

func MustParsePipeline(pipeline any) Pipeline

MustParsePipeline is ParsePipeline that panics on error (tests).

func ParsePipeline

func ParsePipeline(pipeline any) (Pipeline, error)

ParsePipeline parses a JSON-ish aggregation pipeline (any input accepted by the same parser as Find conditions) into stage specs. The result is fully detached from the input value. Rejections are reported as *query.ParseError with Source "pipeline", whose Path's leading segment is the stage index ("1.$match.a.$gt"); see query.ParseError.

type Prefix

type Prefix struct {
	Filter query.Filter // folded leading $match chain; nil if none
	Sort   query.Sort   // nil if no $sort was pushed
	Skip   int
	Limit  int // 0 = none
}

Prefix is the leading pipeline part that can be pushed down into the access plan (the regular Find machinery: index/CBO selection, FTS/vector sources, index-order sorts, cursor-level offset skips).

type ProjectField

type ProjectField struct {
	Name string
	Expr Expr
}

ProjectField is one output field of $project / $addFields.

type ProjectSpec

type ProjectSpec struct{ Fields []ProjectField }

func (ProjectSpec) String

func (s ProjectSpec) String() string

type ProjectStage

type ProjectStage struct {
	Src    Stage
	Fields []ProjectField
	// contains filtered or unexported fields
}

ProjectStage replaces each upstream document with a new object containing only the specified fields. The output is built on RowArena; field values alias subtrees of the input document (zero-copy), which is safe because the input stays valid for the whole row lifetime.

func (*ProjectStage) Close

func (s *ProjectStage) Close()

func (*ProjectStage) Next

func (s *ProjectStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*ProjectStage) String

func (s *ProjectStage) String() string

type ReplaceAllExpr

type ReplaceAllExpr struct {
	Input, Find, Replacement Expr
	// contains filtered or unexported fields
}

ReplaceAllExpr evaluates $replaceAll: every occurrence of find in input replaced by replacement, left to right, non-overlapping; replaced regions are not rescanned. Same contract as ReplaceOneExpr otherwise: object form, all three required, null/missing or non-string operand → null. An empty find matches at position 0 once, prepending the replacement — same pin as $replaceOne (Mongo docs leave the case unstated; a per-position match would loop).

func (*ReplaceAllExpr) Eval

func (e *ReplaceAllExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*ReplaceAllExpr) String

func (e *ReplaceAllExpr) String() string

type ReplaceOneExpr

type ReplaceOneExpr struct {
	Input, Find, Replacement Expr
	// contains filtered or unexported fields
}

ReplaceOneExpr evaluates $replaceOne: the first occurrence of find in input replaced by replacement; no occurrence leaves input unchanged. Object form only, all three parameters required (Mongo). A null or missing operand → null (Mongo); a non-string operand → null too (Mongo errors; same rationale as evalNumber — regex find is likewise out: no regex value type). An empty find matches at position 0, prepending the replacement (Mongo behavior, its docs leave the case unstated). The splice goes through a reusable scratch buffer (per-pipeline, single-goroutine): alloc-free in steady state.

func (*ReplaceOneExpr) Eval

func (e *ReplaceOneExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*ReplaceOneExpr) String

func (e *ReplaceOneExpr) String() string

type RoundExpr

type RoundExpr struct {
	X     Expr
	Place Expr // nil: round to integer
}

RoundExpr evaluates $round with half-to-even (banker's) rounding, Mongo semantics: place in [-20, 100], default 0, negative rounds left of the decimal point; an out-of-range or non-integer place → null. Precision is float64: values round by their binary double value ({"$round":[2.345,2]} is 2.35 — the stored double sits above the midpoint), and a place beyond float64 resolution leaves the value unchanged.

func (*RoundExpr) Eval

func (e *RoundExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*RoundExpr) String

func (e *RoundExpr) String() string

type SizeExpr added in v2.0.1

type SizeExpr struct{ Arg Expr }

SizeExpr evaluates $size: the number of elements in its array operand. A missing, null, or non-array operand → null (Mongo errors; no-error-channel policy, see evalNumber).

func (*SizeExpr) Eval added in v2.0.1

func (e *SizeExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*SizeExpr) String added in v2.0.1

func (e *SizeExpr) String() string

type SkipSpec

type SkipSpec struct{ N int }

func (SkipSpec) String

func (s SkipSpec) String() string

type SkipStage

type SkipStage struct {
	Src Stage
	N   int
	// contains filtered or unexported fields
}

SkipStage drops the first N upstream rows.

func (*SkipStage) Close

func (s *SkipStage) Close()

func (*SkipStage) Next

func (s *SkipStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*SkipStage) String

func (s *SkipStage) String() string

type SortSpec

type SortSpec struct {
	Sort   query.Sorts
	Fields []query.SortField // parsed order, for String()/pushdown checks
}

func (SortSpec) String

func (s SortSpec) String() string

type SortStage

type SortStage struct {
	Src  Stage
	Spec SortSpec
	TopK int
	// contains filtered or unexported fields
}

SortStage is the blocking in-pipeline $sort over synthesized documents (stored-document sorts are pushed down to the access plan instead).

It mirrors qplanner.SortIter's packed-arena design, but each entry carries the full marshaled document after its sort-key tuple — rows here no longer correspond to stored docIds. With TopK > 0 (a following $skip/$limit was folded in by Build) a max-heap keeps only the smallest K entries: a losing row is built in the arena's spare tail and truncated away — zero retained bytes — and a fragmentation guard re-packs the arena when dead bytes from evictions exceed live data, keeping the high-water mark O(K).

Stability: the full sort uses slices.SortStableFunc over insertion-ordered entries; the top-K path appends a row sequence number to each key, which both totals the order (deterministic heap eviction: of equal keys the earliest rows survive) and makes the final sort stable by construction.

func (*SortStage) Close

func (s *SortStage) Close()

func (*SortStage) Next

func (s *SortStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*SortStage) String

func (s *SortStage) String() string

type SplitExpr

type SplitExpr struct {
	Input, Delim Expr
}

SplitExpr evaluates $split [string, delimiter]: the array of substrings between delimiter occurrences — adjacent delimiters produce empty strings, no occurrence yields the whole input as a one-element array. The delimiter must be a non-empty string: an empty literal delimiter is a parse error and a delimiter expression evaluating to "" → null (Mongo errors at runtime; no-error-channel policy — likewise a null/missing or non-string operand, where Mongo errors for non-strings; regex delimiters are out: no regex value type). Elements and the result array are arena-allocated per eval: alloc-free in steady state.

func (*SplitExpr) Eval

func (e *SplitExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*SplitExpr) String

func (e *SplitExpr) String() string

type Stage

type Stage interface {
	// Next returns the next document, or (nil, nil) at the end of the stream.
	// The returned value is valid only until the next Next call on this stage.
	Next(ctx *Ctx) (*anyenc.Value, error)

	// Close releases stage resources. It must be safe to call multiple times
	// and after a mid-stream abort.
	Close()

	// String describes the stage for Explain output. Never called on the hot
	// path.
	fmt.Stringer
}

Stage is a pull-driven aggregation operator over anyenc documents.

The chain is strictly synchronous: a stage produces a row only when its consumer asks for one, so row lifetimes are phase-locked across the chain.

func Build

func Build(source Stage, specs Pipeline, limits Limits, env Env) (Stage, error)

Build compiles parsed stage specs into an executable stage chain on top of source. The source must reset Ctx.RowArena once per row it yields (it is the default row owner). env carries the execution hooks the root package injects (the $lookup point-read function).

Build also performs the arena-ownership analysis: $project / $addFields / $lookup get permission to reset RowArena before pulling (freeing the previous row) when no stage below them keeps arena-allocated data alive across pulls. This keeps the arena footprint O(row) under $unwind row multiplication.

type StageSpec

type StageSpec interface {
	fmt.Stringer
	// contains filtered or unexported methods
}

StageSpec is a parsed pipeline stage description (not yet an executable Stage; see Build).

type StrLenExpr added in v2.0.1

type StrLenExpr struct {
	CP  bool // count code points ($strLenCP) instead of bytes ($strLenBytes)
	Arg Expr
}

StrLenExpr evaluates $strLenBytes/$strLenCP: the length of the string operand in UTF-8 bytes or code points. A missing, null, or non-string operand → null, and so is a $strLenCP operand that is not valid UTF-8 (Mongo errors for both; no-error-channel policy, see evalNumber). $strLenBytes counts bytes verbatim, valid UTF-8 or not, as Mongo does.

func (*StrLenExpr) Eval added in v2.0.1

func (e *StrLenExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*StrLenExpr) String added in v2.0.1

func (e *StrLenExpr) String() string

type SwitchExpr

type SwitchExpr struct {
	Cases   []Expr
	Thens   []Expr
	Default Expr // nil: no default
}

SwitchExpr evaluates $switch: cases run lazily in spec order and the first truthy case selects its then branch. No match with no default is a Mongo runtime error; with no per-document error channel the result is null instead (see docs/aggregation.md).

func (*SwitchExpr) Eval

func (e *SwitchExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*SwitchExpr) String

func (e *SwitchExpr) String() string

type TrimExpr

type TrimExpr struct {
	Mode  TrimMode
	Input Expr
	Chars Expr // nil: default whitespace set
	// contains filtered or unexported fields
}

TrimExpr evaluates $trim/$ltrim/$rtrim {input, chars?}: strips the leading and/or trailing code points that appear in chars — whole runes, so a multibyte member never shreds mid-character. Absent chars means the default whitespace set (isTrimWhitespace); chars "" is an empty set and trims nothing. A null/missing input, or a non-string input or chars → null (Mongo errors for non-strings; no-error-channel policy). The chars set decodes into a reusable rune scratch: alloc-free in steady state.

func (*TrimExpr) Eval

func (e *TrimExpr) Eval(a *anyenc.Arena, doc *anyenc.Value) (*anyenc.Value, error)

func (*TrimExpr) String

func (e *TrimExpr) String() string

type TrimMode

type TrimMode uint8

TrimMode selects which side(s) $trim/$ltrim/$rtrim strip.

const (
	TrimBoth TrimMode = iota
	TrimLeft
	TrimRight
)

func (TrimMode) String

func (m TrimMode) String() string

type UnwindSpec

type UnwindSpec struct {
	Field                      string // original spelling without "$"
	Path                       []string
	PreserveNullAndEmptyArrays bool
}

func (UnwindSpec) String

func (s UnwindSpec) String() string

type UnwindStage

type UnwindStage struct {
	Src                        Stage
	Field                      string
	Path                       []string
	PreserveNullAndEmptyArrays bool
	// contains filtered or unexported fields
}

UnwindStage emits one row per element of an array field, replacing the array with the current element by mutating the document in place: the element values stay alive because they belong to the original (detached) array value, while the document's field slot is repointed per emission. No arena allocations, no document rebuild.

Before pulling the next source row the original array is restored into the field slot. An upstream stage may hold and re-emit the same document (another $unwind multiplying rows does), so leaving the field pointing at the last element would corrupt every later row of that document.

Mongo semantics: missing / null / empty array drop the document (unless PreserveNullAndEmptyArrays, which emits it as-is — with the field removed for an empty array); a non-array value passes through unchanged.

func (*UnwindStage) Close

func (s *UnwindStage) Close()

func (*UnwindStage) Next

func (s *UnwindStage) Next(ctx *Ctx) (*anyenc.Value, error)

func (*UnwindStage) String

func (s *UnwindStage) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL