ruby-druid 0.10.0 → 0.11.3

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