diff --git a/.golangci.yml b/.golangci.yml index ee46e7c..f07fa37 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,3 +27,5 @@ linters: - prealloc - unconvert - unparam + - golint + - godox diff --git a/aggregations.go b/aggregations.go index f1d7479..8cff870 100644 --- a/aggregations.go +++ b/aggregations.go @@ -8,15 +8,26 @@ import ( "github.com/elastic/go-elasticsearch/v7/esapi" ) +// AggregationRequest represents a complete request of type "aggregations" +// (a.k.a "aggs") to ElasticSearch's search API. It simply wraps a map of named +// aggregations, which are values of a type that implements the Mappable +// interface. +type AggregationRequest struct { + Aggs map[string]Mappable +} + +// Aggregation is an interface that each aggregation type must implement. It +// is simply an extension of the Mappable interface to include a Named function, +// which returns the name of the aggregation. type Aggregation interface { Mappable Name() string } -type AggregationRequest struct { - Aggs map[string]Mappable -} - +// Aggregate generates a search request of type "aggs", represented by a +// *AggregationRequest object. It receives a variadic amount of values that +// implement the Aggregation interface, whether provided internally by the +// library or custom aggregations provided by consuming code. func Aggregate(aggs ...Aggregation) *AggregationRequest { req := &AggregationRequest{ Aggs: make(map[string]Mappable), @@ -28,6 +39,8 @@ func Aggregate(aggs ...Aggregation) *AggregationRequest { return req } +// Map implements the Mappable interface. It converts the "aggs" request into a +// (potentially nested) map[string]interface{}. func (req *AggregationRequest) Map() map[string]interface{} { m := make(map[string]interface{}) @@ -40,10 +53,15 @@ func (req *AggregationRequest) Map() map[string]interface{} { } } +// MarshalJSON implements the json.Marshaler interface, it simply encodes the +// map representation of the request (provided by the Map method) as JSON. func (req *AggregationRequest) MarshalJSON() ([]byte, error) { return json.Marshal(req.Map()) } +// Run executes the request using the provided ElasticSearch client. Zero or +// more search options can be provided as well. It returns the standard Response +// type of the official Go client. func (req *AggregationRequest) Run( api *elasticsearch.Client, o ...func(*esapi.SearchRequest), @@ -58,3 +76,24 @@ func (req *AggregationRequest) Run( return api.Search(opts...) } + +// RunSearch is the same as the Run method, except that it accepts a value of +// type esapi.Search (usually this is the Search field of an elasticsearch.Client +// object). Since the ElasticSearch client does not provide an interface type +// for its API (which would allow implementation of mock clients), this provides +// a workaround. The Search function in the ES client is actually a field of a +// function type. +func (req *AggregationRequest) RunSearch( + search esapi.Search, + o ...func(*esapi.SearchRequest), +) (res *esapi.Response, err error) { + var b bytes.Buffer + err = json.NewEncoder(&b).Encode(req.Map()) + if err != nil { + return nil, err + } + + opts := append([]func(*esapi.SearchRequest){search.WithBody(&b)}, o...) + + return search(opts...) +} diff --git a/aggs_custom.go b/aggs_custom.go deleted file mode 100644 index 6a03a1c..0000000 --- a/aggs_custom.go +++ /dev/null @@ -1,13 +0,0 @@ -package esquery - -type CustomAggregation struct { - m map[string]interface{} -} - -func CustomAgg(m map[string]interface{}) *CustomAggregation { - return &CustomAggregation{m} -} - -func (agg *CustomAggregation) Map() map[string]interface{} { - return agg.m -} diff --git a/aggs_metric.go b/aggs_metric.go index c120759..ab1262b 100644 --- a/aggs_metric.go +++ b/aggs_metric.go @@ -2,15 +2,21 @@ package esquery import "github.com/fatih/structs" +// BaseAgg contains several fields that are common for all aggregation types. type BaseAgg struct { name string apiName string *BaseAggParams `structs:",flatten"` } +// BaseAggParams contains fields that are common to most metric-aggregation +// types. type BaseAggParams struct { - Field string `structs:"field"` - Miss interface{} `structs:"missing,omitempty"` + // Field is the name of the field to aggregate on. + Field string `structs:"field"` + // Miss is a value to provide for documents that are missing a value for the + // field. + Miss interface{} `structs:"missing,omitempty"` } func newBaseAgg(apiName, name, field string) *BaseAgg { @@ -23,50 +29,60 @@ func newBaseAgg(apiName, name, field string) *BaseAgg { } } +// Name returns the name of the aggregation, allowing implementation of the +// Aggregation interface. func (agg *BaseAgg) Name() string { return agg.name } +// Map returns a map representation of the aggregation, implementing the +// Mappable interface. func (agg *BaseAgg) Map() map[string]interface{} { return map[string]interface{}{ agg.apiName: structs.Map(agg.BaseAggParams), } } -/******************************************************************************* - * Avg Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-avg-aggregation.html - ******************************************************************************/ - +// AvgAgg represents an aggregation of type "avg", as described in +// https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-avg-aggregation.html type AvgAgg struct { *BaseAgg `structs:",flatten"` } +// Avg creates an aggregation of type "avg", with the provided name and on the +// provided field. func Avg(name, field string) *AvgAgg { return &AvgAgg{ BaseAgg: newBaseAgg("avg", name, field), } } +// Missing sets the value to provide for documents missing a value for the +// selected field. func (agg *AvgAgg) Missing(val interface{}) *AvgAgg { agg.Miss = val return agg } -/******************************************************************************* - * Weighed Avg Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-weight-avg-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// WeightedAvgAgg represents an aggregation of type "weighted_avg", as described +// in https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-weight-avg-aggregation.html type WeightedAvgAgg struct { name string apiName string - Val *BaseAggParams `structs:"value"` - Weig *BaseAggParams `structs:"weight"` + + // Val is the value component of the aggregation + Val *BaseAggParams `structs:"value"` + + // Weig is the weight component of the aggregation + Weig *BaseAggParams `structs:"weight"` } +// WeightedAvg creates a new aggregation of type "weighted_agg" with the +// provided name. func WeightedAvg(name string) *WeightedAvgAgg { return &WeightedAvgAgg{ name: name, @@ -74,10 +90,13 @@ func WeightedAvg(name string) *WeightedAvgAgg { } } +// Name returns the name of the aggregation. func (agg *WeightedAvgAgg) Name() string { return agg.name } +// Value sets the value field and optionally a value to use when records are +// missing a value for the field. func (agg *WeightedAvgAgg) Value(field string, missing ...interface{}) *WeightedAvgAgg { agg.Val = new(BaseAggParams) agg.Val.Field = field @@ -87,6 +106,8 @@ func (agg *WeightedAvgAgg) Value(field string, missing ...interface{}) *Weighted return agg } +// Value sets the weight field and optionally a value to use when records are +// missing a value for the field. func (agg *WeightedAvgAgg) Weight(field string, missing ...interface{}) *WeightedAvgAgg { agg.Weig = new(BaseAggParams) agg.Weig.Field = field @@ -96,227 +117,279 @@ func (agg *WeightedAvgAgg) Weight(field string, missing ...interface{}) *Weighte return agg } +// Map returns a map representation of the aggregation, thus implementing the +// Mappable interface. func (agg *WeightedAvgAgg) Map() map[string]interface{} { return map[string]interface{}{ agg.apiName: structs.Map(agg), } } -/******************************************************************************* - * Cardinality Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-cardinality-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// CardinalityAgg represents an aggregation of type "cardinality", as described +// in https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-cardinality-aggregation.html type CardinalityAgg struct { - *BaseAgg `structs:",flatten"` + *BaseAgg `structs:",flatten"` + + // PrecisionThr is the precision threshold of the aggregation PrecisionThr uint16 `structs:"precision_threshold,omitempty"` } +// Cardinality creates a new aggregation of type "cardinality" with the provided +// name and on the provided field. func Cardinality(name, field string) *CardinalityAgg { return &CardinalityAgg{ BaseAgg: newBaseAgg("cardinality", name, field), } } +// Missing sets the value to provide for records that are missing a value for +// the field. func (agg *CardinalityAgg) Missing(val interface{}) *CardinalityAgg { agg.Miss = val return agg } +// PrecisionThreshold sets the precision threshold of the aggregation. func (agg *CardinalityAgg) PrecisionThreshold(val uint16) *CardinalityAgg { agg.PrecisionThr = val return agg } +// Map returns a map representation of the aggregation, thus implementing the +// Mappable interface func (agg *CardinalityAgg) Map() map[string]interface{} { return map[string]interface{}{ agg.apiName: structs.Map(agg), } } -/******************************************************************************* - * Max Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-max-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// MaxAgg represents an aggregation of type "max", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-max-aggregation.html type MaxAgg struct { *BaseAgg `structs:",flatten"` } +// Max creates a new aggregation of type "max", with the provided name and on +// the provided field. func Max(name, field string) *MaxAgg { return &MaxAgg{ BaseAgg: newBaseAgg("max", name, field), } } +// Missing sets the value to provide for records that are missing a value for +// the field. func (agg *MaxAgg) Missing(val interface{}) *MaxAgg { agg.Miss = val return agg } -/******************************************************************************* - * Min Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-min-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// MinAgg represents an aggregation of type "min", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-min-aggregation.html type MinAgg struct { *BaseAgg `structs:",flatten"` } +// Min creates a new aggregation of type "min", with the provided name and on +// the provided field. func Min(name, field string) *MinAgg { return &MinAgg{ BaseAgg: newBaseAgg("min", name, field), } } +// Missing sets the value to provide for records that are missing a value for +// the field. func (agg *MinAgg) Missing(val interface{}) *MinAgg { agg.Miss = val return agg } -/******************************************************************************* - * Sum Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-sum-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// SumAgg represents an aggregation of type "sum", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-sum-aggregation.html type SumAgg struct { *BaseAgg `structs:",flatten"` } +// Sum creates a new aggregation of type "sum", with the provided name and on +// the provided field. func Sum(name, field string) *SumAgg { return &SumAgg{ BaseAgg: newBaseAgg("sum", name, field), } } +// Missing sets the value to provide for records that are missing a value for +// the field. func (agg *SumAgg) Missing(val interface{}) *SumAgg { agg.Miss = val return agg } -/******************************************************************************* - * Value Count Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-valuecount-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// ValueCountAgg represents an aggregation of type "value_count", as described +// in https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-valuecount-aggregation.html type ValueCountAgg struct { *BaseAgg `structs:",flatten"` } +// ValueCount creates a new aggregation of type "value_count", with the provided +// name and on the provided field func ValueCount(name, field string) *ValueCountAgg { return &ValueCountAgg{ BaseAgg: newBaseAgg("value_count", name, field), } } -/******************************************************************************* - * Percentiles Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-percentile-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// PercentilesAgg represents an aggregation of type "percentiles", as described +// in https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-percentile-aggregation.html type PercentilesAgg struct { *BaseAgg `structs:",flatten"` - Prcnts []float32 `structs:"percents,omitempty"` - Key *bool `structs:"keyed,omitempty"` - TDigest struct { + + // Prcnts is the aggregation's percentages + Prcnts []float32 `structs:"percents,omitempty"` + + // Key denotes whether the aggregation is keyed or not + Key *bool `structs:"keyed,omitempty"` + + // TDigest includes options for the TDigest algorithm + TDigest struct { + // Compression is the compression level to use Compression uint16 `structs:"compression,omitempty"` } `structs:"tdigest,omitempty"` + + // HDR includes options for the HDR implementation HDR struct { + // NumHistogramDigits defines the resolution of values for the histogram + // in number of significant digits NumHistogramDigits uint8 `structs:"number_of_significant_value_digits,omitempty"` } `structs:"hdr,omitempty"` } +// Percentiles creates a new aggregation of type "percentiles" with the provided +// name and on the provided field. func Percentiles(name, field string) *PercentilesAgg { return &PercentilesAgg{ BaseAgg: newBaseAgg("percentiles", name, field), } } +// Percents sets the aggregation's percentages func (agg *PercentilesAgg) Percents(percents ...float32) *PercentilesAgg { agg.Prcnts = percents return agg } +// Missing sets the value to provide for records that are missing a value for +// the field. func (agg *PercentilesAgg) Missing(val interface{}) *PercentilesAgg { agg.Miss = val return agg } +// Keyed sets whether the aggregate is keyed or not. func (agg *PercentilesAgg) Keyed(b bool) *PercentilesAgg { agg.Key = &b return agg } +// Compression sets the compression level for the aggregation. func (agg *PercentilesAgg) Compression(val uint16) *PercentilesAgg { agg.TDigest.Compression = val return agg } +// NumHistogramDigits specifies the resolution of values for the histogram in +// number of significant digits. func (agg *PercentilesAgg) NumHistogramDigits(val uint8) *PercentilesAgg { agg.HDR.NumHistogramDigits = val return agg } +// Map returns a map representation of the aggregation, thus implementing the +// Mappable interface. func (agg *PercentilesAgg) Map() map[string]interface{} { return map[string]interface{}{ agg.apiName: structs.Map(agg), } } -/******************************************************************************* - * Stats Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-stats-aggregation.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// StatsAgg represents an aggregation of type "stats", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-stats-aggregation.html type StatsAgg struct { *BaseAgg `structs:",flatten"` } +// Stats creates a new "stats" aggregation with the provided name and on the +// provided field. func Stats(name, field string) *StatsAgg { return &StatsAgg{ BaseAgg: newBaseAgg("stats", name, field), } } +// Missing sets the value to provide for records missing a value for the field. func (agg *StatsAgg) Missing(val interface{}) *StatsAgg { agg.Miss = val return agg } -/******************************************************************************* - * String Stats Aggregation - * https://www.elastic.co/guide/en/elasticsearch/reference/ - * current/search-aggregations-metrics-string-stats-aggregation.html - ******************************************************************************/ +// ---------------------------------------------------------------------------// +// StringStatsAgg represents an aggregation of type "string_stats", as described +// in https://www.elastic.co/guide/en/elasticsearch/reference/ +// current/search-aggregations-metrics-string-stats-aggregation.html type StringStatsAgg struct { *BaseAgg `structs:",flatten"` + + // ShowDist indicates whether to ask ElasticSearch to return a probability + // distribution for all characters ShowDist *bool `structs:"show_distribution,omitempty"` } +// StringStats creates a new "string_stats" aggregation with the provided name +// and on the provided field. func StringStats(name, field string) *StringStatsAgg { return &StringStatsAgg{ BaseAgg: newBaseAgg("string_stats", name, field), } } +// Missing sets the value to provide for records missing a value for the field. func (agg *StringStatsAgg) Missing(val interface{}) *StringStatsAgg { agg.Miss = val return agg } +// ShowDistribution sets whether to show the probability distribution for all +// characters func (agg *StringStatsAgg) ShowDistribution(b bool) *StringStatsAgg { agg.ShowDist = &b return agg } +// Map returns a map representation of the aggregation, thus implementing the +// Mappable interface. func (agg *StringStatsAgg) Map() map[string]interface{} { return map[string]interface{}{ agg.apiName: structs.Map(agg), diff --git a/custom.go b/custom.go new file mode 100644 index 0000000..608bed4 --- /dev/null +++ b/custom.go @@ -0,0 +1,63 @@ +package esquery + +import ( + "github.com/elastic/go-elasticsearch/v7" + "github.com/elastic/go-elasticsearch/v7/esapi" +) + +// CustomQueryMap represents an arbitrary query map for custom queries. +type CustomQueryMap map[string]interface{} + +// CustomQuery generates a custom request of type "query" from an arbitrary map +// provided by the user. It is useful for issuing a search request with a syntax +// that is not yet supported by the library. CustomQuery values are versatile, +// they can either be used as parameters for the library's Query function, or +// standlone by invoking their Run method. +func CustomQuery(m map[string]interface{}) *CustomQueryMap { + q := CustomQueryMap(m) + return &q +} + +// Map returns the custom query as a map[string]interface{}, thus implementing +// the Mappable interface. +func (m *CustomQueryMap) Map() map[string]interface{} { + return map[string]interface{}(*m) +} + +// Run executes the custom query using the provided ElasticSearch client. Zero +// or more search options can be provided as well. It returns the standard +// Response type of the official Go client. +func (m *CustomQueryMap) Run( + api *elasticsearch.Client, + o ...func(*esapi.SearchRequest), +) (res *esapi.Response, err error) { + return Query(m).Run(api, o...) +} + +//----------------------------------------------------------------------------// + +// CustomAggMap represents an arbitrary aggregation map for custom aggregations. +type CustomAggMap struct { + name string + agg map[string]interface{} +} + +// CustomAgg generates a custom aggregation from an arbitrary map provided by +// the user. +func CustomAgg(name string, m map[string]interface{}) *CustomAggMap { + return &CustomAggMap{ + name: name, + agg: m, + } +} + +// Name returns the name of the aggregation +func (agg *CustomAggMap) Name() string { + return agg.name +} + +// Map returns a map representation of the custom aggregation, thus implementing +// the Mappable interface +func (agg *CustomAggMap) Map() map[string]interface{} { + return agg.agg +} diff --git a/aggs_custom_test.go b/custom_test.go similarity index 64% rename from aggs_custom_test.go rename to custom_test.go index a807f5f..f4d5e59 100644 --- a/aggs_custom_test.go +++ b/custom_test.go @@ -2,6 +2,26 @@ package esquery import "testing" +func TestCustomQuery(t *testing.T) { + m := map[string]interface{}{ + "geo_distance": map[string]interface{}{ + "distance": "200km", + "pin.location": map[string]interface{}{ + "lat": 40, + "lon": -70, + }, + }, + } + + runMapTests(t, []mapTest{ + { + "custom query", + CustomQuery(m), + m, + }, + }) +} + func TestCustomAgg(t *testing.T) { m := map[string]interface{}{ "genres": map[string]interface{}{ @@ -28,7 +48,7 @@ func TestCustomAgg(t *testing.T) { runMapTests(t, []mapTest{ { "custom aggregation", - CustomAgg(m), + CustomAgg("custom_agg", m), m, }, }) diff --git a/es.go b/es.go index 3c21c4b..ee269a6 100644 --- a/es.go +++ b/es.go @@ -1,5 +1,107 @@ +// Package esquery provides a non-obtrusive, idiomatic and easy-to-use query +// and aggregation builder for the official Go client +// (https://github.com/elastic/go-elasticsearch) for the ElasticSearch +// database (https://www.elastic.co/products/elasticsearch). +// +// esquery alleviates the need to use extremely nested maps +// (map[string]interface{}) and serializing queries to JSON manually. It also +// helps eliminating common mistakes such as misspelling query types, as +// everything is statically typed. +// +// Using `esquery` can make your code much easier to write, read and maintain, +// and significantly reduce the amount of code you write. +// +// +// +// Usage +// +// +// +// esquery provides a method chaining-style API for building and executing +// queries and aggregations. It does not wrap the official Go client nor does it +// require you to change your existing code in order to integrate the library. +// Queries can be directly built with `esquery`, and executed by passing an +// `*elasticsearch.Client` instance (with optional search parameters). Results +// are returned as-is from the official client (e.g. `*esapi.Response` objects). +// +// Getting started is extremely simple: +// +// package main +// +// import ( +// "context" +// "log" +// +// "github.com/aquasecurity/esquery" +// "github.com/elastic/go-elasticsearch/v7" +// ) +// +// func main() { +// // connect to an ElasticSearch instance +// es, err := elasticsearch.NewDefaultClient() +// if err != nil { +// log.Fatalf("Failed creating client: %s", err) +// } +// +// // run a boolean search query +// qRes, err := esquery.Query( +// esquery. +// Bool(). +// Must(esquery.Term("title", "Go and Stuff")). +// Filter(esquery.Term("tag", "tech")), +// ).Run( +// es, +// es.Search.WithContext(context.TODO()), +// es.Search.WithIndex("test"), +// ) +// if err != nil { +// log.Fatalf("Failed searching for stuff: %s", err) +// } +// +// defer qRes.Body.Close() +// +// // run an aggregation +// aRes, err := esquery.Aggregate( +// esquery.Avg("average_score", "score"), +// esquery.Max("max_score", "score"), +// ).Run( +// es, +// es.Search.WithContext(context.TODO()), +// es.Search.WithIndex("test"), +// ) +// if err != nil { +// log.Fatalf("Failed searching for stuff: %s", err) +// } +// +// defer aRes.Body.Close() +// +// // ... +// } +// +// +// +// Notes +// +// +// +//* esquery currently supports version 7 of the ElasticSearch Go client. +//* The library cannot currently generate "short queries". For example, +// whereas ElasticSearch can accept this: +// +// { "query": { "term": { "user": "Kimchy" } } } +// +// The library will always generate this: +// +// { "query": { "term": { "user": { "value": "Kimchy" } } } } +// +// This is also true for queries such as "bool", where fields like "must" can +// either receive one query object, or an array of query objects. `esquery` will +// generate an array even if there's only one query object. package esquery +// Mappable is the interface implemented by the various query and aggregation +// types provided by the package. It allows the library to easily transform the +// different queries to "generic" maps that can be easily encoded to JSON. type Mappable interface { Map() map[string]interface{} } diff --git a/queries.go b/queries.go index e4edfa7..1a1762a 100644 --- a/queries.go +++ b/queries.go @@ -8,24 +8,38 @@ import ( "github.com/elastic/go-elasticsearch/v7/esapi" ) +// QueryRequest represents a complete request of type "query" to ElasticSearch's +// search API. It simply wraps a value of a type that implements the Mappable +// interface. type QueryRequest struct { Query Mappable } +// Query generates a search request of type "query", represented by a +// *QueryRequest object. It receives any query type that implements the +// Mappable interface, whether provided internally by the library or custom +// types provided by consuming code. func Query(q Mappable) *QueryRequest { return &QueryRequest{q} } +// Map implements the Mappable interface. It converts the "query" request into a +// (potentially nested) map[string]interface{}. func (req *QueryRequest) Map() map[string]interface{} { return map[string]interface{}{ "query": req.Query.Map(), } } +// MarshalJSON implements the json.Marshaler interface, it simply encodes the +// map representation of the query (provided by the Map method) as JSON. func (req *QueryRequest) MarshalJSON() ([]byte, error) { return json.Marshal(req.Map()) } +// Run executes the request using the provided ElasticSearch client. Zero or +// more search options can be provided as well. It returns the standard Response +// type of the official Go client. func (req *QueryRequest) Run( api *elasticsearch.Client, o ...func(*esapi.SearchRequest), @@ -40,3 +54,24 @@ func (req *QueryRequest) Run( return api.Search(opts...) } + +// RunSearch is the same as the Run method, except that it accepts a value of +// type esapi.Search (usually this is the Search field of an elasticsearch.Client +// object). Since the ElasticSearch client does not provide an interface type +// for its API (which would allow implementation of mock clients), this provides +// a workaround. The Search function in the ES client is actually a field of a +// function type. +func (req *QueryRequest) RunSearch( + search esapi.Search, + o ...func(*esapi.SearchRequest), +) (res *esapi.Response, err error) { + var b bytes.Buffer + err = json.NewEncoder(&b).Encode(req.Map()) + if err != nil { + return nil, err + } + + opts := append([]func(*esapi.SearchRequest){search.WithBody(&b)}, o...) + + return search(opts...) +} diff --git a/queries_test.go b/queries_test.go index 029f042..432e0ed 100644 --- a/queries_test.go +++ b/queries_test.go @@ -23,7 +23,7 @@ func TestQueryMaps(t *testing.T) { Range("date"). Gt("some time in the past"). Lte("now"). - Relation(CONTAINS). + Relation(RangeContains). TimeZone("Asia/Jerusalem"). Boost(2.3), diff --git a/query_boolean.go b/query_boolean.go index 3e93c28..18acb92 100644 --- a/query_boolean.go +++ b/query_boolean.go @@ -2,11 +2,8 @@ package esquery import "github.com/fatih/structs" -/******************************************************************************* - * Boolean Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html - ******************************************************************************/ - +// BoolQuery represents a compound query of type "bool", as described in +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html type BoolQuery struct { must []Mappable filter []Mappable @@ -16,40 +13,54 @@ type BoolQuery struct { boost float32 } +// Bool creates a new compound query of type "bool". func Bool() *BoolQuery { return &BoolQuery{} } +// Must adds one or more queries of type "must" to the bool query. Must can be +// called multiple times, queries will be appended to existing ones. func (q *BoolQuery) Must(must ...Mappable) *BoolQuery { q.must = append(q.must, must...) return q } +// Filter adds one or more queries of type "filter" to the bool query. Filter +// can be called multiple times, queries will be appended to existing ones. func (q *BoolQuery) Filter(filter ...Mappable) *BoolQuery { q.filter = append(q.filter, filter...) return q } +// Must adds one or more queries of type "must_not" to the bool query. MustNot +// can be called multiple times, queries will be appended to existing ones. func (q *BoolQuery) MustNot(mustnot ...Mappable) *BoolQuery { q.mustNot = append(q.mustNot, mustnot...) return q } +// Should adds one or more queries of type "should" to the bool query. Should can be +// called multiple times, queries will be appended to existing ones. func (q *BoolQuery) Should(should ...Mappable) *BoolQuery { q.should = append(q.should, should...) return q } +// MinimumShouldMatch sets the number or percentage of should clauses returned +// documents must match. func (q *BoolQuery) MinimumShouldMatch(val int16) *BoolQuery { q.minimumShouldMatch = val return q } +// Boost sets the boost value for the query. func (q *BoolQuery) Boost(val float32) *BoolQuery { q.boost = val return q } +// Map returns a map representation of the bool query, thus implementing +// the Mappable interface. func (q *BoolQuery) Map() map[string]interface{} { var data struct { Must []map[string]interface{} `structs:"must,omitempty"` diff --git a/query_boosting.go b/query_boosting.go index 9342164..24d2c9d 100644 --- a/query_boosting.go +++ b/query_boosting.go @@ -1,35 +1,41 @@ package esquery -/******************************************************************************* - * Boosting Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html - ******************************************************************************/ - +// BoostingQuery represents a compound query of type "boosting", as described in +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html type BoostingQuery struct { - Pos Mappable - Neg Mappable + // Pos is the positive part of the query. + Pos Mappable + // Neg is the negative part of the query. + Neg Mappable + // NegBoost is the negative boost value. NegBoost float32 } +// Boosting creates a new compound query of type "boosting". func Boosting() *BoostingQuery { return &BoostingQuery{} } +// Positive sets the positive part of the boosting query. func (q *BoostingQuery) Positive(p Mappable) *BoostingQuery { q.Pos = p return q } +// Negative sets the negative part of the boosting query. func (q *BoostingQuery) Negative(p Mappable) *BoostingQuery { q.Neg = p return q } +// NegativeBoost sets the negative boost value. func (q *BoostingQuery) NegativeBoost(b float32) *BoostingQuery { q.NegBoost = b return q } +// Map returns a map representation of the boosting query, thus implementing +// the Mappable interface. func (q *BoostingQuery) Map() map[string]interface{} { return map[string]interface{}{ "boosting": map[string]interface{}{ diff --git a/query_constant_score.go b/query_constant_score.go index cf65090..c93b74e 100644 --- a/query_constant_score.go +++ b/query_constant_score.go @@ -2,27 +2,30 @@ package esquery import "github.com/fatih/structs" -/******************************************************************************* - * Constant Score Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-constant-score-query.html - ******************************************************************************/ - +// ConstantScoreQuery represents a compound query of type "constant_score", as +// described in +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-constant-score-query.html type ConstantScoreQuery struct { filter Mappable boost float32 } +// ConstantScore creates a new query of type "contant_score" with the provided +// filter query. func ConstantScore(filter Mappable) *ConstantScoreQuery { return &ConstantScoreQuery{ filter: filter, } } +// Boost sets the boost value of the query. func (q *ConstantScoreQuery) Boost(b float32) *ConstantScoreQuery { q.boost = b return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *ConstantScoreQuery) Map() map[string]interface{} { return map[string]interface{}{ "constant_score": structs.Map(struct { diff --git a/query_custom.go b/query_custom.go deleted file mode 100644 index 8c69dae..0000000 --- a/query_custom.go +++ /dev/null @@ -1,13 +0,0 @@ -package esquery - -type CustomQry struct { - m map[string]interface{} -} - -func CustomQuery(m map[string]interface{}) *CustomQry { - return &CustomQry{m} -} - -func (q *CustomQry) Map() map[string]interface{} { - return q.m -} diff --git a/query_custom_test.go b/query_custom_test.go deleted file mode 100644 index 34ecfae..0000000 --- a/query_custom_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package esquery - -import "testing" - -func TestCustomQuery(t *testing.T) { - m := map[string]interface{}{ - "geo_distance": map[string]interface{}{ - "distance": "200km", - "pin.location": map[string]interface{}{ - "lat": 40, - "lon": -70, - }, - }, - } - - runMapTests(t, []mapTest{ - { - "custom query", - CustomQuery(m), - m, - }, - }) -} diff --git a/query_dis_max.go b/query_dis_max.go index 2889e3b..e0fda44 100644 --- a/query_dis_max.go +++ b/query_dis_max.go @@ -2,27 +2,29 @@ package esquery import "github.com/fatih/structs" -/******************************************************************************* - * Disjunction Max Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html - ******************************************************************************/ - +// DisMaxQuery represents a compound query of type "dis_max", as described in +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html type DisMaxQuery struct { queries []Mappable tieBreaker float32 } +// DisMax creates a new compound query of type "dis_max" with the provided +// queries. func DisMax(queries ...Mappable) *DisMaxQuery { return &DisMaxQuery{ queries: queries, } } +// TieBreaker sets the "tie_breaker" value for the query. func (q *DisMaxQuery) TieBreaker(b float32) *DisMaxQuery { q.tieBreaker = b return q } +// Map returns a map representation of the dis_max query, thus implementing +// the Mappable interface. func (q *DisMaxQuery) Map() map[string]interface{} { inner := make([]map[string]interface{}, len(q.queries)) for i, iq := range q.queries { diff --git a/query_match.go b/query_match.go index 0341746..f34779b 100644 --- a/query_match.go +++ b/query_match.go @@ -1,37 +1,45 @@ package esquery import ( - "bytes" - "io" - "github.com/fatih/structs" ) -/******************************************************************************* - * Match Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-bool-prefix-query.html - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase-prefix.html - ******************************************************************************/ type matchType uint8 const ( + // TypeMatch denotes a query of type "match" TypeMatch matchType = iota + + // TypeMatchBool denotes a query of type "match_bool_prefix" TypeMatchBoolPrefix + + // TypeMatchPhrase denotes a query of type "match_phrase" TypeMatchPhrase + + // TypeMatchPhrasePrefix denotes a query of type "match_phrase_prefix" TypeMatchPhrasePrefix ) +// MatchQuery represents a query of type "match", "match_bool_prefix", +// "match_phrase" and "match_phrase_prefix". While all four share the same +// general structure, they don't necessarily support all the same options. The +// library does not attempt to verify provided options are supported. +// See the ElasticSearch documentation for more information: +// - https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html +// - https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-bool-prefix-query.html +// - https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html +// - https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase-prefix.html type MatchQuery struct { field string mType matchType params matchParams } -func (a *MatchQuery) Map() map[string]interface{} { +// Map returns a map representation of the query, thus implementing the +// Mappable interface. +func (q *MatchQuery) Map() map[string]interface{} { var mType string - switch a.mType { + switch q.mType { case TypeMatch: mType = "match" case TypeMatchBoolPrefix: @@ -44,7 +52,7 @@ func (a *MatchQuery) Map() map[string]interface{} { return map[string]interface{}{ mType: map[string]interface{}{ - a.field: structs.Map(a.params), + q.field: structs.Map(q.params), }, } } @@ -65,18 +73,30 @@ type matchParams struct { Slp uint16 `structs:"slop,omitempty"` // only relevant for match_phrase query } +// Match creates a new query of type "match" with the provided field name. +// A comparison value can optionally be provided to quickly create a simple +// query such as { "match": { "message": "this is a test" } } func Match(fieldName string, simpleQuery ...interface{}) *MatchQuery { return newMatch(TypeMatch, fieldName, simpleQuery...) } +// MatchBoolPrefix creates a new query of type "match_bool_prefix" with the +// provided field name. A comparison value can optionally be provided to quickly +// create a simple query such as { "match": { "message": "this is a test" } } func MatchBoolPrefix(fieldName string, simpleQuery ...interface{}) *MatchQuery { return newMatch(TypeMatchBoolPrefix, fieldName, simpleQuery...) } +// MatchPhrase creates a new query of type "match_phrase" with the +// provided field name. A comparison value can optionally be provided to quickly +// create a simple query such as { "match": { "message": "this is a test" } } func MatchPhrase(fieldName string, simpleQuery ...interface{}) *MatchQuery { return newMatch(TypeMatchPhrase, fieldName, simpleQuery...) } +// MatchPhrasePrefix creates a new query of type "match_phrase_prefix" with the +// provided field name. A comparison value can optionally be provided to quickly +// create a simple query such as { "match": { "message": "this is a test" } } func MatchPhrasePrefix(fieldName string, simpleQuery ...interface{}) *MatchQuery { return newMatch(TypeMatchPhrasePrefix, fieldName, simpleQuery...) } @@ -96,106 +116,136 @@ func newMatch(mType matchType, fieldName string, simpleQuery ...interface{}) *Ma } } +// Query sets the data to find in the query's field (it is the "query" component +// of the query). func (q *MatchQuery) Query(data interface{}) *MatchQuery { q.params.Qry = data return q } +// Analyzer sets the analyzer used to convert the text in the "query" value into +// tokens. func (q *MatchQuery) Analyzer(a string) *MatchQuery { q.params.Anl = a return q } +// AutoGenerateSynonymsPhraseQuery sets the "auto_generate_synonyms_phrase_query" +// boolean. func (q *MatchQuery) AutoGenerateSynonymsPhraseQuery(b bool) *MatchQuery { q.params.AutoGenerate = &b return q } +// Fuzziness set the maximum edit distance allowed for matching. func (q *MatchQuery) Fuzziness(f string) *MatchQuery { q.params.Fuzz = f return q } +// MaxExpansions sets the maximum number of terms to which the query will expand. func (q *MatchQuery) MaxExpansions(e uint16) *MatchQuery { q.params.MaxExp = e return q } +// PrefixLength sets the number of beginning characters left unchanged for fuzzy +// matching. func (q *MatchQuery) PrefixLength(l uint16) *MatchQuery { q.params.PrefLen = l return q } +// Transpositions sets whether edits for fuzzy matching include transpositions +// of two adjacent characters. func (q *MatchQuery) Transpositions(b bool) *MatchQuery { q.params.Trans = &b return q } +// FuzzyRewrite sets the method used to rewrite the query. func (q *MatchQuery) FuzzyRewrite(s string) *MatchQuery { q.params.FuzzyRw = s return q } +// Lenient sets whether format-based errors should be ignored. func (q *MatchQuery) Lenient(b bool) *MatchQuery { q.params.Lent = b return q } +// Operator sets the boolean logic used to interpret text in the query value. func (q *MatchQuery) Operator(op MatchOperator) *MatchQuery { q.params.Op = op return q } +// MinimumShouldMatch sets the minimum number of clauses that must match for a +// document to be returned. func (q *MatchQuery) MinimumShouldMatch(s string) *MatchQuery { q.params.MinMatch = s return q } +// Slop sets the maximum number of positions allowed between matching tokens. func (q *MatchQuery) Slop(n uint16) *MatchQuery { q.params.Slp = n return q } +// ZeroTermsQuery sets the "zero_terms_query" option to use. This indicates +// whether no documents are returned if the analyzer removes all tokens, such as +// when using a stop filter. func (q *MatchQuery) ZeroTermsQuery(s ZeroTerms) *MatchQuery { q.params.ZeroTerms = s return q } -func (q *MatchQuery) Reader() io.Reader { - var b bytes.Buffer - return &b -} - +// MatchOperator is an enumeration type representing supported values for a +// match query's "operator" parameter. type MatchOperator uint8 const ( - OR MatchOperator = iota - AND + // OperatorOr is the "or" operator + OperatorOr MatchOperator = iota + + // OperatorAnd is the "and" operator + OperatorAnd ) +// String returns a string representation of the match operator, as known to +// ElasticSearch. func (a MatchOperator) String() string { switch a { - case OR: - return "or" - case AND: - return "and" + case OperatorOr: + return "OR" + case OperatorAnd: + return "AND" default: return "" } } +// ZeroTerms is an enumeration type representing supported values for a match +// query's "zero_terms_query" parameter. type ZeroTerms uint8 const ( - None ZeroTerms = iota - All + // ZeroTermsNone is the "none" value + ZeroTermsNone ZeroTerms = iota + + // ZeroTermsAll is the "all" value + ZeroTermsAll ) +// String returns a string representation of the zero_terms_query parameter, as +// known to ElasticSearch. func (a ZeroTerms) String() string { switch a { - case None: + case ZeroTermsNone: return "none" - case All: + case ZeroTermsAll: return "all" default: return "" diff --git a/query_match_all.go b/query_match_all.go index 0d7f25a..e2c1161 100644 --- a/query_match_all.go +++ b/query_match_all.go @@ -2,11 +2,9 @@ package esquery import "github.com/fatih/structs" -/******************************************************************************* - * Match All Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-all-query.html - ******************************************************************************/ - +// MatchAllQuery represents a query of type "match_all" or "match_none", as +// described in +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-all-query.html type MatchAllQuery struct { all bool params matchAllParams @@ -16,9 +14,11 @@ type matchAllParams struct { Boost float32 `structs:"boost,omitempty"` } -func (a *MatchAllQuery) Map() map[string]interface{} { +// Map returns a map representation of the query, thus implementing the +// Mappable interface. +func (q *MatchAllQuery) Map() map[string]interface{} { var mType string - switch a.all { + switch q.all { case true: mType = "match_all" default: @@ -26,14 +26,16 @@ func (a *MatchAllQuery) Map() map[string]interface{} { } return map[string]interface{}{ - mType: structs.Map(a.params), + mType: structs.Map(q.params), } } +// MatchAll creates a new query of type "match_all". func MatchAll() *MatchAllQuery { return &MatchAllQuery{all: true} } +// Boost assigns a score boost for documents matching the query. func (q *MatchAllQuery) Boost(b float32) *MatchAllQuery { if q.all { q.params.Boost = b @@ -41,6 +43,7 @@ func (q *MatchAllQuery) Boost(b float32) *MatchAllQuery { return q } +// MatchNone creates a new query of type "match_none". func MatchNone() *MatchAllQuery { return &MatchAllQuery{all: false} } diff --git a/query_match_test.go b/query_match_test.go index 3a3d290..238c68d 100644 --- a/query_match_test.go +++ b/query_match_test.go @@ -19,14 +19,14 @@ func TestMatch(t *testing.T) { }, { "match with more params", - Match("issue_number").Query(16).Transpositions(false).MaxExpansions(32).Operator(AND), + Match("issue_number").Query(16).Transpositions(false).MaxExpansions(32).Operator(OperatorAnd), map[string]interface{}{ "match": map[string]interface{}{ "issue_number": map[string]interface{}{ "query": 16, "max_expansions": 32, "transpositions": false, - "operator": "and", + "operator": "AND", }, }, }, diff --git a/query_term_level.go b/query_term_level.go index 82e3adc..2604d3e 100644 --- a/query_term_level.go +++ b/query_term_level.go @@ -4,61 +4,70 @@ import ( "github.com/fatih/structs" ) -/******************************************************************************* - * Exists Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html - ******************************************************************************/ - +// ExistsQuery represents a query of type "exists", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html type ExistsQuery struct { + // Field is the name of the field to check for existence Field string `structs:"field"` } +// Exists creates a new query of type "exists" on the provided field. func Exists(field string) *ExistsQuery { return &ExistsQuery{field} } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *ExistsQuery) Map() map[string]interface{} { return map[string]interface{}{ "exists": structs.Map(q), } } -/******************************************************************************* - * IDs Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// IDsQuery represents a query of type "ids", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html type IDsQuery struct { + // IDs is the "ids" component of the query IDs struct { + // Values is the list of ID values Values []string `structs:"values"` } `structs:"ids"` } +// IDs creates a new query of type "ids" with the provided values. func IDs(vals ...string) *IDsQuery { q := &IDsQuery{} q.IDs.Values = vals return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *IDsQuery) Map() map[string]interface{} { return structs.Map(q) } -/******************************************************************************* - * Prefix Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// PrefixQuery represents query of type "prefix", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html type PrefixQuery struct { field string params prefixQueryParams } type prefixQueryParams struct { - Value string `structs:"value"` + // Value is the prefix value to look for + Value string `structs:"value"` + + // Rewrite is the method used to rewrite the query Rewrite string `structs:"rewrite,omitempty"` } +// Prefix creates a new query of type "prefix", on the provided field and using +// the provided prefix value. func Prefix(field, value string) *PrefixQuery { return &PrefixQuery{ field: field, @@ -66,11 +75,14 @@ func Prefix(field, value string) *PrefixQuery { } } +// Rewrite sets the rewrite method for the query func (q *PrefixQuery) Rewrite(s string) *PrefixQuery { q.params.Rewrite = s return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *PrefixQuery) Map() map[string]interface{} { return map[string]interface{}{ "prefix": map[string]interface{}{ @@ -79,11 +91,10 @@ func (q *PrefixQuery) Map() map[string]interface{} { } } -/******************************************************************************* - * Range Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// RangeQuery represents a query of type "range", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html type RangeQuery struct { field string params rangeQueryParams @@ -100,50 +111,63 @@ type rangeQueryParams struct { Boost float32 `structs:"boost,omitempty"` } +// Range creates a new query of type "range" on the provided field func Range(field string) *RangeQuery { return &RangeQuery{field: field} } +// Gt sets that the value of field must be greater than the provided value func (a *RangeQuery) Gt(val interface{}) *RangeQuery { a.params.Gt = val return a } +// Gt sets that the value of field must be greater than or equal to the provided +// value func (a *RangeQuery) Gte(val interface{}) *RangeQuery { a.params.Gte = val return a } +// Lt sets that the value of field must be lower than the provided value func (a *RangeQuery) Lt(val interface{}) *RangeQuery { a.params.Lt = val return a } +// Lte sets that the value of field must be lower than or equal to the provided +// value func (a *RangeQuery) Lte(val interface{}) *RangeQuery { a.params.Lte = val return a } +// Format sets the date format for date values func (a *RangeQuery) Format(f string) *RangeQuery { a.params.Format = f return a } +// Relation sets how the query matches values for range fields func (a *RangeQuery) Relation(r RangeRelation) *RangeQuery { a.params.Relation = r return a } +// TimeZone sets the time zone used for date values. func (a *RangeQuery) TimeZone(zone string) *RangeQuery { a.params.TimeZone = zone return a } +// Boost sets the boost value of the query. func (a *RangeQuery) Boost(b float32) *RangeQuery { a.params.Boost = b return a } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (a *RangeQuery) Map() map[string]interface{} { return map[string]interface{}{ "range": map[string]interface{}{ @@ -152,32 +176,41 @@ func (a *RangeQuery) Map() map[string]interface{} { } } +// RangeRelation is an enumeration type for a range query's "relation" field type RangeRelation uint8 const ( - INTERSECTS RangeRelation = iota - CONTAINS - WITHIN + _ RangeRelation = iota + + // RangeIntersects is the "INTERSECTS" relation + RangeIntersects + + // RangeContains is the "CONTAINS" relation + RangeContains + + // RangeWithin is the "WITHIN" relation + RangeWithin ) +// String returns a string representation of the RangeRelation value, as +// accepted by ElasticSearch func (a RangeRelation) String() string { switch a { - case INTERSECTS: + case RangeIntersects: return "INTERSECTS" - case CONTAINS: + case RangeContains: return "CONTAINS" - case WITHIN: + case RangeWithin: return "WITHIN" default: return "" } } -/******************************************************************************* - * Regexp Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// RegexpQuery represents a query of type "regexp", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html type RegexpQuery struct { field string wildcard bool @@ -191,6 +224,8 @@ type regexpQueryParams struct { Rewrite string `structs:"rewrite,omitempty"` } +// Regexp creates a new query of type "regexp" on the provided field and using +// the provided regular expression. func Regexp(field, value string) *RegexpQuery { return &RegexpQuery{ field: field, @@ -200,11 +235,13 @@ func Regexp(field, value string) *RegexpQuery { } } +// Value changes the regular expression value of the query. func (q *RegexpQuery) Value(v string) *RegexpQuery { q.params.Value = v return q } +// Flags sets the regular expression's optional flags. func (q *RegexpQuery) Flags(f string) *RegexpQuery { if !q.wildcard { q.params.Flags = f @@ -212,6 +249,8 @@ func (q *RegexpQuery) Flags(f string) *RegexpQuery { return q } +// MaxDeterminizedStates sets the maximum number of automaton states required +// for the query. func (q *RegexpQuery) MaxDeterminizedStates(m uint16) *RegexpQuery { if !q.wildcard { q.params.MaxDeterminizedStates = m @@ -219,11 +258,14 @@ func (q *RegexpQuery) MaxDeterminizedStates(m uint16) *RegexpQuery { return q } +// Rewrite sets the method used to rewrite the query. func (q *RegexpQuery) Rewrite(r string) *RegexpQuery { q.params.Rewrite = r return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *RegexpQuery) Map() map[string]interface{} { var qType string if q.wildcard { @@ -238,11 +280,13 @@ func (q *RegexpQuery) Map() map[string]interface{} { } } -/******************************************************************************* - * Wildcard Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// Wildcard creates a new query of type "wildcard" on the provided field and +// using the provided regular expression value. Internally, wildcard queries +// are simply specialized RegexpQuery values. +// Wildcard queries are described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html func Wildcard(field, value string) *RegexpQuery { return &RegexpQuery{ field: field, @@ -253,11 +297,10 @@ func Wildcard(field, value string) *RegexpQuery { } } -/******************************************************************************* - * Fuzzy Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// FuzzyQuery represents a query of type "fuzzy", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html type FuzzyQuery struct { field string params fuzzyQueryParams @@ -272,6 +315,8 @@ type fuzzyQueryParams struct { Rewrite string `structs:"rewrite,omitempty"` } +// Fuzzy creates a new query of type "fuzzy" on the provided field and using +// the provided value func Fuzzy(field, value string) *FuzzyQuery { return &FuzzyQuery{ field: field, @@ -281,36 +326,46 @@ func Fuzzy(field, value string) *FuzzyQuery { } } +// Value sets the value of the query. func (q *FuzzyQuery) Value(val string) *FuzzyQuery { q.params.Value = val return q } +// Fuzziness sets the maximum edit distance allowed for matching. func (q *FuzzyQuery) Fuzziness(fuzz string) *FuzzyQuery { q.params.Fuzziness = fuzz return q } +// MaxExpansions sets the maximum number of variations created. func (q *FuzzyQuery) MaxExpansions(m uint16) *FuzzyQuery { q.params.MaxExpansions = m return q } +// PrefixLength sets the number of beginning characters left unchanged when +// creating expansions func (q *FuzzyQuery) PrefixLength(l uint16) *FuzzyQuery { q.params.PrefixLength = l return q } +// Transpositions sets whether edits include transpositions of two adjacent +// characters. func (q *FuzzyQuery) Transpositions(b bool) *FuzzyQuery { q.params.Transpositions = &b return q } +// Rewrite sets the method used to rewrite the query. func (q *FuzzyQuery) Rewrite(s string) *FuzzyQuery { q.params.Rewrite = s return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *FuzzyQuery) Map() map[string]interface{} { return map[string]interface{}{ "fuzzy": map[string]interface{}{ @@ -319,11 +374,10 @@ func (q *FuzzyQuery) Map() map[string]interface{} { } } -/******************************************************************************* - * Term Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// TermQuery represents a query of type "term", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html type TermQuery struct { field string params termQueryParams @@ -334,6 +388,8 @@ type termQueryParams struct { Boost float32 `structs:"boost,omitempty"` } +// Term creates a new query of type "term" on the provided field and using the +// provide value func Term(field string, value interface{}) *TermQuery { return &TermQuery{ field: field, @@ -343,16 +399,20 @@ func Term(field string, value interface{}) *TermQuery { } } +// Value sets the term value for the query. func (q *TermQuery) Value(val interface{}) *TermQuery { q.params.Value = val return q } +// Boost sets the boost value of the query. func (q *TermQuery) Boost(b float32) *TermQuery { q.params.Boost = b return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q *TermQuery) Map() map[string]interface{} { return map[string]interface{}{ "term": map[string]interface{}{ @@ -361,17 +421,18 @@ func (q *TermQuery) Map() map[string]interface{} { } } -/******************************************************************************* - * Terms Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// TermsQuery represents a query of type "terms", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html type TermsQuery struct { field string values []interface{} boost float32 } +// Terms creates a new query of type "terms" on the provided field, and +// optionally with the provided term values. func Terms(field string, values ...interface{}) *TermsQuery { return &TermsQuery{ field: field, @@ -379,16 +440,20 @@ func Terms(field string, values ...interface{}) *TermsQuery { } } +// Values sets the term values for the query. func (q *TermsQuery) Values(values ...interface{}) *TermsQuery { q.values = values return q } +// Boost sets the boost value of the query. func (q *TermsQuery) Boost(b float32) *TermsQuery { q.boost = b return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q TermsQuery) Map() map[string]interface{} { innerMap := map[string]interface{}{q.field: q.values} if q.boost > 0 { @@ -398,11 +463,10 @@ func (q TermsQuery) Map() map[string]interface{} { return map[string]interface{}{"terms": innerMap} } -/******************************************************************************* - * Term Set Queries - * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-set-query.html - ******************************************************************************/ +//----------------------------------------------------------------------------// +// TermsSetQuery represents a query of type "terms_set", as described in: +// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-set-query.html type TermsSetQuery struct { field string params termsSetQueryParams @@ -414,6 +478,8 @@ type termsSetQueryParams struct { MinimumShouldMatchScript string `structs:"minimum_should_match_script,omitempty"` } +// TermsSet creates a new query of type "terms_set" on the provided field and +// optionally using the provided terms. func TermsSet(field string, terms ...string) *TermsSetQuery { return &TermsSetQuery{ field: field, @@ -423,21 +489,28 @@ func TermsSet(field string, terms ...string) *TermsSetQuery { } } +// Terms sets the terms for the query. func (q *TermsSetQuery) Terms(terms ...string) *TermsSetQuery { q.params.Terms = terms return q } +// MinimumShouldMatchField sets the name of the field containing the number of +// matching terms required to return a document. func (q *TermsSetQuery) MinimumShouldMatchField(field string) *TermsSetQuery { q.params.MinimumShouldMatchField = field return q } +// MinimumShouldMatchScript sets the custom script containing the number of +// matching terms required to return a document. func (q *TermsSetQuery) MinimumShouldMatchScript(script string) *TermsSetQuery { q.params.MinimumShouldMatchScript = script return q } +// Map returns a map representation of the query, thus implementing the +// Mappable interface. func (q TermsSetQuery) Map() map[string]interface{} { return map[string]interface{}{ "terms_set": map[string]interface{}{ diff --git a/query_term_level_test.go b/query_term_level_test.go index 8878a68..eff83ad 100644 --- a/query_term_level_test.go +++ b/query_term_level_test.go @@ -62,13 +62,13 @@ func TestTermLevel(t *testing.T) { }, { "string range", - Range("timestamp").Gte("now-1d/d").Lt("now/d").Relation(CONTAINS), + Range("timestamp").Gte("now-1d/d").Lt("now/d").Relation(RangeIntersects), map[string]interface{}{ "range": map[string]interface{}{ "timestamp": map[string]interface{}{ "gte": "now-1d/d", "lt": "now/d", - "relation": "CONTAINS", + "relation": "INTERSECTS", }, }, },