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/7.0/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/7.0/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/7.0/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	ignoreUnmapped *bool
317	ascending      bool
318	sortMode       *string
319	nestedFilter   Query
320	nestedPath     *string
321	nestedSort     *NestedSort
322}
323
324// NewGeoDistanceSort creates a new sorter for geo distances.
325func NewGeoDistanceSort(fieldName string) *GeoDistanceSort {
326	return &GeoDistanceSort{
327		fieldName: fieldName,
328		ascending: true,
329	}
330}
331
332// FieldName specifies the name of the (geo) field to use for sorting.
333func (s *GeoDistanceSort) FieldName(fieldName string) *GeoDistanceSort {
334	s.fieldName = fieldName
335	return s
336}
337
338// Order defines whether sorting ascending (default) or descending.
339func (s *GeoDistanceSort) Order(ascending bool) *GeoDistanceSort {
340	s.ascending = ascending
341	return s
342}
343
344// Asc sets ascending sort order.
345func (s *GeoDistanceSort) Asc() *GeoDistanceSort {
346	s.ascending = true
347	return s
348}
349
350// Desc sets descending sort order.
351func (s *GeoDistanceSort) Desc() *GeoDistanceSort {
352	s.ascending = false
353	return s
354}
355
356// Point specifies a point to create the range distance aggregations from.
357func (s *GeoDistanceSort) Point(lat, lon float64) *GeoDistanceSort {
358	s.points = append(s.points, GeoPointFromLatLon(lat, lon))
359	return s
360}
361
362// Points specifies the geo point(s) to create the range distance aggregations from.
363func (s *GeoDistanceSort) Points(points ...*GeoPoint) *GeoDistanceSort {
364	s.points = append(s.points, points...)
365	return s
366}
367
368// GeoHashes specifies the geo point to create the range distance aggregations from.
369func (s *GeoDistanceSort) GeoHashes(geohashes ...string) *GeoDistanceSort {
370	s.geohashes = append(s.geohashes, geohashes...)
371	return s
372}
373
374// Unit specifies the distance unit to use. It defaults to km.
375// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#distance-units
376// for details.
377func (s *GeoDistanceSort) Unit(unit string) *GeoDistanceSort {
378	s.unit = unit
379	return s
380}
381
382// IgnoreUnmapped indicates whether the unmapped field should be treated as
383// a missing value. Setting it to true is equivalent to specifying an
384// unmapped_type in the field sort. The default is false (unmapped field
385// causes the search to fail).
386func (s *GeoDistanceSort) IgnoreUnmapped(ignoreUnmapped bool) *GeoDistanceSort {
387	s.ignoreUnmapped = &ignoreUnmapped
388	return s
389}
390
391// GeoDistance is an alias for DistanceType.
392func (s *GeoDistanceSort) GeoDistance(geoDistance string) *GeoDistanceSort {
393	return s.DistanceType(geoDistance)
394}
395
396// DistanceType describes how to compute the distance, e.g. "arc" or "plane".
397// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-sort.html#geo-sorting
398// for details.
399func (s *GeoDistanceSort) DistanceType(distanceType string) *GeoDistanceSort {
400	s.distanceType = &distanceType
401	return s
402}
403
404// SortMode specifies what values to pick in case a document contains
405// multiple values for the targeted sort field. Possible values are:
406// min, max, sum, and avg.
407func (s *GeoDistanceSort) SortMode(sortMode string) *GeoDistanceSort {
408	s.sortMode = &sortMode
409	return s
410}
411
412// NestedFilter sets a filter that nested objects should match with
413// in order to be taken into account for sorting.
414func (s *GeoDistanceSort) NestedFilter(nestedFilter Query) *GeoDistanceSort {
415	s.nestedFilter = nestedFilter
416	return s
417}
418
419// NestedPath is used if sorting occurs on a field that is inside a
420// nested object.
421func (s *GeoDistanceSort) NestedPath(nestedPath string) *GeoDistanceSort {
422	s.nestedPath = &nestedPath
423	return s
424}
425
426// NestedSort is available starting with 6.1 and will replace NestedFilter
427// and NestedPath.
428func (s *GeoDistanceSort) NestedSort(nestedSort *NestedSort) *GeoDistanceSort {
429	s.nestedSort = nestedSort
430	return s
431}
432
433// Source returns the JSON-serializable data.
434func (s *GeoDistanceSort) Source() (interface{}, error) {
435	source := make(map[string]interface{})
436	x := make(map[string]interface{})
437	source["_geo_distance"] = x
438
439	// Points
440	var ptarr []interface{}
441	for _, pt := range s.points {
442		ptarr = append(ptarr, pt.Source())
443	}
444	for _, geohash := range s.geohashes {
445		ptarr = append(ptarr, geohash)
446	}
447	x[s.fieldName] = ptarr
448
449	if s.unit != "" {
450		x["unit"] = s.unit
451	}
452	if s.ignoreUnmapped != nil {
453		x["ignore_unmapped"] = *s.ignoreUnmapped
454	}
455	if s.distanceType != nil {
456		x["distance_type"] = *s.distanceType
457	}
458
459	if s.ascending {
460		x["order"] = "asc"
461	} else {
462		x["order"] = "desc"
463	}
464	if s.sortMode != nil {
465		x["mode"] = *s.sortMode
466	}
467	if s.nestedFilter != nil {
468		src, err := s.nestedFilter.Source()
469		if err != nil {
470			return nil, err
471		}
472		x["nested_filter"] = src
473	}
474	if s.nestedPath != nil {
475		x["nested_path"] = *s.nestedPath
476	}
477	if s.nestedSort != nil {
478		src, err := s.nestedSort.Source()
479		if err != nil {
480			return nil, err
481		}
482		x["nested"] = src
483	}
484	return source, nil
485}
486
487// -- ScriptSort --
488
489// ScriptSort sorts by a custom script. See
490// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html#modules-scripting
491// for details about scripting.
492type ScriptSort struct {
493	Sorter
494	script       *Script
495	typ          string
496	ascending    bool
497	sortMode     *string
498	nestedFilter Query
499	nestedPath   *string
500	nestedSort   *NestedSort
501}
502
503// NewScriptSort creates and initializes a new ScriptSort.
504// You must provide a script and a type, e.g. "string" or "number".
505func NewScriptSort(script *Script, typ string) *ScriptSort {
506	return &ScriptSort{
507		script:    script,
508		typ:       typ,
509		ascending: true,
510	}
511}
512
513// Type sets the script type, which can be either "string" or "number".
514func (s *ScriptSort) Type(typ string) *ScriptSort {
515	s.typ = typ
516	return s
517}
518
519// Order defines whether sorting ascending (default) or descending.
520func (s *ScriptSort) Order(ascending bool) *ScriptSort {
521	s.ascending = ascending
522	return s
523}
524
525// Asc sets ascending sort order.
526func (s *ScriptSort) Asc() *ScriptSort {
527	s.ascending = true
528	return s
529}
530
531// Desc sets descending sort order.
532func (s *ScriptSort) Desc() *ScriptSort {
533	s.ascending = false
534	return s
535}
536
537// SortMode specifies what values to pick in case a document contains
538// multiple values for the targeted sort field. Possible values are:
539// min or max.
540func (s *ScriptSort) SortMode(sortMode string) *ScriptSort {
541	s.sortMode = &sortMode
542	return s
543}
544
545// NestedFilter sets a filter that nested objects should match with
546// in order to be taken into account for sorting.
547func (s *ScriptSort) NestedFilter(nestedFilter Query) *ScriptSort {
548	s.nestedFilter = nestedFilter
549	return s
550}
551
552// NestedPath is used if sorting occurs on a field that is inside a
553// nested object.
554func (s *ScriptSort) NestedPath(nestedPath string) *ScriptSort {
555	s.nestedPath = &nestedPath
556	return s
557}
558
559// NestedSort is available starting with 6.1 and will replace NestedFilter
560// and NestedPath.
561func (s *ScriptSort) NestedSort(nestedSort *NestedSort) *ScriptSort {
562	s.nestedSort = nestedSort
563	return s
564}
565
566// Source returns the JSON-serializable data.
567func (s *ScriptSort) Source() (interface{}, error) {
568	if s.script == nil {
569		return nil, errors.New("ScriptSort expected a script")
570	}
571	source := make(map[string]interface{})
572	x := make(map[string]interface{})
573	source["_script"] = x
574
575	src, err := s.script.Source()
576	if err != nil {
577		return nil, err
578	}
579	x["script"] = src
580
581	x["type"] = s.typ
582
583	if s.ascending {
584		x["order"] = "asc"
585	} else {
586		x["order"] = "desc"
587	}
588	if s.sortMode != nil {
589		x["mode"] = *s.sortMode
590	}
591	if s.nestedFilter != nil {
592		src, err := s.nestedFilter.Source()
593		if err != nil {
594			return nil, err
595		}
596		x["nested_filter"] = src
597	}
598	if s.nestedPath != nil {
599		x["nested_path"] = *s.nestedPath
600	}
601	if s.nestedSort != nil {
602		src, err := s.nestedSort.Source()
603		if err != nil {
604			return nil, err
605		}
606		x["nested"] = src
607	}
608	return source, nil
609}
610
611// -- NestedSort --
612
613// NestedSort is used for fields that are inside a nested object.
614// It takes a "path" argument and an optional nested filter that the
615// nested objects should match with in order to be taken into account
616// for sorting.
617//
618// NestedSort is available from 6.1 and replaces nestedFilter and nestedPath
619// in the other sorters.
620type NestedSort struct {
621	Sorter
622	path       string
623	filter     Query
624	nestedSort *NestedSort
625}
626
627// NewNestedSort creates a new NestedSort.
628func NewNestedSort(path string) *NestedSort {
629	return &NestedSort{path: path}
630}
631
632// Filter sets the filter.
633func (s *NestedSort) Filter(filter Query) *NestedSort {
634	s.filter = filter
635	return s
636}
637
638// NestedSort embeds another level of nested sorting.
639func (s *NestedSort) NestedSort(nestedSort *NestedSort) *NestedSort {
640	s.nestedSort = nestedSort
641	return s
642}
643
644// Source returns the JSON-serializable data.
645func (s *NestedSort) Source() (interface{}, error) {
646	source := make(map[string]interface{})
647
648	if s.path != "" {
649		source["path"] = s.path
650	}
651	if s.filter != nil {
652		src, err := s.filter.Source()
653		if err != nil {
654			return nil, err
655		}
656		source["filter"] = src
657	}
658	if s.nestedSort != nil {
659		src, err := s.nestedSort.Source()
660		if err != nil {
661			return nil, err
662		}
663		source["nested"] = src
664	}
665
666	return source, nil
667}
668