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 "errors"
8
9// -- Sorter --
10
11// Sorter is an interface for sorting strategies, e.g. ScoreSort or FieldSort.
12// See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-sort.html.
13type Sorter interface {
14	Source() (interface{}, error)
15}
16
17// -- SortInfo --
18
19// SortInfo contains information about sorting a field.
20type SortInfo struct {
21	Sorter
22	Field          string
23	Ascending      bool
24	Missing        interface{}
25	IgnoreUnmapped *bool
26	UnmappedType   string
27	SortMode       string
28	NestedFilter   Query
29	NestedPath     string
30}
31
32func (info SortInfo) Source() (interface{}, error) {
33	prop := make(map[string]interface{})
34	if info.Ascending {
35		prop["order"] = "asc"
36	} else {
37		prop["order"] = "desc"
38	}
39	if info.Missing != nil {
40		prop["missing"] = info.Missing
41	}
42	if info.IgnoreUnmapped != nil {
43		prop["ignore_unmapped"] = *info.IgnoreUnmapped
44	}
45	if info.UnmappedType != "" {
46		prop["unmapped_type"] = info.UnmappedType
47	}
48	if info.SortMode != "" {
49		prop["mode"] = info.SortMode
50	}
51	if info.NestedFilter != nil {
52		src, err := info.NestedFilter.Source()
53		if err != nil {
54			return nil, err
55		}
56		prop["nested_filter"] = src
57	}
58	if info.NestedPath != "" {
59		prop["nested_path"] = info.NestedPath
60	}
61	source := make(map[string]interface{})
62	source[info.Field] = prop
63	return source, nil
64}
65
66// -- SortByDoc --
67
68// SortByDoc sorts by the "_doc" field, as described in
69// https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-scroll.html.
70//
71// Example:
72//   ss := elastic.NewSearchSource()
73//   ss = ss.SortBy(elastic.SortByDoc{})
74type SortByDoc struct {
75	Sorter
76}
77
78// Source returns the JSON-serializable data.
79func (s SortByDoc) Source() (interface{}, error) {
80	return "_doc", nil
81}
82
83// -- ScoreSort --
84
85// ScoreSort sorts by relevancy score.
86type ScoreSort struct {
87	Sorter
88	ascending bool
89}
90
91// NewScoreSort creates a new ScoreSort.
92func NewScoreSort() *ScoreSort {
93	return &ScoreSort{ascending: false} // Descending by default!
94}
95
96// Order defines whether sorting ascending (default) or descending.
97func (s *ScoreSort) Order(ascending bool) *ScoreSort {
98	s.ascending = ascending
99	return s
100}
101
102// Asc sets ascending sort order.
103func (s *ScoreSort) Asc() *ScoreSort {
104	s.ascending = true
105	return s
106}
107
108// Desc sets descending sort order.
109func (s *ScoreSort) Desc() *ScoreSort {
110	s.ascending = false
111	return s
112}
113
114// Source returns the JSON-serializable data.
115func (s *ScoreSort) Source() (interface{}, error) {
116	source := make(map[string]interface{})
117	x := make(map[string]interface{})
118	source["_score"] = x
119	if s.ascending {
120		x["order"] = "asc"
121	} else {
122		x["order"] = "desc"
123	}
124	return source, nil
125}
126
127// -- FieldSort --
128
129// FieldSort sorts by a given field.
130type FieldSort struct {
131	Sorter
132	fieldName    string
133	ascending    bool
134	missing      interface{}
135	unmappedType *string
136	sortMode     *string
137	nestedFilter Query
138	nestedPath   *string
139}
140
141// NewFieldSort creates a new FieldSort.
142func NewFieldSort(fieldName string) *FieldSort {
143	return &FieldSort{
144		fieldName: fieldName,
145		ascending: true,
146	}
147}
148
149// FieldName specifies the name of the field to be used for sorting.
150func (s *FieldSort) FieldName(fieldName string) *FieldSort {
151	s.fieldName = fieldName
152	return s
153}
154
155// Order defines whether sorting ascending (default) or descending.
156func (s *FieldSort) Order(ascending bool) *FieldSort {
157	s.ascending = ascending
158	return s
159}
160
161// Asc sets ascending sort order.
162func (s *FieldSort) Asc() *FieldSort {
163	s.ascending = true
164	return s
165}
166
167// Desc sets descending sort order.
168func (s *FieldSort) Desc() *FieldSort {
169	s.ascending = false
170	return s
171}
172
173// Missing sets the value to be used when a field is missing in a document.
174// You can also use "_last" or "_first" to sort missing last or first
175// respectively.
176func (s *FieldSort) Missing(missing interface{}) *FieldSort {
177	s.missing = missing
178	return s
179}
180
181// UnmappedType sets the type to use when the current field is not mapped
182// in an index.
183func (s *FieldSort) UnmappedType(typ string) *FieldSort {
184	s.unmappedType = &typ
185	return s
186}
187
188// SortMode specifies what values to pick in case a document contains
189// multiple values for the targeted sort field. Possible values are:
190// min, max, sum, and avg.
191func (s *FieldSort) SortMode(sortMode string) *FieldSort {
192	s.sortMode = &sortMode
193	return s
194}
195
196// NestedFilter sets a filter that nested objects should match with
197// in order to be taken into account for sorting.
198func (s *FieldSort) NestedFilter(nestedFilter Query) *FieldSort {
199	s.nestedFilter = nestedFilter
200	return s
201}
202
203// NestedPath is used if sorting occurs on a field that is inside a
204// nested object.
205func (s *FieldSort) NestedPath(nestedPath string) *FieldSort {
206	s.nestedPath = &nestedPath
207	return s
208}
209
210// Source returns the JSON-serializable data.
211func (s *FieldSort) Source() (interface{}, error) {
212	source := make(map[string]interface{})
213	x := make(map[string]interface{})
214	source[s.fieldName] = x
215	if s.ascending {
216		x["order"] = "asc"
217	} else {
218		x["order"] = "desc"
219	}
220	if s.missing != nil {
221		x["missing"] = s.missing
222	}
223	if s.unmappedType != nil {
224		x["unmapped_type"] = *s.unmappedType
225	}
226	if s.sortMode != nil {
227		x["mode"] = *s.sortMode
228	}
229	if s.nestedFilter != nil {
230		src, err := s.nestedFilter.Source()
231		if err != nil {
232			return nil, err
233		}
234		x["nested_filter"] = src
235	}
236	if s.nestedPath != nil {
237		x["nested_path"] = *s.nestedPath
238	}
239	return source, nil
240}
241
242// -- GeoDistanceSort --
243
244// GeoDistanceSort allows for sorting by geographic distance.
245// See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-sort.html#_geo_distance_sorting.
246type GeoDistanceSort struct {
247	Sorter
248	fieldName    string
249	points       []*GeoPoint
250	geohashes    []string
251	distanceType *string
252	unit         string
253	ascending    bool
254	sortMode     *string
255	nestedFilter Query
256	nestedPath   *string
257}
258
259// NewGeoDistanceSort creates a new sorter for geo distances.
260func NewGeoDistanceSort(fieldName string) *GeoDistanceSort {
261	return &GeoDistanceSort{
262		fieldName: fieldName,
263		ascending: true,
264	}
265}
266
267// FieldName specifies the name of the (geo) field to use for sorting.
268func (s *GeoDistanceSort) FieldName(fieldName string) *GeoDistanceSort {
269	s.fieldName = fieldName
270	return s
271}
272
273// Order defines whether sorting ascending (default) or descending.
274func (s *GeoDistanceSort) Order(ascending bool) *GeoDistanceSort {
275	s.ascending = ascending
276	return s
277}
278
279// Asc sets ascending sort order.
280func (s *GeoDistanceSort) Asc() *GeoDistanceSort {
281	s.ascending = true
282	return s
283}
284
285// Desc sets descending sort order.
286func (s *GeoDistanceSort) Desc() *GeoDistanceSort {
287	s.ascending = false
288	return s
289}
290
291// Point specifies a point to create the range distance aggregations from.
292func (s *GeoDistanceSort) Point(lat, lon float64) *GeoDistanceSort {
293	s.points = append(s.points, GeoPointFromLatLon(lat, lon))
294	return s
295}
296
297// Points specifies the geo point(s) to create the range distance aggregations from.
298func (s *GeoDistanceSort) Points(points ...*GeoPoint) *GeoDistanceSort {
299	s.points = append(s.points, points...)
300	return s
301}
302
303// GeoHashes specifies the geo point to create the range distance aggregations from.
304func (s *GeoDistanceSort) GeoHashes(geohashes ...string) *GeoDistanceSort {
305	s.geohashes = append(s.geohashes, geohashes...)
306	return s
307}
308
309// Unit specifies the distance unit to use. It defaults to km.
310// See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#distance-units
311// for details.
312func (s *GeoDistanceSort) Unit(unit string) *GeoDistanceSort {
313	s.unit = unit
314	return s
315}
316
317// GeoDistance is an alias for DistanceType.
318func (s *GeoDistanceSort) GeoDistance(geoDistance string) *GeoDistanceSort {
319	return s.DistanceType(geoDistance)
320}
321
322// DistanceType describes how to compute the distance, e.g. "arc" or "plane".
323// See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-sort.html#geo-sorting
324// for details.
325func (s *GeoDistanceSort) DistanceType(distanceType string) *GeoDistanceSort {
326	s.distanceType = &distanceType
327	return s
328}
329
330// SortMode specifies what values to pick in case a document contains
331// multiple values for the targeted sort field. Possible values are:
332// min, max, sum, and avg.
333func (s *GeoDistanceSort) SortMode(sortMode string) *GeoDistanceSort {
334	s.sortMode = &sortMode
335	return s
336}
337
338// NestedFilter sets a filter that nested objects should match with
339// in order to be taken into account for sorting.
340func (s *GeoDistanceSort) NestedFilter(nestedFilter Query) *GeoDistanceSort {
341	s.nestedFilter = nestedFilter
342	return s
343}
344
345// NestedPath is used if sorting occurs on a field that is inside a
346// nested object.
347func (s *GeoDistanceSort) NestedPath(nestedPath string) *GeoDistanceSort {
348	s.nestedPath = &nestedPath
349	return s
350}
351
352// Source returns the JSON-serializable data.
353func (s *GeoDistanceSort) Source() (interface{}, error) {
354	source := make(map[string]interface{})
355	x := make(map[string]interface{})
356	source["_geo_distance"] = x
357
358	// Points
359	var ptarr []interface{}
360	for _, pt := range s.points {
361		ptarr = append(ptarr, pt.Source())
362	}
363	for _, geohash := range s.geohashes {
364		ptarr = append(ptarr, geohash)
365	}
366	x[s.fieldName] = ptarr
367
368	if s.unit != "" {
369		x["unit"] = s.unit
370	}
371	if s.distanceType != nil {
372		x["distance_type"] = *s.distanceType
373	}
374
375	if s.ascending {
376		x["order"] = "asc"
377	} else {
378		x["order"] = "desc"
379	}
380	if s.sortMode != nil {
381		x["mode"] = *s.sortMode
382	}
383	if s.nestedFilter != nil {
384		src, err := s.nestedFilter.Source()
385		if err != nil {
386			return nil, err
387		}
388		x["nested_filter"] = src
389	}
390	if s.nestedPath != nil {
391		x["nested_path"] = *s.nestedPath
392	}
393	return source, nil
394}
395
396// -- ScriptSort --
397
398// ScriptSort sorts by a custom script. See
399// https://www.elastic.co/guide/en/elasticsearch/reference/5.6/modules-scripting.html#modules-scripting
400// for details about scripting.
401type ScriptSort struct {
402	Sorter
403	script       *Script
404	typ          string
405	ascending    bool
406	sortMode     *string
407	nestedFilter Query
408	nestedPath   *string
409}
410
411// NewScriptSort creates and initializes a new ScriptSort.
412// You must provide a script and a type, e.g. "string" or "number".
413func NewScriptSort(script *Script, typ string) *ScriptSort {
414	return &ScriptSort{
415		script:    script,
416		typ:       typ,
417		ascending: true,
418	}
419}
420
421// Type sets the script type, which can be either "string" or "number".
422func (s *ScriptSort) Type(typ string) *ScriptSort {
423	s.typ = typ
424	return s
425}
426
427// Order defines whether sorting ascending (default) or descending.
428func (s *ScriptSort) Order(ascending bool) *ScriptSort {
429	s.ascending = ascending
430	return s
431}
432
433// Asc sets ascending sort order.
434func (s *ScriptSort) Asc() *ScriptSort {
435	s.ascending = true
436	return s
437}
438
439// Desc sets descending sort order.
440func (s *ScriptSort) Desc() *ScriptSort {
441	s.ascending = false
442	return s
443}
444
445// SortMode specifies what values to pick in case a document contains
446// multiple values for the targeted sort field. Possible values are:
447// min or max.
448func (s *ScriptSort) SortMode(sortMode string) *ScriptSort {
449	s.sortMode = &sortMode
450	return s
451}
452
453// NestedFilter sets a filter that nested objects should match with
454// in order to be taken into account for sorting.
455func (s *ScriptSort) NestedFilter(nestedFilter Query) *ScriptSort {
456	s.nestedFilter = nestedFilter
457	return s
458}
459
460// NestedPath is used if sorting occurs on a field that is inside a
461// nested object.
462func (s *ScriptSort) NestedPath(nestedPath string) *ScriptSort {
463	s.nestedPath = &nestedPath
464	return s
465}
466
467// Source returns the JSON-serializable data.
468func (s *ScriptSort) Source() (interface{}, error) {
469	if s.script == nil {
470		return nil, errors.New("ScriptSort expected a script")
471	}
472	source := make(map[string]interface{})
473	x := make(map[string]interface{})
474	source["_script"] = x
475
476	src, err := s.script.Source()
477	if err != nil {
478		return nil, err
479	}
480	x["script"] = src
481
482	x["type"] = s.typ
483
484	if s.ascending {
485		x["order"] = "asc"
486	} else {
487		x["order"] = "desc"
488	}
489	if s.sortMode != nil {
490		x["mode"] = *s.sortMode
491	}
492	if s.nestedFilter != nil {
493		src, err := s.nestedFilter.Source()
494		if err != nil {
495			return nil, err
496		}
497		x["nested_filter"] = src
498	}
499	if s.nestedPath != nil {
500		x["nested_path"] = *s.nestedPath
501	}
502	return source, nil
503}
504