1// Copyright 2012-present Oliver Eilhard. All rights reserved.
2// Use of this source code is governed by a MIT-license.
3// See http://olivere.mit-license.org/license.txt for details.
4
5package elastic
6
7import "fmt"
8
9// A bool query matches documents matching boolean
10// combinations of other queries.
11// For more details, see:
12// https://www.elastic.co/guide/en/elasticsearch/reference/5.2/query-dsl-bool-query.html
13type BoolQuery struct {
14	Query
15	mustClauses        []Query
16	mustNotClauses     []Query
17	filterClauses      []Query
18	shouldClauses      []Query
19	boost              *float64
20	disableCoord       *bool
21	minimumShouldMatch string
22	adjustPureNegative *bool
23	queryName          string
24}
25
26// Creates a new bool query.
27func NewBoolQuery() *BoolQuery {
28	return &BoolQuery{
29		mustClauses:    make([]Query, 0),
30		mustNotClauses: make([]Query, 0),
31		filterClauses:  make([]Query, 0),
32		shouldClauses:  make([]Query, 0),
33	}
34}
35
36func (q *BoolQuery) Must(queries ...Query) *BoolQuery {
37	q.mustClauses = append(q.mustClauses, queries...)
38	return q
39}
40
41func (q *BoolQuery) MustNot(queries ...Query) *BoolQuery {
42	q.mustNotClauses = append(q.mustNotClauses, queries...)
43	return q
44}
45
46func (q *BoolQuery) Filter(filters ...Query) *BoolQuery {
47	q.filterClauses = append(q.filterClauses, filters...)
48	return q
49}
50
51func (q *BoolQuery) Should(queries ...Query) *BoolQuery {
52	q.shouldClauses = append(q.shouldClauses, queries...)
53	return q
54}
55
56func (q *BoolQuery) Boost(boost float64) *BoolQuery {
57	q.boost = &boost
58	return q
59}
60
61func (q *BoolQuery) DisableCoord(disableCoord bool) *BoolQuery {
62	q.disableCoord = &disableCoord
63	return q
64}
65
66func (q *BoolQuery) MinimumShouldMatch(minimumShouldMatch string) *BoolQuery {
67	q.minimumShouldMatch = minimumShouldMatch
68	return q
69}
70
71func (q *BoolQuery) MinimumNumberShouldMatch(minimumNumberShouldMatch int) *BoolQuery {
72	q.minimumShouldMatch = fmt.Sprintf("%d", minimumNumberShouldMatch)
73	return q
74}
75
76func (q *BoolQuery) AdjustPureNegative(adjustPureNegative bool) *BoolQuery {
77	q.adjustPureNegative = &adjustPureNegative
78	return q
79}
80
81func (q *BoolQuery) QueryName(queryName string) *BoolQuery {
82	q.queryName = queryName
83	return q
84}
85
86// Creates the query source for the bool query.
87func (q *BoolQuery) Source() (interface{}, error) {
88	// {
89	//	"bool" : {
90	//		"must" : {
91	//			"term" : { "user" : "kimchy" }
92	//		},
93	//		"must_not" : {
94	//			"range" : {
95	//				"age" : { "from" : 10, "to" : 20 }
96	//			}
97	//		},
98	//    "filter" : [
99	//      ...
100	//    ]
101	//		"should" : [
102	//			{
103	//				"term" : { "tag" : "wow" }
104	//			},
105	//			{
106	//				"term" : { "tag" : "elasticsearch" }
107	//			}
108	//		],
109	//		"minimum_number_should_match" : 1,
110	//		"boost" : 1.0
111	//	}
112	// }
113
114	query := make(map[string]interface{})
115
116	boolClause := make(map[string]interface{})
117	query["bool"] = boolClause
118
119	// must
120	if len(q.mustClauses) == 1 {
121		src, err := q.mustClauses[0].Source()
122		if err != nil {
123			return nil, err
124		}
125		boolClause["must"] = src
126	} else if len(q.mustClauses) > 1 {
127		var clauses []interface{}
128		for _, subQuery := range q.mustClauses {
129			src, err := subQuery.Source()
130			if err != nil {
131				return nil, err
132			}
133			clauses = append(clauses, src)
134		}
135		boolClause["must"] = clauses
136	}
137
138	// must_not
139	if len(q.mustNotClauses) == 1 {
140		src, err := q.mustNotClauses[0].Source()
141		if err != nil {
142			return nil, err
143		}
144		boolClause["must_not"] = src
145	} else if len(q.mustNotClauses) > 1 {
146		var clauses []interface{}
147		for _, subQuery := range q.mustNotClauses {
148			src, err := subQuery.Source()
149			if err != nil {
150				return nil, err
151			}
152			clauses = append(clauses, src)
153		}
154		boolClause["must_not"] = clauses
155	}
156
157	// filter
158	if len(q.filterClauses) == 1 {
159		src, err := q.filterClauses[0].Source()
160		if err != nil {
161			return nil, err
162		}
163		boolClause["filter"] = src
164	} else if len(q.filterClauses) > 1 {
165		var clauses []interface{}
166		for _, subQuery := range q.filterClauses {
167			src, err := subQuery.Source()
168			if err != nil {
169				return nil, err
170			}
171			clauses = append(clauses, src)
172		}
173		boolClause["filter"] = clauses
174	}
175
176	// should
177	if len(q.shouldClauses) == 1 {
178		src, err := q.shouldClauses[0].Source()
179		if err != nil {
180			return nil, err
181		}
182		boolClause["should"] = src
183	} else if len(q.shouldClauses) > 1 {
184		var clauses []interface{}
185		for _, subQuery := range q.shouldClauses {
186			src, err := subQuery.Source()
187			if err != nil {
188				return nil, err
189			}
190			clauses = append(clauses, src)
191		}
192		boolClause["should"] = clauses
193	}
194
195	if q.boost != nil {
196		boolClause["boost"] = *q.boost
197	}
198	if q.disableCoord != nil {
199		boolClause["disable_coord"] = *q.disableCoord
200	}
201	if q.minimumShouldMatch != "" {
202		boolClause["minimum_should_match"] = q.minimumShouldMatch
203	}
204	if q.adjustPureNegative != nil {
205		boolClause["adjust_pure_negative"] = *q.adjustPureNegative
206	}
207	if q.queryName != "" {
208		boolClause["_name"] = q.queryName
209	}
210
211	return query, nil
212}
213