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
- func EncodeTimeBound(instant time.Time, search Search, mapping *TimeFieldMapping) (any, error)
- func EscapeLucene(s string) string
- func MatchOperators() []string
- func MultiMatchTypes() []string
- func NestedClause(path string, clauses []any) map[string]any
- func QualifierNames() []string
- func Qualifiers() map[string][]Operator
- func RangeClause(field string, bounds RangeBounds) map[string]any
- func ScoreModes() []string
- func SortOrders() []string
- func TermClause(field string, value any) map[string]any
- func TermsClause(field string, values []any) map[string]any
- func ValidateFieldName(name string) error
- type Arity
- type CompileRequest
- type Compiled
- type Condition
- type Occur
- type Operator
- type OperatorInfo
- type ParamBinding
- type ParamUse
- type RangeBounds
- type Search
- type SortBy
- type Source
- type TimeFieldFormat
- type TimeFieldMapping
- type TrackTotalHits
- type Value
Constants ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ValidateFieldName ¶
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) PrettyJSON ¶
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.
type Occur ¶
type Occur string
Occur is the bool clause a condition contributes to. Empty means filter.
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" )
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 ¶
ParamBinding is one resolved profile parameter handed to Compile. Role mirrors query.ParamRole; an empty role behaves as RoleFilter.
type RangeBounds ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 (Value) MarshalJSON ¶
MarshalJSON is the inverse of UnmarshalJSON. A literal that would itself read back as a reference is wrapped in {"literal":…}.
func (*Value) UnmarshalJSON ¶
UnmarshalJSON reads {"param":"name"} as a parameter reference, {"literal":x} as an explicit literal, and anything else as a bare literal.