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/6.7/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 // deprecated in 6.1 and replaced by Filter
29	Filter         Query
30	NestedPath     string // deprecated in 6.1 and replaced by Path
31	Path           string
32	NestedSort     *NestedSort // deprecated in 6.1 and replaced by Nested
33	Nested         *NestedSort
34}
35
36func (info SortInfo) Source() (interface{}, error) {
37	prop := make(map[string]interface{})
38	if info.Ascending {
39		prop["order"] = "asc"
40	} else {
41		prop["order"] = "desc"
42	}
43	if info.Missing != nil {
44		prop["missing"] = info.Missing
45	}
46	if info.IgnoreUnmapped != nil {
47		prop["ignore_unmapped"] = *info.IgnoreUnmapped
48	}
49	if info.UnmappedType != "" {
50		prop["unmapped_type"] = info.UnmappedType
51	}
52	if info.SortMode != "" {
53		prop["mode"] = info.SortMode
54	}
55	if info.Filter != nil {
56		src, err := info.Filter.Source()
57		if err != nil {
58			return nil, err
59		}
60		prop["filter"] = src
61	} else if info.NestedFilter != nil {
62		src, err := info.NestedFilter.Source()
63		if err != nil {
64			return nil, err
65		}
66		prop["nested_filter"] = src // deprecated in 6.1
67	}
68	if info.Path != "" {
69		prop["path"] = info.Path
70	} else if info.NestedPath != "" {
71		prop["nested_path"] = info.NestedPath // deprecated in 6.1
72	}
73	if info.Nested != nil {
74		src, err := info.Nested.Source()
75		if err != nil {
76			return nil, err
77		}
78		prop["nested"] = src
79	} else if info.NestedSort != nil {
80		src, err := info.NestedSort.Source()
81		if err != nil {
82			return nil, err
83		}
84		prop["nested"] = src
85	}
86	source := make(map[string]interface{})
87	source[info.Field] = prop
88	return source, nil
89}
90
91// -- SortByDoc --
92
93// SortByDoc sorts by the "_doc" field, as described in
94// https://www.elastic.co/guide/en/elasticsearch/reference/6.7/search-request-scroll.html.
95//
96// Example:
97//   ss := elastic.NewSearchSource()
98//   ss = ss.SortBy(elastic.SortByDoc{})
99type SortByDoc struct {
100	Sorter
101}
102
103// Source returns the JSON-serializable data.
104func (s SortByDoc) Source() (interface{}, error) {
105	return "_doc", nil
106}
107
108// -- ScoreSort --
109
110// ScoreSort sorts by relevancy score.
111type ScoreSort struct {
112	Sorter
113	ascending bool
114}
115
116// NewScoreSort creates a new ScoreSort.
117func NewScoreSort() *ScoreSort {
118	return &ScoreSort{ascending: false} // Descending by default!
119}
120
121// Order defines whether sorting ascending (default) or descending.
122func (s *ScoreSort) Order(ascending bool) *ScoreSort {
123	s.ascending = ascending
124	return s
125}
126
127// Asc sets ascending sort order.
128func (s *ScoreSort) Asc() *ScoreSort {
129	s.ascending = true
130	return s
131}
132
133// Desc sets descending sort order.
134func (s *ScoreSort) Desc() *ScoreSort {
135	s.ascending = false
136	return s
137}
138
139// Source returns the JSON-serializable data.
140func (s *ScoreSort) Source() (interface{}, error) {
141	source := make(map[string]interface{})
142	x := make(map[string]interface{})
143	source["_score"] = x
144	if s.ascending {
145		x["order"] = "asc"
146	} else {
147		x["order"] = "desc"
148	}
149	return source, nil
150}
151
152// -- FieldSort --
153
154// FieldSort sorts by a given field.
155type FieldSort struct {
156	Sorter
157	fieldName    string
158	ascending    bool
159	missing      interface{}
160	unmappedType *string
161	sortMode     *string
162	filter       Query
163	path         *string
164	nested       *NestedSort
165}
166
167// NewFieldSort creates a new FieldSort.
168func NewFieldSort(fieldName string) *FieldSort {
169	return &FieldSort{
170		fieldName: fieldName,
171		ascending: true,
172	}
173}
174
175// FieldName specifies the name of the field to be used for sorting.
176func (s *FieldSort) FieldName(fieldName string) *FieldSort {
177	s.fieldName = fieldName
178	return s
179}
180
181// Order defines whether sorting ascending (default) or descending.
182func (s *FieldSort) Order(ascending bool) *FieldSort {
183	s.ascending = ascending
184	return s
185}
186
187// Asc sets ascending sort order.
188func (s *FieldSort) Asc() *FieldSort {
189	s.ascending = true
190	return s
191}
192
193// Desc sets descending sort order.
194func (s *FieldSort) Desc() *FieldSort {
195	s.ascending = false
196	return s
197}
198
199// Missing sets the value to be used when a field is missing in a document.
200// You can also use "_last" or "_first" to sort missing last or first
201// respectively.
202func (s *FieldSort) Missing(missing interface{}) *FieldSort {
203	s.missing = missing
204	return s
205}
206
207// UnmappedType sets the type to use when the current field is not mapped
208// in an index.
209func (s *FieldSort) UnmappedType(typ string) *FieldSort {
210	s.unmappedType = &typ
211	return s
212}
213
214// SortMode specifies what values to pick in case a document contains
215// multiple values for the targeted sort field. Possible values are:
216// min, max, sum, and avg.
217func (s *FieldSort) SortMode(sortMode string) *FieldSort {
218	s.sortMode = &sortMode
219	return s
220}
221
222// NestedFilter sets a filter that nested objects should match with
223// in order to be taken into account for sorting.
224// Deprecated: Use Filter instead.
225func (s *FieldSort) NestedFilter(nestedFilter Query) *FieldSort {
226	s.filter = nestedFilter
227	return s
228}
229
230// Filter sets a filter that nested objects should match with
231// in order to be taken into account for sorting.
232func (s *FieldSort) Filter(filter Query) *FieldSort {
233	s.filter = filter
234	return s
235}
236
237// NestedPath is used if sorting occurs on a field that is inside a
238// nested object.
239// Deprecated: Use Path instead.
240func (s *FieldSort) NestedPath(nestedPath string) *FieldSort {
241	s.path = &nestedPath
242	return s
243}
244
245// Path is used if sorting occurs on a field that is inside a
246// nested object.
247func (s *FieldSort) Path(path string) *FieldSort {
248	s.path = &path
249	return s
250}
251
252// NestedSort is available starting with 6.1 and will replace NestedFilter
253// and NestedPath.
254// Deprecated: Use Nested instead.
255func (s *FieldSort) NestedSort(nestedSort *NestedSort) *FieldSort {
256	s.nested = nestedSort
257	return s
258}
259
260// Nested is available starting with 6.1 and will replace Filter and Path.
261func (s *FieldSort) Nested(nested *NestedSort) *FieldSort {
262	s.nested = nested
263	return s
264}
265
266// Source returns the JSON-serializable data.
267func (s *FieldSort) Source() (interface{}, error) {
268	source := make(map[string]interface{})
269	x := make(map[string]interface{})
270	source[s.fieldName] = x
271	if s.ascending {
272		x["order"] = "asc"
273	} else {
274		x["order"] = "desc"
275	}
276	if s.missing != nil {
277		x["missing"] = s.missing
278	}
279	if s.unmappedType != nil {
280		x["unmapped_type"] = *s.unmappedType
281	}
282	if s.sortMode != nil {
283		x["mode"] = *s.sortMode
284	}
285	if s.filter != nil {
286		src, err := s.filter.Source()
287		if err != nil {
288			return nil, err
289		}
290		x["filter"] = src
291	}
292	if s.path != nil {
293		x["path"] = *s.path
294	}
295	if s.nested != nil {
296		src, err := s.nested.Source()
297		if err != nil {
298			return nil, err
299		}
300		x["nested"] = src
301	}
302	return source, nil
303}
304
305// -- GeoDistanceSort --
306
307// GeoDistanceSort allows for sorting by geographic distance.
308// See https://www.elastic.co/guide/en/elasticsearch/reference/6.7/search-request-sort.html#_geo_distance_sorting.
309type GeoDistanceSort struct {
310	Sorter
311	fieldName    string
312	points       []*GeoPoint
313	geohashes    []string
314	distanceType *string
315	unit         string
316	ascending    bool
317	sortMode     *string
318	nestedFilter Query
319	nestedPath   *string
320	nestedSort   *NestedSort
321}
322
323// NewGeoDistanceSort creates a new sorter for geo distances.
324func NewGeoDistanceSort(fieldName string) *GeoDistanceSort {
325	return &GeoDistanceSort{
326		fieldName: fieldName,
327		ascending: true,
328	}
329}
330
331// FieldName specifies the name of the (geo) field to use for sorting.
332func (s *GeoDistanceSort) FieldName(fieldName string) *GeoDistanceSort {
333	s.fieldName = fieldName
334	return s
335}
336
337// Order defines whether sorting ascending (default) or descending.
338func (s *GeoDistanceSort) Order(ascending bool) *GeoDistanceSort {
339	s.ascending = ascending
340	return s
341}
342
343// Asc sets ascending sort order.
344func (s *GeoDistanceSort) Asc() *GeoDistanceSort {
345	s.ascending = true
346	return s
347}
348
349// Desc sets descending sort order.
350func (s *GeoDistanceSort) Desc() *GeoDistanceSort {
351	s.ascending = false
352	return s
353}
354
355// Point specifies a point to create the range distance aggregations from.
356func (s *GeoDistanceSort) Point(lat, lon float64) *GeoDistanceSort {
357	s.points = append(s.points, GeoPointFromLatLon(lat, lon))
358	return s
359}
360
361// Points specifies the geo point(s) to create the range distance aggregations from.
362func (s *GeoDistanceSort) Points(points ...*GeoPoint) *GeoDistanceSort {
363	s.points = append(s.points, points...)
364	return s
365}
366
367// GeoHashes specifies the geo point to create the range distance aggregations from.
368func (s *GeoDistanceSort) GeoHashes(geohashes ...string) *GeoDistanceSort {
369	s.geohashes = append(s.geohashes, geohashes...)
370	return s
371}
372
373// Unit specifies the distance unit to use. It defaults to km.
374// See https://www.elastic.co/guide/en/elasticsearch/reference/6.7/common-options.html#distance-units
375// for details.
376func (s *GeoDistanceSort) Unit(unit string) *GeoDistanceSort {
377	s.unit = unit
378	return s
379}
380
381// GeoDistance is an alias for DistanceType.
382func (s *GeoDistanceSort) GeoDistance(geoDistance string) *GeoDistanceSort {
383	return s.DistanceType(geoDistance)
384}
385
386// DistanceType describes how to compute the distance, e.g. "arc" or "plane".
387// See https://www.elastic.co/guide/en/elasticsearch/reference/6.7/search-request-sort.html#geo-sorting
388// for details.
389func (s *GeoDistanceSort) DistanceType(distanceType string) *GeoDistanceSort {
390	s.distanceType = &distanceType
391	return s
392}
393
394// SortMode specifies what values to pick in case a document contains
395// multiple values for the targeted sort field. Possible values are:
396// min, max, sum, and avg.
397func (s *GeoDistanceSort) SortMode(sortMode string) *GeoDistanceSort {
398	s.sortMode = &sortMode
399	return s
400}
401
402// NestedFilter sets a filter that nested objects should match with
403// in order to be taken into account for sorting.
404func (s *GeoDistanceSort) NestedFilter(nestedFilter Query) *GeoDistanceSort {
405	s.nestedFilter = nestedFilter
406	return s
407}
408
409// NestedPath is used if sorting occurs on a field that is inside a
410// nested object.
411func (s *GeoDistanceSort) NestedPath(nestedPath string) *GeoDistanceSort {
412	s.nestedPath = &nestedPath
413	return s
414}
415
416// NestedSort is available starting with 6.1 and will replace NestedFilter
417// and NestedPath.
418func (s *GeoDistanceSort) NestedSort(nestedSort *NestedSort) *GeoDistanceSort {
419	s.nestedSort = nestedSort
420	return s
421}
422
423// Source returns the JSON-serializable data.
424func (s *GeoDistanceSort) Source() (interface{}, error) {
425	source := make(map[string]interface{})
426	x := make(map[string]interface{})
427	source["_geo_distance"] = x
428
429	// Points
430	var ptarr []interface{}
431	for _, pt := range s.points {
432		ptarr = append(ptarr, pt.Source())
433	}
434	for _, geohash := range s.geohashes {
435		ptarr = append(ptarr, geohash)
436	}
437	x[s.fieldName] = ptarr
438
439	if s.unit != "" {
440		x["unit"] = s.unit
441	}
442	if s.distanceType != nil {
443		x["distance_type"] = *s.distanceType
444	}
445
446	if s.ascending {
447		x["order"] = "asc"
448	} else {
449		x["order"] = "desc"
450	}
451	if s.sortMode != nil {
452		x["mode"] = *s.sortMode
453	}
454	if s.nestedFilter != nil {
455		src, err := s.nestedFilter.Source()
456		if err != nil {
457			return nil, err
458		}
459		x["nested_filter"] = src
460	}
461	if s.nestedPath != nil {
462		x["nested_path"] = *s.nestedPath
463	}
464	if s.nestedSort != nil {
465		src, err := s.nestedSort.Source()
466		if err != nil {
467			return nil, err
468		}
469		x["nested"] = src
470	}
471	return source, nil
472}
473
474// -- ScriptSort --
475
476// ScriptSort sorts by a custom script. See
477// https://www.elastic.co/guide/en/elasticsearch/reference/6.7/modules-scripting.html#modules-scripting
478// for details about scripting.
479type ScriptSort struct {
480	Sorter
481	script       *Script
482	typ          string
483	ascending    bool
484	sortMode     *string
485	nestedFilter Query
486	nestedPath   *string
487	nestedSort   *NestedSort
488}
489
490// NewScriptSort creates and initializes a new ScriptSort.
491// You must provide a script and a type, e.g. "string" or "number".
492func NewScriptSort(script *Script, typ string) *ScriptSort {
493	return &ScriptSort{
494		script:    script,
495		typ:       typ,
496		ascending: true,
497	}
498}
499
500// Type sets the script type, which can be either "string" or "number".
501func (s *ScriptSort) Type(typ string) *ScriptSort {
502	s.typ = typ
503	return s
504}
505
506// Order defines whether sorting ascending (default) or descending.
507func (s *ScriptSort) Order(ascending bool) *ScriptSort {
508	s.ascending = ascending
509	return s
510}
511
512// Asc sets ascending sort order.
513func (s *ScriptSort) Asc() *ScriptSort {
514	s.ascending = true
515	return s
516}
517
518// Desc sets descending sort order.
519func (s *ScriptSort) Desc() *ScriptSort {
520	s.ascending = false
521	return s
522}
523
524// SortMode specifies what values to pick in case a document contains
525// multiple values for the targeted sort field. Possible values are:
526// min or max.
527func (s *ScriptSort) SortMode(sortMode string) *ScriptSort {
528	s.sortMode = &sortMode
529	return s
530}
531
532// NestedFilter sets a filter that nested objects should match with
533// in order to be taken into account for sorting.
534func (s *ScriptSort) NestedFilter(nestedFilter Query) *ScriptSort {
535	s.nestedFilter = nestedFilter
536	return s
537}
538
539// NestedPath is used if sorting occurs on a field that is inside a
540// nested object.
541func (s *ScriptSort) NestedPath(nestedPath string) *ScriptSort {
542	s.nestedPath = &nestedPath
543	return s
544}
545
546// NestedSort is available starting with 6.1 and will replace NestedFilter
547// and NestedPath.
548func (s *ScriptSort) NestedSort(nestedSort *NestedSort) *ScriptSort {
549	s.nestedSort = nestedSort
550	return s
551}
552
553// Source returns the JSON-serializable data.
554func (s *ScriptSort) Source() (interface{}, error) {
555	if s.script == nil {
556		return nil, errors.New("ScriptSort expected a script")
557	}
558	source := make(map[string]interface{})
559	x := make(map[string]interface{})
560	source["_script"] = x
561
562	src, err := s.script.Source()
563	if err != nil {
564		return nil, err
565	}
566	x["script"] = src
567
568	x["type"] = s.typ
569
570	if s.ascending {
571		x["order"] = "asc"
572	} else {
573		x["order"] = "desc"
574	}
575	if s.sortMode != nil {
576		x["mode"] = *s.sortMode
577	}
578	if s.nestedFilter != nil {
579		src, err := s.nestedFilter.Source()
580		if err != nil {
581			return nil, err
582		}
583		x["nested_filter"] = src
584	}
585	if s.nestedPath != nil {
586		x["nested_path"] = *s.nestedPath
587	}
588	if s.nestedSort != nil {
589		src, err := s.nestedSort.Source()
590		if err != nil {
591			return nil, err
592		}
593		x["nested"] = src
594	}
595	return source, nil
596}
597
598// -- NestedSort --
599
600// NestedSort is used for fields that are inside a nested object.
601// It takes a "path" argument and an optional nested filter that the
602// nested objects should match with in order to be taken into account
603// for sorting.
604//
605// NestedSort is available from 6.1 and replaces nestedFilter and nestedPath
606// in the other sorters.
607type NestedSort struct {
608	Sorter
609	path       string
610	filter     Query
611	nestedSort *NestedSort
612}
613
614// NewNestedSort creates a new NestedSort.
615func NewNestedSort(path string) *NestedSort {
616	return &NestedSort{path: path}
617}
618
619// Filter sets the filter.
620func (s *NestedSort) Filter(filter Query) *NestedSort {
621	s.filter = filter
622	return s
623}
624
625// NestedSort embeds another level of nested sorting.
626func (s *NestedSort) NestedSort(nestedSort *NestedSort) *NestedSort {
627	s.nestedSort = nestedSort
628	return s
629}
630
631// Source returns the JSON-serializable data.
632func (s *NestedSort) Source() (interface{}, error) {
633	source := make(map[string]interface{})
634
635	if s.path != "" {
636		source["path"] = s.path
637	}
638	if s.filter != nil {
639		src, err := s.filter.Source()
640		if err != nil {
641			return nil, err
642		}
643		source["filter"] = src
644	}
645	if s.nestedSort != nil {
646		src, err := s.nestedSort.Source()
647		if err != nil {
648			return nil, err
649		}
650		source["nested"] = src
651	}
652
653	return source, nil
654}
655