hotdog 0.12.0 → 0.13.0

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: 81c8f88ce41f3d20b7099237e335f0baa453ad3a
4
- data.tar.gz: 3aa663a263433092de26f1d5ec33cffb871e1b2e
3
+ metadata.gz: edea5108e33e32670a80a206e1dbde6fa4810d00
4
+ data.tar.gz: 56538e388dd9a43a987ade29b48c241ec3c17c2e
5
5
  SHA512:
6
- metadata.gz: 04dfbc1dc88f135b680ee989ac02b708d2adf86a7cab3e315f3c0875eb5071b618ed97064ebefdc48d0b1bf991ee4f6c5bd9d623c7af13f0e5b555e40ecdea2e
7
- data.tar.gz: 74a9fc5f60194286c07926a199f5e6a51064b62cce8057159d84195352686fbaa3dacdc42ed5e7a6f5d09508dbd4cb824ee741df34ed02be6afd2486dfa7a26a
6
+ metadata.gz: 4646c09bac8ae0384e2fdf239776628724a34cfce6e42c734886d7c19f490285e5c850e064f3c31e0157ec4ad0f7ef09c9beaac7c402f7b85f64bb8436ac8331
7
+ data.tar.gz: 071de235c649f8bb57800d0ad5621588c58db07595456e51664d8ef1eac8c0bd669327d4886af6869e79cb592e1fcffd5444ceaf8cfba7230d3c1eaffc855fb7
data/README.md CHANGED
@@ -129,16 +129,35 @@ expression4: '!' atom
129
129
  | '~' atom
130
130
  | '!' expression
131
131
  | '~' expression
132
- | atom
132
+ | primary
133
133
  ;
134
134
 
135
- atom: '(' expression ')'
136
- | IDENTIFIER separator ATTRIBUTE
137
- | IDENTIFIER separator
138
- | separator ATTRIBUTE
139
- | IDENTIFIER
140
- | ATTRIBUTE
141
- ;
135
+ primary: '(' expression ')'
136
+ | funcall
137
+ | tag
138
+ ;
139
+
140
+ funcall: IDENTIFIER '(' ')'
141
+ | IDENTIFIER '(' funcall_args ')'
142
+ ;
143
+
144
+ funcall_args: funcall_arg ',' funcall_args
145
+ | funcall_arg
146
+ ;
147
+
148
+ funcall_arg: FLOAT
149
+ | INTEGER
150
+ | STRING
151
+ | REGEXP
152
+ | primary
153
+ ;
154
+
155
+ tag: IDENTIFIER separator IDENTIFIER
156
+ | IDENTIFIER separator
157
+ | separator IDENTIFIER
158
+ | IDENTIFIER
159
+ | IDENTIFIER
160
+ ;
142
161
 
143
162
  separator: ':'
144
163
  | '='
@@ -9,6 +9,8 @@ require "hotdog/formatters"
9
9
  require "hotdog/version"
10
10
 
11
11
  module Hotdog
12
+ SQLITE_LIMIT_COMPOUND_SELECT = 500 # TODO: get actual value from `sqlite3_limit()`?
13
+
12
14
  class Application
13
15
  def initialize()
14
16
  @optparse = OptionParser.new
@@ -101,7 +103,11 @@ module Hotdog
101
103
  STDERR.puts(error)
102
104
  rescue => error
103
105
  STDERR.puts(error)
104
- exit(1)
106
+ if @options[:debug]
107
+ raise # to show error stacktrace
108
+ else
109
+ exit(1)
110
+ end
105
111
  end
106
112
  end
107
113
 
@@ -11,8 +11,6 @@ require "uri"
11
11
 
12
12
  module Hotdog
13
13
  module Commands
14
- SQLITE_LIMIT_COMPOUND_SELECT = 500 # TODO: get actual value from `sqlite3_limit()`?
15
-
16
14
  class BaseCommand
17
15
  PERSISTENT_DB = "persistent.db"
18
16
  MASK_DATABASE = 0xffff0000
@@ -2,19 +2,7 @@
2
2
 
3
3
  require "json"
4
4
  require "parslet"
5
-
6
- # Monkey patch to prevent `NoMethodError` after some parse error in parselet
7
- module Parslet
8
- class Cause
9
- def cause
10
- self
11
- end
12
-
13
- def backtrace
14
- []
15
- end
16
- end
17
- end
5
+ require "hotdog/expression"
18
6
 
19
7
  module Hotdog
20
8
  module Commands
@@ -74,16 +62,16 @@ module Hotdog
74
62
  case
75
63
  when n[:left] && n[:right] then drilldown.(n[:left]) + drilldown.(n[:right])
76
64
  when n[:expression] then drilldown.(n[:expression])
77
- when n[:identifier] then [n[:identifier]]
65
+ when n[:tag_name] then [n[:tag_name]]
78
66
  else []
79
67
  end
80
68
  }
81
69
  if options[:display_search_tags]
82
- identifiers = drilldown.call(node).map(&:to_s)
70
+ tag_names = drilldown.call(node).map(&:to_s)
83
71
  if options[:primary_tag]
84
- tags = [options[:primary_tag]] + identifiers
72
+ tags = [options[:primary_tag]] + tag_names
85
73
  else
86
- tags = identifiers
74
+ tags = tag_names
87
75
  end
88
76
  else
89
77
  tags = nil
@@ -93,7 +81,7 @@ module Hotdog
93
81
 
94
82
  def parse(expression)
95
83
  logger.debug(expression)
96
- parser = ExpressionParser.new
84
+ parser = Hotdog::Expression::ExpressionParser.new
97
85
  parser.parse(expression).tap do |parsed|
98
86
  logger.debug {
99
87
  begin
@@ -106,1227 +94,16 @@ module Hotdog
106
94
  end
107
95
 
108
96
  def evaluate(data, environment)
109
- node = ExpressionTransformer.new.apply(data)
110
- optimized = node.optimize.tap do |optimized|
111
- logger.debug {
112
- JSON.pretty_generate(optimized.dump)
113
- }
114
- end
115
- optimized.evaluate(environment)
116
- end
117
-
118
- class ExpressionParser < Parslet::Parser
119
- root(:expression)
120
- rule(:expression) {
121
- ( expression0 \
122
- )
123
- }
124
- rule(:expression0) {
125
- ( expression1.as(:left) >> spacing.maybe >> binary_op.as(:binary_op) >> spacing.maybe >> expression.as(:right) \
126
- | expression1 \
127
- )
128
- }
129
- rule(:expression1) {
130
- ( unary_op.as(:unary_op) >> spacing >> expression.as(:expression) \
131
- | unary_op.as(:unary_op) >> spacing.maybe >> str('(') >> spacing.maybe >> expression.as(:expression) >> spacing.maybe >> str(')') \
132
- | expression2 \
133
- )
134
- }
135
- rule(:expression2) {
136
- ( expression3.as(:left) >> spacing.maybe.as(:binary_op) >> expression.as(:right) \
137
- | expression3 \
138
- )
139
- }
140
- rule(:expression3) {
141
- ( expression4.as(:left) >> spacing.maybe >> str('&&').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
142
- | expression4.as(:left) >> spacing.maybe >> str('||').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
143
- | expression4.as(:left) >> spacing.maybe >> str('&').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
144
- | expression4.as(:left) >> spacing.maybe >> str(',').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
145
- | expression4.as(:left) >> spacing.maybe >> str('^').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
146
- | expression4.as(:left) >> spacing.maybe >> str('|').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
147
- | expression4 \
148
- )
149
- }
150
- rule(:expression4) {
151
- ( str('!').as(:unary_op) >> spacing.maybe >> atom.as(:expression) \
152
- | str('~').as(:unary_op) >> spacing.maybe >> atom.as(:expression) \
153
- | str('!').as(:unary_op) >> spacing.maybe >> expression.as(:expression) \
154
- | str('~').as(:unary_op) >> spacing.maybe >> expression.as(:expression) \
155
- | atom \
156
- )
157
- }
158
- rule(:binary_op) {
159
- ( str('AND') \
160
- | str('OR') \
161
- | str('XOR') \
162
- | str('and') \
163
- | str('or') \
164
- | str('xor') \
165
- )
166
- }
167
- rule(:unary_op) {
168
- ( str('NOT') \
169
- | str('not') \
170
- )
171
- }
172
- rule(:atom) {
173
- ( spacing.maybe >> str('(') >> expression >> str(')') >> spacing.maybe \
174
- | spacing.maybe >> str('/') >> identifier_regexp.as(:identifier_regexp) >> str('/') >> separator.as(:separator) >> str('/') >> attribute_regexp.as(:attribute_regexp) >> str('/') >> spacing.maybe \
175
- | spacing.maybe >> str('/') >> identifier_regexp.as(:identifier_regexp) >> str('/') >> separator.as(:separator) >> spacing.maybe \
176
- | spacing.maybe >> str('/') >> identifier_regexp.as(:identifier_regexp) >> str('/') >> spacing.maybe \
177
- | spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator.as(:separator) >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
178
- | spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator.as(:separator) >> attribute.as(:attribute) >> spacing.maybe \
179
- | spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator.as(:separator) >> spacing.maybe \
180
- | spacing.maybe >> identifier_glob.as(:identifier_glob) >> spacing.maybe \
181
- | spacing.maybe >> identifier.as(:identifier) >> separator.as(:separator) >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
182
- | spacing.maybe >> identifier.as(:identifier) >> separator.as(:separator) >> attribute.as(:attribute) >> spacing.maybe \
183
- | spacing.maybe >> identifier.as(:identifier) >> separator.as(:separator) >> spacing.maybe \
184
- | spacing.maybe >> identifier.as(:identifier) >> spacing.maybe \
185
- | spacing.maybe >> separator.as(:separator) >> str('/') >> attribute_regexp.as(:attribute_regexp) >> str('/') >> spacing.maybe \
186
- | spacing.maybe >> separator.as(:separator) >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
187
- | spacing.maybe >> separator.as(:separator) >> attribute.as(:attribute) >> spacing.maybe \
188
- | spacing.maybe >> str('/') >> attribute_regexp.as(:attribute_regexp) >> str('/') >> spacing.maybe \
189
- | spacing.maybe >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
190
- | spacing.maybe >> attribute.as(:attribute) >> spacing.maybe \
191
- )
192
- }
193
- rule(:identifier_regexp) {
194
- ( (str('/').absent? >> any).repeat(0) \
195
- )
196
- }
197
- rule(:identifier_glob) {
198
- ( binary_op.absent? >> unary_op.absent? >> identifier.repeat(0) >> (glob >> identifier.maybe).repeat(1) \
199
- | binary_op >> (glob >> identifier.maybe).repeat(1) \
200
- | unary_op >> (glob >> identifier.maybe).repeat(1) \
201
- )
202
- }
203
- rule(:identifier) {
204
- ( binary_op.absent? >> unary_op.absent? >> match('[A-Za-z]') >> match('[-./0-9A-Z_a-z]').repeat(0) \
205
- | binary_op >> match('[-./0-9A-Z_a-z]').repeat(1) \
206
- | unary_op >> match('[-./0-9A-Z_a-z]').repeat(1) \
207
- )
208
- }
209
- rule(:separator) {
210
- ( str(':') \
211
- | str('=') \
212
- )
213
- }
214
- rule(:attribute_regexp) {
215
- ( (str('/').absent? >> any).repeat(0) \
216
- )
217
- }
218
- rule(:attribute_glob) {
219
- ( binary_op.absent? >> unary_op.absent? >> attribute.repeat(0) >> (glob >> attribute.maybe).repeat(1) \
220
- | binary_op >> (glob >> attribute.maybe).repeat(1) \
221
- | unary_op >> (glob >> attribute.maybe).repeat(1) \
222
- )
223
- }
224
- rule(:attribute) {
225
- ( binary_op.absent? >> unary_op.absent? >> match('[-./0-9:A-Z_a-z]').repeat(1) \
226
- | binary_op >> match('[-./0-9:A-Z_a-z]').repeat(1) \
227
- | unary_op >> match('[-./0-9:A-Z_a-z]').repeat(1) \
228
- )
229
- }
230
- rule(:glob) {
231
- ( str('*') | str('?') | str('[') | str(']') )
232
- }
233
- rule(:spacing) {
234
- ( match('[\t\n\r ]').repeat(1) \
235
- )
236
- }
237
- end
238
-
239
- class ExpressionTransformer < Parslet::Transform
240
- rule(binary_op: simple(:binary_op), left: simple(:left), right: simple(:right)) {
241
- BinaryExpressionNode.new(binary_op, left, right)
242
- }
243
- rule(unary_op: simple(:unary_op), expression: simple(:expression)) {
244
- UnaryExpressionNode.new(unary_op, expression)
245
- }
246
- rule(identifier_regexp: simple(:identifier_regexp), separator: simple(:separator), attribute_regexp: simple(:attribute_regexp)) {
247
- if "host" == identifier_regexp
248
- RegexpHostNode.new(attribute_regexp.to_s, separator)
249
- else
250
- RegexpTagNode.new(identifier_regexp.to_s, attribute_regexp.to_s, separator)
251
- end
252
- }
253
- rule(identifier_regexp: simple(:identifier_regexp), separator: simple(:separator)) {
254
- if "host" == identifier_regexp
255
- EverythingNode.new()
256
- else
257
- RegexpTagNameNode.new(identifier_regexp.to_s, separator)
258
- end
259
- }
260
- rule(identifier_regexp: simple(:identifier_regexp)) {
261
- if "host" == identifier_regexp
262
- EverythingNode.new()
263
- else
264
- RegexpNode.new(identifier_regexp.to_s)
265
- end
266
- }
267
- rule(identifier_glob: simple(:identifier_glob), separator: simple(:separator), attribute_glob: simple(:attribute_glob)) {
268
- if "host" == identifier_glob
269
- GlobHostNode.new(attribute_glob.to_s, separator)
270
- else
271
- GlobTagNode.new(identifier_glob.to_s, attribute_glob.to_s, separator)
272
- end
273
- }
274
- rule(identifier_glob: simple(:identifier_glob), separator: simple(:separator), attribute: simple(:attribute)) {
275
- if "host" == identifier_glob
276
- GlobHostNode.new(attribute.to_s, separator)
277
- else
278
- GlobTagNode.new(identifier.to_s, attribute.to_s, separator)
279
- end
280
- }
281
- rule(identifier_glob: simple(:identifier_glob), separator: simple(:separator)) {
282
- if "host" == identifier_glob
283
- EverythingNode.new()
284
- else
285
- GlobTagNameNode.new(identifier_glob.to_s, separator)
286
- end
287
- }
288
- rule(identifier_glob: simple(:identifier_glob)) {
289
- if "host" == identifier_glob
290
- EverythingNode.new()
291
- else
292
- GlobNode.new(identifier_glob.to_s)
293
- end
294
- }
295
- rule(identifier: simple(:identifier), separator: simple(:separator), attribute_glob: simple(:attribute_glob)) {
296
- if "host" == identifier
297
- GlobHostNode.new(attribute_glob.to_s, separator)
298
- else
299
- GlobTagNode.new(identifier.to_s, attribute_glob.to_s, separator)
300
- end
301
- }
302
- rule(identifier: simple(:identifier), separator: simple(:separator), attribute: simple(:attribute)) {
303
- if "host" == identifier
304
- StringHostNode.new(attribute.to_s, separator)
305
- else
306
- StringTagNode.new(identifier.to_s, attribute.to_s, separator)
307
- end
308
- }
309
- rule(identifier: simple(:identifier), separator: simple(:separator)) {
310
- if "host" == identifier
311
- EverythingNode.new()
312
- else
313
- StringTagNameNode.new(identifier.to_s, separator)
314
- end
315
- }
316
- rule(identifier: simple(:identifier)) {
317
- if "host" == identifier
318
- EverythingNode.new()
319
- else
320
- StringNode.new(identifier.to_s)
321
- end
322
- }
323
- rule(separator: simple(:separator), attribute_regexp: simple(:attribute_regexp)) {
324
- RegexpTagValueNode.new(attribute_regexp.to_s, separator)
325
- }
326
- rule(attribute_regexp: simple(:attribute_regexp)) {
327
- RegexpTagValueNode.new(attribute_regexp.to_s)
328
- }
329
- rule(separator: simple(:separator), attribute_glob: simple(:attribute_glob)) {
330
- GlobTagValueNode.new(attribute_glob.to_s, separator)
331
- }
332
- rule(attribute_glob: simple(:attribute_glob)) {
333
- GlobTagValueNode.new(attribute_glob.to_s)
334
- }
335
- rule(separator: simple(:separator), attribute: simple(:attribute)) {
336
- StringTagValueNode.new(attribute.to_s, separator)
337
- }
338
- rule(attribute: simple(:attribute)) {
339
- StringTagValueNode.new(attribute.to_s)
340
- }
341
- end
342
-
343
- class ExpressionNode
344
- def evaluate(environment, options={})
345
- raise(NotImplementedError.new("must be overridden"))
346
- end
347
-
348
- def optimize(options={})
349
- self
350
- end
351
-
352
- def dump(options={})
353
- {}
354
- end
355
- end
356
-
357
- class UnaryExpressionNode < ExpressionNode
358
- attr_reader :op, :expression
359
-
360
- def initialize(op, expression)
361
- case (op || "not").to_s
362
- when "!", "~", "NOT", "not"
363
- @op = :NOT
364
- else
365
- raise(SyntaxError.new("unknown unary operator: #{@op.inspect}"))
366
- end
367
- @expression = expression
368
- end
369
-
370
- def evaluate(environment, options={})
371
- case @op
372
- when :NOT
373
- values = @expression.evaluate(environment, options).tap do |values|
374
- environment.logger.debug("expr: #{values.length} value(s)")
375
- end
376
- if values.empty?
377
- EverythingNode.new().evaluate(environment, options).tap do |values|
378
- environment.logger.debug("NOT expr: #{values.length} value(s)")
379
- end
380
- else
381
- # workaround for "too many terms in compound SELECT"
382
- min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
383
- (min / (SQLITE_LIMIT_COMPOUND_SELECT - 2)).upto(max / (SQLITE_LIMIT_COMPOUND_SELECT - 2)).flat_map { |i|
384
- range = ((SQLITE_LIMIT_COMPOUND_SELECT - 2) * i)...((SQLITE_LIMIT_COMPOUND_SELECT - 2) * (i + 1))
385
- selected = values.select { |n| range === n }
386
- q = "SELECT id FROM hosts " \
387
- "WHERE ? <= id AND id < ? AND id NOT IN (%s);"
388
- environment.execute(q % selected.map { "?" }.join(", "), [range.first, range.last] + selected).map { |row| row.first }
389
- }.tap do |values|
390
- environment.logger.debug("NOT expr: #{values.length} value(s)")
391
- end
392
- end
393
- else
394
- []
395
- end
396
- end
397
-
398
- def optimize(options={})
399
- @expression = @expression.optimize(options)
400
- case op
401
- when :NOT
402
- case expression
403
- when EverythingNode
404
- NothingNode.new(options)
405
- when NothingNode
406
- EverythingNode.new(options)
407
- else
408
- optimize1(options)
409
- end
410
- else
411
- self
412
- end
413
- end
414
-
415
- def ==(other)
416
- self.class === other and @op == other.op and @expression == other.expression
417
- end
418
-
419
- def dump(options={})
420
- {unary_op: @op.to_s, expression: @expression.dump(options)}
421
- end
422
-
423
- private
424
- def optimize1(options={})
425
- case op
426
- when :NOT
427
- if UnaryExpressionNode === expression and expression.op == :NOT
428
- expression.expression
429
- else
430
- case expression
431
- when QueryExpressionNode
432
- q = expression.query
433
- v = expression.values
434
- if q and v.length <= SQLITE_LIMIT_COMPOUND_SELECT
435
- QueryExpressionNode.new("SELECT id AS host_id FROM hosts EXCEPT #{q.sub(/\s*;\s*\z/, "")};", v)
436
- else
437
- self
438
- end
439
- when TagExpressionNode
440
- q = expression.maybe_query(options)
441
- v = expression.condition_values(options)
442
- if q and v.length <= SQLITE_LIMIT_COMPOUND_SELECT
443
- QueryExpressionNode.new("SELECT id AS host_id FROM hosts EXCEPT #{q.sub(/\s*;\s*\z/, "")};", v)
444
- else
445
- self
446
- end
447
- else
448
- self
449
- end
450
- end
451
- else
452
- self
453
- end
454
- end
455
- end
456
-
457
- class BinaryExpressionNode < ExpressionNode
458
- attr_reader :op, :left, :right
459
-
460
- def initialize(op, left, right)
461
- case (op || "or").to_s
462
- when "&&", "&", "AND", "and"
463
- @op = :AND
464
- when ",", "||", "|", "OR", "or"
465
- @op = :OR
466
- when "^", "XOR", "xor"
467
- @op = :XOR
468
- else
469
- raise(SyntaxError.new("unknown binary operator: #{op.inspect}"))
470
- end
471
- @left = left
472
- @right = right
473
- end
474
-
475
- def evaluate(environment, options={})
476
- case @op
477
- when :AND
478
- left_values = @left.evaluate(environment, options).tap do |values|
479
- environment.logger.debug("lhs: #{values.length} value(s)")
480
- end
481
- if left_values.empty?
482
- []
483
- else
484
- right_values = @right.evaluate(environment, options).tap do |values|
485
- environment.logger.debug("rhs: #{values.length} value(s)")
486
- end
487
- if right_values.empty?
488
- []
489
- else
490
- # workaround for "too many terms in compound SELECT"
491
- min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
492
- (min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).flat_map { |i|
493
- range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * (i + 1))
494
- left_selected = left_values.select { |n| range === n }
495
- right_selected = right_values.select { |n| range === n }
496
- q = "SELECT id FROM hosts " \
497
- "WHERE ? <= id AND id < ? AND ( id IN (%s) AND id IN (%s) );"
498
- environment.execute(q % [left_selected.map { "?" }.join(", "), right_selected.map { "?" }.join(", ")], [range.first, range.last] + left_selected + right_selected).map { |row| row.first }
499
- }.tap do |values|
500
- environment.logger.debug("lhs AND rhs: #{values.length} value(s)")
501
- end
502
- end
503
- end
504
- when :OR
505
- left_values = @left.evaluate(environment, options).tap do |values|
506
- environment.logger.debug("lhs: #{values.length} value(s)")
507
- end
508
- right_values = @right.evaluate(environment, options).tap do |values|
509
- environment.logger.debug("rhs: #{values.length} value(s)")
510
- end
511
- if left_values.empty?
512
- right_values
513
- else
514
- if right_values.empty?
515
- []
516
- else
517
- # workaround for "too many terms in compound SELECT"
518
- min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
519
- (min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).flat_map { |i|
520
- range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * (i + 1))
521
- left_selected = left_values.select { |n| range === n }
522
- right_selected = right_values.select { |n| range === n }
523
- q = "SELECT id FROM hosts " \
524
- "WHERE ? <= id AND id < ? AND ( id IN (%s) OR id IN (%s) );"
525
- environment.execute(q % [left_selected.map { "?" }.join(", "), right_selected.map { "?" }.join(", ")], [range.first, range.last] + left_selected + right_selected).map { |row| row.first }
526
- }.tap do |values|
527
- environment.logger.debug("lhs OR rhs: #{values.length} value(s)")
528
- end
529
- end
530
- end
531
- when :XOR
532
- left_values = @left.evaluate(environment, options).tap do |values|
533
- environment.logger.debug("lhs: #{values.length} value(s)")
534
- end
535
- right_values = @right.evaluate(environment, options).tap do |values|
536
- environment.logger.debug("rhs: #{values.length} value(s)")
537
- end
538
- if left_values.empty?
539
- right_values
540
- else
541
- if right_values.empty?
542
- []
543
- else
544
- # workaround for "too many terms in compound SELECT"
545
- min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
546
- (min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4)).flat_map { |i|
547
- range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4) * (i + 1))
548
- left_selected = left_values.select { |n| range === n }
549
- right_selected = right_values.select { |n| range === n }
550
- q = "SELECT id FROM hosts " \
551
- "WHERE ? <= id AND id < ? AND NOT (id IN (%s) AND id IN (%s)) AND ( id IN (%s) OR id IN (%s) );"
552
- lq = left_selected.map { "?" }.join(", ")
553
- rq = right_selected.map { "?" }.join(", ")
554
- environment.execute(q % [lq, rq, lq, rq], [range.first, range.last] + left_selected + right_selected + left_selected + right_selected).map { |row| row.first }
555
- }.tap do |values|
556
- environment.logger.debug("lhs XOR rhs: #{values.length} value(s)")
557
- end
558
- end
559
- end
560
- else
561
- []
562
- end
563
- end
564
-
565
- def optimize(options={})
566
- @left = @left.optimize(options)
567
- @right = @right.optimize(options)
568
- case op
569
- when :AND
570
- case left
571
- when EverythingNode
572
- right
573
- when NothingNode
574
- left
575
- else
576
- if left == right
577
- left
578
- else
579
- optimize1(options)
580
- end
581
- end
582
- when :OR
583
- case left
584
- when EverythingNode
585
- left
586
- when NothingNode
587
- right
588
- else
589
- if left == right
590
- left
591
- else
592
- if MultinaryExpressionNode === left
593
- if left.op == op
594
- left.merge(right, fallback: self)
595
- else
596
- optimize1(options)
597
- end
598
- else
599
- if MultinaryExpressionNode === right
600
- if right.op == op
601
- right.merge(left, fallback: self)
602
- else
603
- optimize1(options)
604
- end
605
- else
606
- MultinaryExpressionNode.new(op, [left, right], fallback: self)
607
- end
608
- end
609
- end
610
- end
611
- when :XOR
612
- if left == right
613
- []
614
- else
615
- optimize1(options)
616
- end
617
- else
618
- self
619
- end
620
- end
621
-
622
- def ==(other)
623
- self.class === other and @op == other.op and @left == other.left and @right == other.right
624
- end
625
-
626
- def dump(options={})
627
- {left: @left.dump(options), binary_op: @op.to_s, right: @right.dump(options)}
628
- end
629
-
630
- private
631
- def optimize1(options)
632
- if TagExpressionNode === left and TagExpressionNode === right
633
- lq = left.maybe_query(options)
634
- lv = left.condition_values(options)
635
- rq = right.maybe_query(options)
636
- rv = right.condition_values(options)
637
- if lq and rq and lv.length + rv.length <= SQLITE_LIMIT_COMPOUND_SELECT
638
- case op
639
- when :AND
640
- q = "#{lq.sub(/\s*;\s*\z/, "")} INTERSECT #{rq.sub(/\s*;\s*\z/, "")};"
641
- QueryExpressionNode.new(q, lv + rv, fallback: self)
642
- when :OR
643
- q = "#{lq.sub(/\s*;\s*\z/, "")} UNION #{rq.sub(/\s*;\s*\z/, "")};"
644
- QueryExpressionNode.new(q, lv + rv, fallback: self)
645
- when :XOR
646
- q = "#{lq.sub(/\s*;\s*\z/, "")} UNION #{rq.sub(/\s*;\s*\z/, "")} " \
647
- "EXCEPT #{lq.sub(/\s*;\s*\z/, "")} " \
648
- "INTERSECT #{rq.sub(/\s*;\s*\z/, "")};"
649
- QueryExpressionNode.new(q, lv + rv, fallback: self)
650
- else
651
- self
652
- end
653
- else
654
- self
655
- end
656
- else
657
- self
658
- end
659
- end
660
- end
661
-
662
- class MultinaryExpressionNode < ExpressionNode
663
- attr_reader :op, :expressions
664
-
665
- def initialize(op, expressions, options={})
666
- case (op || "or").to_s
667
- when ",", "||", "|", "OR", "or"
668
- @op = :OR
669
- else
670
- raise(SyntaxError.new("unknown multinary operator: #{op.inspect}"))
671
- end
672
- if SQLITE_LIMIT_COMPOUND_SELECT < expressions.length
673
- raise(ArgumentError.new("expressions limit exceeded: #{expressions.length} for #{SQLITE_LIMIT_COMPOUND_SELECT}"))
674
- end
675
- @expressions = expressions
676
- @fallback = options[:fallback]
677
- end
678
-
679
- def merge(other, options={})
680
- if MultinaryExpressionNode === other and op == other.op
681
- MultinaryExpressionNode.new(op, expressions + other.expressions, options)
682
- else
683
- MultinaryExpressionNode.new(op, expressions + [other], options)
684
- end
685
- end
686
-
687
- def evaluate(environment, options={})
688
- case @op
689
- when :OR
690
- if expressions.all? { |expression| TagExpressionNode === expression }
691
- values = expressions.group_by { |expression| expression.class }.values.flat_map { |expressions|
692
- query_without_condition = expressions.first.maybe_query_without_condition(options)
693
- if query_without_condition
694
- condition_length = expressions.map { |expression| expression.condition_values(options).length }.max
695
- expressions.each_slice(SQLITE_LIMIT_COMPOUND_SELECT / condition_length).flat_map { |expressions|
696
- q = query_without_condition.sub(/\s*;\s*\z/, "") + " WHERE " + expressions.map { |expression| "( %s )" % expression.condition(options) }.join(" OR ") + ";"
697
- environment.execute(q, expressions.flat_map { |expression| expression.condition_values(options) }).map { |row| row.first }
698
- }
699
- else
700
- []
701
- end
702
- }
703
- else
704
- values = []
705
- end
706
- else
707
- values = []
708
- end
709
- if values.empty?
710
- if @fallback
711
- @fallback.evaluate(environment, options={})
712
- else
713
- []
714
- end
715
- else
716
- values
717
- end
718
- end
719
-
720
- def dump(options={})
721
- {multinary_op: @op.to_s, expressions: expressions.map { |expression| expression.dump(options) }}
722
- end
723
- end
724
-
725
- class QueryExpressionNode < ExpressionNode
726
- def initialize(query, values=[], options={})
727
- @query = query
728
- @values = values
729
- @fallback = options[:fallback]
730
- end
731
- attr_reader :query
732
- attr_reader :values
733
-
734
- def evaluate(environment, options={})
735
- values = environment.execute(@query, @values).map { |row| row.first }
736
- if values.empty? and @fallback
737
- @fallback.evaluate(environment, options)
738
- else
739
- values
740
- end
741
- end
742
-
743
- def dump(options={})
744
- data = {query: @query, values: @values}
745
- data[:fallback] = @fallback.dump(options) if @fallback
746
- data
747
- end
748
- end
749
-
750
- class EverythingNode < QueryExpressionNode
751
- def initialize(options={})
752
- super("SELECT id AS host_id FROM hosts", [], options)
753
- end
754
- end
755
-
756
- class NothingNode < QueryExpressionNode
757
- def initialize(options={})
758
- super("SELECT NULL AS host_id WHERE host_id NOT NULL", [], options)
759
- end
760
-
761
- def evaluate(environment, options={})
762
- if @fallback
763
- @fallback.evaluate(environment, options)
764
- else
765
- []
766
- end
767
- end
768
- end
769
-
770
- class TagExpressionNode < ExpressionNode
771
- def initialize(identifier, attribute, separator=nil)
772
- @identifier = identifier
773
- @attribute = attribute
774
- @separator = separator
775
- @fallback = nil
776
- end
777
- attr_reader :identifier
778
- attr_reader :attribute
779
- attr_reader :separator
780
-
781
- def identifier?
782
- !(identifier.nil? or identifier.to_s.empty?)
783
- end
784
-
785
- def attribute?
786
- !(attribute.nil? or attribute.to_s.empty?)
787
- end
788
-
789
- def separator?
790
- !(separator.nil? or separator.to_s.empty?)
791
- end
792
-
793
- def maybe_query(options={})
794
- query_without_condition = maybe_query_without_condition(options)
795
- if query_without_condition
796
- query_without_condition.sub(/\s*;\s*\z/, "") + " WHERE " + condition(options) + ";"
797
- else
798
- nil
799
- end
800
- end
801
-
802
- def maybe_query_without_condition(options={})
803
- tables = condition_tables(options)
804
- if tables.empty?
805
- nil
806
- else
807
- case tables
808
- when [:hosts]
809
- "SELECT hosts.id AS host_id FROM hosts;"
810
- when [:hosts, :tags]
811
- "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags INNER JOIN hosts ON hosts_tags.host_id = hosts.id INNER JOIN tags ON hosts_tags.tag_id = tags.id;"
812
- when [:tags]
813
- "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags INNER JOIN tags ON hosts_tags.tag_id = tags.id;"
814
- else
815
- raise(NotImplementedError.new("unknown tables: #{tables.join(", ")}"))
816
- end
817
- end
818
- end
819
-
820
- def condition(options={})
821
- raise(NotImplementedError.new("must be overridden"))
822
- end
823
-
824
- def condition_tables(options={})
825
- raise(NotImplementedError.new("must be overridden"))
826
- end
827
-
828
- def condition_values(options={})
829
- raise(NotImplementedError.new("must be overridden"))
830
- end
831
-
832
- def evaluate(environment, options={})
833
- q = maybe_query(options)
834
- if q
835
- values = environment.execute(q, condition_values(options)).map { |row| row.first }
836
- if values.empty?
837
- if options[:did_fallback]
838
- []
839
- else
840
- if not environment.fixed_string? and @fallback
841
- # avoid optimizing @fallback to prevent infinite recursion
842
- values = @fallback.evaluate(environment, options.merge(did_fallback: true))
843
- if values.empty?
844
- if reload(environment, options)
845
- evaluate(environment, options).tap do |values|
846
- if values.empty?
847
- environment.logger.info("no result: #{self.dump.inspect}")
848
- end
849
- end
850
- else
851
- []
852
- end
853
- else
854
- values
855
- end
856
- else
857
- if reload(environment, options)
858
- evaluate(environment, options).tap do |values|
859
- if values.empty?
860
- environment.logger.info("no result: #{self.dump.inspect}")
861
- end
862
- end
863
- else
864
- []
865
- end
866
- end
867
- end
868
- else
869
- values
870
- end
871
- else
872
- []
873
- end
874
- end
875
-
876
- def ==(other)
877
- self.class == other.class and @identifier == other.identifier and @attribute == other.attribute
878
- end
879
-
880
- def optimize(options={})
881
- # fallback to glob expression
882
- @fallback = maybe_fallback(options)
883
- self
884
- end
885
-
886
- def to_glob(s)
887
- (s.start_with?("*") ? "" : "*") + s.gsub(/[-.\/_]/, "?") + (s.end_with?("*") ? "" : "*")
888
- end
889
-
890
- def maybe_glob(s)
891
- s ? to_glob(s.to_s) : nil
892
- end
893
-
894
- def reload(environment, options={})
895
- $did_reload ||= false
896
- if $did_reload
897
- false
898
- else
899
- $did_reload = true
900
- environment.logger.info("force reloading all hosts and tags.")
901
- environment.reload(force: true)
902
- true
903
- end
904
- end
905
-
906
- def dump(options={})
907
- data = {}
908
- data[:identifier] = identifier.to_s if identifier
909
- data[:separator] = separator.to_s if separator
910
- data[:attribute] = attribute.to_s if attribute
911
- data[:fallback ] = @fallback.dump(options) if @fallback
912
- data
913
- end
914
-
915
- def maybe_fallback(options={})
916
- nil
917
- end
918
- end
919
-
920
- class AnyHostNode < TagExpressionNode
921
- def initialize(separator=nil)
922
- super("host", nil, separator)
923
- end
924
-
925
- def condition(options={})
926
- "1"
927
- end
928
-
929
- def condition_tables(options={})
930
- [:hosts]
931
- end
932
-
933
- def condition_values(options={})
934
- []
935
- end
936
- end
937
-
938
- class StringExpressionNode < TagExpressionNode
939
- end
940
-
941
- class StringHostNode < StringExpressionNode
942
- def initialize(attribute, separator=nil)
943
- super("host", attribute, separator)
944
- end
945
-
946
- def condition(options={})
947
- "hosts.name = ?"
948
- end
949
-
950
- def condition_tables(options={})
951
- [:hosts]
952
- end
953
-
954
- def condition_values(options={})
955
- [attribute]
956
- end
957
-
958
- def maybe_fallback(options={})
959
- fallback = GlobHostNode.new(to_glob(attribute), separator)
960
- query = fallback.maybe_query(options)
961
- if query
962
- QueryExpressionNode.new(query, fallback.condition_values(options))
963
- else
964
- nil
965
- end
966
- end
967
- end
968
-
969
- class StringTagNode < StringExpressionNode
970
- def initialize(identifier, attribute, separator=nil)
971
- super(identifier, attribute, separator)
972
- end
973
-
974
- def condition(options={})
975
- "tags.name = ? AND tags.value = ?"
976
- end
977
-
978
- def condition_tables(options={})
979
- [:tags]
980
- end
981
-
982
- def condition_values(options={})
983
- [identifier, attribute]
984
- end
985
-
986
- def maybe_fallback(options={})
987
- fallback = GlobTagNode.new(to_glob(identifier), to_glob(attribute), separator)
988
- query = fallback.maybe_query(options)
989
- if query
990
- QueryExpressionNode.new(query, fallback.condition_values(options))
991
- else
992
- nil
993
- end
994
- end
995
- end
996
-
997
- class StringTagNameNode < StringExpressionNode
998
- def initialize(identifier, separator=nil)
999
- super(identifier, nil, separator)
1000
- end
1001
-
1002
- def condition(options={})
1003
- "tags.name = ?"
1004
- end
1005
-
1006
- def condition_tables(options={})
1007
- [:tags]
1008
- end
1009
-
1010
- def condition_values(options={})
1011
- [identifier]
1012
- end
1013
-
1014
- def maybe_fallback(options={})
1015
- fallback = GlobTagNameNode.new(to_glob(identifier), separator)
1016
- query = fallback.maybe_query(options)
1017
- if query
1018
- QueryExpressionNode.new(query, fallback.condition_values(options))
1019
- else
1020
- nil
1021
- end
1022
- end
1023
- end
1024
-
1025
- class StringTagValueNode < StringExpressionNode
1026
- def initialize(attribute, separator=nil)
1027
- super(nil, attribute, separator)
1028
- end
1029
-
1030
- def condition(options={})
1031
- "hosts.name = ? OR tags.value = ?"
1032
- end
1033
-
1034
- def condition_tables(options={})
1035
- [:hosts, :tags]
1036
- end
1037
-
1038
- def condition_values(options={})
1039
- [attribute, attribute]
1040
- end
1041
-
1042
- def maybe_fallback(options={})
1043
- fallback = GlobTagValueNode.new(to_glob(attribute), separator)
1044
- query = fallback.maybe_query(options)
1045
- if query
1046
- QueryExpressionNode.new(query, fallback.condition_values(options))
1047
- else
1048
- nil
1049
- end
1050
- end
1051
- end
1052
-
1053
- class StringNode < StringExpressionNode
1054
- def initialize(identifier, separator=nil)
1055
- super(identifier, nil, separator)
1056
- end
1057
-
1058
- def condition(options={})
1059
- "hosts.name = ? OR tags.name = ? OR tags.value = ?"
1060
- end
1061
-
1062
- def condition_tables(options={})
1063
- [:hosts, :tags]
1064
- end
1065
-
1066
- def condition_values(options={})
1067
- [identifier, identifier, identifier]
1068
- end
1069
-
1070
- def maybe_fallback(options={})
1071
- fallback = GlobNode.new(to_glob(identifier), separator)
1072
- query = fallback.maybe_query(options)
1073
- if query
1074
- QueryExpressionNode.new(query, fallback.condition_values(options))
1075
- else
1076
- nil
1077
- end
1078
- end
1079
- end
1080
-
1081
- class GlobExpressionNode < TagExpressionNode
1082
- def dump(options={})
1083
- data = {}
1084
- data[:identifier_glob] = identifier.to_s if identifier
1085
- data[:separator] = separator.to_s if separator
1086
- data[:attribute_glob] = attribute.to_s if attribute
1087
- data[:fallback] = @fallback.dump(options) if @fallback
1088
- data
1089
- end
1090
- end
1091
-
1092
- class GlobHostNode < GlobExpressionNode
1093
- def initialize(attribute, separator=nil)
1094
- super("host", attribute, separator)
1095
- end
1096
-
1097
- def condition(options={})
1098
- "LOWER(hosts.name) GLOB LOWER(?)"
1099
- end
1100
-
1101
- def condition_tables(options={})
1102
- [:hosts]
1103
- end
1104
-
1105
- def condition_values(options={})
1106
- [attribute]
1107
- end
1108
-
1109
- def maybe_fallback(options={})
1110
- fallback = GlobHostNode.new(to_glob(attribute), separator)
1111
- query = fallback.maybe_query(options)
1112
- if query
1113
- QueryExpressionNode.new(query, fallback.condition_values(options))
1114
- else
1115
- nil
1116
- end
1117
- end
1118
- end
1119
-
1120
- class GlobTagNode < GlobExpressionNode
1121
- def initialize(identifier, attribute, separator=nil)
1122
- super(identifier, attribute, separator)
1123
- end
1124
-
1125
- def condition(options={})
1126
- "LOWER(tags.name) GLOB LOWER(?) AND LOWER(tags.value) GLOB LOWER(?)"
1127
- end
1128
-
1129
- def condition_tables(options={})
1130
- [:tags]
1131
- end
1132
-
1133
- def condition_values(options={})
1134
- [identifier, attribute]
1135
- end
1136
-
1137
- def maybe_fallback(options={})
1138
- fallback = GlobTagNode.new(to_glob(identifier), to_glob(attribute), separator)
1139
- query = fallback.maybe_query(options)
1140
- if query
1141
- QueryExpressionNode.new(query, fallback.condition_values(options))
1142
- else
1143
- nil
1144
- end
1145
- end
1146
- end
1147
-
1148
- class GlobTagNameNode < GlobExpressionNode
1149
- def initialize(identifier, separator=nil)
1150
- super(identifier, nil, separator)
1151
- end
1152
-
1153
- def condition(options={})
1154
- "LOWER(tags.name) GLOB LOWER(?)"
1155
- end
1156
-
1157
- def condition_tables(options={})
1158
- [:tags]
1159
- end
1160
-
1161
- def condition_values(options={})
1162
- [identifier]
1163
- end
1164
-
1165
- def maybe_fallback(options={})
1166
- fallback = GlobTagNameNode.new(to_glob(identifier), separator)
1167
- query = fallback.maybe_query(options)
1168
- if query
1169
- QueryExpressionNode.new(query, fallback.condition_values(options))
1170
- else
1171
- nil
1172
- end
1173
- end
1174
- end
1175
-
1176
- class GlobTagValueNode < GlobExpressionNode
1177
- def initialize(attribute, separator=nil)
1178
- super(nil, attribute, separator)
1179
- end
1180
-
1181
- def condition(options={})
1182
- "LOWER(hosts.name) GLOB LOWER(?) OR LOWER(tags.value) GLOB LOWER(?)"
1183
- end
1184
-
1185
- def condition_tables(options={})
1186
- [:hosts, :tags]
1187
- end
1188
-
1189
- def condition_values(options={})
1190
- [attribute, attribute]
1191
- end
1192
-
1193
- def maybe_fallback(options={})
1194
- fallback = GlobTagValueNode.new(to_glob(attribute), separator)
1195
- query = fallback.maybe_query(options)
1196
- if query
1197
- QueryExpressionNode.new(query, fallback.condition_values(options))
1198
- else
1199
- nil
1200
- end
1201
- end
1202
- end
1203
-
1204
- class GlobNode < GlobExpressionNode
1205
- def initialize(identifier, separator=nil)
1206
- super(identifier, nil, separator)
1207
- end
1208
-
1209
- def condition(options={})
1210
- "LOWER(hosts.name) GLOB LOWER(?) OR LOWER(tags.name) GLOB LOWER(?) OR LOWER(tags.value) GLOB LOWER(?)"
1211
- end
1212
-
1213
- def condition_tables(options={})
1214
- [:hosts, :tags]
1215
- end
1216
-
1217
- def condition_values(options={})
1218
- [identifier, identifier, identifier]
1219
- end
1220
-
1221
- def maybe_fallback(options={})
1222
- fallback = GlobNode.new(to_glob(identifier), separator)
1223
- query = fallback.maybe_query(options)
1224
- if query
1225
- QueryExpressionNode.new(query, fallback.condition_values(options))
1226
- else
1227
- nil
1228
- end
1229
- end
1230
- end
1231
-
1232
- class RegexpExpressionNode < TagExpressionNode
1233
- def dump(options={})
1234
- data = {}
1235
- data[:identifier_regexp] = identifier.to_s if identifier
1236
- data[:separator] = separator.to_s if separator
1237
- data[:attribute_regexp] = attribute.to_s if attribute
1238
- data[:fallback] = @fallback.dump(options) if @fallback
1239
- data
1240
- end
1241
- end
1242
-
1243
- class RegexpHostNode < RegexpExpressionNode
1244
- def initialize(attribute, separator=nil)
1245
- super("host", attribute, separator)
1246
- end
1247
-
1248
- def condition(options={})
1249
- "hosts.name REGEXP ?"
1250
- end
1251
-
1252
- def condition_tables(options={})
1253
- [:hosts]
1254
- end
1255
-
1256
- def condition_values(options={})
1257
- [attribute]
1258
- end
1259
- end
1260
-
1261
- class RegexpTagNode < RegexpExpressionNode
1262
- def initialize(identifier, attribute, separator=nil)
1263
- super(identifier, attribute, separator)
1264
- end
1265
-
1266
- def condition(options={})
1267
- "tags.name REGEXP ? AND tags.value REGEXP ?"
1268
- end
1269
-
1270
- def condition_tables(options={})
1271
- [:tags]
1272
- end
1273
-
1274
- def condition_values(options={})
1275
- [identifier, attribute]
1276
- end
1277
- end
1278
-
1279
- class RegexpTagNameNode < RegexpExpressionNode
1280
- def initialize(identifier, separator=nil)
1281
- super(identifier, nil, separator)
1282
- end
1283
-
1284
- def condition(options={})
1285
- "tags.name REGEXP ?"
1286
- end
1287
-
1288
- def condition_tables(options={})
1289
- [:tags]
1290
- end
1291
-
1292
- def condition_values(options={})
1293
- [identifier]
1294
- end
1295
- end
1296
-
1297
- class RegexpTagValueNode < RegexpExpressionNode
1298
- def initialize(attribute, separator=nil)
1299
- super(nil, attribute, separator)
1300
- end
1301
-
1302
- def condition(options={})
1303
- "hosts.name REGEXP ? OR tags.value REGEXP ?"
1304
- end
1305
-
1306
- def condition_tables(options={})
1307
- [:hosts, :tags]
1308
- end
1309
-
1310
- def condition_values(options={})
1311
- [attribute, attribute]
1312
- end
1313
- end
1314
-
1315
- class RegexpNode < RegexpExpressionNode
1316
- def initialize(identifier, separator=nil)
1317
- super(identifier, separator)
1318
- end
1319
-
1320
- def condition(options={})
1321
- "hosts.name REGEXP ? OR tags.name REGEXP ? OR tags.value REGEXP ?"
1322
- end
1323
-
1324
- def condition_tables(options={})
1325
- [:hosts, :tags]
1326
- end
1327
-
1328
- def condition_values(options={})
1329
- [identifier, identifier, identifier]
97
+ node = Hotdog::Expression::ExpressionTransformer.new.apply(data)
98
+ if Hotdog::Expression::ExpressionNode === node
99
+ optimized = node.optimize.tap do |optimized|
100
+ logger.debug {
101
+ JSON.pretty_generate(optimized.dump)
102
+ }
103
+ end
104
+ optimized.evaluate(environment)
105
+ else
106
+ raise("parser error: unknown expression: #{node.inspect}")
1330
107
  end
1331
108
  end
1332
109
  end