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