Initial commit
This commit is the initial commit for a Go library providing an idiomatic, easy-to-use query builder for ElasticSearch. The library can build queries and execute them using the structures from the official Go SDK provided by the ES project (https://github.com/elastic/go-elasticsearch). The library currently provides the capabilities to create and execute simple ElasticSearch queries, specifically Match queries (match, match_bool_prefix, match_phrase and match_phrase_prefix), Match All queries (match_all, match_none), and all of the Term-level queries (e.g. range, regexp, etc.). Unit tests are included for each support query, and the code is linted using golangci-lint (see enabled linters in .golangci-lint). The unit tests currently only verify the builder creates valid JSON queries and does not attempt to actually run queries against a (mock) ES instance.
This commit is contained in:
parent
b6dbef6245
commit
9ef149ec94
|
@ -0,0 +1,27 @@
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- dupl
|
||||||
|
- funlen
|
||||||
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- gocritic
|
||||||
|
- goimports
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- lll
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- prealloc
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
|
@ -0,0 +1,41 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/elastic/go-elasticsearch/esapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ESQuery struct {
|
||||||
|
Query json.Marshaler `json:"query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(q json.Marshaler, b *bytes.Buffer) (err error) {
|
||||||
|
b.Reset()
|
||||||
|
err = json.NewEncoder(b).Encode(q)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed encoding query to JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func search(q json.Marshaler, api *esapi.API, o ...func(*esapi.SearchRequest)) (res *esapi.Response, err error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
err = encode(ESQuery{q}, &b)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := append([]func(*esapi.SearchRequest){api.Search.WithBody(&b)}, o...)
|
||||||
|
|
||||||
|
return api.Search(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q ESQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]json.Marshaler{
|
||||||
|
"query": q.Query,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type queryTest struct {
|
||||||
|
name string
|
||||||
|
q json.Marshaler
|
||||||
|
expJSON string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTests(t *testing.T, tests []queryTest) {
|
||||||
|
for _, test := range tests {
|
||||||
|
var b bytes.Buffer
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
err := encode(test.q, &b)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpectedly failed: %s", err)
|
||||||
|
} else if b.String() != test.expJSON {
|
||||||
|
t.Errorf("expected %q, got %q", test.expJSON, b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module bitbucket.org/scalock/esquery
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require github.com/elastic/go-elasticsearch v0.0.0
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA=
|
||||||
|
github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg=
|
|
@ -0,0 +1,261 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/elastic/go-elasticsearch/esapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* 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 matchType = iota
|
||||||
|
TypeMatchBoolPrefix
|
||||||
|
TypeMatchPhrase
|
||||||
|
TypeMatchPhrasePrefix
|
||||||
|
)
|
||||||
|
|
||||||
|
type MatchQuery struct {
|
||||||
|
field string
|
||||||
|
mType matchType
|
||||||
|
params matchParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MatchQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
var mType string
|
||||||
|
switch a.mType {
|
||||||
|
case TypeMatch:
|
||||||
|
mType = "match"
|
||||||
|
case TypeMatchBoolPrefix:
|
||||||
|
mType = "match_bool_prefix"
|
||||||
|
case TypeMatchPhrase:
|
||||||
|
mType = "match_phrase"
|
||||||
|
case TypeMatchPhrasePrefix:
|
||||||
|
mType = "match_phrase_prefix"
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
mType: map[string]interface{}{
|
||||||
|
a.field: a.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MatchQuery) Run(api *esapi.API, o ...func(*esapi.SearchRequest)) (res *esapi.Response, err error) {
|
||||||
|
return search(*a, api, o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchParams struct {
|
||||||
|
Qry interface{} `json:"query"`
|
||||||
|
Anl string `json:"analyzer,omitempty"`
|
||||||
|
AutoGenerate *bool `json:"auto_generate_synonyms_phrase_query,omitempty"`
|
||||||
|
Fuzz string `json:"fuzziness,omitempty"`
|
||||||
|
MaxExp uint16 `json:"max_expansions,omitempty"`
|
||||||
|
PrefLen uint16 `json:"prefix_length,omitempty"`
|
||||||
|
Trans *bool `json:"transpositions,omitempty"`
|
||||||
|
FuzzyRw string `json:"fuzzy_rewrite,omitempty"`
|
||||||
|
Lent bool `json:"lenient,omitempty"`
|
||||||
|
Op MatchOperator `json:"operator,omitempty"`
|
||||||
|
MinMatch string `json:"minimum_should_match,omitempty"`
|
||||||
|
ZeroTerms string `json:"zero_terms_query,omitempty"`
|
||||||
|
Slp uint16 `json:"slop,omitempty"` // only relevant for match_phrase query
|
||||||
|
}
|
||||||
|
|
||||||
|
func Match(fieldName string, simpleQuery ...interface{}) *MatchQuery {
|
||||||
|
return newMatch(TypeMatch, fieldName, simpleQuery...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchBoolPrefix(fieldName string, simpleQuery ...interface{}) *MatchQuery {
|
||||||
|
return newMatch(TypeMatchBoolPrefix, fieldName, simpleQuery...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchPhrase(fieldName string, simpleQuery ...interface{}) *MatchQuery {
|
||||||
|
return newMatch(TypeMatchPhrase, fieldName, simpleQuery...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchPhrasePrefix(fieldName string, simpleQuery ...interface{}) *MatchQuery {
|
||||||
|
return newMatch(TypeMatchPhrasePrefix, fieldName, simpleQuery...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMatch(mType matchType, fieldName string, simpleQuery ...interface{}) *MatchQuery {
|
||||||
|
var qry interface{}
|
||||||
|
if simpleQuery != nil && len(simpleQuery) > 0 {
|
||||||
|
qry = simpleQuery[len(simpleQuery)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MatchQuery{
|
||||||
|
field: fieldName,
|
||||||
|
mType: mType,
|
||||||
|
params: matchParams{
|
||||||
|
Qry: qry,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Query(data interface{}) *MatchQuery {
|
||||||
|
q.params.Qry = data
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Analyzer(a string) *MatchQuery {
|
||||||
|
q.params.Anl = a
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) AutoGenerateSynonymsPhraseQuery(b bool) *MatchQuery {
|
||||||
|
q.params.AutoGenerate = &b
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Fuzziness(f string) *MatchQuery {
|
||||||
|
q.params.Fuzz = f
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) MaxExpansions(e uint16) *MatchQuery {
|
||||||
|
q.params.MaxExp = e
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) PrefixLength(l uint16) *MatchQuery {
|
||||||
|
q.params.PrefLen = l
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Transpositions(b bool) *MatchQuery {
|
||||||
|
q.params.Trans = &b
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) FuzzyRewrite(s string) *MatchQuery {
|
||||||
|
q.params.FuzzyRw = s
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Lenient(b bool) *MatchQuery {
|
||||||
|
q.params.Lent = b
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Operator(op MatchOperator) *MatchQuery {
|
||||||
|
q.params.Op = op
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) MinimumShouldMatch(s string) *MatchQuery {
|
||||||
|
q.params.MinMatch = s
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Slop(n uint16) *MatchQuery {
|
||||||
|
q.params.Slp = n
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) ZeroTermsQuery(s string) *MatchQuery {
|
||||||
|
q.params.ZeroTerms = s
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchQuery) Reader() io.Reader {
|
||||||
|
var b bytes.Buffer
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatchOperator uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
OR MatchOperator = iota
|
||||||
|
AND
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrInvalidValue = errors.New("invalid constant value")
|
||||||
|
|
||||||
|
func (a MatchOperator) MarshalJSON() ([]byte, error) {
|
||||||
|
var s string
|
||||||
|
switch a {
|
||||||
|
case OR:
|
||||||
|
s = "or"
|
||||||
|
case AND:
|
||||||
|
s = "and"
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZeroTerms uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
None ZeroTerms = iota
|
||||||
|
All
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a ZeroTerms) MarshalJSON() ([]byte, error) {
|
||||||
|
var s string
|
||||||
|
switch a {
|
||||||
|
case None:
|
||||||
|
s = "none"
|
||||||
|
case All:
|
||||||
|
s = "all"
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Multi-Match Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
|
||||||
|
* NOTE: uncommented for now, article is too long
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
//type MultiMatchQuery struct {
|
||||||
|
//fields []string
|
||||||
|
//mType multiMatchType
|
||||||
|
//params multiMatchQueryParams
|
||||||
|
//}
|
||||||
|
|
||||||
|
//type multiMatchType uint8
|
||||||
|
|
||||||
|
//const (
|
||||||
|
//BestFields multiMatchType = iota
|
||||||
|
//MostFields
|
||||||
|
//CrossFields
|
||||||
|
//Phrase
|
||||||
|
//PhrasePrefix
|
||||||
|
//BoolPrefix
|
||||||
|
//)
|
||||||
|
|
||||||
|
//func (a multiMatchType) MarshalJSON() ([]byte, error) {
|
||||||
|
//var s string
|
||||||
|
//switch a {
|
||||||
|
//case BestFields:
|
||||||
|
//s = "best_fields"
|
||||||
|
//case MostFields:
|
||||||
|
//s = "most_fields"
|
||||||
|
//case CrossFields:
|
||||||
|
//s = "cross_fields"
|
||||||
|
//case Phrase:
|
||||||
|
//s = "phrase"
|
||||||
|
//case PhrasePrefix:
|
||||||
|
//s = "phrase_prefix"
|
||||||
|
//case BoolPrefix:
|
||||||
|
//s = "bool_prefix"
|
||||||
|
//default:
|
||||||
|
//return nil, ErrInvalidValue
|
||||||
|
//}
|
||||||
|
//return json.Marshal(s)
|
||||||
|
//}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/elastic/go-elasticsearch/esapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-all-query.html
|
||||||
|
type MatchAllQuery struct {
|
||||||
|
all bool
|
||||||
|
params matchAllParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchAllParams struct {
|
||||||
|
Boost float32 `json:"boost,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a MatchAllQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
var mType string
|
||||||
|
switch a.all {
|
||||||
|
case true:
|
||||||
|
mType = "match_all"
|
||||||
|
default:
|
||||||
|
mType = "match_none"
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(map[string]matchAllParams{mType: a.params})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MatchAllQuery) Run(api *esapi.API, o ...func(*esapi.SearchRequest)) (res *esapi.Response, err error) {
|
||||||
|
return search(*a, api, o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchAll() *MatchAllQuery {
|
||||||
|
return &MatchAllQuery{all: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *MatchAllQuery) Boost(b float32) *MatchAllQuery {
|
||||||
|
if q.all {
|
||||||
|
q.params.Boost = b
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchNone() *MatchAllQuery {
|
||||||
|
return &MatchAllQuery{all: false}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchAll(t *testing.T) {
|
||||||
|
runTests(t, []queryTest{
|
||||||
|
{"match_all without a boost", MatchAll(), "{\"match_all\":{}}\n"},
|
||||||
|
{"match_all with a boost", MatchAll().Boost(2.3), "{\"match_all\":{\"boost\":2.3}}\n"},
|
||||||
|
{"match_none", MatchNone(), "{\"match_none\":{}}\n"},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatch(t *testing.T) {
|
||||||
|
runTests(t, []queryTest{
|
||||||
|
{"simple match", Match("title", "sample text"), "{\"match\":{\"title\":{\"query\":\"sample text\"}}}\n"},
|
||||||
|
{"match with more params", Match("issue_number").Query(16).Transpositions(false).MaxExpansions(32).Operator(AND), "{\"match\":{\"issue_number\":{\"query\":16,\"max_expansions\":32,\"transpositions\":false,\"operator\":\"and\"}}}\n"},
|
||||||
|
{"match_bool_prefix", MatchBoolPrefix("title", "sample text"), "{\"match_bool_prefix\":{\"title\":{\"query\":\"sample text\"}}}\n"},
|
||||||
|
{"match_phrase", MatchPhrase("title", "sample text"), "{\"match_phrase\":{\"title\":{\"query\":\"sample text\"}}}\n"},
|
||||||
|
{"match_phrase_prefix", MatchPhrasePrefix("title", "sample text"), "{\"match_phrase_prefix\":{\"title\":{\"query\":\"sample text\"}}}\n"},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,449 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Exists Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type ExistsQuery string
|
||||||
|
|
||||||
|
func Exists(field string) *ExistsQuery {
|
||||||
|
q := ExistsQuery(field)
|
||||||
|
return &q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q ExistsQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"exists": map[string]string{
|
||||||
|
"field": string(q),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* IDs Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type IDsQuery []string
|
||||||
|
|
||||||
|
func IDs(vals ...string) *IDsQuery {
|
||||||
|
q := IDsQuery(vals)
|
||||||
|
return &q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q IDsQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"ids": map[string][]string{
|
||||||
|
"values": []string(q),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Prefix Queries
|
||||||
|
* 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 `json:"value"`
|
||||||
|
Rewrite string `json:"rewrite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Prefix(field, value string) *PrefixQuery {
|
||||||
|
return &PrefixQuery{
|
||||||
|
field: field,
|
||||||
|
params: prefixQueryParams{Value: value},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *PrefixQuery) Rewrite(s string) *PrefixQuery {
|
||||||
|
q.params.Rewrite = s
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q PrefixQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"prefix": map[string]prefixQueryParams{
|
||||||
|
q.field: q.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Range Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type RangeQuery struct {
|
||||||
|
field string
|
||||||
|
params rangeQueryParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type rangeQueryParams struct {
|
||||||
|
Gt interface{} `json:"gt,omitempty"`
|
||||||
|
Gte interface{} `json:"gte,omitempty"`
|
||||||
|
Lt interface{} `json:"lt,omitempty"`
|
||||||
|
Lte interface{} `json:"lte,omitempty"`
|
||||||
|
Format string `json:"format,omitempty"`
|
||||||
|
Relation RangeRelation `json:"relation,omitempty"`
|
||||||
|
TimeZone string `json:"time_zone,omitempty"`
|
||||||
|
Boost float32 `json:"boost,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Range(field string) *RangeQuery {
|
||||||
|
return &RangeQuery{field: field}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Gt(val interface{}) *RangeQuery {
|
||||||
|
a.params.Gt = val
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Gte(val interface{}) *RangeQuery {
|
||||||
|
a.params.Gte = val
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Lt(val interface{}) *RangeQuery {
|
||||||
|
a.params.Lt = val
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Lte(val interface{}) *RangeQuery {
|
||||||
|
a.params.Lte = val
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Format(f string) *RangeQuery {
|
||||||
|
a.params.Format = f
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Relation(r RangeRelation) *RangeQuery {
|
||||||
|
a.params.Relation = r
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) TimeZone(zone string) *RangeQuery {
|
||||||
|
a.params.TimeZone = zone
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RangeQuery) Boost(b float32) *RangeQuery {
|
||||||
|
a.params.Boost = b
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a RangeQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"range": map[string]rangeQueryParams{
|
||||||
|
a.field: a.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type RangeRelation uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
INTERSECTS RangeRelation = iota
|
||||||
|
CONTAINS
|
||||||
|
WITHIN
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a RangeRelation) MarshalJSON() ([]byte, error) {
|
||||||
|
var s string
|
||||||
|
switch a {
|
||||||
|
case INTERSECTS:
|
||||||
|
s = "INTERSECTS"
|
||||||
|
case CONTAINS:
|
||||||
|
s = "CONTAINS"
|
||||||
|
case WITHIN:
|
||||||
|
s = "WITHIN"
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Regexp Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type RegexpQuery struct {
|
||||||
|
field string
|
||||||
|
wildcard bool
|
||||||
|
params regexpQueryParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type regexpQueryParams struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Flags string `json:"flags,omitempty"`
|
||||||
|
MaxDeterminizedStates uint16 `json:"max_determinized_states,omitempty"`
|
||||||
|
Rewrite string `json:"rewrite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Regexp(field, value string) *RegexpQuery {
|
||||||
|
return &RegexpQuery{
|
||||||
|
field: field,
|
||||||
|
params: regexpQueryParams{
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *RegexpQuery) Value(v string) *RegexpQuery {
|
||||||
|
q.params.Value = v
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *RegexpQuery) Flags(f string) *RegexpQuery {
|
||||||
|
if !q.wildcard {
|
||||||
|
q.params.Flags = f
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *RegexpQuery) MaxDeterminizedStates(m uint16) *RegexpQuery {
|
||||||
|
if !q.wildcard {
|
||||||
|
q.params.MaxDeterminizedStates = m
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *RegexpQuery) Rewrite(r string) *RegexpQuery {
|
||||||
|
q.params.Rewrite = r
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q RegexpQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
var qType string
|
||||||
|
if q.wildcard {
|
||||||
|
qType = "wildcard"
|
||||||
|
} else {
|
||||||
|
qType = "regexp"
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
qType: map[string]regexpQueryParams{
|
||||||
|
q.field: q.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Wildcard Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
func Wildcard(field, value string) *RegexpQuery {
|
||||||
|
return &RegexpQuery{
|
||||||
|
field: field,
|
||||||
|
wildcard: true,
|
||||||
|
params: regexpQueryParams{
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Fuzzy Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type FuzzyQuery struct {
|
||||||
|
field string
|
||||||
|
params fuzzyQueryParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type fuzzyQueryParams struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Fuzziness string `json:"fuzziness,omitempty"`
|
||||||
|
MaxExpansions uint16 `json:"max_expansions,omitempty"`
|
||||||
|
PrefixLength uint16 `json:"prefix_length,omitempty"`
|
||||||
|
Transpositions *bool `json:"transpositions,omitempty"`
|
||||||
|
Rewrite string `json:"rewrite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fuzzy(field, value string) *FuzzyQuery {
|
||||||
|
return &FuzzyQuery{
|
||||||
|
field: field,
|
||||||
|
params: fuzzyQueryParams{
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FuzzyQuery) Value(val string) *FuzzyQuery {
|
||||||
|
q.params.Value = val
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FuzzyQuery) Fuzziness(fuzz string) *FuzzyQuery {
|
||||||
|
q.params.Fuzziness = fuzz
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FuzzyQuery) MaxExpansions(m uint16) *FuzzyQuery {
|
||||||
|
q.params.MaxExpansions = m
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FuzzyQuery) PrefixLength(l uint16) *FuzzyQuery {
|
||||||
|
q.params.PrefixLength = l
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FuzzyQuery) Transpositions(b bool) *FuzzyQuery {
|
||||||
|
q.params.Transpositions = &b
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FuzzyQuery) Rewrite(s string) *FuzzyQuery {
|
||||||
|
q.params.Rewrite = s
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q FuzzyQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"fuzzy": map[string]fuzzyQueryParams{
|
||||||
|
q.field: q.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Term Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type TermQuery struct {
|
||||||
|
field string
|
||||||
|
params termQueryParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type termQueryParams struct {
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
Boost float32 `json:"boost,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Term(field string, value interface{}) *TermQuery {
|
||||||
|
return &TermQuery{
|
||||||
|
field: field,
|
||||||
|
params: termQueryParams{
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermQuery) Value(val interface{}) *TermQuery {
|
||||||
|
q.params.Value = val
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermQuery) Boost(b float32) *TermQuery {
|
||||||
|
q.params.Boost = b
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q TermQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"term": map[string]termQueryParams{
|
||||||
|
q.field: q.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Terms Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type TermsQuery struct {
|
||||||
|
field string
|
||||||
|
values []interface{}
|
||||||
|
boost float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func Terms(field string, values ...interface{}) *TermsQuery {
|
||||||
|
return &TermsQuery{
|
||||||
|
field: field,
|
||||||
|
values: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermsQuery) Values(values ...interface{}) *TermsQuery {
|
||||||
|
q.values = values
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermsQuery) Boost(b float32) *TermsQuery {
|
||||||
|
q.boost = b
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q TermsQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
innerMap := map[string]interface{}{q.field: q.values}
|
||||||
|
if q.boost > 0 {
|
||||||
|
innerMap["boost"] = q.boost
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]interface{}{"terms": innerMap})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Term Set Queries
|
||||||
|
* https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-set-query.html
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
type TermsSetQuery struct {
|
||||||
|
field string
|
||||||
|
params termsSetQueryParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type termsSetQueryParams struct {
|
||||||
|
Terms []string `json:"terms"`
|
||||||
|
MinimumShouldMatchField string `json:"minimum_should_match_field,omitempty"`
|
||||||
|
MinimumShouldMatchScript string `json:"minimum_should_match_script,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TermsSet(field string, terms ...string) *TermsSetQuery {
|
||||||
|
return &TermsSetQuery{
|
||||||
|
field: field,
|
||||||
|
params: termsSetQueryParams{
|
||||||
|
Terms: terms,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermsSetQuery) Terms(terms ...string) *TermsSetQuery {
|
||||||
|
q.params.Terms = terms
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermsSetQuery) MinimumShouldMatchField(field string) *TermsSetQuery {
|
||||||
|
q.params.MinimumShouldMatchField = field
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TermsSetQuery) MinimumShouldMatchScript(script string) *TermsSetQuery {
|
||||||
|
q.params.MinimumShouldMatchScript = script
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q TermsSetQuery) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"terms_set": map[string]termsSetQueryParams{
|
||||||
|
q.field: q.params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package esquery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTermLevel(t *testing.T) {
|
||||||
|
runTests(t, []queryTest{
|
||||||
|
{"exists", Exists("title"), "{\"exists\":{\"field\":\"title\"}}\n"},
|
||||||
|
|
||||||
|
{"ids", IDs("1", "4", "100"), "{\"ids\":{\"values\":[\"1\",\"4\",\"100\"]}}\n"},
|
||||||
|
|
||||||
|
{"simple prefix", Prefix("user", "ki"), "{\"prefix\":{\"user\":{\"value\":\"ki\"}}}\n"},
|
||||||
|
|
||||||
|
{"complex prefix", Prefix("user", "ki").Rewrite("ji"), "{\"prefix\":{\"user\":{\"value\":\"ki\",\"rewrite\":\"ji\"}}}\n"},
|
||||||
|
|
||||||
|
{"int range", Range("age").Gte(10).Lte(20).Boost(2.0), "{\"range\":{\"age\":{\"gte\":10,\"lte\":20,\"boost\":2}}}\n"},
|
||||||
|
|
||||||
|
{"string range", Range("timestamp").Gte("now-1d/d").Lt("now/d").Relation(CONTAINS), "{\"range\":{\"timestamp\":{\"gte\":\"now-1d/d\",\"lt\":\"now/d\",\"relation\":\"CONTAINS\"}}}\n"},
|
||||||
|
|
||||||
|
{"regexp", Regexp("user", "k.*y").Flags("ALL").MaxDeterminizedStates(10000).Rewrite("constant_score"), "{\"regexp\":{\"user\":{\"value\":\"k.*y\",\"flags\":\"ALL\",\"max_determinized_states\":10000,\"rewrite\":\"constant_score\"}}}\n"},
|
||||||
|
|
||||||
|
{"wildcard", Wildcard("user", "ki*y").Rewrite("constant_score"), "{\"wildcard\":{\"user\":{\"value\":\"ki*y\",\"rewrite\":\"constant_score\"}}}\n"},
|
||||||
|
|
||||||
|
{"fuzzy", Fuzzy("user", "ki").Fuzziness("AUTO").MaxExpansions(50).Transpositions(true), "{\"fuzzy\":{\"user\":{\"value\":\"ki\",\"fuzziness\":\"AUTO\",\"max_expansions\":50,\"transpositions\":true}}}\n"},
|
||||||
|
|
||||||
|
{"term", Term("user", "Kimchy").Boost(1.3), "{\"term\":{\"user\":{\"value\":\"Kimchy\",\"boost\":1.3}}}\n"},
|
||||||
|
|
||||||
|
{"terms", Terms("user").Values("bla", "pl").Boost(1.3), "{\"terms\":{\"boost\":1.3,\"user\":[\"bla\",\"pl\"]}}\n"},
|
||||||
|
|
||||||
|
{"terms_set", TermsSet("programming_languages", "go", "rust", "COBOL").MinimumShouldMatchField("required_matches"), "{\"terms_set\":{\"programming_languages\":{\"terms\":[\"go\",\"rust\",\"COBOL\"],\"minimum_should_match_field\":\"required_matches\"}}}\n"},
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue