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.
- checksums.yaml +4 -4
- data/LICENSE +18 -16
- data/README.md +78 -111
- data/lib/druid.rb +0 -6
- data/lib/druid/aggregation.rb +66 -0
- data/lib/druid/client.rb +10 -82
- data/lib/druid/context.rb +37 -0
- data/lib/druid/data_source.rb +95 -0
- data/lib/druid/filter.rb +228 -172
- data/lib/druid/granularity.rb +39 -0
- data/lib/druid/having.rb +149 -29
- data/lib/druid/post_aggregation.rb +191 -77
- data/lib/druid/query.rb +422 -156
- data/lib/druid/version.rb +3 -0
- data/lib/druid/zk.rb +141 -0
- data/ruby-druid.gemspec +24 -12
- data/spec/lib/client_spec.rb +14 -61
- data/spec/lib/data_source_spec.rb +65 -0
- data/spec/lib/query_spec.rb +359 -250
- data/spec/lib/{zoo_handler_spec.rb → zk_spec.rb} +51 -66
- metadata +142 -34
- data/.gitignore +0 -6
- data/.rspec +0 -2
- data/.travis.yml +0 -9
- data/Gemfile +0 -12
- data/Rakefile +0 -2
- data/bin/dripl +0 -38
- data/dot_driplrc_example +0 -12
- data/lib/druid/console.rb +0 -74
- data/lib/druid/response_row.rb +0 -32
- data/lib/druid/serializable.rb +0 -19
- data/lib/druid/zoo_handler.rb +0 -129
@@ -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
|
data/lib/druid/having.rb
CHANGED
@@ -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 <
|
23
|
-
def
|
24
|
-
|
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
|
-
|
29
|
-
@value = value
|
30
|
-
self
|
118
|
+
set_clause('lessThan', value)
|
31
119
|
end
|
32
120
|
|
33
121
|
def >(value)
|
34
|
-
|
35
|
-
@value = value
|
36
|
-
self
|
122
|
+
set_clause('greaterThan', value)
|
37
123
|
end
|
38
124
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
49
|
-
def initialize(type)
|
138
|
+
def set_clause(type, value)
|
50
139
|
@type = type
|
51
|
-
@
|
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
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
43
|
-
@
|
44
|
-
@
|
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.
|
152
|
+
@name = field.fieldName.to_s
|
49
153
|
self
|
50
154
|
end
|
51
155
|
|
52
|
-
def
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@name = name
|
164
|
+
def initialize(attributes = {})
|
165
|
+
super
|
166
|
+
@type = 'fieldAccess'
|
81
167
|
end
|
82
168
|
|
83
|
-
def
|
84
|
-
@
|
169
|
+
def field_names
|
170
|
+
[@fieldName]
|
85
171
|
end
|
172
|
+
end
|
86
173
|
|
87
|
-
|
88
|
-
|
89
|
-
end
|
174
|
+
class PostAggregationConstant < PostAggregation
|
175
|
+
include PostAggregationOperators
|
90
176
|
|
91
|
-
def
|
92
|
-
|
177
|
+
def initialize(attributes = {})
|
178
|
+
super
|
179
|
+
@type = 'constant'
|
93
180
|
end
|
94
181
|
|
95
|
-
def
|
96
|
-
|
182
|
+
def field_names
|
183
|
+
[]
|
97
184
|
end
|
98
185
|
end
|
99
186
|
|
100
|
-
class
|
187
|
+
class PostAggregationJavascript < PostAggregation
|
101
188
|
include PostAggregationOperators
|
102
189
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
@
|
190
|
+
def initialize(function)
|
191
|
+
super()
|
192
|
+
@type = 'javascript'
|
193
|
+
@fieldNames = extract_fields(function)
|
194
|
+
@function = function
|
107
195
|
end
|
108
196
|
|
109
|
-
def
|
110
|
-
|
197
|
+
def field_names
|
198
|
+
@fieldNames
|
111
199
|
end
|
112
200
|
|
113
|
-
def
|
114
|
-
|
201
|
+
def as(field)
|
202
|
+
@name = field.fieldName.to_s
|
203
|
+
self
|
115
204
|
end
|
116
205
|
|
117
|
-
|
118
|
-
|
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
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
241
|
+
class PostAggregationHistogramMin < PostAggregation
|
242
|
+
def initialize(attributes = {})
|
243
|
+
super
|
244
|
+
@type = "min"
|
138
245
|
end
|
246
|
+
end
|
139
247
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
255
|
+
class PostAggregationHistogramQuantile < PostAggregation
|
256
|
+
attr_accessor :propability
|
257
|
+
def initialize(attributes = {})
|
258
|
+
super
|
259
|
+
@type = "quantile"
|
260
|
+
end
|
261
|
+
end
|
150
262
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
263
|
+
class PostAggregationHistogramQuantiles < PostAggregation
|
264
|
+
attr_accessor :probabilities
|
265
|
+
def initialize(attributes = {})
|
266
|
+
super
|
267
|
+
@type = "quantiles"
|
155
268
|
end
|
156
269
|
end
|
157
|
-
|
270
|
+
|
271
|
+
end
|