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.
- 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
|