esdsl

package
v0.1.30 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package esdsl models an OpenSearch search as a structured specification and compiles it to a Query DSL request body. The specification — not hand-written JSON — is what profiles and the query builder store, so parameters are bound structurally instead of being interpolated into a query string.

Index

Constants

View Source
const (
	FamilyKeyword = "keyword"
	FamilyText    = "text"
	FamilyDate    = "date"
	FamilyNumber  = "number"
	FamilyBoolean = "boolean"
	FamilyIP      = "ip"
	FamilyNested  = "nested"
	// FamilyAny marks an operator that does not depend on the field type.
	FamilyAny = "any"
)

Field type families. A field's mapping type is reduced to one of these before operators are offered for it.

View Source
const (
	RoleFilter   = "filter"
	RoleLimit    = "limit"
	RoleOffset   = "offset"
	RoleTimeFrom = "time-from"
	RoleTimeTo   = "time-to"
)

Parameter roles a compiled specification understands. They mirror query.ParamRole without importing it — esdsl stays a leaf package.

Variables

This section is empty.

Functions

func EncodeTimeBound

func EncodeTimeBound(instant time.Time, search Search, mapping *TimeFieldMapping) (any, error)

EncodeTimeBound renders instant as a value comparable against search.TimeField under mapping: an RFC3339 string for a date field, an epoch integer in the declared unit for a numeric one.

It is exported because a tail bounds its polls at an instant of its own choosing (see the OpenSearch provider's tailLag) rather than at a parameter somebody supplied. Encoding that instant anywhere else would be a second answer to "how does this index spell a time", and the two would disagree the day one of them learned a new mapping type.

func EscapeLucene

func EscapeLucene(s string) string

EscapeLucene neutralises the query-string syntax in s so a supplied parameter is matched as text rather than interpreted as an operator.

func MatchOperators

func MatchOperators() []string

MatchOperators, MultiMatchTypes, ScoreModes and SortOrders return the accepted values for the qualifiers that take a closed vocabulary.

func MultiMatchTypes

func MultiMatchTypes() []string

func NestedClause

func NestedClause(path string, clauses []any) map[string]any

NestedClause scopes clauses to one entry of a `nested` field.

Everything inside has to be satisfied by the same entry, which is the whole point: it is what keeps the key and the value of a tag from being matched against different tags of the same document. A clause on such a field written without this wrapper is not merely less precise — it matches nothing at all, because OpenSearch indexes each entry as its own hidden document.

func QualifierNames

func QualifierNames() []string

QualifierNames returns the qualifier names in a stable order.

func Qualifiers

func Qualifiers() map[string][]Operator

Qualifiers reports, per advanced qualifier, the operators that emit it. The builder's advanced editor offers only the qualifiers the selected operator actually uses, so it needs the same table Validate enforces.

func RangeClause

func RangeClause(field string, bounds RangeBounds) map[string]any

func ScoreModes

func ScoreModes() []string

func SortOrders

func SortOrders() []string

func TermClause

func TermClause(field string, value any) map[string]any

TermClause, TermsClause and RangeClause render the leaf clauses a runtime column filter compiles to.

They are exported because an authored condition compiles to the same JSON, and a filter the operator picked from a list must not reach OpenSearch in a different shape from the identical condition they could have written by hand.

func TermsClause

func TermsClause(field string, values []any) map[string]any

func ValidateFieldName

func ValidateFieldName(name string) error

ValidateFieldName rejects a field name that could break out of the JSON body it is emitted into. Field names always come from the specification, never from a supplied parameter, so this is a fail-fast authoring check.

Types

type Arity

type Arity string

Arity describes the operand shape an operator takes, which is what the builder uses to pick a value editor.

const (
	// ArityNone takes no operand (exists, match_all).
	ArityNone Arity = "none"
	// AritySingle takes one operand.
	AritySingle Arity = "single"
	// ArityMultiple takes a list of operands (terms, ids).
	ArityMultiple Arity = "multiple"
	// ArityRange takes any of gt/gte/lt/lte.
	ArityRange Arity = "range"
	// ArityGroup takes child conditions (bool, nested).
	ArityGroup Arity = "group"
)

type CompileRequest

type CompileRequest struct {
	// Search is the specification to compile.
	Search Search

	// Params are the resolved profile parameters, with their roles.
	Params []ParamBinding

	// Referenced names parameters already consumed outside the specification —
	// a parameter the engine interpolated into the provider options, say. They
	// count toward the all-parameters-referenced check without appearing as an
	// operand of their own.
	Referenced []string

	// PageSize is how many hits the caller asked this page for. Zero leaves the
	// specification's own size in place.
	PageSize int

	// TimeFieldMapping is the live OpenSearch mapping for Search.TimeField. A
	// provider supplies it when role-carrying time parameters are present.
	TimeFieldMapping *TimeFieldMapping
}

CompileRequest is the input to Compile.

type Compiled

type Compiled struct {
	// Body is the Query DSL request body. It never contains size: the searcher
	// sends size as a URL parameter, so a body size would be overridden.
	Body map[string]any

	// Size is the resolved hit cap. Zero means unspecified.
	Size int

	// From is the resolved offset. Zero means no offset.
	From int

	// Capped reports that the specification's own size held the page below the
	// PageSize asked for, so a short page is not read as the end of the index.
	Capped bool

	// ParamUses reports each condition field that structurally consumed a
	// parameter. Providers use it to avoid applying the same include twice when
	// a parameter also has a native include/exclude field binding.
	ParamUses []ParamUse
}

Compiled is a search request ready to send.

func Compile

func Compile(req CompileRequest) (Compiled, error)

Compile validates the specification, binds its parameters, and renders the OpenSearch request body.

func (Compiled) JSON

func (c Compiled) JSON() (string, error)

JSON encodes the request body.

func (Compiled) PrettyJSON

func (c Compiled) PrettyJSON() (string, error)

PrettyJSON encodes the request body for display.

type Condition

type Condition struct {
	// Occur is the parent bool clause this condition contributes to.
	Occur Occur `json:"occur,omitempty"`

	// Op selects the operator. See Catalog for the supported set.
	Op Operator `json:"op"`

	// Field is the target field for single-field operators.
	Field string `json:"field,omitempty"`

	// Fields targets multi_match, query_string and simple_query_string.
	Fields []string `json:"fields,omitempty"`

	// Value carries the single operand; Values carries the operand list.
	Value  *Value  `json:"value,omitempty"`
	Values []Value `json:"values,omitempty"`

	// Gt, Gte, Lt and Lte bound a range. Date fields accept date math.
	Gt  *Value `json:"gt,omitempty"`
	Gte *Value `json:"gte,omitempty"`
	Lt  *Value `json:"lt,omitempty"`
	Lte *Value `json:"lte,omitempty"`

	// Format and TimeZone qualify a range over a date field.
	Format   string `json:"format,omitempty"`
	TimeZone string `json:"timeZone,omitempty"`

	// Analyzer overrides the search analyzer for analyzed operators.
	Analyzer string `json:"analyzer,omitempty"`

	// MatchOperator is "and" or "or" for match and simple_query_string.
	MatchOperator string `json:"matchOperator,omitempty"`

	// MultiMatchType selects the multi_match strategy (best_fields, phrase, …).
	MultiMatchType string `json:"multiMatchType,omitempty"`

	// Fuzziness is an edit distance or "AUTO".
	Fuzziness string `json:"fuzziness,omitempty"`

	// Slop allows transposed terms in a phrase match.
	Slop *int `json:"slop,omitempty"`

	// Boost weights this condition's contribution to _score.
	Boost *float64 `json:"boost,omitempty"`

	// CaseInsensitive applies to term, prefix, wildcard and regexp.
	CaseInsensitive *bool `json:"caseInsensitive,omitempty"`

	// Escape controls Lucene escaping of a parameter-sourced query_string
	// operand. It defaults to true and is always a specification literal, so a
	// supplied parameter can never turn escaping off.
	Escape *bool `json:"escape,omitempty"`

	// Path and ScoreMode configure a nested group.
	Path      string `json:"path,omitempty"`
	ScoreMode string `json:"scoreMode,omitempty"`

	// MinimumShouldMatch overrides the should-clause requirement on a bool.
	MinimumShouldMatch string `json:"minimumShouldMatch,omitempty"`

	// Conditions are the children of a bool or nested group.
	Conditions []Condition `json:"conditions,omitempty"`

	// Optional drops this condition when its parameter resolves to nothing,
	// instead of failing.
	Optional bool `json:"optional,omitempty"`

	// When names a parameter that gates this condition: it is emitted only when
	// that parameter resolves to a non-empty value, whatever the value is. It is
	// the structural form of a toggle filter, and gates a whole group when set on
	// a bool or nested condition.
	When string `json:"when,omitempty"`
}

Condition is one node of the query tree: either a leaf operator on a field or a bool/nested group over child conditions.

func (Condition) Validate

func (c Condition) Validate(path string) error

Validate reports the first structural problem in the condition tree rooted at c. path names the node in error messages.

type Occur

type Occur string

Occur is the bool clause a condition contributes to. Empty means filter.

const (
	OccurFilter  Occur = "filter"
	OccurMust    Occur = "must"
	OccurShould  Occur = "should"
	OccurMustNot Occur = "must_not"
)

func Occurs

func Occurs() []Occur

Occurs lists the bool clauses in the order they are emitted.

type Operator

type Operator string

Operator names one OpenSearch query clause.

const (
	OpTerm              Operator = "term"
	OpTerms             Operator = "terms"
	OpMatch             Operator = "match"
	OpMatchPhrase       Operator = "match_phrase"
	OpMatchPhrasePrefix Operator = "match_phrase_prefix"
	OpMultiMatch        Operator = "multi_match"
	OpPrefix            Operator = "prefix"
	OpWildcard          Operator = "wildcard"
	OpRegexp            Operator = "regexp"
	OpFuzzy             Operator = "fuzzy"
	OpRange             Operator = "range"
	OpExists            Operator = "exists"
	OpIDs               Operator = "ids"
	OpQueryString       Operator = "query_string"
	OpSimpleQueryString Operator = "simple_query_string"
	OpNested            Operator = "nested"
	OpBool              Operator = "bool"
	OpMatchAll          Operator = "match_all"
)

func Operators

func Operators() []Operator

Operators returns the supported operator names in catalog order.

type OperatorInfo

type OperatorInfo struct {
	// Op is the operator name used in a Condition.
	Op Operator `json:"op"`

	// Label is the human-facing name.
	Label string `json:"label"`

	// Arity is the operand shape.
	Arity Arity `json:"arity"`

	// NeedsField marks operators that target exactly one field.
	NeedsField bool `json:"needsField"`

	// AcceptsFields marks operators that target a list of fields.
	AcceptsFields bool `json:"acceptsFields,omitempty"`

	// FieldTypes lists the field families the operator applies to.
	FieldTypes []string `json:"fieldTypes"`

	// Analyzed marks operators whose operand runs through the search analyzer,
	// so applying them to a keyword field rarely does what an author expects.
	Analyzed bool `json:"analyzed,omitempty"`

	// Group marks the operators that hold child conditions rather than operands.
	Group bool `json:"group,omitempty"`
}

OperatorInfo describes one operator to the query builder. Catalog is the single source of truth: it is emitted into the profile JSON schema, so the frontend never hardcodes the operator set.

func Catalog

func Catalog() []OperatorInfo

Catalog returns every supported operator in a stable order.

func Lookup

func Lookup(op Operator) (OperatorInfo, bool)

Lookup returns the catalog entry for op.

type ParamBinding

type ParamBinding struct {
	Name  string
	Role  string
	Value any
}

ParamBinding is one resolved profile parameter handed to Compile. Role mirrors query.ParamRole; an empty role behaves as RoleFilter.

type ParamUse

type ParamUse struct {
	Name  string
	Field string
}

ParamUse is one structural parameter operand and its condition field.

type RangeBounds

type RangeBounds struct {
	Gt  any
	Gte any
	Lt  any
	Lte any
}

RangeBounds is the four edges of a range clause; a nil edge is unbounded. The values are rendered as given — a date field's operand may be an instant or date math, and OpenSearch is the only thing that should resolve the latter.

type Search struct {
	// Query is the root condition. Nil selects every document.
	Query *Condition `json:"query,omitempty"`

	// Sort orders the hits. Empty leaves the backend default (_score).
	Sort []SortBy `json:"sort,omitempty"`

	// Size caps the returned hits. A limit-role parameter overrides it.
	Size *int `json:"size,omitempty"`

	// From skips the first N hits. Rejected in a scroll context.
	From *int `json:"from,omitempty"`

	// Source selects which _source fields are returned.
	Source *Source `json:"source,omitempty"`

	// TrackTotalHits controls whether the backend counts beyond 10k.
	TrackTotalHits *TrackTotalHits `json:"trackTotalHits,omitempty"`

	// StoredFields and Fields request non-_source values on each hit.
	StoredFields []string `json:"storedFields,omitempty"`
	Fields       []string `json:"fields,omitempty"`

	// Aggregations are preserved verbatim on round-trip. The builder does not
	// edit them; hand-authored aggregations survive a save from the UI.
	Aggregations map[string]json.RawMessage `json:"aggregations,omitempty"`

	// TimeField is the date field that time-from/time-to parameters fold into.
	TimeField string `json:"timeField,omitempty"`

	// TimeFieldFormat declares how a numeric TimeField stores instants. Empty
	// lets a mapped date/date_nanos field use its native date representation.
	TimeFieldFormat TimeFieldFormat `json:"timeFieldFormat,omitempty"`
}

Search is the structured search specification stored at provider.options.search.

func (Search) Validate

func (s Search) Validate() error

Validate reports the first structural problem in the specification.

type SortBy

type SortBy struct {
	Field        string `json:"field"`
	Order        string `json:"order,omitempty"`
	Mode         string `json:"mode,omitempty"`
	Missing      string `json:"missing,omitempty"`
	UnmappedType string `json:"unmappedType,omitempty"`
}

SortBy is one entry of the sort array.

type Source

type Source struct {
	Enabled  *bool    `json:"enabled,omitempty"`
	Includes []string `json:"includes,omitempty"`
	Excludes []string `json:"excludes,omitempty"`
}

Source selects the returned _source fields. Enabled=false disables _source entirely; includes/excludes narrow it.

func (*Source) UnmarshalJSON

func (s *Source) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the three shapes OpenSearch itself accepts for _source: a boolean, a field list, or an includes/excludes object.

type TimeFieldFormat

type TimeFieldFormat string

TimeFieldFormat is the explicit epoch unit for a numeric time field.

const (
	TimeFieldFormatEpochSecond TimeFieldFormat = "epoch_second"
	TimeFieldFormatEpochMillis TimeFieldFormat = "epoch_millis"
	TimeFieldFormatEpochMicros TimeFieldFormat = "epoch_micros"
	TimeFieldFormatEpochNanos  TimeFieldFormat = "epoch_nanos"
)

func TimeFieldFormats

func TimeFieldFormats() []TimeFieldFormat

TimeFieldFormats lists the supported numeric timestamp encodings.

type TimeFieldMapping

type TimeFieldMapping struct {
	Type   string
	Format string
	Now    time.Time
}

TimeFieldMapping is runtime metadata and is never persisted in a profile.

type TrackTotalHits

type TrackTotalHits struct {
	Enabled   *bool `json:"enabled,omitempty"`
	Threshold *int  `json:"threshold,omitempty"`
}

TrackTotalHits is either a boolean or a counting threshold.

func (*TrackTotalHits) UnmarshalJSON

func (t *TrackTotalHits) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a boolean, a number, or an object.

type Value

type Value struct {
	Literal any
	Param   string
}

Value is an operand: either a literal or a reference to a profile parameter. A parameter is substituted structurally — never concatenated into a query string — which is what makes a compiled specification injection-proof.

A profile parameter can also reach an operand the other way, as `"{{.params.country}}-api"` in a string literal: the engine interpolates the provider options before the specification is decoded, so the value arrives here already substituted. The two forms are deliberately different, and an author picks between them:

{"param": "country"}       substituted here, Lucene-escaped in query_string,
                           and prunes an optional condition when empty
"{{.params.country}}-api"  interpolated verbatim, exactly like a raw query,
                           and fails when the parameter has no value

func Literal

func Literal(v any) *Value

Literal builds a literal operand.

func Param

func Param(name string) *Value

Param builds a parameter-backed operand.

func (Value) MarshalJSON

func (v Value) MarshalJSON() ([]byte, error)

MarshalJSON is the inverse of UnmarshalJSON. A literal that would itself read back as a reference is wrapped in {"literal":…}.

func (*Value) UnmarshalJSON

func (v *Value) UnmarshalJSON(data []byte) error

UnmarshalJSON reads {"param":"name"} as a parameter reference, {"literal":x} as an explicit literal, and anything else as a bare literal.

Jump to

Keyboard shortcuts

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