ruby-druid 0.1.9 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ module Druid
2
+ class Granularity
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :type
6
+ validates :type, inclusion: { in: %w(period) }
7
+
8
+ class PeriodValidator < ActiveModel::EachValidator
9
+ def validate_each(record, attribute, value)
10
+ valid = ISO8601::Duration.new(value) rescue nil
11
+ record.errors.add(attribute, 'must be a valid ISO 8601 period') unless valid
12
+ end
13
+ end
14
+
15
+ attr_accessor :period
16
+ validates :period, period: true
17
+
18
+ attr_accessor :timeZone
19
+ validates :timeZone, allow_nil: true, inclusion: {
20
+ in: ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }
21
+ }
22
+
23
+ class OriginValidator < ActiveModel::EachValidator
24
+ def validate_each(record, attribute, value)
25
+ return unless value
26
+ valid = ISO8601::DateTime.new(value) rescue nil
27
+ record.errors.add(attribute, 'must be a valid ISO 8601 time') unless valid
28
+ end
29
+ end
30
+
31
+ attr_accessor :origin
32
+ validates :origin, origin: true
33
+
34
+ def as_json(options = {})
35
+ super(options.merge(except: %w(errors validation_context)))
36
+ end
37
+
38
+ end
39
+ end
@@ -1,14 +1,68 @@
1
1
  module Druid
2
2
  class Having
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :type
6
+ validates :type, inclusion: { in: %w(and or not greaterThan lessThan equalTo) }
7
+
8
+ class HavingsValidator < ActiveModel::EachValidator
9
+ TYPES = %w(and or)
10
+ def validate_each(record, attribute, value)
11
+ if TYPES.include?(record.queryType)
12
+ value.each(&:valid?) # trigger validation
13
+ value.each do |avalue|
14
+ avalue.errors.messages.each do |k, v|
15
+ record.errors.add(attribute, { k => v })
16
+ end
17
+ end
18
+ else
19
+ record.errors.add(attribute, "is not supported by type=#{record.queryType}") if value
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_accessor :havingSpecs
25
+ validates :havingSpecs, havings: true
26
+
27
+ def havingSpecs
28
+ @havingSpecs ||= []
29
+ end
30
+
31
+ class HavingValidator < ActiveModel::EachValidator
32
+ TYPES = %w(not)
33
+ def validate_each(record, attribute, value)
34
+ if TYPES.include?(record.type)
35
+ record.errors.add(attribute, 'may not be blank') if value.blank?
36
+ else
37
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
38
+ end
39
+ end
40
+ end
41
+
42
+ attr_accessor :havingSpec
43
+ validates :havingSpec, having: true
44
+
45
+ class AggregationValidator < ActiveModel::EachValidator
46
+ TYPES = %w(greaterThan lessThan equalTo)
47
+ def validate_each(record, attribute, value)
48
+ if TYPES.include?(record.type)
49
+ record.errors.add(attribute, 'may not be blank') if value.blank?
50
+ else
51
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
52
+ end
53
+ end
54
+ end
55
+
56
+ attr_accessor :aggregation
57
+ validates :aggregation, aggregation: true
58
+
59
+ attr_accessor :value
60
+
3
61
  def method_missing(name, *args)
4
62
  if args.empty?
5
- HavingClause.new(name)
63
+ HavingClause.new(aggregation: name)
6
64
  end
7
65
  end
8
- end
9
-
10
- class HavingFilter
11
- include Serializable
12
66
 
13
67
  def clause?
14
68
  is_a?(HavingClause)
@@ -17,38 +71,80 @@ module Druid
17
71
  def operator?
18
72
  is_a?(HavingOperator)
19
73
  end
74
+
75
+ def chain(other)
76
+ return unless other
77
+ if self.operator? && self.and?
78
+ having = self
79
+ else
80
+ having = HavingOperator.new(type: 'and')
81
+ having.havingSpecs << self
82
+ end
83
+ having.havingSpecs << other
84
+ having
85
+ end
86
+
87
+ def as_json(options = {})
88
+ super(options.merge(except: %w(errors validation_context)))
89
+ end
20
90
  end
21
91
 
22
- class HavingClause < HavingFilter
23
- def initialize(metric)
24
- @metric = metric
92
+ class HavingClause < Having
93
+ def &(other)
94
+ create_operator('and', other)
95
+ end
96
+
97
+ def |(other)
98
+ create_operator('or', other)
25
99
  end
26
100
 
101
+ def !
102
+ create_operator('not')
103
+ end
104
+
105
+ def eq(value)
106
+ set_clause('equalTo', value)
107
+ end
108
+
109
+ alias :'==' :eq
110
+
111
+ def neq(value)
112
+ !eq(value)
113
+ end
114
+
115
+ alias :'!=' :neq
116
+
27
117
  def <(value)
28
- @type = "lessThan"
29
- @value = value
30
- self
118
+ set_clause('lessThan', value)
31
119
  end
32
120
 
33
121
  def >(value)
34
- @type = "greaterThan"
35
- @value = value
36
- self
122
+ set_clause('greaterThan', value)
37
123
  end
38
124
 
39
- def to_hash
40
- {
41
- :type => @type,
42
- :aggregation => @metric,
43
- :value => @value
44
- }
125
+ private
126
+
127
+ def create_operator(type, other = nil)
128
+ operator = HavingOperator.new(type: type)
129
+ if type.to_s == 'not'
130
+ operator.havingSpec = self
131
+ else
132
+ operator.havingSpecs << self
133
+ operator.havingSpecs << other if other
134
+ end
135
+ operator
45
136
  end
46
- end
47
137
 
48
- class HavingOperator < HavingFilter
49
- def initialize(type)
138
+ def set_clause(type, value)
50
139
  @type = type
51
- @elements = []
140
+ @value = value
141
+ self
142
+ end
143
+ end
144
+
145
+ class HavingOperator < Having
146
+ def and?
147
+ @type == 'and'
52
148
  end
53
149
 
54
150
  def and?
@@ -59,11 +155,35 @@ module Druid
59
155
  @elements << element
60
156
  end
61
157
 
62
- def to_hash
63
- {
64
- :type => @type,
65
- :havingSpecs => @elements
66
- }
158
+ def &(other)
159
+ apply_operator('and', other)
160
+ end
161
+
162
+ def |(other)
163
+ apply_operator('or', other)
164
+ end
165
+
166
+ def !
167
+ if @type == 'not'
168
+ @elements.first
169
+ else
170
+ operator = HavingOperator.new(type: 'not')
171
+ operator.havingSpec = self
172
+ operator
173
+ end
174
+ end
175
+
176
+ private
177
+
178
+ def apply_operator(type, other)
179
+ if @type == type
180
+ operator = self
181
+ else
182
+ operator = HavingOperator.new(type: type)
183
+ operator.havingSpecs << self
184
+ end
185
+ operator.havingSpecs << other
186
+ operator
67
187
  end
68
188
  end
69
189
  end
@@ -1,14 +1,116 @@
1
1
  module Druid
2
2
  class PostAggregation
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :numBuckets
6
+
7
+ attr_accessor :type
8
+ validates :type, inclusion: { in: %w(arithmetic fieldAccess constant javascript hyperUniqueCardinality) }
9
+
10
+ class NameValidator < ActiveModel::EachValidator
11
+ TYPES = %w(arithmetic constant javascript)
12
+ def validate_each(record, attribute, value)
13
+ if !TYPES.include?(record.type)
14
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_accessor :name
20
+ validates :name, name: true
21
+
22
+ class FnValidator < ActiveModel::EachValidator
23
+ TYPES = %w(arithmetic)
24
+ def validate_each(record, attribute, value)
25
+ if TYPES.include?(record.type)
26
+ record.errors.add(attribute, 'must be a valid arithmetic operation') unless %w(+ - * /).include?(value)
27
+ else
28
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
29
+ end
30
+ end
31
+ end
32
+
33
+ attr_accessor :fn
34
+ validates :fn, fn: true
35
+
36
+ class FieldsValidator < ActiveModel::EachValidator
37
+ TYPES = %w(arithmetic)
38
+ def validate_each(record, attribute, value)
39
+ if TYPES.include?(record.type)
40
+ value.each(&:valid?) # trigger validation
41
+ value.each do |fvalue|
42
+ fvalue.errors.messages.each do |k, v|
43
+ record.errors.add(attribute, { k => v })
44
+ end
45
+ end
46
+ else
47
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
48
+ end
49
+ end
50
+ end
51
+
52
+ attr_accessor :fields
53
+ validates :fields, fields: true
54
+
55
+ def fields=(value)
56
+ @fields = [value].flatten.compact.map do |aggregation|
57
+ PostAggregation.new(aggregation)
58
+ end
59
+ end
60
+
61
+ class FieldnameValidator < ActiveModel::EachValidator
62
+ TYPES = %w(fieldAccess hyperUniqueCardinality)
63
+ def validate_each(record, attribute, value)
64
+ if TYPES.include?(record.type)
65
+ record.errors.add(attribute, 'may not be blank') if value.blank?
66
+ else
67
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
68
+ end
69
+ end
70
+ end
71
+
72
+ attr_accessor :fieldName
73
+ validates :fieldName, fieldname: true
74
+
75
+ class ValueValidator < ActiveModel::EachValidator
76
+ TYPES = %w(constant)
77
+ def validate_each(record, attribute, value)
78
+ if TYPES.include?(record.type)
79
+ record.errors.add(attribute, 'must be numeric') if !value.is_a?(Numeric)
80
+ else
81
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
82
+ end
83
+ end
84
+ end
85
+
86
+ attr_accessor :value
87
+ validates :value, value: true
88
+
89
+ class FunctionValidator < ActiveModel::EachValidator
90
+ TYPES = %w(javascript)
91
+ def validate_each(record, attribute, value)
92
+ if TYPES.include?(record.type)
93
+ record.errors.add(attribute, 'may not be blank') if value.blank?
94
+ else
95
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
96
+ end
97
+ end
98
+ end
99
+
100
+ attr_accessor :function
101
+ validates :function, function: true
102
+
103
+ attr_reader :fieldNames
104
+
3
105
  def method_missing(name, *args)
4
106
  if args.empty?
5
- PostAggregationField.new(name)
107
+ PostAggregationField.new(fieldName: name)
6
108
  end
7
109
  end
8
110
 
9
111
  def js(*args)
10
112
  if args.empty?
11
- PostAggregationField.new(:js)
113
+ PostAggregationField.new(fieldName: :js)
12
114
  else
13
115
  PostAggregationJavascript.new(args.first)
14
116
  end
@@ -33,125 +135,137 @@ module Druid
33
135
  end
34
136
  end
35
137
 
36
- class PostAggregationOperation
138
+ class PostAggregationOperation < PostAggregation
37
139
  include PostAggregationOperators
38
140
 
39
- attr_reader :left, :operator, :right, :name
40
-
41
141
  def initialize(left, operator, right)
42
- @left = left.is_a?(Numeric) ? PostAggregationConstant.new(left) : left
43
- @operator = operator
44
- @right = right.is_a?(Numeric) ? PostAggregationConstant.new(right) : right
142
+ super()
143
+ @type = 'arithmetic'
144
+ @fn = operator
145
+ @fields = [
146
+ left.is_a?(Numeric) ? PostAggregationConstant.new(value: left) : left,
147
+ right.is_a?(Numeric) ? PostAggregationConstant.new(value: right) : right,
148
+ ]
45
149
  end
46
150
 
47
151
  def as(field)
48
- @name = field.name.to_s
152
+ @name = field.fieldName.to_s
49
153
  self
50
154
  end
51
155
 
52
- def get_field_names
53
- field_names = []
54
- field_names << left.get_field_names if left.respond_to?(:get_field_names)
55
- field_names << right.get_field_names if right.respond_to?(:get_field_names)
56
- field_names
57
- end
58
-
59
- def to_hash
60
- hash = { "type" => "arithmetic", "fn" => @operator, "fields" => [@left.to_hash, @right.to_hash] }
61
- hash["name"] = @name if @name
62
- hash
63
- end
64
-
65
- def to_json(*a)
66
- to_hash.to_json(*a)
67
- end
68
-
69
- def as_json(*a)
70
- to_hash
156
+ def field_names
157
+ fields.map(&:field_names).flatten.compact.uniq
71
158
  end
72
159
  end
73
160
 
74
- class PostAggregationField
161
+ class PostAggregationField < PostAggregation
75
162
  include PostAggregationOperators
76
163
 
77
- attr_reader :name
78
-
79
- def initialize(name)
80
- @name = name
164
+ def initialize(attributes = {})
165
+ super
166
+ @type = 'fieldAccess'
81
167
  end
82
168
 
83
- def get_field_names
84
- @name
169
+ def field_names
170
+ [@fieldName]
85
171
  end
172
+ end
86
173
 
87
- def to_hash
88
- { "type" => "fieldAccess", "name" => @name, "fieldName" => @name }
89
- end
174
+ class PostAggregationConstant < PostAggregation
175
+ include PostAggregationOperators
90
176
 
91
- def to_json(*a)
92
- to_hash.to_json(*a)
177
+ def initialize(attributes = {})
178
+ super
179
+ @type = 'constant'
93
180
  end
94
181
 
95
- def as_json(*a)
96
- to_hash
182
+ def field_names
183
+ []
97
184
  end
98
185
  end
99
186
 
100
- class PostAggregationConstant
187
+ class PostAggregationJavascript < PostAggregation
101
188
  include PostAggregationOperators
102
189
 
103
- attr_reader :value
104
-
105
- def initialize(value)
106
- @value = value
190
+ def initialize(function)
191
+ super()
192
+ @type = 'javascript'
193
+ @fieldNames = extract_fields(function)
194
+ @function = function
107
195
  end
108
196
 
109
- def to_hash
110
- { "type" => "constant", "value" => @value }
197
+ def field_names
198
+ @fieldNames
111
199
  end
112
200
 
113
- def to_json(*a)
114
- to_hash.to_json(*a)
201
+ def as(field)
202
+ @name = field.fieldName.to_s
203
+ self
115
204
  end
116
205
 
117
- def as_json(*a)
118
- to_hash
206
+ private
207
+
208
+ def extract_fields(function)
209
+ match = function.match(/function\((.+)\)/)
210
+ raise 'Invalid Javascript function' unless match && match.captures
211
+ match.captures.first.split(',').map(&:strip)
119
212
  end
120
213
  end
121
214
 
122
- class PostAggregationJavascript
123
- include PostAggregationOperators
124
- include Serializable
215
+ class PostAggregationHistogramEqualBuckets < PostAggregation
216
+ attr_accessor :numBuckets
217
+ def initialize(attributes = {})
218
+ super(attributes)
219
+ @type = "equalBuckets"
220
+ @numBuckets ||= 10
221
+ end
222
+ end
125
223
 
126
- def initialize(function)
127
- @field_names = extract_fields(function)
128
- @function = function
224
+ class PostAggregationHistogramBuckets < PostAggregation
225
+ attr_accessor :bucketSize
226
+ attr_accessor :offset
227
+ def initialize(attributes = {})
228
+ super
229
+ @type = "buckets"
129
230
  end
231
+ end
130
232
 
131
- def get_field_names
132
- @field_names
233
+ class PostAggregationHistogramCustomBuckets < PostAggregation
234
+ attr_accessor :breaks
235
+ def initialize(attributes = {})
236
+ super
237
+ @type = "customBuckets"
133
238
  end
239
+ end
134
240
 
135
- def as(field)
136
- @name = field.name.to_s
137
- self
241
+ class PostAggregationHistogramMin < PostAggregation
242
+ def initialize(attributes = {})
243
+ super
244
+ @type = "min"
138
245
  end
246
+ end
139
247
 
140
- def to_hash
141
- {
142
- "type" => "javascript",
143
- "name" => @name,
144
- "fieldNames" => @field_names,
145
- "function" => @function
146
- }
248
+ class PostAggregationHistogramMax < PostAggregation
249
+ def initialize(attributes = {})
250
+ super
251
+ @type = "max"
147
252
  end
253
+ end
148
254
 
149
- private
255
+ class PostAggregationHistogramQuantile < PostAggregation
256
+ attr_accessor :propability
257
+ def initialize(attributes = {})
258
+ super
259
+ @type = "quantile"
260
+ end
261
+ end
150
262
 
151
- def extract_fields(function)
152
- match = function.match(/function\((.+)\)/)
153
- raise 'Invalid Javascript function' unless match && match.captures
154
- match.captures.first.split(',').map {|field| field.strip }
263
+ class PostAggregationHistogramQuantiles < PostAggregation
264
+ attr_accessor :probabilities
265
+ def initialize(attributes = {})
266
+ super
267
+ @type = "quantiles"
155
268
  end
156
269
  end
157
- end
270
+
271
+ end