Add documentation, fix small bugs, improve CustomQuery (#6)

This commit performs the following modifications:

- The library is properly documented in Godoc format.

- The CustomQuery function is made to be a bit more versatile by
  allowing it to also be used standalone (i.e. instead of passing a
  `CustomQuery` as a parameter to the `Query` function, they now have
  their own `Run` method).

- Queries and aggregations can now also be executed using the
  `RunSearch` method. This method is the same as the `Run` method,
  except that instead of an `*elasticSearch.Client` value, it accepts an
  `esapi.Search` value. This is provided for consuming code that needs
  to implement mock clients of ElasticSearch (e.g. for test purposes).
  The ElasticSearch client does not provide an interface type describing
  its API, so its Search function (which is actually a field of a
  function type) can be used instead.

- Bugfix: the CustomAgg function was unusable as it did not accept a
  name parameter and thus did not implement the Aggregation interface.

- Bugfix: the enumeration types are rewritten according to Go standards,
  and the `RangeRelation` type's default value is now empty.

- The golint and godox linters are added.
This commit is contained in:
Ido Perlmuter 2020-02-27 14:19:07 +00:00 committed by GitHub
parent 7d175c8f0d
commit 73b274a13b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 657 additions and 224 deletions

View File

@ -27,3 +27,5 @@ linters:
- prealloc
- unconvert
- unparam
- golint
- godox

View File

@ -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...)
}

View File

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

View File

@ -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),

63
custom.go Normal file
View File

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

View File

@ -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,
},
})

102
es.go
View File

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

View File

@ -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...)
}

View File

@ -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),

View File

@ -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"`

View File

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

View File

@ -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 {

View File

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

View File

@ -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,
},
})
}

View File

@ -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 {

View File

@ -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 ""

View File

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

View File

@ -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",
},
},
},

View File

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

View File

@ -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",
},
},
},