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