query

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: 17 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// KnnMaxK bounds $k. Policy, not semantics: raising it later is
	// non-breaking, lowering it is not — start conservative.
	KnnMaxK = 10_000
	// KnnMaxEf bounds $ef, the ANN candidate/beam depth.
	KnnMaxEf = 65_536
)
View Source
const (
	TypeNull      = Type(anyenc.TypeNull)
	TypeNumber    = Type(anyenc.TypeNumber)
	TypeString    = Type(anyenc.TypeString)
	TypeFalse     = Type(anyenc.TypeFalse)
	TypeTrue      = Type(anyenc.TypeTrue)
	TypeArray     = Type(anyenc.TypeArray)
	TypeObject    = Type(anyenc.TypeObject)
	TypeVectorF32 = Type(anyenc.TypeVectorF32)
	TypeObjectID  = Type(anyenc.TypeObjectID)
	TypeDateTime  = Type(anyenc.TypeDateTime)
)

Variables

View Source
var ErrUnknownOperator = errors.New("unknown operator")

ErrUnknownOperator is the class sentinel for input that names an operator outside its grammar's vocabulary — a filter operator ({"tags": {"$contains": "x"}}), a modifier ({"$sett": …}), an aggregation stage or accumulator. It is a client fault: callers parsing untrusted input can errors.Is it to answer with a bad-request rather than an internal error. The rejection itself is a *ParseError (errors.As) whose Op names the bad token.

View Source
var ErrVectorNotOrderable = errors.New("any-store: a vector is not orderable")

ErrVectorNotOrderable is returned when an ordering operator ($gt/$gte/$lt/$lte) is given a vector operand. Vectors are not points on the scalar order (Rule V): the anyenc tag order sorts every vector above every scalar, which made such a comparison true for every document — emptying the collection on Delete. Equality ($eq/$ne) against a vector remains legal: it is byte equality.

Functions

func ContainsKnn

func ContainsKnn(f Filter) bool

ContainsKnn reports whether the tree contains a Knn ANYWHERE. It MUST walk the whole tree, pointer nodes included: a Knn under Not would evaluate !false == match-all (see the Knn type comment), so every consumer that rejects bad placements needs the full walk.

func ContainsSourceFilter

func ContainsSourceFilter(f Filter) bool

ContainsSourceFilter reports whether the tree contains a SOURCE filter — one matched by an index (Text, Knn) rather than by Ok. RECURSIVE, not a shallow type check: the only legal shapes are Key{path, Knn} and And{Text, …}, so a shallow test would return false for every real query and protect nobody. External consumers that post-filter by calling Filter.Ok directly (the subscription pattern) MUST reject these: Text.Ok matches everything, Knn.Ok matches nothing.

func ContainsText

func ContainsText(f Filter) bool

ContainsText reports whether the tree contains a Text ANYWHERE, pointer nodes included — same full-walk contract as ContainsKnn. Consumers use it both to reject bad placements ($text under $or/$nor/$not) and as the post-condition that the fts residual extraction stripped every Text node.

func FilterTreeAny

func FilterTreeAny(f Filter, pred func(Filter) bool) bool

FilterTreeAny walks the standard filter tree and reports whether pred is true for any node. It descends And/Or/Nor/Not/Key — in BOTH their value and pointer forms: every composite here has value-receiver methods, so a pointer-built &Not{…}/&Key{…}/&Or{…} satisfies Filter too, and a walker that switches only on value types silently skips such nodes. That is not a stylistic gap — ContainsKnn is a mass-delete guard (see Knn), and a missed node is a guard bypass.

func GuaranteesPresence

func GuaranteesPresence(f Filter, fieldName string) bool

GuaranteesPresence reports whether every document matching f must carry a present, non-null value at fieldName. It is true only when some top-level conjunct constrains fieldName with a predicate that rejects BOTH a missing field (a nil value) and an explicit null — exactly the two cases a sparse index omits (see index.writeValues: it drops a key when any indexed field is nil or TypeNull).

The planner uses this to decide whether a SPARSE index can represent the whole matching set: a sparse index that omits some matching document would silently drop rows if seeked. Probing the field's own sub-filter with Ok keeps this in lockstep with match semantics, so it is exact rather than a rule-of-thumb — e.g. {$exists:true} yields false here because an explicit-null document matches $exists:true yet is absent from the sparse index. An OR, a $exists:false, a $ne, an equality-to-null, an $in containing null, or no predicate on the field at all all yield false, keeping that sparse index out of consideration (the planner then falls back to a complete index or scan).

func IsTextScoreSort

func IsTextScoreSort(s Sort) bool

IsTextScoreSort reports whether s requests relevance ordering (directly or as the sole element of a Sorts list).

func MayTighten

func MayTighten(f Filter) bool

MayTighten reports whether f contains any conjunction that could make TightIndexBounds differ from IndexBounds for SOME field — an And node with more than one child, reachable outside Or/Not/Nor (which the tight channel delegates wide). Callers use it to skip the tight walk for the common single-predicate filters where the channels are provably identical.

func ModifierOperators

func ModifierOperators() []string

ModifierOperators returns the operator vocabulary accepted by the modifier parser — every top-level '$'-key ParseModifier recognizes, 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.

func Operators

func Operators() []string

Operators returns the operator vocabulary accepted by the filter parser — every '$'-prefixed key ParseCondition recognizes, top-level and field-level alike, 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.

func PrefixSuccessor

func PrefixSuccessor(p []byte) []byte

PrefixSuccessor returns the smallest byte string greater than every string prefixed by p: trailing 0xFF bytes are stripped and the last remaining byte incremented, into a fresh slice. Used as an EXCLUSIVE End, the result admits exactly the p-prefixed keys — unlike the append-0xFF inclusive idiom, which excludes any key continuing with a 0xFF byte after the prefix (longer sorts greater). Returns nil (+inf) when p is all-0xFF or empty.

Types

type All

type All struct{}

func (All) IndexBounds

func (a All) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (All) Ok

func (a All) Ok(_ *anyenc.Value, buf *syncpool.DocBuffer) bool

func (All) String

func (a All) String() string

type And

type And []Filter

func (And) IndexBounds

func (e And) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

IndexBounds returns a SOUND OVER-APPROXIMATION of the index bounds for this conjunction: the bounds of the first conjunct that constrains fieldName. The result must be a SUPERSET of the matching set so the index seek (and Iter) never miss a doc.

Intersecting conjunct bounds is UNSOUND for ARRAY/multi-key fields: array filter semantics match each conjunct against the whole array independently, so a doc matches {$gte:2,$lte:3} when one element is >=2 and a DIFFERENT element is <=3 — it need not have any element in [2,3]. Narrowing the seek to the intersection drops such docs from both Count and Iter (and a FilterIter cannot re-add what the seek skipped). The over-approximation here is correct for scalar AND array fields because every matching doc has an element satisfying the first conjunct, hence in its bounds; a FilterIter re-checks the full conjunction.

The CountOnly fast path skips that FilterIter, so it would over-count when these over-approx bounds are a strict superset of the matches. It is gated separately: indexCoversFilter rejects a covered field carrying more than one predicate, so the fast path is only taken when bounds exactly equal the matches (a single In/Eq/range per field).

Consumers that can prove fan-out entries are absent (pk namespace, indexes with a scalar-proven multikey flag) — or that only ESTIMATE — get the intersected variant from TightIndexBounds (query/tight_bounds.go); this method's contract stays over-approximating regardless.

func (And) Ok

func (e And) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (And) OkRaw

func (e And) OkRaw(doc []byte, buf *syncpool.DocBuffer) (bool, bool)

func (And) String

func (e And) String() string

type Bound

type Bound struct {
	Start, End anyenc.Tuple

	StartInclude bool
	EndInclude   bool
	// contains filtered or unexported fields
}

func (Bound) EndIsTypeEdge added in v2.0.2

func (b Bound) EndIsTypeEdge() bool

EndIsTypeEdge reports whether End is a bracket edge (see Bound).

func (Bound) OpenEnd added in v2.0.2

func (b Bound) OpenEnd() Bound

OpenEnd returns b with an open upper side.

func (Bound) OpenStart added in v2.0.2

func (b Bound) OpenStart() Bound

OpenStart returns b with an open lower side.

func (Bound) PadExclusiveStart added in v2.0.2

func (b Bound) PadExclusiveStart() Bound

PadExclusiveStart makes an exclusive Start exact on full keys: entry keys are enc(v)‖suffix, so (enc(v) becomes [enc(v)‖0xFF — v's own entries (suffix byte < 0xFF) fall below it, the escape continuations enc(v)‖0xFF… stay admitted. The append reallocates (bound bytes are cap == len).

func (Bound) PadInclusiveEnd added in v2.0.2

func (b Bound) PadInclusiveEnd() Bound

PadInclusiveEnd extends an inclusive End to every key suffix: enc(v)‖0xFF.

func (Bound) StartIsTypeEdge added in v2.0.2

func (b Bound) StartIsTypeEdge() bool

StartIsTypeEdge reports whether Start is a bracket edge (see Bound).

func (Bound) String

func (b Bound) String() string

func (Bound) WithTypeEdges added in v2.0.2

func (b Bound) WithTypeEdges(start, end bool) Bound

WithTypeEdges returns b with its edge marks set (planner use: the marks must follow an endpoint through inversion and tuple concatenation).

type Bounds

type Bounds []Bound

func TightIndexBounds

func TightIndexBounds(f Filter, fieldName string) (bounds Bounds, empty bool)

TightIndexBounds computes the INTERSECTED ("tight") bounds for fieldName: where a conjunction carries several bound-contributing conjuncts on the same field ({"f":{"$gt":lo,"$lt":hi}}, {$in:[...],$ne:v}, nested $and forms), the contributions are interval-intersected instead of keeping only the first one the way And.IndexBounds does.

SOUNDNESS — read before adding consumers. Tight bounds are NOT a sound seek range in general: array filter semantics match each conjunct against the whole array independently, so {tags:{$gte:2,$lte:3}} matches {tags:[5,1]} (5>=2 via one element, 1<=3 via another) with NO index entry in [2,3] — a seek narrowed to the intersection silently drops the doc from Iter AND Count. Tight bounds may drive:

  • cost/selectivity ESTIMATION: always (estimates cannot drop docs);
  • actual SEEKS: only when the scanned namespace provably holds no fan-out (multi-key) entries — the primary-key namespace (array pks are rejected on write, see ErrArrayPrimaryKey) or a secondary index whose persisted multikey flag proves it scalar-only.

empty=true reports that the intersection is PROVABLY EMPTY as a value set ({"f":{"$gt":5,"$lt":3}}). It does NOT mean the filter is unsatisfiable: under array semantics {f:[6,1]} MATCHES that filter (6>5, 1<3), so callers may treat empty as "zero results" only under the same fan-out-free proof as seeks; everywhere else they must fall back to the wide bounds. When empty=true the returned bounds are nil — never feed that to code that reads len==0 as "unconstrained".

Node handling: Key routes the field; And/*And intersect their contributing children (children contributing nothing are unconstrained, not narrowing); every other node — including Or — delegates to its own IndexBounds, so Or keeps its all-or-nothing union and never produces empty. Bounds bytes alias filter-owned pre-clipped (cap==len) memory and are never mutated in place, per docs/query-filter-contract.md.

func (Bounds) Append

func (bs Bounds) Append(b Bound) Bounds

func (Bounds) Contains

func (bs Bounds) Contains(val []byte) bool

Contains reports whether val lies within any range in bs. val is a raw anyenc-encoded tuple component (e.g. the field-value prefix of an index key, with docId suffix stripped).

func (Bounds) ContainsSorted added in v2.0.1

func (bs Bounds) ContainsSorted(val []byte) bool

ContainsSorted reports whether val lies within any range of a CANONICAL (sorted, disjoint — see SortedDisjoint) bound set, by binary search. On a non-canonical set the answer is unsound BY DESIGN — the caller owns the precondition; this is the hot-loop variant of Contains (which stays linear and assumption-free).

func (Bounds) Intersect

func (bs Bounds) Intersect(other Bounds) Bounds

Intersect returns the lex-intersection of this bound set with other. A bound set is a union of value ranges; the intersection is the set of values present in BOTH unions. Used by TightIndexBounds to combine the bounds contributed by multiple conjuncts on the same field — NEVER by IndexBounds itself, whose over-approximation contract (see And.IndexBounds) is what keeps array/multi-key seeks sound.

An empty result (returned as a zero-length Bounds) means the two sets share no values — distinct from "no narrowing", which preserves the input. Callers must not feed a zero-length result into code that reads len==0 as "unconstrained". The pairwise intersections are sorted and merged before returning. Result endpoints alias the input bounds' Start/End bytes and are never mutated, preserving the cap==len sharing contract (docs/query-filter-contract.md).

func (Bounds) SortAndMerge

func (bs Bounds) SortAndMerge() Bounds

SortAndMerge sorts bounds by Start key and merges overlapping/adjacent entries in a single O(N log N) pass. Use after batch-appending multiple bounds.

func (Bounds) SortedDisjoint added in v2.0.1

func (bs Bounds) SortedDisjoint() bool

SortedDisjoint reports whether bs is in canonical form: sorted ascending by Start with pairwise non-overlapping ranges — the precondition of ContainsSorted. IndexBounds results are canonical (SortAndMerge), but a caller holding bounds of unknown provenance must check before switching from the linear Contains to the binary-search path.

func (Bounds) String

func (bs Bounds) String() string

type Comp

type Comp struct {
	EqValue []byte
	CompOp  CompOp
	// contains filtered or unexported fields
}

func NewComp

func NewComp(op CompOp, value any) *Comp

func NewCompValue

func NewCompValue(op CompOp, value *anyenc.Value) *Comp

func (*Comp) IndexBounds

func (e *Comp) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

IndexBounds emits the EXACT key image of the predicate: an ordering op's range is clamped to the operand's bracket ($gt X is (X, hi), $lt X is [lo, X)), so the bounds admit exactly the keys Ok accepts. Exactness is load- bearing — the planner elides the residual filter where a single predicate's bounds cover it (indexScanCoversFilter) — and it is what keeps a wrong-typed literal from walking a whole index: a number-tagged $gte on a dateTime index is an empty range, not a full scan, and interpolation prices it as such. Degenerate shapes contribute no bounds rather than an unsatisfiable one: an empty operand, and a half-open range that is empty because the operand IS its bracket's lower edge ({"$lt":null}, {"$lt":false}) — Start == End would read as a fixed point.

func (*Comp) Ok

func (e *Comp) Ok(v *anyenc.Value, docBuf *syncpool.DocBuffer) bool

Rule V: a vector is not a point on the scalar order.

Bracketing already makes an ordering op against a vector false (a vector is its own bracket). These guards predate it and stay as defense in depth: before them, TypeVectorF32 (10) sorting above every scalar tag made {"v":{"$gt":1}} on a vector field — and {"anyField":{"$lt":{"$vector":[..]}}} on any field, even an absent one — true for EVERY document, which on Query.Delete emptied the collection with err=nil. $eq/$ne are unaffected: byte equality is well-defined for a vector.

The parser rejects the operand-side spelling outright (ErrVectorNotOrderable); these guards are what protect hand-built filters, which never see the parser.

func (*Comp) String

func (e *Comp) String() string

type CompOp

type CompOp uint8
const (
	CompOpEq CompOp = iota
	CompOpGt
	CompOpGte
	CompOpLt
	CompOpLte
	CompOpNe
)

type Exists

type Exists struct{}

func (Exists) IndexBounds

func (e Exists) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (Exists) Ok

func (e Exists) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Exists) String

func (e Exists) String() string

type Filter

type Filter interface {
	// Ok evaluates whether the given value satisfies the filter condition.
	// docBuf is optional and can be nil, it's used to reuse memory between
	// calls: with a warm per-query DocBuffer, Ok is alloc-free
	// (TestFilterOkAllocFree).
	Ok(v *anyenc.Value, docBuf *syncpool.DocBuffer) bool

	// IndexBounds appends bounds to the bs for the given field if it is applicable
	IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

	fmt.Stringer
}

Filter is built once (ParseCondition or a constructor) and is immutable afterwards: it may be shared by any number of queries running concurrently. Ok and IndexBounds never mutate filter state and need no synchronization; bound bytes returned by IndexBounds that alias filter memory are clipped to cap == len so downstream appends reallocate instead of writing into the shared filter. Pinned by TestFilterConcurrentReuse and TestIndexBounds_FilterOwnedBytesAreCapped; see docs/query-filter-contract.md.

func MustParseCondition

func MustParseCondition(cond any) Filter

func ParseCondition

func ParseCondition(cond any) (Filter, error)

type In

type In struct {
	// Values is the membership set, keyed by anyenc-marshaled value. A
	// built filter is immutable: once handed to a query it may be used by
	// many queries concurrently, so Values must not be mutated (IndexBounds
	// and Ok read it and the pre-sorted view without synchronization).
	// Build In via NewInValue.
	Values map[string]struct{}
	// contains filtered or unexported fields
}

func NewInValue

func NewInValue(values ...*anyenc.Value) In

func (In) IndexBounds

func (e In) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (In) Ok

func (e In) Ok(v *anyenc.Value, docBuf *syncpool.DocBuffer) bool

func (In) String

func (e In) String() string

type Key

type Key struct {
	Path []string
	Filter
}

func (Key) IndexBounds

func (e Key) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (Key) Ok

func (e Key) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Key) OkRaw

func (e Key) OkRaw(doc []byte, buf *syncpool.DocBuffer) (bool, bool)

OkRaw extracts the field's raw bytes with a length-only walk and evaluates the inner filter on just that fragment. Falls back (handled=false) when the walk cannot mirror Value.Get exactly (array container on the path, malformed bytes) or when no scratch buffer is available.

func (Key) String

func (e Key) String() string

type Knn

type Knn struct {
	Query []float32 // non-empty, finite; len checked against the index dim at detection
	K     int       // required, 1..KnnMaxK
	Ef    int       // 0 = auto; candidate/beam depth (numCandidates); >= K when set
	Index string    // optional: vector index name (disambiguates 2 indexes on one field)
}

Knn is the {"<field>":{"$knn":{"$query":[…],"$k":N}}} approximate-nearest-neighbour clause. It is NOT a predicate: it selects the K documents whose vector at this field the field's vector index ranks closest to Query. "Is this document one of the k nearest?" is a property of the CANDIDATE SET, not of the document, so no per-document truth value exists.

Ok therefore returns FALSE, unconditionally, and is unreachable in a correct plan: the executor detects the clause on EVERY verb, drives the query from the ANN source, strips the clause from the residual, and hard-errors on any placement where it could not be stripped. The false FAILS CLOSED — a Knn that somehow reaches a document-by-document evaluator matches nothing rather than everything. Contrast query.Text, whose Ok returns TRUE: fail-open on Query.Delete costs the collection. Never do that here.

FAIL-CLOSED IS LOAD-BEARING IN TWO PLACES, both of which must be kept in sync:

  • ContainsKnn must walk Not/Nor: Not{Knn}.Ok == !false == MATCH-ALL. A $knn that reaches a Not is a mass delete through the front door, reachable from Query.Delete.
  • GuaranteesPresence must special-case Knn: it calls the inner filter's Ok DIRECTLY and reads !Ok(nil) && !Ok(null) as "guarantees presence" — so a fail-closed Ok yields TRUE, the AGGRESSIVE answer.

func NewKnn

func NewKnn(vec []float32, k int, opts ...KnnOpt) Knn

NewKnn is the programmatic constructor for the $knn clause: wrap it in a query.Key naming the vector field. It exists because programmatic consumers build their ANN filter as a Go value and never touch JSON; every validation the parser applies is re-checked at query build time (detection), which is the only validation a hand-built filter ever sees.

func (Knn) IndexBounds

func (k Knn) IndexBounds(_ string, bs Bounds) Bounds

IndexBounds returns bs verbatim: a Knn clause contributes no range-index bounds, which Or.IndexBounds reads as "cannot narrow".

func (Knn) Ok

Ok fails closed: a Knn is not a predicate (see the type comment).

func (Knn) String

func (k Knn) String() string

String renders the clause losslessly: MustParseCondition round-trips it.

func (Knn) Validate

func (k Knn) Validate() error

Validate checks the $knn argument rules — the ONE copy shared by the parser (parseKnn, after its shape/presence checks) and the executor's detection walk (which re-validates because programmatic NewKnn filters never see the parser). Message texts are the normative parse-error strings.

type KnnOpt

type KnnOpt func(*Knn)

KnnOpt is an option for NewKnn.

func KnnEf

func KnnEf(ef int) KnnOpt

KnnEf sets the explicit ANN candidate/beam depth ($ef).

func KnnIndex

func KnnIndex(name string) KnnOpt

KnnIndex names the vector index to search ($index) — required only when the field has more than one vector index.

type Modifier

type Modifier interface {
	Modify(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error)
}

func MustParseModifier

func MustParseModifier(modifier any) Modifier

func ParseModifier

func ParseModifier(modifier any) (Modifier, error)

ParseModifier parses an update modifier document. Rejections are reported as *ParseError with Source "modifier"; see ParseError.

type ModifierChain

type ModifierChain []Modifier

func (ModifierChain) Modify

func (mRoot ModifierChain) Modify(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error)

type ModifyFunc

type ModifyFunc func(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error)

func (ModifyFunc) Modify

func (m ModifyFunc) Modify(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error)

type Nor

type Nor []Filter

func (Nor) IndexBounds

func (e Nor) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (Nor) Ok

func (e Nor) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Nor) OkRaw

func (e Nor) OkRaw(doc []byte, buf *syncpool.DocBuffer) (bool, bool)

func (Nor) String

func (e Nor) String() string

type Not

type Not struct {
	Filter
}

func (Not) IndexBounds

func (e Not) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (Not) Ok

func (e Not) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Not) OkRaw

func (e Not) OkRaw(doc []byte, buf *syncpool.DocBuffer) (bool, bool)

func (Not) String

func (e Not) String() string

type Operator

type Operator uint8

type Or

type Or []Filter

func (Or) IndexBounds

func (e Or) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

IndexBounds is the union of the branches' bounds. Every branch must constrain fieldName, or the disjunction cannot narrow at all and bs is returned unchanged. Each branch is asked on its own (against nil): judging a branch by whether it grew the accumulated list misreads a range that merges into a sibling's — {"$or":[{"a":{"$lt":5}},{"a":{"$lt":7}}]} — as "no bounds", which dropped the index for the whole $or.

func (Or) Ok

func (e Or) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Or) OkRaw

func (e Or) OkRaw(doc []byte, buf *syncpool.DocBuffer) (bool, bool)

func (Or) String

func (e Or) String() string

type ParseError

type ParseError struct {
	// Source names the grammar that rejected the input: "filter" (the
	// default; empty means filter), "modifier", or "pipeline".
	Source string
	// Path locates the failure inside the input document as a dotted key
	// path whose leaf is the offending key, e.g. "tags.$sizee",
	// "$and.1.price.$gt", or — pipeline errors, where the leading segment is
	// the stage index — "1.$match.a.$gt". Array positions appear as indexes.
	// Empty when the failure concerns the top-level document itself.
	Path string
	// Op is the operator whose grammar was violated, including the leading
	// '$' (for an unknown operator: the unrecognized token itself). Empty
	// when the failure is not tied to an operator.
	Op string
	// Reason is the human-readable description of the rejection. It is
	// self-contained: rendering it without Path/Op loses location, not
	// meaning.
	Reason string

	// Err preserves the finer error class underneath the uniform type:
	// ErrUnknownOperator for unrecognized operators, ErrVectorNotOrderable
	// for ordering ops on vectors, the regexp.Compile error for a bad
	// $regex, … Nil when there is no finer class.
	Err error
}

ParseError is the structured rejection for input that does not parse: a query filter, an update modifier, or an aggregation pipeline (Source tells which). Every parse-rejection class — unknown operator, wrong operand type, malformed $and/$or/$nor array, bad $regex, unknown stage, … — is reported as a *ParseError, so a caller exposing these grammars to untrusted input can errors.As one error type and answer with a precise bad-request instead of string-matching parser messages.

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

type RawFilter

type RawFilter interface {
	OkRaw(doc []byte, buf *syncpool.DocBuffer) (ok bool, handled bool)
}

RawFilter is an optional fast path implemented by filters that can evaluate against the raw encoded document (as stored, root already decoded — see anyenc.Parser.DecodedDoc) without building the full Value tree. Unindexed scans use it to REJECT documents cheaply: the field walk skips unrelated values length-only, so a low-selectivity scan avoids parsing ~everything it discards (the full parse dominated the scan profile). Accepted documents are re-parsed by the caller as before, so downstream semantics are unchanged.

handled=false means this filter (or a required sub-filter) cannot decide from the raw bytes — the caller must fall back to Ok on the parsed document. Implementations must guarantee that when handled=true, ok equals what Ok would return on the parsed document (TestFilterOkRawParity).

type RawSort

type RawSort interface {
	AppendKeyRaw(k anyenc.Tuple, doc []byte, buf *syncpool.DocBuffer) (anyenc.Tuple, bool)
}

RawSort is an optional fast path implemented by sorts that can build their key from the raw encoded document (root already decoded — see anyenc.Parser.DecodedDoc) without a full parse. The unindexed sort path fetches and parses every candidate document just to read the sort fields; seeking the fields' raw fragments instead skips the tree build and the scanning of everything past them (see RawFilter for the same idea on the filter side).

handled=false means the key could not be built from the raw bytes (an array container on a path, malformed bytes) — k is returned unchanged (any partial append truncated) and the caller must fall back to AppendKey on the parsed document. When handled=true, the appended bytes are identical to what AppendKey would produce (TestSortAppendKeyRawParity).

type Regexp

type Regexp struct {
	Regexp *regexp.Regexp
	// Options are the Mongo $options flags ("i", "m", "s") the parser baked
	// into Regexp as a leading (?flags) group. i and m make an anchored
	// ^literal prefix unsound as an index bound (case folding / any-line
	// anchoring admit keys outside the literal range), so IndexBounds keeps
	// the scan wide for them; s cannot affect a literal prefix and keeps the
	// bounds.
	Options string
}

func (Regexp) IndexBounds

func (r Regexp) IndexBounds(_ string, bs Bounds) (bounds Bounds)

func (Regexp) Ok

func (r Regexp) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Regexp) String

func (r Regexp) String() string

type Size

type Size struct {
	Size int64
}

func (Size) IndexBounds

func (s Size) IndexBounds(_ string, bs Bounds) (bounds Bounds)

func (Size) Ok

func (s Size) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Size) String

func (s Size) String() string

type Sort

type Sort interface {
	Fields() []SortField
	AppendKey(k anyenc.Tuple, v *anyenc.Value) anyenc.Tuple
}

func MustParseSort

func MustParseSort(sorts ...any) Sort

func ParseSort

func ParseSort(sorts ...any) (Sort, error)

type SortField

type SortField struct {
	Field   string
	Path    []string
	Reverse bool
}

func (*SortField) AppendKey

func (s *SortField) AppendKey(tuple anyenc.Tuple, v *anyenc.Value) anyenc.Tuple

func (*SortField) AppendKeyRaw

func (s *SortField) AppendKeyRaw(k anyenc.Tuple, doc []byte, buf *syncpool.DocBuffer) (anyenc.Tuple, bool)

func (*SortField) Fields

func (s *SortField) Fields() []SortField

type Sorts

type Sorts []Sort

func (Sorts) AppendKey

func (ss Sorts) AppendKey(k anyenc.Tuple, v *anyenc.Value) anyenc.Tuple

func (Sorts) AppendKeyRaw

func (ss Sorts) AppendKeyRaw(k anyenc.Tuple, doc []byte, buf *syncpool.DocBuffer) (anyenc.Tuple, bool)

func (Sorts) Fields

func (ss Sorts) Fields() []SortField

type Text

type Text struct {
	// Search is the raw $search string, preserved verbatim for String() and as
	// the fallback source when Clauses is nil (a directly-constructed Text).
	Search string
	// DefaultAnd is set by {"$defaultOperator":"and"}: bare (should) terms then
	// become required (AND) rather than the default OR.
	DefaultAnd bool
	// Clauses are the parsed clauses of the whole $text predicate: the should
	// clauses from $search (phrases, prefix*, plain terms) plus the required and
	// excluded clauses from the $require / $exclude sub-fields. Populated by
	// ParseCondition; a Text built directly with only Search set has nil Clauses
	// and the executor re-parses Search on demand (yielding should clauses only).
	Clauses []TextClause
}

Text is the {"$text":{"$search":"..."}} full-text predicate. Matching and ranking are performed by the full-text index (the query is driven by an FTS scan), so as a residual predicate Ok always passes: any document the FTS scan yields has already matched. It contributes no index bounds.

CAUTION: this makes Text meaningful only inside an FTS-driven query. Used standalone — calling Ok directly to post-filter documents, as subscription-style consumers of this package do — a $text condition silently matches EVERY document; Ok never evaluates the search text. Placement rules ($text only at the top level or inside $and, at most one per query) are also enforced by the executor, not here. See docs/fts/DESIGN.md.

func (Text) IndexBounds

func (t Text) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (Text) Ok

func (t Text) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (Text) ParsedClauses

func (t Text) ParsedClauses() []TextClause

ParsedClauses returns the parsed clauses, parsing Search on demand for a Text that was constructed directly (Clauses nil) rather than via ParseCondition.

func (Text) String

func (t Text) String() string

type TextClause

type TextClause struct {
	// Raw is the unanalyzed clause text: a single word, or the inner text of a
	// "quoted phrase".
	Raw string
	// Phrase marks a "..." clause: Raw analyzes to an ordered term list matched
	// by positional adjacency.
	Phrase bool
	// Prefix marks a trailing-* clause (foo*): Raw's single analyzed term is
	// expanded against the vocabulary. Mutually exclusive with Phrase.
	Prefix bool
	// Op is the clause's boolean role.
	Op TextOp
}

TextClause is one parsed unit of a $text $search string: a single term, a quoted phrase, or a prefix term, together with its boolean role. The raw text is NOT analyzed here — the query package stays free of the fts analyzer; the FTS executor analyzes each clause with the index's own pipeline.

func ParseTextSearch

func ParseTextSearch(s string) []TextClause

ParseTextSearch splits a $text $search string into clauses. It is a purely syntactic scan — no analysis/tokenization happens here (that is the FTS executor's job, using the index's analyzer). Every clause it returns is a should (OR) clause; required/excluded clauses come from the typed $require / $exclude sub-fields instead (see parseText), not from inline string signs.

  • "double quoted" → a phrase clause (positional adjacency).
  • word* → a prefix clause (vocabulary expansion).
  • word → a plain term clause.

Deliberately there is NO inline +/- boolean syntax. Parsing boolean intent out of a raw human search string is a footgun for an embedded library whose common use is "forward the end-user's search box straight into $search": a user typing "-9" (meaning −9°C) or "+1" (a vote) would silently get an exclusion/require. The same characters are harmless here — they are punctuation the analyzer drops — so "-9" is just the term "9". Boolean require/exclude lives in the structured $require / $exclude arrays, which are unambiguous and run through the same analyzer. An app that wants single-box power-user syntax can parse it itself and populate those fields. A trailing * is still honoured on bare words (prefix expansion has negligible false-positive rate in natural text); it is left literal inside a phrase, where positional expansion would be combinatorial.

type TextOp

type TextOp uint8

TextOp classifies a $text clause's boolean role within the query.

const (
	// TextShould is a default clause: OR'd with the other should-clauses, unless
	// the query sets DefaultAnd, in which case it is required.
	TextShould TextOp = iota
	// TextMust is a +term: always required (AND), regardless of DefaultAnd.
	TextMust
	// TextMustNot is a -term: documents containing it are excluded.
	TextMustNot
)

type TextScoreSort

type TextScoreSort struct {
	Field string
}

TextScoreSort marks a request to order full-text results by descending relevance ({"<field>":{"$meta":"textScore"}}). It carries no document field — the full-text scan already yields documents in score order — so AppendKey and Fields are inert; the query layer recognises the type and keeps scan order.

func (TextScoreSort) AppendKey

func (TextScoreSort) AppendKey(k anyenc.Tuple, _ *anyenc.Value) anyenc.Tuple

func (TextScoreSort) AppendKeyRaw

func (TextScoreSort) AppendKeyRaw(k anyenc.Tuple, _ []byte, _ *syncpool.DocBuffer) (anyenc.Tuple, bool)

AppendKeyRaw is inert like AppendKey: relevance order comes from the full-text scan itself, no document field is read.

func (TextScoreSort) Fields

func (TextScoreSort) Fields() []SortField

func (TextScoreSort) String

func (TextScoreSort) String() string

type Type

type Type anyenc.Type

func (Type) String

func (t Type) String() string

type TypeFilter

type TypeFilter struct {
	Type anyenc.Type
}

func (TypeFilter) IndexBounds

func (e TypeFilter) IndexBounds(fieldName string, bs Bounds) (bounds Bounds)

func (TypeFilter) Ok

func (e TypeFilter) Ok(v *anyenc.Value, buf *syncpool.DocBuffer) bool

func (TypeFilter) String

func (e TypeFilter) String() string

Jump to

Keyboard shortcuts

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