query

package
v2.0.0-beta.2 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 16 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)
)

Variables

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 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 (the original I-04 fix) 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). See docs/known-issues.md (I-04).

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) String

func (b Bound) String() string

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 (docs/known-issues.md I-04, revert ba92bc7). 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) 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) 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 stays a sound over-approximation under Rule V: it may select keys that Ok now rejects (an ordering op against a vector selects a tag range and then matches nothing), never the reverse. Every plan that uses these bounds still runs the residual filter — the covering CountOnly shortcuts require a PointLookup with FIXED bounds, and an ordering op is neither — so a stricter Ok cannot make a plan return rows it should not.

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.

anyenc resolves a cross-type comparison on the type tag, and TypeVectorF32 (10) sorts above every scalar tag (1..9). So an ordering op with a vector on either side used to be true for EVERY document — {"v":{"$gt":1}} on a vector field, and symmetrically {"anyField":{"$lt":{"$vector":[..]}}} on any field at all, even an absent one. On Query.Delete that emptied the collection with err=nil, no vector index required. $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. Verbatim matters — Or.IndexBounds detects "this branch contributed nothing" by comparing lengths, and a shorter return silently discards accumulated sibling bounds.

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)

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)

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 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
}

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