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 (
8	"time"
9)
10
11// DateRangeAggregation is a range aggregation that is dedicated for
12// date values. The main difference between this aggregation and the
13// normal range aggregation is that the from and to values can be expressed
14// in Date Math expressions, and it is also possible to specify a
15// date format by which the from and to response fields will be returned.
16// Note that this aggregration includes the from value and excludes the to
17// value for each range.
18// See: https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-aggregations-bucket-daterange-aggregation.html
19type DateRangeAggregation struct {
20	field           string
21	script          *Script
22	subAggregations map[string]Aggregation
23	meta            map[string]interface{}
24	keyed           *bool
25	unmapped        *bool
26	format          string
27	entries         []DateRangeAggregationEntry
28}
29
30type DateRangeAggregationEntry struct {
31	Key  string
32	From interface{}
33	To   interface{}
34}
35
36func NewDateRangeAggregation() *DateRangeAggregation {
37	return &DateRangeAggregation{
38		subAggregations: make(map[string]Aggregation),
39		entries:         make([]DateRangeAggregationEntry, 0),
40	}
41}
42
43func (a *DateRangeAggregation) Field(field string) *DateRangeAggregation {
44	a.field = field
45	return a
46}
47
48func (a *DateRangeAggregation) Script(script *Script) *DateRangeAggregation {
49	a.script = script
50	return a
51}
52
53func (a *DateRangeAggregation) SubAggregation(name string, subAggregation Aggregation) *DateRangeAggregation {
54	a.subAggregations[name] = subAggregation
55	return a
56}
57
58// Meta sets the meta data to be included in the aggregation response.
59func (a *DateRangeAggregation) Meta(metaData map[string]interface{}) *DateRangeAggregation {
60	a.meta = metaData
61	return a
62}
63
64func (a *DateRangeAggregation) Keyed(keyed bool) *DateRangeAggregation {
65	a.keyed = &keyed
66	return a
67}
68
69func (a *DateRangeAggregation) Unmapped(unmapped bool) *DateRangeAggregation {
70	a.unmapped = &unmapped
71	return a
72}
73
74func (a *DateRangeAggregation) Format(format string) *DateRangeAggregation {
75	a.format = format
76	return a
77}
78
79func (a *DateRangeAggregation) AddRange(from, to interface{}) *DateRangeAggregation {
80	a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: to})
81	return a
82}
83
84func (a *DateRangeAggregation) AddRangeWithKey(key string, from, to interface{}) *DateRangeAggregation {
85	a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: to})
86	return a
87}
88
89func (a *DateRangeAggregation) AddUnboundedTo(from interface{}) *DateRangeAggregation {
90	a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: nil})
91	return a
92}
93
94func (a *DateRangeAggregation) AddUnboundedToWithKey(key string, from interface{}) *DateRangeAggregation {
95	a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: nil})
96	return a
97}
98
99func (a *DateRangeAggregation) AddUnboundedFrom(to interface{}) *DateRangeAggregation {
100	a.entries = append(a.entries, DateRangeAggregationEntry{From: nil, To: to})
101	return a
102}
103
104func (a *DateRangeAggregation) AddUnboundedFromWithKey(key string, to interface{}) *DateRangeAggregation {
105	a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: nil, To: to})
106	return a
107}
108
109func (a *DateRangeAggregation) Lt(to interface{}) *DateRangeAggregation {
110	a.entries = append(a.entries, DateRangeAggregationEntry{From: nil, To: to})
111	return a
112}
113
114func (a *DateRangeAggregation) LtWithKey(key string, to interface{}) *DateRangeAggregation {
115	a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: nil, To: to})
116	return a
117}
118
119func (a *DateRangeAggregation) Between(from, to interface{}) *DateRangeAggregation {
120	a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: to})
121	return a
122}
123
124func (a *DateRangeAggregation) BetweenWithKey(key string, from, to interface{}) *DateRangeAggregation {
125	a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: to})
126	return a
127}
128
129func (a *DateRangeAggregation) Gt(from interface{}) *DateRangeAggregation {
130	a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: nil})
131	return a
132}
133
134func (a *DateRangeAggregation) GtWithKey(key string, from interface{}) *DateRangeAggregation {
135	a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: nil})
136	return a
137}
138
139func (a *DateRangeAggregation) Source() (interface{}, error) {
140	// Example:
141	// {
142	//     "aggs" : {
143	//         "range" : {
144	//             "date_range": {
145	//                 "field": "date",
146	//                 "format": "MM-yyy",
147	//                 "ranges": [
148	//                     { "to": "now-10M/M" },
149	//                     { "from": "now-10M/M" }
150	//                 ]
151	//             }
152	//         }
153	//         }
154	//     }
155	// }
156	//
157	// This method returns only the { "date_range" : { ... } } part.
158
159	source := make(map[string]interface{})
160	opts := make(map[string]interface{})
161	source["date_range"] = opts
162
163	// ValuesSourceAggregationBuilder
164	if a.field != "" {
165		opts["field"] = a.field
166	}
167	if a.script != nil {
168		src, err := a.script.Source()
169		if err != nil {
170			return nil, err
171		}
172		opts["script"] = src
173	}
174
175	if a.keyed != nil {
176		opts["keyed"] = *a.keyed
177	}
178	if a.unmapped != nil {
179		opts["unmapped"] = *a.unmapped
180	}
181	if a.format != "" {
182		opts["format"] = a.format
183	}
184
185	var ranges []interface{}
186	for _, ent := range a.entries {
187		r := make(map[string]interface{})
188		if ent.Key != "" {
189			r["key"] = ent.Key
190		}
191		if ent.From != nil {
192			switch from := ent.From.(type) {
193			case int, int16, int32, int64, float32, float64:
194				r["from"] = from
195			case *int, *int16, *int32, *int64, *float32, *float64:
196				r["from"] = from
197			case time.Time:
198				r["from"] = from.Format(time.RFC3339)
199			case *time.Time:
200				r["from"] = from.Format(time.RFC3339)
201			case string:
202				r["from"] = from
203			case *string:
204				r["from"] = from
205			}
206		}
207		if ent.To != nil {
208			switch to := ent.To.(type) {
209			case int, int16, int32, int64, float32, float64:
210				r["to"] = to
211			case *int, *int16, *int32, *int64, *float32, *float64:
212				r["to"] = to
213			case time.Time:
214				r["to"] = to.Format(time.RFC3339)
215			case *time.Time:
216				r["to"] = to.Format(time.RFC3339)
217			case string:
218				r["to"] = to
219			case *string:
220				r["to"] = to
221			}
222		}
223		ranges = append(ranges, r)
224	}
225	opts["ranges"] = ranges
226
227	// AggregationBuilder (SubAggregations)
228	if len(a.subAggregations) > 0 {
229		aggsMap := make(map[string]interface{})
230		source["aggregations"] = aggsMap
231		for name, aggregate := range a.subAggregations {
232			src, err := aggregate.Source()
233			if err != nil {
234				return nil, err
235			}
236			aggsMap[name] = src
237		}
238	}
239
240	// Add Meta data if available
241	if len(a.meta) > 0 {
242		source["meta"] = a.meta
243	}
244
245	return source, nil
246}
247