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