ruby-druid 0.10.0 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ef87663c236bf89b7937fef4c7709d3937e5f0e6
4
- data.tar.gz: 82d29a256a0cebc175aeb9d88173f1455e9a8475
3
+ metadata.gz: 50802fa6fd17c18928330f89aa8a9e8aa1b552c9
4
+ data.tar.gz: 52827d8bb44dc95de2335f9792ab79f211ed0ad0
5
5
  SHA512:
6
- metadata.gz: a81d42c823310788cf35fcde9c683674fd863ea3f71557d1ab0e3c01d21f66cdeedd601a816deced10367b27db19d625fc19803d6463bfdaea3aa42f063f8875
7
- data.tar.gz: 1a0c049e54420fc1c6773e489cbef4a118f751f4e5c9172377ace9474173dab10cba6d41253056e305e5c4156c02cc8594814f1138852ad7806272c249532583
6
+ metadata.gz: 9606e256ba1341ba5de30371eb5a65307506131be89ebff99cbc21e2546f5e692a8487b89ad8d410e8491cfc2980dec00a79476400e1091a8cf92247726ab0c2
7
+ data.tar.gz: 82a3948ec8332ff637ba32b0a981fb07fdce8efc7e44c5d59a5c37ca70d5cd27d406988bb8c74071936ac28ec3a3cbfeef07a5453cad2825796413e35db5c1f0
data/README.md CHANGED
@@ -29,32 +29,22 @@ gem install ruby-druid
29
29
 
30
30
  ## Usage
31
31
 
32
- ```ruby
33
- Druid::Client.new('zk1:2181,zk2:2181/druid').query('service/source')
34
- ```
35
-
36
- returns a query object on which all other methods can be called to create a full and valid Druid query.
37
-
38
- A query object can be sent like this:
32
+ A query can be constructed and sent like so:
39
33
 
40
34
  ```ruby
41
- client = Druid::Client.new('zk1:2181,zk2:2181/druid')
42
- query = Druid::Query.new('service/source')
43
- client.send(query)
35
+ data_source = Druid::Client.new('zk1:2181,zk2:2181/druid').data_source('service/source')
36
+ query = Druid::Query::Builder.new.long_sum(:aggregate1).last(1.day).granularity(:all)
37
+ result = data_source.post(query)
44
38
  ```
45
39
 
46
- The `send` method returns the parsed response from the druid server as an array. If the response is not empty it contains one `ResponseRow` object for each row. The timestamp by can be received by a method with the same name (i.e. `row.timestamp`), all row values by hashlike syntax (i.e. `row['dimension'])
40
+ The `post` method on the `DataSource` returns the parsed response from the Druid server as an array.
47
41
 
48
- An options hash can be passed when creating `Druid::Client` instance:
42
+ If you don't want to use ZooKeeper for broker discovery, you can explicitly construct a `DataSource`:
49
43
 
50
44
  ```ruby
51
- client = Druid::Client.new('zk1:2181,zk2:2181/druid', http_timeout: 20)
45
+ data_source = Druid::DataSource.new('service/source', 'http://localhost:8080/druid/v2')
52
46
  ```
53
47
 
54
- Supported options are:
55
- * `static_setup` to explicitly specify a broker url, e.g. `static_setup: { 'my/source_name' => 'http://1.2.3.4:8080/druid/v2/' }`
56
- * `http_timeout` to define a timeout for sending http queries to a broker (in minutes, default value is 2)
57
-
58
48
  ### GroupBy
59
49
 
60
50
  A [GroupByQuery](http://druid.io/docs/latest/querying/groupbyquery.html) sets the
@@ -63,7 +53,7 @@ dimensions to group the data.
63
53
  `queryType` is set automatically to `groupBy`.
64
54
 
65
55
  ```ruby
66
- Druid::Query.new('service/source').group_by([:dimension1, :dimension2])
56
+ Druid::Query::Builder.new.group_by([:dimension1, :dimension2])
67
57
  ```
68
58
 
69
59
  ### TimeSeries
@@ -71,7 +61,7 @@ Druid::Query.new('service/source').group_by([:dimension1, :dimension2])
71
61
  A [TimeSeriesQuery](http://druid.io/docs/latest/querying/timeseriesquery.html) returns an array of JSON objects where each object represents a value asked for by the timeseries query.
72
62
 
73
63
  ```ruby
74
- Druid::Query.new('service/source').time_series([:aggregate1, :aggregate2])
64
+ Druid::Query::Builder.new.time_series([:aggregate1, :aggregate2])
75
65
  ```
76
66
 
77
67
  ### Aggregations
@@ -79,7 +69,7 @@ Druid::Query.new('service/source').time_series([:aggregate1, :aggregate2])
79
69
  #### longSum, doubleSum, count, min, max, hyperUnique
80
70
 
81
71
  ```ruby
82
- Druid::Query.new('service/source').long_sum([:aggregate1, :aggregate2])
72
+ Druid::Query::Builder.new.long_sum([:aggregate1, :aggregate2])
83
73
  ```
84
74
 
85
75
  In the same way could be used the following methods for [aggregations](http://druid.io/docs/latest/querying/aggregations.html) adding: `double_sum, count, min, max, hyper_unique`
@@ -87,7 +77,7 @@ In the same way could be used the following methods for [aggregations](http://dr
87
77
  #### cardinality
88
78
 
89
79
  ```ruby
90
- Druid::Query.new('service/source').cardinality(:aggregate, [:dimension1, dimension2], <by_row: true | false>)
80
+ Druid::Query::Builder.new.cardinality(:aggregate, [:dimension1, dimension2], <by_row: true | false>)
91
81
  ```
92
82
 
93
83
  #### javascript
@@ -95,19 +85,29 @@ Druid::Query.new('service/source').cardinality(:aggregate, [:dimension1, dimensi
95
85
  For example calculation for `sum(log(x)/y) + 10`:
96
86
 
97
87
  ```ruby
98
- Druid::Query.new('service/source').js_aggregation(:aggregate, [:x, :y],
88
+ Druid::Query::Builder.new.js_aggregation(:aggregate, [:x, :y],
99
89
  aggregate: "function(current, a, b) { return current + (Math.log(a) * b); }",
100
90
  combine: "function(partialA, partialB) { return partialA + partialB; }",
101
91
  reset: "function() { return 10; }"
102
92
  )
103
93
  ```
104
94
 
95
+ #### filtered aggregation
96
+
97
+ A filtered aggregator wraps any given aggregator, but only aggregates the values for which the given dimension filter matches.
98
+
99
+ ```ruby
100
+ Druid::Query::Builder.new.filtered_aggregation(:aggregate1, :aggregate_1_name, :longSum) do
101
+ dimension1.neq 1 & dimension2.neq 2
102
+ end
103
+ ```
104
+
105
105
  ### Post Aggregations
106
106
 
107
107
  A simple syntax for post aggregations with +,-,/,* can be used like:
108
108
 
109
109
  ```ruby
110
- query = Druid::Query.new('service/source').long_sum([:aggregate1, :aggregate2])
110
+ query = Druid::Query::Builder.new.long_sum([:aggregate1, :aggregate2])
111
111
  query.postagg { (aggregate2 + aggregate2).as output_field_name }
112
112
  ```
113
113
 
@@ -124,7 +124,7 @@ query.postagg { js('function(aggregate1, aggregate2) { return aggregate1 + aggre
124
124
  The interval for the query takes a string with date and time or objects that provide an `iso8601` method.
125
125
 
126
126
  ```ruby
127
- query = Druid::Query.new('service/source').long_sum(:aggregate1)
127
+ query = Druid::Query::Builder.new.long_sum(:aggregate1)
128
128
  query.interval("2013-01-01T00", Time.now)
129
129
  ```
130
130
 
@@ -139,14 +139,14 @@ The period `'day'` or `:day` will be interpreted as `'P1D'`.
139
139
  If a period granularity is specifed, the (optional) second parameter is a time zone. It defaults to the machines local time zone. i.e.
140
140
 
141
141
  ```ruby
142
- query = Druid::Query.new('service/source').long_sum(:aggregate1)
142
+ query = Druid::Query::Builder.new.long_sum(:aggregate1)
143
143
  query.granularity(:day)
144
144
  ```
145
145
 
146
146
  is (on my box) the same as
147
147
 
148
148
  ```ruby
149
- query = Druid::Query.new('service/source').long_sum(:aggregate1)
149
+ query = Druid::Query::Builder.new.long_sum(:aggregate1)
150
150
  query.granularity('P1D', 'Europe/Berlin')
151
151
  ```
152
152
 
@@ -154,18 +154,18 @@ query.granularity('P1D', 'Europe/Berlin')
154
154
 
155
155
  ```ruby
156
156
  # equality
157
- Druid::Query.new('service/source').having { metric == 10 }
157
+ Druid::Query::Builder.new.having { metric == 10 }
158
158
  ```
159
159
 
160
160
  ```ruby
161
161
  # inequality
162
- Druid::Query.new('service/source').having { metric != 10 }
162
+ Druid::Query::Builder.new.having { metric != 10 }
163
163
  ```
164
164
 
165
165
  ```ruby
166
166
  # greater, less
167
- Druid::Query.new('service/source').having { metric > 10 }
168
- Druid::Query.new('service/source').having { metric < 10 }
167
+ Druid::Query::Builder.new.having { metric > 10 }
168
+ Druid::Query::Builder.new.having { metric < 10 }
169
169
  ```
170
170
 
171
171
  #### Compound having filters
@@ -174,17 +174,17 @@ Having filters can be combined with boolean logic.
174
174
 
175
175
  ```ruby
176
176
  # and
177
- Druid::Query.new('service/source').having { (metric != 1) & (metric2 != 2) }
177
+ Druid::Query::Builder.new.having { (metric != 1) & (metric2 != 2) }
178
178
  ```
179
179
 
180
180
  ```ruby
181
181
  # or
182
- Druid::Query.new('service/source').having { (metric == 1) | (metric2 == 2) }
182
+ Druid::Query::Builder.new.having { (metric == 1) | (metric2 == 2) }
183
183
  ```
184
184
 
185
185
  ```ruby
186
186
  # not
187
- Druid::Query.new('service/source').having{ !metric.eq(1) }
187
+ Druid::Query::Builder.new.having{ !metric.eq(1) }
188
188
  ```
189
189
 
190
190
  ### Filters
@@ -197,27 +197,27 @@ Filters can be chained `filter{...}.filter{...}`
197
197
 
198
198
  ```ruby
199
199
  # equality
200
- Druid::Query.new('service/source').filter{dimension.eq 1}
201
- Druid::Query.new('service/source').filter{dimension == 1}
200
+ Druid::Query::Builder.new.filter{dimension.eq 1}
201
+ Druid::Query::Builder.new.filter{dimension == 1}
202
202
  ```
203
203
 
204
204
  ```ruby
205
205
  # inequality
206
- Druid::Query.new('service/source').filter{dimension.neq 1}
207
- Druid::Query.new('service/source').filter{dimension != 1}
206
+ Druid::Query::Builder.new.filter{dimension.neq 1}
207
+ Druid::Query::Builder.new.filter{dimension != 1}
208
208
  ```
209
209
 
210
210
  ```ruby
211
211
  # greater, less
212
- Druid::Query.new('service/source').filter{dimension > 1}
213
- Druid::Query.new('service/source').filter{dimension >= 1}
214
- Druid::Query.new('service/source').filter{dimension < 1}
215
- Druid::Query.new('service/source').filter{dimension <= 1}
212
+ Druid::Query::Builder.new.filter{dimension > 1}
213
+ Druid::Query::Builder.new.filter{dimension >= 1}
214
+ Druid::Query::Builder.new.filter{dimension < 1}
215
+ Druid::Query::Builder.new.filter{dimension <= 1}
216
216
  ```
217
217
 
218
218
  ```ruby
219
219
  # JavaScript
220
- Druid::Query.new('service/source').filter{a.javascript('dimension >= 1 && dimension < 5')}
220
+ Druid::Query::Builder.new.filter{a.javascript('dimension >= 1 && dimension < 5')}
221
221
  ```
222
222
 
223
223
  #### Compound Filters
@@ -226,17 +226,17 @@ Filters can be combined with boolean logic.
226
226
 
227
227
  ```ruby
228
228
  # and
229
- Druid::Query.new('service/source').filter{dimension.neq 1 & dimension2.neq 2}
229
+ Druid::Query::Builder.new.filter{dimension.neq 1 & dimension2.neq 2}
230
230
  ```
231
231
 
232
232
  ```ruby
233
233
  # or
234
- Druid::Query.new('service/source').filter{dimension.neq 1 | dimension2.neq 2}
234
+ Druid::Query::Builder.new.filter{dimension.neq 1 | dimension2.neq 2}
235
235
  ```
236
236
 
237
237
  ```ruby
238
238
  # not
239
- Druid::Query.new('service/source').filter{!dimension.eq(1)}
239
+ Druid::Query::Builder.new.filter{!dimension.eq(1)}
240
240
  ```
241
241
 
242
242
  #### Inclusion Filter
@@ -244,18 +244,18 @@ Druid::Query.new('service/source').filter{!dimension.eq(1)}
244
244
  This filter creates a set of equals filters in an or filter.
245
245
 
246
246
  ```ruby
247
- Druid::Query.new('service/source').filter{dimension.in(1,2,3)}
247
+ Druid::Query::Builder.new.filter{dimension.in(1,2,3)}
248
248
  ```
249
249
  #### Geographic filter
250
250
 
251
251
  These filters have to be combined with time_series and do only work when coordinates is a spatial dimension [GeographicQueries](http://druid.io/docs/latest/development/geo.html)
252
252
 
253
253
  ```ruby
254
- Druid::Query.new('service/source').time_series().long_sum([:aggregate1]).filter{coordinates.in_rec [[50.0,13.0],[54.0,15.0]]}
254
+ Druid::Query::Builder.new.time_series().long_sum([:aggregate1]).filter{coordinates.in_rec [[50.0,13.0],[54.0,15.0]]}
255
255
  ```
256
256
 
257
257
  ```ruby
258
- Druid::Query.new('service/source').time_series().long_sum([:aggregate1]).filter{coordinates.in_circ [[53.0,13.0], 5.0]}
258
+ Druid::Query::Builder.new.time_series().long_sum([:aggregate1]).filter{coordinates.in_circ [[53.0,13.0], 5.0]}
259
259
  ```
260
260
 
261
261
  #### Exclusion Filter
@@ -263,7 +263,7 @@ Druid::Query.new('service/source').time_series().long_sum([:aggregate1]).filter{
263
263
  This filter creates a set of not-equals fitlers in an and filter.
264
264
 
265
265
  ```ruby
266
- Druid::Query.new('service/source').filter{dimension.nin(1,2,3)}
266
+ Druid::Query::Builder.new.filter{dimension.nin(1,2,3)}
267
267
  ```
268
268
 
269
269
  #### Hash syntax
@@ -271,11 +271,18 @@ Druid::Query.new('service/source').filter{dimension.nin(1,2,3)}
271
271
  Sometimes it can be useful to use a hash syntax for filtering for example if you already get them from a list or parameter hash.
272
272
 
273
273
  ```ruby
274
- Druid::Query.new('service/source').filter{dimension => 1, dimension1 =>2, dimension2 => 3}
274
+ Druid::Query::Builder.new.filter{dimension => 1, dimension1 =>2, dimension2 => 3}
275
275
  # which is equivalent to
276
- Druid::Query.new('service/source').filter{dimension.eq(1) & dimension1.eq(2) & dimension2.eq(3)}
276
+ Druid::Query::Builder.new.filter{dimension.eq(1) & dimension1.eq(2) & dimension2.eq(3)}
277
277
  ```
278
278
 
279
+ ## Instrumentation
280
+
281
+ Provides a single event `post.druid`. Payload:
282
+
283
+ - `data_source`
284
+ - `query`
285
+
279
286
  ## Contributing
280
287
 
281
288
  1. Fork it
@@ -59,6 +59,34 @@ module Druid
59
59
  attr_accessor :byRow
60
60
  validates :byRow, allow_nil: true, inclusion: { in: [true, false] }
61
61
 
62
+ class FilterValidator < ActiveModel::EachValidator
63
+ TYPES = %w[filtered].freeze
64
+ def validate_each(record, attribute, value)
65
+ if TYPES.include?(record.type)
66
+ record.errors.add(attribute, 'may not be blank') if value.blank?
67
+ else
68
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
69
+ end
70
+ end
71
+ end
72
+
73
+ attr_accessor :filter
74
+ validates :filter, filter: true
75
+
76
+ class AggregatorValidator < ActiveModel::EachValidator
77
+ TYPES = %w[filtered].freeze
78
+ def validate_each(record, attribute, value)
79
+ if TYPES.include?(record.type)
80
+ record.errors.add(attribute, 'may not be blank') if value.blank?
81
+ else
82
+ record.errors.add(attribute, "is not supported by type=#{record.type}") if value
83
+ end
84
+ end
85
+ end
86
+
87
+ attr_accessor :aggregator
88
+ validates :aggregator, aggregator: true
89
+
62
90
  def as_json(options = {})
63
91
  super(options.merge(except: %w(errors validation_context)))
64
92
  end
@@ -59,12 +59,16 @@ module Druid
59
59
  query.dataSource = name
60
60
 
61
61
  req = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
62
- req.body = query.to_json
62
+ query_as_json = query.as_json
63
+ req.body = MultiJson.dump(query_as_json)
63
64
 
64
- response = Net::HTTP.new(uri.host, uri.port).start do |http|
65
- http.open_timeout = 10 # if druid is down fail fast
66
- http.read_timeout = nil # we wait until druid is finished
67
- http.request(req)
65
+
66
+ response = ActiveSupport::Notifications.instrument('post.druid', data_source: name, query: query_as_json) do
67
+ Net::HTTP.new(uri.host, uri.port).start do |http|
68
+ http.open_timeout = 10 # if druid is down fail fast
69
+ http.read_timeout = nil # we wait until druid is finished
70
+ http.request(req)
71
+ end
68
72
  end
69
73
 
70
74
  if response.code != '200'
@@ -74,22 +78,49 @@ module Druid
74
78
  return self.post(query)
75
79
  end
76
80
 
77
- raise Error.new(response), "request failed"
81
+ raise Error.new(response)
78
82
  end
79
83
 
80
84
  MultiJson.load(response.body)
81
85
  end
82
86
 
83
87
  class Error < StandardError
84
- attr_reader :response
88
+ attr_reader :error, :error_message, :error_class, :host, :response
89
+
85
90
  def initialize(response)
86
91
  @response = response
92
+ parsed_body = MultiJson.load(response.body)
93
+ @error, @error_message, @error_class, @host = parsed_body.values_at(*%w(
94
+ error
95
+ errorMessage
96
+ errorClass
97
+ host
98
+ ))
87
99
  end
88
100
 
89
101
  def message
90
- MultiJson.load(response.body)["error"]
102
+ error
103
+ end
104
+
105
+ def query_timeout?
106
+ error == 'Query timeout'.freeze
107
+ end
108
+
109
+ def query_interrupted?
110
+ error == 'Query interrupted'.freeze
111
+ end
112
+
113
+ def query_cancelled?
114
+ error == 'Query cancelled'.freeze
91
115
  end
92
- end
93
116
 
117
+ def resource_limit_exceeded?
118
+ error == 'Resource limit exceeded'.freeze
119
+ end
120
+
121
+ def unknown_exception?
122
+ error == 'Unknown exception'.freeze
123
+ end
124
+ end
94
125
  end
95
126
  end
@@ -37,11 +37,11 @@ module Druid
37
37
  super(options.merge(except: %w(errors validation_context)))
38
38
  end
39
39
 
40
- def self.lookup(dimension, namespace, retain: true, injective: false)
40
+ def self.lookup(dimension, namespace, outputName: nil, retain: true, injective: false)
41
41
  new({
42
42
  type: 'extraction',
43
43
  dimension: dimension,
44
- outputName: dimension,
44
+ outputName: outputName || namespace,
45
45
  extractionFn: {
46
46
  type: 'registeredLookup',
47
47
  lookup: namespace,
@@ -166,6 +166,14 @@ module Druid
166
166
  CircFilter.new(@dimension, bounds)
167
167
  end
168
168
 
169
+ def bound(params)
170
+ BoundFilter.new(@dimension, params)
171
+ end
172
+
173
+ def search(params)
174
+ SearchFilter.new(@dimension, params)
175
+ end
176
+
169
177
  def eq(value)
170
178
  case value
171
179
  when ::Array
@@ -299,7 +307,35 @@ module Druid
299
307
  @bound = {
300
308
  type: 'radius',
301
309
  coords: bounds.first,
302
- radius: bounds.last,
310
+ radius: bounds.last
311
+ }
312
+ end
313
+ end
314
+
315
+ class BoundFilter < Filter
316
+ include BooleanOperators
317
+
318
+ def initialize(dimension, params)
319
+ super()
320
+ @type = 'bound'
321
+ @dimension = dimension
322
+ @ordering = params[:ordering]
323
+ @upper = params[:upper]
324
+ @upperStrict = params[:upperStrict]
325
+ end
326
+ end
327
+
328
+ class SearchFilter < Filter
329
+ include BooleanOperators
330
+
331
+ def initialize(dimension, params)
332
+ super()
333
+ @type = 'search'
334
+ @dimension = dimension
335
+ @query = {
336
+ type: 'contains',
337
+ value: params[:value],
338
+ caseSensitive: params[:case_sensitive] || false
303
339
  }
304
340
  end
305
341
  end
@@ -365,7 +365,7 @@ module Druid
365
365
 
366
366
  def group_by(*dimensions)
367
367
  query_type(:groupBy)
368
- @query.dimensions = dimensions.map do |dimension|
368
+ @query.dimensions = dimensions.flatten.map do |dimension|
369
369
  dimension.is_a?(Dimension) ? dimension : Dimension.new(dimension)
370
370
  end
371
371
  self
@@ -451,6 +451,19 @@ module Druid
451
451
  self
452
452
  end
453
453
 
454
+ def filtered_aggregation(metric, name, aggregation_type, &filter)
455
+ @query.aggregations << Aggregation.new(
456
+ type: 'filtered',
457
+ filter: Filter.new.instance_exec(&filter),
458
+ aggregator: Aggregation.new(
459
+ type: aggregation_type.to_s.camelize(:lower),
460
+ name: name,
461
+ fieldName: metric
462
+ )
463
+ ) unless @query.contains_aggregation?(name)
464
+ self
465
+ end
466
+
454
467
  ## post aggregations
455
468
 
456
469
  def postagg(type = :long_sum, &block)
@@ -1,3 +1,3 @@
1
1
  module Druid
2
- VERSION = "0.10.0"
2
+ VERSION = '0.11.3'
3
3
  end
@@ -13,37 +13,37 @@ module Druid
13
13
  end
14
14
 
15
15
  def register
16
- $log.info("druid.zk register discovery path") if $log
16
+ $log.debug("druid.zk register discovery path") if $log
17
17
  @zk.on_expired_session { register }
18
18
  @zk.register(@discovery_path, only: :child) do |event|
19
- $log.info("druid.zk got event on discovery path") if $log
19
+ $log.debug("druid.zk got event on discovery path") if $log
20
20
  check_services
21
21
  end
22
22
  check_services
23
23
  end
24
24
 
25
25
  def close!
26
- $log.info("druid.zk shutting down") if $log
26
+ $log.debug("druid.zk shutting down") if $log
27
27
  @zk.close!
28
28
  end
29
29
 
30
30
  def register_service(service, brokers)
31
- $log.info("druid.zk register", service: service, brokers: brokers) if $log
31
+ $log.debug("druid.zk register", service: service, brokers: brokers) if $log
32
32
  # poor mans load balancing
33
33
  @registry[service] = brokers.shuffle
34
34
  end
35
35
 
36
36
  def unregister_service(service)
37
- $log.info("druid.zk unregister", service: service) if $log
37
+ $log.debug("druid.zk unregister", service: service) if $log
38
38
  @registry.delete(service)
39
39
  unwatch_service(service)
40
40
  end
41
41
 
42
42
  def watch_service(service)
43
43
  return if @watched_services.include?(service)
44
- $log.info("druid.zk watch", service: service) if $log
44
+ $log.debug("druid.zk watch", service: service) if $log
45
45
  watch = @zk.register(watch_path(service), only: :child) do |event|
46
- $log.info("druid.zk got event on watch path for", service: service, event: event) if $log
46
+ $log.debug("druid.zk got event on watch path for", service: service, event: event) if $log
47
47
  unwatch_service(service)
48
48
  check_service(service)
49
49
  end
@@ -52,12 +52,12 @@ module Druid
52
52
 
53
53
  def unwatch_service(service)
54
54
  return unless @watched_services.include?(service)
55
- $log.info("druid.zk unwatch", service: service) if $log
55
+ $log.debug("druid.zk unwatch", service: service) if $log
56
56
  @watched_services.delete(service).unregister
57
57
  end
58
58
 
59
59
  def check_services
60
- $log.info("druid.zk checking services") if $log
60
+ $log.debug("druid.zk checking services") if $log
61
61
  zk_services = @zk.children(@discovery_path, watch: true)
62
62
 
63
63
  (services - zk_services).each do |service|
@@ -70,7 +70,7 @@ module Druid
70
70
  end
71
71
 
72
72
  def verify_broker(service, name)
73
- $log.info("druid.zk verify", broker: name, service: service) if $log
73
+ $log.debug("druid.zk verify", broker: name, service: service) if $log
74
74
  info = @zk.get("#{watch_path(service)}/#{name}")
75
75
  node = MultiJson.load(info[0])
76
76
  uri = "http://#{node['address']}:#{node['port']}/druid/v2/"
@@ -78,7 +78,7 @@ module Druid
78
78
  method: :get, url: "#{uri}datasources/",
79
79
  timeout: 5, open_timeout: 5
80
80
  })
81
- $log.info("druid.zk verified", uri: uri, sources: check) if $log
81
+ $log.debug("druid.zk verified", uri: uri, sources: check) if $log
82
82
  return [uri, MultiJson.load(check.to_str)] if check.code == 200
83
83
  rescue
84
84
  return false
@@ -96,7 +96,7 @@ module Druid
96
96
  known = @registry[service].map { |node| node[:name] }
97
97
  live = @zk.children(watch_path(service), watch: true)
98
98
  new_list = @registry[service].select { |node| live.include?(node[:name]) }
99
- $log.info("druid.zk checking", service: service, known: known, live: live, new_list: new_list) if $log
99
+ $log.debug("druid.zk checking", service: service, known: known, live: live, new_list: new_list) if $log
100
100
 
101
101
  # verify the new entries to be living brokers
102
102
  (live - known).each do |name|
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency "multi_json", "~> 1.0"
26
26
  spec.add_dependency "rest-client", ">= 1.8", "< 3.0"
27
27
  spec.add_dependency "zk", "~> 1.9"
28
- spec.add_development_dependency "bundler", ">= 1.3.0", "< 2.0"
28
+ spec.add_development_dependency "bundler", ">= 1.3.0", "< 2.2"
29
29
  spec.add_development_dependency "rake", "~> 11.2"
30
30
  spec.add_development_dependency "rspec", "~> 3.4"
31
31
  spec.add_development_dependency "webmock", "~> 2.1"
@@ -1,7 +1,8 @@
1
1
  describe Druid::DataSource do
2
2
 
3
3
  context '#post' do
4
- it 'parses response on 200' do
4
+ context 'if the request is succesful' do
5
+ before do
5
6
  # MRI
6
7
  stub_request(:post, "http://www.example.com/druid/v2").
7
8
  with(:body => "{\"context\":{\"queryId\":null},\"queryType\":\"timeseries\",\"intervals\":[\"2013-04-04T00:00:00+00:00/2013-04-04T00:00:00+00:00\"],\"granularity\":\"all\",\"dataSource\":\"test\"}",
@@ -12,27 +13,55 @@ describe Druid::DataSource do
12
13
  with(:body => "{\"context\":{\"queryId\":null},\"granularity\":\"all\",\"intervals\":[\"2013-04-04T00:00:00+00:00/2013-04-04T00:00:00+00:00\"],\"queryType\":\"timeseries\",\"dataSource\":\"test\"}",
13
14
  :headers => { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type' => 'application/json', 'User-Agent' => 'Ruby' }).
14
15
  to_return(:status => 200, :body => '[]', :headers => {})
16
+ end
17
+
18
+ it 'parses response on 200' do
15
19
  ds = Druid::DataSource.new('test/test', 'http://www.example.com/druid/v2')
16
20
  query = Druid::Query::Builder.new.interval('2013-04-04', '2013-04-04').granularity(:all).query
17
21
  query.context.queryId = nil
18
22
  expect(ds.post(query)).to be_empty
19
23
  end
20
24
 
25
+ it 'instruments requests' do
26
+ expect(ActiveSupport::Notifications).to receive(:instrument).with('post.druid', data_source: 'test', query: instance_of(Hash)).and_call_original
27
+
28
+ ds = Druid::DataSource.new('test/test', 'http://www.example.com/druid/v2')
29
+ query = Druid::Query::Builder.new.interval('2013-04-04', '2013-04-04').granularity(:all).query
30
+ query.context.queryId = nil
31
+ ds.post(query)
32
+ end
33
+ end
34
+
21
35
  it 'raises on request failure' do
22
- # MRI
23
- stub_request(:post, 'http://www.example.com/druid/v2').
24
- with(:body => "{\"context\":{\"queryId\":null},\"queryType\":\"timeseries\",\"intervals\":[\"2013-04-04T00:00:00+00:00/2013-04-04T00:00:00+00:00\"],\"granularity\":\"all\",\"dataSource\":\"test\"}",
25
- :headers => { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type' => 'application/json', 'User-Agent' => 'Ruby' }).
26
- to_return(:status => 666, :body => 'Strange server error', :headers => {})
27
- # JRuby ... *sigh
28
- stub_request(:post, 'http://www.example.com/druid/v2').
29
- with(:body => "{\"context\":{\"queryId\":null},\"granularity\":\"all\",\"intervals\":[\"2013-04-04T00:00:00+00:00/2013-04-04T00:00:00+00:00\"],\"queryType\":\"timeseries\",\"dataSource\":\"test\"}",
30
- :headers => { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type' => 'application/json', 'User-Agent' => 'Ruby' }).
31
- to_return(:status => 666, :body => 'Strange server error', :headers => {})
36
+ stub_request(:post, 'http://www.example.com/druid/v2')
37
+ .with(
38
+ :body => %q({"context":{"queryId":null},"queryType":"timeseries","intervals":["2013-04-04T00:00:00+00:00/2013-04-04T00:00:00+00:00"],"granularity":"all","dataSource":"test"}),
39
+ :headers => {
40
+ 'Accept' => '*/*',
41
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
42
+ 'Content-Type' => 'application/json',
43
+ 'User-Agent' => 'Ruby',
44
+ }
45
+ )
46
+ .to_return(
47
+ :status => 500,
48
+ :body => %q({"error":"Unknown exception","errorMessage":"NullPointerException","errorClass":"java.lang.NullPointerException","host":"www.example.com"}),
49
+ :headers => {},
50
+ )
51
+
32
52
  ds = Druid::DataSource.new('test/test', 'http://www.example.com/druid/v2')
33
53
  query = Druid::Query::Builder.new.interval('2013-04-04', '2013-04-04').granularity(:all).query
34
54
  query.context.queryId = nil
35
- expect { ds.post(query) }.to raise_error(Druid::DataSource::Error)
55
+
56
+ expect { ds.post(query) }.to raise_error { |error|
57
+ expect(error).to be_a(Druid::DataSource::Error)
58
+ expect(error.message).to eq(error.error)
59
+ expect(error.error).to eq('Unknown exception')
60
+ expect(error.error_message).to eq('NullPointerException')
61
+ expect(error.error_class).to eq('java.lang.NullPointerException')
62
+ expect(error.host).to eq('www.example.com')
63
+ expect(error).to be_unknown_exception
64
+ }
36
65
  end
37
66
  end
38
67
 
@@ -209,6 +209,32 @@ describe Druid::Query do
209
209
  end
210
210
  end
211
211
 
212
+ describe '#filtered_aggregation' do
213
+ it 'builds filtered aggregations' do
214
+ @query.filtered_aggregation(:a, :a_filtered, :longSum) do
215
+ b.eq(2) & c.neq(3)
216
+ end
217
+ expect(JSON.parse(@query.query.to_json)['aggregations']).to eq [
218
+ {
219
+ 'type' => 'filtered',
220
+ 'filter' => {
221
+ 'type' => 'and',
222
+ 'fields' => [
223
+ { 'dimension' => 'b', 'type' => 'selector', 'value' => 2 },
224
+ {
225
+ 'type' => 'not',
226
+ 'field' => {
227
+ 'dimension' => 'c', 'type' => 'selector', 'value' => 3
228
+ }
229
+ }
230
+ ]
231
+ },
232
+ 'aggregator' => { 'type' => 'longSum', 'name' => 'a_filtered', 'fieldName' => 'a' }
233
+ }
234
+ ]
235
+ end
236
+ end
237
+
212
238
  it 'appends long_sum properties from aggregations on calling long_sum again' do
213
239
  @query.long_sum(:a, :b, :c)
214
240
  @query.double_sum(:x,:y)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-druid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruby Druid Community
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-14 00:00:00.000000000 Z
11
+ date: 2020-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -109,7 +109,7 @@ dependencies:
109
109
  version: 1.3.0
110
110
  - - "<"
111
111
  - !ruby/object:Gem::Version
112
- version: '2.0'
112
+ version: '2.2'
113
113
  type: :development
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
@@ -119,7 +119,7 @@ dependencies:
119
119
  version: 1.3.0
120
120
  - - "<"
121
121
  - !ruby/object:Gem::Version
122
- version: '2.0'
122
+ version: '2.2'
123
123
  - !ruby/object:Gem::Dependency
124
124
  name: rake
125
125
  requirement: !ruby/object:Gem::Requirement
@@ -211,13 +211,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  version: '0'
212
212
  requirements: []
213
213
  rubyforge_project:
214
- rubygems_version: 2.4.8
214
+ rubygems_version: 2.6.11
215
215
  signing_key:
216
216
  specification_version: 4
217
217
  summary: A Ruby client for Druid
218
218
  test_files:
219
219
  - spec/spec_helper.rb
220
220
  - spec/lib/client_spec.rb
221
- - spec/lib/data_source_spec.rb
222
221
  - spec/lib/zk_spec.rb
222
+ - spec/lib/data_source_spec.rb
223
223
  - spec/lib/query_spec.rb