diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/aggregations_test.go b/aggregations_test.go index 39eeaa3..949123d 100644 --- a/aggregations_test.go +++ b/aggregations_test.go @@ -61,8 +61,8 @@ func TestAggregations(t *testing.T) { { "a complex, multi-aggregation, nested", Aggregate( - NestedAgg("categories","categories"). - Aggs(TermsAgg("type","outdoors")), + NestedAgg("categories", "categories"). + Aggs(TermsAgg("type", "outdoors")), FilterAgg("filtered", Term("type", "t-shirt")), ), @@ -72,9 +72,9 @@ func TestAggregations(t *testing.T) { "nested": map[string]interface{}{ "path": "categories", }, - "aggs": map[string]interface{} { - "type": map[string]interface{} { - "terms": map[string]interface{} { + "aggs": map[string]interface{}{ + "type": map[string]interface{}{ + "terms": map[string]interface{}{ "field": "outdoors", }, }, @@ -83,7 +83,7 @@ func TestAggregations(t *testing.T) { "filtered": map[string]interface{}{ "filter": map[string]interface{}{ "term": map[string]interface{}{ - "type": map[string]interface{} { + "type": map[string]interface{}{ "value": "t-shirt", }, }, @@ -92,5 +92,69 @@ func TestAggregations(t *testing.T) { }, }, }, + { + "order for termsAggs", + //eq.Aggregate(eq.TermsAgg("a1", "FIELD1").Size(0).Aggs(eq.Sum("a2", "FIELD2.SUBFIELD"))) + Aggregate( + TermsAgg("categories", "categories"). + Order(map[string]string{"priceSum": "desc"}). + Size(5).Aggs(Sum("priceSum", "price"))), + map[string]interface{}{ + "aggs": map[string]interface{}{ + "categories": map[string]interface{}{ + "terms": map[string]interface{}{ + "field": "categories", + "order": map[string]interface{}{ + "priceSum": "desc", + }, + "size": 5, + }, + "aggs": map[string]interface{}{ + "priceSum": map[string]interface{}{ + "sum": map[string]interface{}{ + "field": "price", + }, + }, + }, + }, + }, + }, + }, + { + "Single include for termsAggs", + //eq.Aggregate(eq.TermsAgg("a1", "FIELD1").Size(0).Aggs(eq.Sum("a2", "FIELD2.SUBFIELD"))) + Aggregate( + TermsAgg("categories", "categories"). + Include("red.*|blue.*"), + ), + map[string]interface{}{ + "aggs": map[string]interface{}{ + "categories": map[string]interface{}{ + "terms": map[string]interface{}{ + "field": "categories", + "include": "red.*|blue.*", + }, + }, + }, + }, + }, + { + "Multi include for termsAggs", + //eq.Aggregate(eq.TermsAgg("a1", "FIELD1").Size(0).Aggs(eq.Sum("a2", "FIELD2.SUBFIELD"))) + Aggregate( + TermsAgg("categories", "categories"). + Include("red", "blue"), + ), + map[string]interface{}{ + "aggs": map[string]interface{}{ + "categories": map[string]interface{}{ + "terms": map[string]interface{}{ + "field": "categories", + "include": []string{"red", "blue"}, + }, + }, + }, + }, + }, }) } diff --git a/aggs_bucket.go b/aggs_bucket.go index 09b5185..1a45a14 100644 --- a/aggs_bucket.go +++ b/aggs_bucket.go @@ -12,6 +12,8 @@ type TermsAggregation struct { shardSize *float64 showTermDoc *bool aggs []Aggregation + order map[string]string + include []string } // TermsAgg creates a new aggregation of type "terms". The method name includes @@ -54,6 +56,18 @@ func (agg *TermsAggregation) Aggs(aggs ...Aggregation) *TermsAggregation { return agg } +// Order sets the sort for terms agg +func (agg *TermsAggregation) Order(order map[string]string) *TermsAggregation { + agg.order = order + return agg +} + +// Include filter the values for buckets +func (agg *TermsAggregation) Include(include ...string) *TermsAggregation { + agg.include = include + return agg +} + // Map returns a map representation of the aggregation, thus implementing the // Mappable interface. func (agg *TermsAggregation) Map() map[string]interface{} { @@ -70,6 +84,18 @@ func (agg *TermsAggregation) Map() map[string]interface{} { if agg.showTermDoc != nil { innerMap["show_term_doc_count_error"] = *agg.showTermDoc } + if agg.order != nil { + innerMap["order"] = agg.order + } + + if agg.include != nil { + if len(agg.include) <= 1 { + innerMap["include"] = agg.include[0] + } else { + innerMap["include"] = agg.include + } + + } outerMap := map[string]interface{}{ "terms": innerMap, diff --git a/go.sum b/go.sum index 4108027..fae67ff 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA= -github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg= github.com/elastic/go-elasticsearch/v7 v7.6.0 h1:sYpGLpEFHgLUKLsZUBfuaVI9QgHjS3JdH9fX4/z8QI8= github.com/elastic/go-elasticsearch/v7 v7.6.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= -github.com/elastic/go-elasticsearch/v8 v8.0.0-20200210103600-aff00e5adfde h1:Y9SZx8RQqFycLxi5W5eFmxMqnmijULVc3LMjBTtZQdM= -github.com/elastic/go-elasticsearch/v8 v8.0.0-20200210103600-aff00e5adfde/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/jgroeneveld/schema v1.0.0 h1:J0E10CrOkiSEsw6dfb1IfrDJD14pf6QLVJ3tRPl/syI= diff --git a/search.go b/search.go index 39dabf6..5c661a2 100644 --- a/search.go +++ b/search.go @@ -15,16 +15,18 @@ import ( // Not all features of the search API are currently supported, but a request can // currently include a query, aggregations, and more. type SearchRequest struct { - aggs []Aggregation - explain *bool - from *uint64 - highlight Mappable - postFilter Mappable - query Mappable - size *uint64 - sort Sort - source Source - timeout *time.Duration + aggs []Aggregation + explain *bool + from *uint64 + highlight Mappable + searchAfter []interface{} + postFilter Mappable + query Mappable + size *uint64 + sort Sort + source Source + timeout *time.Duration + } // Search creates a new SearchRequest object, to be filled via method chaining. @@ -74,6 +76,12 @@ func (req *SearchRequest) Sort(name string, order Order) *SearchRequest { return req } +// SearchAfter retrieve the sorted result +func (req *SearchRequest) SearchAfter(s ...interface{}) *SearchRequest { + req.searchAfter = append(req.searchAfter, s...) + return req +} + // Explain sets whether the ElasticSearch API should return an explanation for // how each hit's score was calculated. func (req *SearchRequest) Explain(b bool) *SearchRequest { @@ -106,6 +114,7 @@ func (req *SearchRequest) Highlight(highlight Mappable) *SearchRequest { } + // Map implements the Mappable interface. It converts the request to into a // nested map[string]interface{}, as expected by the go-elasticsearch library. func (req *SearchRequest) Map() map[string]interface{} { @@ -142,6 +151,10 @@ func (req *SearchRequest) Map() map[string]interface{} { if req.highlight != nil { m["highlight"] = req.highlight.Map() } + if req.searchAfter != nil { + m["search_after"] = req.searchAfter + } + source := req.source.Map() if len(source) > 0 { diff --git a/search_test.go b/search_test.go index ad8a1ce..1bcf948 100644 --- a/search_test.go +++ b/search_test.go @@ -7,6 +7,13 @@ import ( func TestSearchMaps(t *testing.T) { runMapTests(t, []mapTest{ + { + "a simple query with search after", + Search().SearchAfter("_id", "name"), + map[string]interface{}{ + "search_after": []string{"_id", "name"}, + }, + }, { "a simple match_all query with a size and no aggs", Search().Query(MatchAll()).Size(20),