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