406 lines
8.7 KiB
Go
406 lines
8.7 KiB
Go
package elasticsearch
|
|
|
|
import (
|
|
"encoding/json"
|
|
)
|
|
|
|
// This file contains structures that represent all of the various JSON-
|
|
// marshalable queries and sub-queries that are part of the ElasticSearch
|
|
// grammar. These structures are one-way: they're only meant to be Marshaled.
|
|
|
|
type SubQuery interface{}
|
|
|
|
var nilSubQuery SubQuery
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// Wrapper gives a dynamic name to a SubQuery. For example, Name="foo"
|
|
// Wrapped=`{"bar": 123}` marshals to `{"foo": {"bar": 123}}`.
|
|
//
|
|
// You *can* use this directly in your business-logic code, but if you do, it's
|
|
// probably a sign you should file an issue (or make a pull request) to give
|
|
// your specific use-case proper, first class support, via a FooQuery[Params]
|
|
// type-set.
|
|
type Wrapper struct {
|
|
Name string
|
|
Wrapped SubQuery
|
|
}
|
|
|
|
func (w *Wrapper) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(map[string]SubQuery{
|
|
w.Name: w.Wrapped,
|
|
})
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
func QueryWrapper(q SubQuery) SubQuery {
|
|
return &Wrapper{
|
|
Name: "query",
|
|
Wrapped: q,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// GenericQueryParams marshal to a valid query object for a large number of
|
|
// query types. You generally use them applied to a particular field, ie. scope;
|
|
// see FieldedGenericQuery.
|
|
type GenericQueryParams struct {
|
|
Query string `json:"query,omitempty"`
|
|
Analyzer string `json:"analyzer,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
MaxExpansions string `json:"max_expansions,omitempty"`
|
|
Boost float32 `json:"boost,omitempty"`
|
|
Operator string `json:"operator,omitempty"`
|
|
MinimumShouldMatch string `json:"minimum_should_match,omitempty"`
|
|
CutoffFrequency float32 `json:"cutoff_frequency,omitempty"`
|
|
}
|
|
|
|
// FieldedGenericQuery returns a SubQuery representing the passed QueryParams
|
|
// applied to the given scope, ie. field. The returned SubQuery can be used as
|
|
// the Query in a MatchQuery, for example.
|
|
func FieldedGenericQuery(field string, p GenericQueryParams) SubQuery {
|
|
return &Wrapper{
|
|
Name: field,
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html
|
|
type MatchQueryParams struct {
|
|
Query SubQuery `json:"match"`
|
|
}
|
|
|
|
func MatchQuery(p MatchQueryParams) SubQuery {
|
|
return p
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/term-query.html
|
|
// Typically `Query` would be &Wrapper{Name: "fieldname", Wrapped: "value"}.
|
|
type TermQueryParams struct {
|
|
Query SubQuery `json:"term"`
|
|
}
|
|
|
|
func TermQuery(p TermQueryParams) SubQuery {
|
|
return p
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/terms-query.html
|
|
// "a simpler syntax query for using a `bool` query with several `term` queries
|
|
// in the `should` clauses."
|
|
type TermsQueryParams struct {
|
|
Query SubQuery `json:"terms"` // often `{ "field": ["value1", "value2"] }`
|
|
}
|
|
|
|
func TermsQuery(p TermsQueryParams) SubQuery {
|
|
return p
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/dis-max-query.html
|
|
type DisMaxQueryParams struct {
|
|
Queries []SubQuery `json:"queries"`
|
|
Boost float32 `json:"boost,omitempty"`
|
|
TieBreaker float32 `json:"tie_breaker,omitempty"`
|
|
}
|
|
|
|
func DisMaxQuery(p DisMaxQueryParams) SubQuery {
|
|
return &Wrapper{
|
|
Name: "dis_max",
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html
|
|
type BoolQueryParams struct {
|
|
Must SubQuery `json:"must,omitempty"` // can be slice!
|
|
Should SubQuery `json:"should,omitempty"`
|
|
MustNot SubQuery `json:"must_not,omitempty"`
|
|
MinimumNumberShouldMatch int `json:"minimum_number_should_match,omitempty"`
|
|
Boost float32 `json:"boost,omitempty"`
|
|
}
|
|
|
|
func BoolQuery(p BoolQueryParams) SubQuery {
|
|
return &Wrapper{
|
|
Name: "bool",
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/custom-score-query.html
|
|
type CustomScoreQueryParams struct {
|
|
Script string `json:"script"`
|
|
Lang string `json:"lang"`
|
|
Params map[string]interface{} `json:"params"`
|
|
Query SubQuery `json:"query"`
|
|
}
|
|
|
|
func CustomScoreQuery(p CustomScoreQueryParams) SubQuery {
|
|
return &Wrapper{
|
|
Name: "custom_score",
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
type ConstantScoreQueryParams struct {
|
|
Query SubQuery `json:"query,omitempty"`
|
|
Filter FilterSubQuery `json:"filter,omitempty"`
|
|
Boost float32 `json:"boost,omitempty"`
|
|
}
|
|
|
|
func ConstantScoreQuery(p ConstantScoreQueryParams) SubQuery {
|
|
return &Wrapper{
|
|
Name: "constant_score",
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
func MatchAllQuery() SubQuery {
|
|
return &Wrapper{
|
|
Name: "match_all",
|
|
Wrapped: map[string]interface{}{}, // render to '{}'
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// Haven't quite figured out how to best represent this.
|
|
// TODO break these up into embeddable query-parts?
|
|
type OffsetLimitFacetsFilterQueryParams struct {
|
|
Offset int `json:"from"`
|
|
Limit int `json:"size"`
|
|
Facets FacetSubQuery `json:"facets,omitempty"`
|
|
Filter FilterSubQuery `json:"filter,omitempty"`
|
|
Query SubQuery `json:"query"`
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
// =============================================================================
|
|
// HERE BE FILTERS
|
|
// =============================================================================
|
|
//
|
|
//
|
|
//
|
|
|
|
type FilterSubQuery SubQuery
|
|
|
|
func MakeFilter(filter SubQuery) FilterSubQuery {
|
|
return FilterSubQuery(filter)
|
|
}
|
|
|
|
func MakeFilters(filters []SubQuery) []FilterSubQuery {
|
|
a := []FilterSubQuery{}
|
|
for _, filter := range filters {
|
|
a = append(a, MakeFilter(filter))
|
|
}
|
|
return a
|
|
}
|
|
|
|
type BooleanFiltersParams struct {
|
|
AndFilters []FilterSubQuery
|
|
OrFilters []FilterSubQuery
|
|
}
|
|
|
|
func BooleanFilters(p BooleanFiltersParams) SubQuery {
|
|
switch nAnd, nOr := len(p.AndFilters), len(p.OrFilters); true {
|
|
case nAnd <= 0 && nOr <= 0:
|
|
return map[string]interface{}{} // render to '{}'
|
|
|
|
case nAnd > 0 && nOr <= 0:
|
|
return &Wrapper{
|
|
Name: "and",
|
|
Wrapped: p.AndFilters,
|
|
}
|
|
|
|
case nAnd <= 0 && nOr > 0:
|
|
return &Wrapper{
|
|
Name: "or",
|
|
Wrapped: p.OrFilters,
|
|
}
|
|
|
|
case nAnd > 0 && nOr > 0:
|
|
combinedFilters := append(p.AndFilters, &Wrapper{
|
|
Name: "or",
|
|
Wrapped: p.OrFilters,
|
|
})
|
|
return &Wrapper{
|
|
Name: "and",
|
|
Wrapped: combinedFilters,
|
|
}
|
|
}
|
|
panic("unreachable")
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
type QueryFilterParams struct {
|
|
Query SubQuery `json:"query"`
|
|
}
|
|
|
|
func QueryFilter(p QueryFilterParams) FilterSubQuery {
|
|
return p // no need for another layer of indirection; just like a typecast
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
type TermFilterParams struct {
|
|
Field string
|
|
Value string // for multiple values, use TermsFilter
|
|
}
|
|
|
|
func TermFilter(p TermFilterParams) FilterSubQuery {
|
|
return &Wrapper{
|
|
Name: "term",
|
|
Wrapped: &Wrapper{
|
|
Name: p.Field,
|
|
Wrapped: p.Value,
|
|
},
|
|
}
|
|
}
|
|
|
|
type TermsFilterParams struct {
|
|
Field string
|
|
Values []string
|
|
Execution string
|
|
}
|
|
|
|
func TermsFilter(p TermsFilterParams) FilterSubQuery {
|
|
terms := map[string]interface{}{
|
|
p.Field: p.Values,
|
|
}
|
|
if p.Execution != "" {
|
|
terms["execution"] = p.Execution
|
|
}
|
|
return map[string]interface{}{
|
|
"terms": terms,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/type-filter.html
|
|
type FieldedFilterParams struct {
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
// TODO I guess remove all Fielded* functions except FieldedGenericQuery?
|
|
// TODO and rename FieldedGenericQuery to like FieldedSubObject, maybe?
|
|
func FieldedFilter(fieldName string, p FieldedFilterParams) FilterSubQuery {
|
|
return &Wrapper{
|
|
Name: fieldName,
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
type RangeSubQuery SubQuery
|
|
|
|
// http://www.elasticsearch.org/guide/reference/query-dsl/range-filter.html
|
|
type RangeFilterParams struct {
|
|
From string `json:"from,omitempty"`
|
|
To string `json:"to,omitempty"`
|
|
IncludeLower bool `json:"include_lower"`
|
|
IncludeUpper bool `json:"include_upper"`
|
|
}
|
|
|
|
func FieldedRangeSubQuery(field string, p RangeFilterParams) RangeSubQuery {
|
|
return &Wrapper{
|
|
Name: field,
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
func RangeFilter(q RangeSubQuery) FilterSubQuery {
|
|
return &Wrapper{
|
|
Name: "range",
|
|
Wrapped: q,
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
// =============================================================================
|
|
// HERE BE FACETS
|
|
// =============================================================================
|
|
//
|
|
//
|
|
//
|
|
|
|
type FacetSubQuery SubQuery
|
|
|
|
// http://www.elasticsearch.org/guide/reference/api/search/facets/terms-facet.html
|
|
type TermsFacetParams struct {
|
|
Field string `json:"field"`
|
|
Size int `json:"size"`
|
|
}
|
|
|
|
func TermsFacet(p TermsFacetParams) FacetSubQuery {
|
|
return &Wrapper{
|
|
Name: "terms",
|
|
Wrapped: p,
|
|
}
|
|
}
|
|
|
|
// TODO other types of facets
|
|
|
|
// NamedFacet wraps any FooFacet SubQuery so that it can be used
|
|
// wherever a facet is called for.
|
|
func NamedFacet(name string, q FacetSubQuery) FacetSubQuery {
|
|
return &Wrapper{
|
|
Name: name,
|
|
Wrapped: q,
|
|
}
|
|
}
|