ruby-druid 0.1.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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