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