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