From 6c8e71c18874ea5720996bca6854c3d497768a37 Mon Sep 17 00:00:00 2001 From: Ido Perlmuter Date: Tue, 18 Feb 2020 17:40:57 +0200 Subject: [PATCH] Add support for compound queries This commit adds support for the compound queries "bool", "boosting", "constant_score" and "dis_max". The "function_score" query is not supported yet. Compound queries are simple. They act just like simple queries, except that they are recursive, wrapping other simple/compound queries. For example: esquery.Bool(). Must(Term("user", "kimchy"), Term("author", "kimchy")). Filter(Term("tag", "tech")) --- boolean.go | 61 ++++++++++++++++++++++++++++++++++++++++++ boolean_test.go | 31 +++++++++++++++++++++ boosting.go | 43 +++++++++++++++++++++++++++++ boosting_test.go | 18 +++++++++++++ constant_score.go | 29 ++++++++++++++++++++ constant_score_test.go | 20 ++++++++++++++ dis_max.go | 31 +++++++++++++++++++++ dis_max_test.go | 15 +++++++++++ 8 files changed, 248 insertions(+) create mode 100644 boolean.go create mode 100644 boolean_test.go create mode 100644 boosting.go create mode 100644 boosting_test.go create mode 100644 constant_score.go create mode 100644 constant_score_test.go create mode 100644 dis_max.go create mode 100644 dis_max_test.go diff --git a/boolean.go b/boolean.go new file mode 100644 index 0000000..251cb16 --- /dev/null +++ b/boolean.go @@ -0,0 +1,61 @@ +package esquery + +import "encoding/json" + +/******************************************************************************* + * Boolean Queries + * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html + ******************************************************************************/ + +type BoolQuery struct { + params boolQueryParams +} + +type boolQueryParams struct { + Must []json.Marshaler `json:"must,omitempty"` + Filter []json.Marshaler `json:"filter,omitempty"` + MustNot []json.Marshaler `json:"must_not,omitempty"` + Should []json.Marshaler `json:"should,omitempty"` + MinimumShouldMatch int16 `json:"minimum_should_match,omitempty"` + Boost float32 `json:"boost,omitempty"` +} + +func Bool() *BoolQuery { + return &BoolQuery{} +} + +func (q *BoolQuery) Must(must ...json.Marshaler) *BoolQuery { + q.params.Must = append(q.params.Must, must...) + return q +} + +func (q *BoolQuery) Filter(filter ...json.Marshaler) *BoolQuery { + q.params.Filter = append(q.params.Filter, filter...) + return q +} + +func (q *BoolQuery) MustNot(mustnot ...json.Marshaler) *BoolQuery { + q.params.MustNot = append(q.params.MustNot, mustnot...) + return q +} + +func (q *BoolQuery) Should(should ...json.Marshaler) *BoolQuery { + q.params.Should = append(q.params.Should, should...) + return q +} + +func (q *BoolQuery) MinimumShouldMatch(val int16) *BoolQuery { + q.params.MinimumShouldMatch = val + return q +} + +func (q *BoolQuery) Boost(val float32) *BoolQuery { + q.params.Boost = val + return q +} + +func (q BoolQuery) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]boolQueryParams{ + "bool": q.params, + }) +} diff --git a/boolean_test.go b/boolean_test.go new file mode 100644 index 0000000..9fc5a9c --- /dev/null +++ b/boolean_test.go @@ -0,0 +1,31 @@ +package esquery + +import ( + "testing" +) + +func TestBool(t *testing.T) { + runTests(t, []queryTest{ + { + "bool with only a simple must", + Bool().Must(Term("tag", "tech")), + "{\"bool\":{\"must\":[{\"term\":{\"tag\":{\"value\":\"tech\"}}}]}}\n", + }, + { + "bool which must match_all and filter", + Bool().Must(MatchAll()).Filter(Term("status", "active")), + "{\"bool\":{\"must\":[{\"match_all\":{}}],\"filter\":[{\"term\":{\"status\":{\"value\":\"active\"}}}]}}\n", + }, + { + "bool with a lot of stuff", + Bool(). + Must(Term("user", "kimchy")). + Filter(Term("tag", "tech")). + MustNot(Range("age").Gte(10).Lte(20)). + Should(Term("tag", "wow"), Term("tag", "elasticsearch")). + MinimumShouldMatch(1). + Boost(1.1), + "{\"bool\":{\"must\":[{\"term\":{\"user\":{\"value\":\"kimchy\"}}}],\"filter\":[{\"term\":{\"tag\":{\"value\":\"tech\"}}}],\"must_not\":[{\"range\":{\"age\":{\"gte\":10,\"lte\":20}}}],\"should\":[{\"term\":{\"tag\":{\"value\":\"wow\"}}},{\"term\":{\"tag\":{\"value\":\"elasticsearch\"}}}],\"minimum_should_match\":1,\"boost\":1.1}}\n", + }, + }) +} diff --git a/boosting.go b/boosting.go new file mode 100644 index 0000000..245bf6a --- /dev/null +++ b/boosting.go @@ -0,0 +1,43 @@ +package esquery + +import "encoding/json" + +/******************************************************************************* + * Boosting Queries + * https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html + ******************************************************************************/ + +type BoostingQuery struct { + params boostingQueryParams +} + +type boostingQueryParams struct { + Positive json.Marshaler `json:"positive"` + Negative json.Marshaler `json:"negative"` + NegativeBoost float32 `json:"negative_boost"` +} + +func Boosting() *BoostingQuery { + return &BoostingQuery{} +} + +func (q *BoostingQuery) Positive(p json.Marshaler) *BoostingQuery { + q.params.Positive = p + return q +} + +func (q *BoostingQuery) Negative(p json.Marshaler) *BoostingQuery { + q.params.Negative = p + return q +} + +func (q *BoostingQuery) NegativeBoost(b float32) *BoostingQuery { + q.params.NegativeBoost = b + return q +} + +func (q *BoostingQuery) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]boostingQueryParams{ + "boosting": q.params, + }) +} diff --git a/boosting_test.go b/boosting_test.go new file mode 100644 index 0000000..1fb57a0 --- /dev/null +++ b/boosting_test.go @@ -0,0 +1,18 @@ +package esquery + +import ( + "testing" +) + +func TestBoost(t *testing.T) { + runTests(t, []queryTest{ + { + "boosting query", + Boosting(). + Positive(Term("text", "apple")). + Negative(Term("text", "pie tart")). + NegativeBoost(0.5), + "{\"boosting\":{\"positive\":{\"term\":{\"text\":{\"value\":\"apple\"}}},\"negative\":{\"term\":{\"text\":{\"value\":\"pie tart\"}}},\"negative_boost\":0.5}}\n", + }, + }) +} diff --git a/constant_score.go b/constant_score.go new file mode 100644 index 0000000..6398464 --- /dev/null +++ b/constant_score.go @@ -0,0 +1,29 @@ +package esquery + +import "encoding/json" + +type ConstantScoreQuery struct { + params constantScoreParams +} + +type constantScoreParams struct { + Filter json.Marshaler `json:"filter"` + Boost float32 `json:"boost,omitempty"` +} + +func ConstantScore(filter json.Marshaler) *ConstantScoreQuery { + return &ConstantScoreQuery{ + params: constantScoreParams{Filter: filter}, + } +} + +func (q *ConstantScoreQuery) Boost(b float32) *ConstantScoreQuery { + q.params.Boost = b + return q +} + +func (q ConstantScoreQuery) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]constantScoreParams{ + "constant_score": q.params, + }) +} diff --git a/constant_score_test.go b/constant_score_test.go new file mode 100644 index 0000000..f3c23cc --- /dev/null +++ b/constant_score_test.go @@ -0,0 +1,20 @@ +package esquery + +import ( + "testing" +) + +func TestConstantScore(t *testing.T) { + runTests(t, []queryTest{ + { + "constant_score query without boost", + ConstantScore(Term("user", "kimchy")), + "{\"constant_score\":{\"filter\":{\"term\":{\"user\":{\"value\":\"kimchy\"}}}}}\n", + }, + { + "constant_score query with boost", + ConstantScore(Term("user", "kimchy")).Boost(2.2), + "{\"constant_score\":{\"filter\":{\"term\":{\"user\":{\"value\":\"kimchy\"}}},\"boost\":2.2}}\n", + }, + }) +} diff --git a/dis_max.go b/dis_max.go new file mode 100644 index 0000000..31fda6a --- /dev/null +++ b/dis_max.go @@ -0,0 +1,31 @@ +package esquery + +import "encoding/json" + +type DisMaxQuery struct { + params disMaxParams +} + +type disMaxParams struct { + Queries []json.Marshaler `json:"queries"` + TieBreaker float32 `json:"tie_breaker,omitempty"` +} + +func DisMax(queries ...json.Marshaler) *DisMaxQuery { + return &DisMaxQuery{ + params: disMaxParams{ + Queries: queries, + }, + } +} + +func (q *DisMaxQuery) TieBreaker(b float32) *DisMaxQuery { + q.params.TieBreaker = b + return q +} + +func (q DisMaxQuery) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]disMaxParams{ + "dis_max": q.params, + }) +} diff --git a/dis_max_test.go b/dis_max_test.go new file mode 100644 index 0000000..8e2fc35 --- /dev/null +++ b/dis_max_test.go @@ -0,0 +1,15 @@ +package esquery + +import ( + "testing" +) + +func TestDisMax(t *testing.T) { + runTests(t, []queryTest{ + { + "dis_max", + DisMax(Term("title", "Quick pets"), Term("body", "Quick pets")).TieBreaker(0.7), + "{\"dis_max\":{\"queries\":[{\"term\":{\"title\":{\"value\":\"Quick pets\"}}},{\"term\":{\"body\":{\"value\":\"Quick pets\"}}}],\"tie_breaker\":0.7}}\n", + }, + }) +}