hotdog 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hotdog/commands/pssh.rb +1 -1
- data/lib/hotdog/commands/search.rb +366 -112
- data/lib/hotdog/commands/ssh.rb +1 -1
- data/lib/hotdog/version.rb +1 -1
- data/spec/parser/parser_spec.rb +11 -11
- data/spec/parser/tag_expression_spec.rb +45 -7
- data/spec/parser/tag_glob_expression_spec.rb +45 -7
- data/spec/parser/tag_regexp_expression_spec.rb +45 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2649bee0b66f057008d4276174d397b21ac87dd
|
4
|
+
data.tar.gz: 29d8d1620fab0110ac684b0059fc42e095c8c7bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7024f0e5e985792bd9fe1105b8fab9c65291a7bea66c6cd22f0e33fb7c838873649ae8ee3dc3b889f51882c68c3641ad75da37ff857cc3fc5e898d100abbb07
|
7
|
+
data.tar.gz: acf1ba39e5aecf2df6a9e2c7312c38ae3cea71a818d82dc681c68f6068a4cf5ab4f40f3ad8e33ccd69aac8b30f143faf0382ce52afb2264ae292a3cc39183965
|
data/lib/hotdog/commands/pssh.rb
CHANGED
@@ -26,7 +26,7 @@ module Hotdog
|
|
26
26
|
exit(1)
|
27
27
|
end
|
28
28
|
|
29
|
-
result = evaluate(node, self)
|
29
|
+
result = evaluate(node, self)
|
30
30
|
if 0 < result.length
|
31
31
|
_result, fields = get_hosts_with_search_tags(result, node)
|
32
32
|
result = _result.take(search_options.fetch(:limit, _result.size))
|
@@ -73,7 +73,10 @@ module Hotdog
|
|
73
73
|
|
74
74
|
def evaluate(data, environment)
|
75
75
|
node = ExpressionTransformer.new.apply(data)
|
76
|
-
node.optimize.
|
76
|
+
optimized = node.optimize.tap do |optimized|
|
77
|
+
logger.debug(JSON.pretty_generate(optimized.dump))
|
78
|
+
end
|
79
|
+
optimized.evaluate(environment)
|
77
80
|
end
|
78
81
|
|
79
82
|
class ExpressionParser < Parslet::Parser
|
@@ -101,6 +104,7 @@ module Hotdog
|
|
101
104
|
( expression4.as(:left) >> spacing.maybe >> str('&&').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
|
102
105
|
| expression4.as(:left) >> spacing.maybe >> str('||').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
|
103
106
|
| expression4.as(:left) >> spacing.maybe >> str('&').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
|
107
|
+
| expression4.as(:left) >> spacing.maybe >> str('^').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
|
104
108
|
| expression4.as(:left) >> spacing.maybe >> str('|').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
|
105
109
|
| expression4 \
|
106
110
|
)
|
@@ -116,8 +120,10 @@ module Hotdog
|
|
116
120
|
rule(:binary_op) {
|
117
121
|
( str('AND') \
|
118
122
|
| str('OR') \
|
123
|
+
| str('XOR') \
|
119
124
|
| str('and') \
|
120
125
|
| str('or') \
|
126
|
+
| str('xor') \
|
121
127
|
)
|
122
128
|
}
|
123
129
|
rule(:unary_op) {
|
@@ -127,20 +133,20 @@ module Hotdog
|
|
127
133
|
}
|
128
134
|
rule(:atom) {
|
129
135
|
( spacing.maybe >> str('(') >> expression >> str(')') >> spacing.maybe \
|
130
|
-
| spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> separator >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
|
131
|
-
| spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> separator >> spacing.maybe \
|
136
|
+
| spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> separator.as(:separator) >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
|
137
|
+
| spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> separator.as(:separator) >> spacing.maybe \
|
132
138
|
| spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> spacing.maybe \
|
133
|
-
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
134
|
-
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator >> attribute.as(:attribute) >> spacing.maybe \
|
135
|
-
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator >> spacing.maybe \
|
139
|
+
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator.as(:separator) >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
140
|
+
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator.as(:separator) >> attribute.as(:attribute) >> spacing.maybe \
|
141
|
+
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> separator.as(:separator) >> spacing.maybe \
|
136
142
|
| spacing.maybe >> identifier_glob.as(:identifier_glob) >> spacing.maybe \
|
137
|
-
| spacing.maybe >> identifier.as(:identifier) >> separator >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
138
|
-
| spacing.maybe >> identifier.as(:identifier) >> separator >> attribute.as(:attribute) >> spacing.maybe \
|
139
|
-
| spacing.maybe >> identifier.as(:identifier) >> separator >> spacing.maybe \
|
143
|
+
| spacing.maybe >> identifier.as(:identifier) >> separator.as(:separator) >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
144
|
+
| spacing.maybe >> identifier.as(:identifier) >> separator.as(:separator) >> attribute.as(:attribute) >> spacing.maybe \
|
145
|
+
| spacing.maybe >> identifier.as(:identifier) >> separator.as(:separator) >> spacing.maybe \
|
140
146
|
| spacing.maybe >> identifier.as(:identifier) >> spacing.maybe \
|
141
|
-
| spacing.maybe >> separator >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
|
142
|
-
| spacing.maybe >> separator >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
143
|
-
| spacing.maybe >> separator >> attribute.as(:attribute) >> spacing.maybe \
|
147
|
+
| spacing.maybe >> separator.as(:separator) >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
|
148
|
+
| spacing.maybe >> separator.as(:separator) >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
149
|
+
| spacing.maybe >> separator.as(:separator) >> attribute.as(:attribute) >> spacing.maybe \
|
144
150
|
| spacing.maybe >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
|
145
151
|
| spacing.maybe >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
|
146
152
|
| spacing.maybe >> attribute.as(:attribute) >> spacing.maybe \
|
@@ -185,44 +191,62 @@ module Hotdog
|
|
185
191
|
end
|
186
192
|
|
187
193
|
class ExpressionTransformer < Parslet::Transform
|
188
|
-
rule(:
|
194
|
+
rule(binary_op: simple(:binary_op), left: simple(:left), right: simple(:right)) {
|
189
195
|
BinaryExpressionNode.new(binary_op, left, right)
|
190
196
|
}
|
191
|
-
rule(:
|
197
|
+
rule(unary_op: simple(:unary_op), expression: simple(:expression)) {
|
192
198
|
UnaryExpressionNode.new(unary_op, expression)
|
193
199
|
}
|
194
|
-
rule(:identifier_regexp
|
195
|
-
TagRegexpExpressionNode.new(identifier_regexp.to_s, attribute_regexp.to_s)
|
200
|
+
rule(identifier_regexp: simple(:identifier_regexp), separator: simple(:separator), attribute_regexp: simple(:attribute_regexp)) {
|
201
|
+
TagRegexpExpressionNode.new(identifier_regexp.to_s, attribute_regexp.to_s, separator)
|
202
|
+
}
|
203
|
+
rule(identifier_regexp: simple(:identifier_regexp), separator: simple(:separator)) {
|
204
|
+
TagRegexpExpressionNode.new(identifier_regexp.to_s, nil, nil)
|
205
|
+
}
|
206
|
+
rule(identifier_regexp: simple(:identifier_regexp)) {
|
207
|
+
TagRegexpExpressionNode.new(identifier_regexp.to_s, nil, nil)
|
208
|
+
}
|
209
|
+
rule(identifier_glob: simple(:identifier_glob), separator: simple(:separator), attribute_glob: simple(:attribute_glob)) {
|
210
|
+
TagGlobExpressionNode.new(identifier_glob.to_s, attribute_glob.to_s, separator)
|
196
211
|
}
|
197
|
-
rule(:
|
198
|
-
|
212
|
+
rule(identifier_glob: simple(:identifier_glob), separator: simple(:separator), attribute: simple(:attribute)) {
|
213
|
+
TagGlobExpressionNode.new(identifier_glob.to_s, attribute.to_s, separator)
|
199
214
|
}
|
200
|
-
rule(:
|
201
|
-
TagGlobExpressionNode.new(identifier_glob.to_s,
|
215
|
+
rule(identifier_glob: simple(:identifier_glob), separator: simple(:separator)) {
|
216
|
+
TagGlobExpressionNode.new(identifier_glob.to_s, nil, separator)
|
202
217
|
}
|
203
|
-
rule(:
|
204
|
-
TagGlobExpressionNode.new(identifier_glob.to_s,
|
218
|
+
rule(identifier_glob: simple(:identifier_glob)) {
|
219
|
+
TagGlobExpressionNode.new(identifier_glob.to_s, nil, nil)
|
205
220
|
}
|
206
|
-
rule(:
|
207
|
-
TagGlobExpressionNode.new(
|
221
|
+
rule(identifier: simple(:identifier), separator: simple(:separator), attribute_glob: simple(:attribute_glob)) {
|
222
|
+
TagGlobExpressionNode.new(identifier.to_s, attribute_glob.to_s, separator)
|
208
223
|
}
|
209
|
-
rule(:identifier
|
210
|
-
|
224
|
+
rule(identifier: simple(:identifier), separator: simple(:separator), attribute: simple(:attribute)) {
|
225
|
+
TagExpressionNode.new(identifier.to_s, attribute.to_s, separator)
|
211
226
|
}
|
212
|
-
rule(:
|
213
|
-
TagExpressionNode.new(identifier.to_s,
|
227
|
+
rule(identifier: simple(:identifier), separator: simple(:separator)) {
|
228
|
+
TagExpressionNode.new(identifier.to_s, nil, separator)
|
214
229
|
}
|
215
|
-
rule(:
|
216
|
-
TagExpressionNode.new(identifier.to_s, nil)
|
230
|
+
rule(identifier: simple(:identifier)) {
|
231
|
+
TagExpressionNode.new(identifier.to_s, nil, nil)
|
217
232
|
}
|
218
|
-
rule(:attribute_regexp
|
219
|
-
TagRegexpExpressionNode.new(nil, attribute_regexp.to_s)
|
233
|
+
rule(separator: simple(:separator), attribute_regexp: simple(:attribute_regexp)) {
|
234
|
+
TagRegexpExpressionNode.new(nil, attribute_regexp.to_s, separator)
|
220
235
|
}
|
221
|
-
rule(:
|
222
|
-
|
236
|
+
rule(attribute_regexp: simple(:attribute_regexp)) {
|
237
|
+
TagRegexpExpressionNode.new(nil, attribute_regexp.to_s, nil)
|
223
238
|
}
|
224
|
-
rule(:
|
225
|
-
|
239
|
+
rule(separator: simple(:separator), attribute_glob: simple(:attribute_glob)) {
|
240
|
+
TagGlobExpressionNode.new(nil, attribute_glob.to_s, separator)
|
241
|
+
}
|
242
|
+
rule(attribute_glob: simple(:attribute_glob)) {
|
243
|
+
TagGlobExpressionNode.new(nil, attribute_glob.to_s, nil)
|
244
|
+
}
|
245
|
+
rule(separator: simple(:separator), attribute: simple(:attribute)) {
|
246
|
+
TagExpressionNode.new(nil, attribute.to_s, separator)
|
247
|
+
}
|
248
|
+
rule(attribute: simple(:attribute)) {
|
249
|
+
TagExpressionNode.new(nil, attribute.to_s, nil)
|
226
250
|
}
|
227
251
|
end
|
228
252
|
|
@@ -234,6 +258,10 @@ module Hotdog
|
|
234
258
|
def optimize(options={})
|
235
259
|
self
|
236
260
|
end
|
261
|
+
|
262
|
+
def dump(options={})
|
263
|
+
{}
|
264
|
+
end
|
237
265
|
end
|
238
266
|
|
239
267
|
class BinaryExpressionNode < ExpressionNode
|
@@ -245,6 +273,8 @@ module Hotdog
|
|
245
273
|
@op = :AND
|
246
274
|
when "||", "|", /\Aor\z/i
|
247
275
|
@op = :OR
|
276
|
+
when "^", /\Axor\z/i
|
277
|
+
@op = :XOR
|
248
278
|
else
|
249
279
|
raise(SyntaxError.new("unknown binary operator: #{op.inspect}"))
|
250
280
|
end
|
@@ -255,24 +285,87 @@ module Hotdog
|
|
255
285
|
def evaluate(environment, options={})
|
256
286
|
case @op
|
257
287
|
when :AND
|
258
|
-
left_values = @left.evaluate(environment)
|
259
|
-
|
288
|
+
left_values = @left.evaluate(environment, options).tap do |values|
|
289
|
+
environment.logger.debug("lhs: #{values.length} value(s)")
|
290
|
+
end
|
260
291
|
if left_values.empty?
|
261
292
|
[]
|
262
293
|
else
|
263
|
-
right_values = @right.evaluate(environment)
|
264
|
-
|
265
|
-
|
266
|
-
|
294
|
+
right_values = @right.evaluate(environment, options).tap do |values|
|
295
|
+
environment.logger.debug("rhs: #{values.length} value(s)")
|
296
|
+
end
|
297
|
+
if right_values.empty?
|
298
|
+
[]
|
299
|
+
else
|
300
|
+
# workaround for "too many terms in compound SELECT"
|
301
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
302
|
+
(min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).flat_map { |i|
|
303
|
+
range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * (i + 1))
|
304
|
+
left_selected = left_values.select { |n| range === n }
|
305
|
+
right_selected = right_values.select { |n| range === n }
|
306
|
+
q = "SELECT id FROM hosts " \
|
307
|
+
"WHERE ? <= id AND id < ? AND ( id IN (%s) AND id IN (%s) );"
|
308
|
+
environment.execute(q % [left_selected.map { "?" }.join(", "), right_selected.map { "?" }.join(", ")], [range.first, range.last] + left_selected + right_selected).map { |row| row.first }
|
309
|
+
}.tap do |values|
|
310
|
+
environment.logger.debug("lhs AND rhs: #{values.length} value(s)")
|
311
|
+
end
|
267
312
|
end
|
268
313
|
end
|
269
314
|
when :OR
|
270
|
-
left_values = @left.evaluate(environment)
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
315
|
+
left_values = @left.evaluate(environment, options).tap do |values|
|
316
|
+
environment.logger.debug("lhs: #{values.length} value(s)")
|
317
|
+
end
|
318
|
+
right_values = @right.evaluate(environment, options).tap do |values|
|
319
|
+
environment.logger.debug("rhs: #{values.length} value(s)")
|
320
|
+
end
|
321
|
+
if left_values.empty?
|
322
|
+
right_values
|
323
|
+
else
|
324
|
+
if right_values.empty?
|
325
|
+
[]
|
326
|
+
else
|
327
|
+
# workaround for "too many terms in compound SELECT"
|
328
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
329
|
+
(min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).flat_map { |i|
|
330
|
+
range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * (i + 1))
|
331
|
+
left_selected = left_values.select { |n| range === n }
|
332
|
+
right_selected = right_values.select { |n| range === n }
|
333
|
+
q = "SELECT id FROM hosts " \
|
334
|
+
"WHERE ? <= id AND id < ? AND ( id IN (%s) OR id IN (%s) );"
|
335
|
+
environment.execute(q % [left_selected.map { "?" }.join(", "), right_selected.map { "?" }.join(", ")], [range.first, range.last] + left_selected + right_selected).map { |row| row.first }
|
336
|
+
}.tap do |values|
|
337
|
+
environment.logger.debug("lhs OR rhs: #{values.length} value(s)")
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
when :XOR
|
342
|
+
left_values = @left.evaluate(environment, options).tap do |values|
|
343
|
+
environment.logger.debug("lhs: #{values.length} value(s)")
|
344
|
+
end
|
345
|
+
right_values = @right.evaluate(environment, options).tap do |values|
|
346
|
+
environment.logger.debug("rhs: #{values.length} value(s)")
|
347
|
+
end
|
348
|
+
if left_values.empty?
|
349
|
+
right_values
|
350
|
+
else
|
351
|
+
if right_values.empty?
|
352
|
+
[]
|
353
|
+
else
|
354
|
+
# workaround for "too many terms in compound SELECT"
|
355
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
356
|
+
(min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4)).flat_map { |i|
|
357
|
+
range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4) * (i + 1))
|
358
|
+
left_selected = left_values.select { |n| range === n }
|
359
|
+
right_selected = right_values.select { |n| range === n }
|
360
|
+
q = "SELECT id FROM hosts " \
|
361
|
+
"WHERE ? <= id AND id < ? AND NOT (id IN (%s) AND id IN (%s)) AND ( id IN (%s) OR id IN (%s) );"
|
362
|
+
lq = left_selected.map { "?" }.join(", ")
|
363
|
+
rq = right_selected.map { "?" }.join(", ")
|
364
|
+
environment.execute(q % [lq, rq, lq, rq], [range.first, range.last] + left_selected + right_selected + left_selected + right_selected).map { |row| row.first }
|
365
|
+
}.tap do |values|
|
366
|
+
environment.logger.debug("lhs XOR rhs: #{values.length} value(s)")
|
367
|
+
end
|
368
|
+
end
|
276
369
|
end
|
277
370
|
else
|
278
371
|
[]
|
@@ -282,8 +375,21 @@ module Hotdog
|
|
282
375
|
def optimize(options={})
|
283
376
|
@left = @left.optimize(options)
|
284
377
|
@right = @right.optimize(options)
|
285
|
-
|
286
|
-
|
378
|
+
case op
|
379
|
+
when :AND
|
380
|
+
if left == right
|
381
|
+
left
|
382
|
+
else
|
383
|
+
optimize1(options)
|
384
|
+
end
|
385
|
+
when :OR
|
386
|
+
if left == right
|
387
|
+
left
|
388
|
+
else
|
389
|
+
optimize1(options)
|
390
|
+
end
|
391
|
+
when :XOR
|
392
|
+
optimize1(options)
|
287
393
|
else
|
288
394
|
self
|
289
395
|
end
|
@@ -292,6 +398,42 @@ module Hotdog
|
|
292
398
|
def ==(other)
|
293
399
|
self.class === other and @op == other.op and @left == other.left and @right == other.right
|
294
400
|
end
|
401
|
+
|
402
|
+
def dump(options={})
|
403
|
+
{left: @left.dump(options), op: @op.to_s, right: @right.dump(options)}
|
404
|
+
end
|
405
|
+
|
406
|
+
private
|
407
|
+
def optimize1(options)
|
408
|
+
if TagExpressionNode === left and TagExpressionNode === right
|
409
|
+
lhs = left.plan(options)
|
410
|
+
rhs = right.plan(options)
|
411
|
+
if lhs and rhs and lhs[1].length + rhs[1].length <= SQLITE_LIMIT_COMPOUND_SELECT
|
412
|
+
case op
|
413
|
+
when :AND
|
414
|
+
q = "SELECT host_id FROM ( #{lhs[0].sub(/\s*;\s*\z/, "")} ) " \
|
415
|
+
"INTERSECT #{rhs[0].sub(/\s*;\s*\z/, "")};"
|
416
|
+
QueryExpressionNode.new(q, lhs[1] + rhs[1], fallback: self)
|
417
|
+
when :OR
|
418
|
+
q = "SELECT host_id FROM ( #{lhs[0].sub(/\s*;\s*\z/, "")} ) " \
|
419
|
+
"UNION #{rhs[0].sub(/\s*;\s*\z/, "")};"
|
420
|
+
QueryExpressionNode.new(q, lhs[1] + rhs[1], fallback: self)
|
421
|
+
when :XOR
|
422
|
+
q = "SELECT host_id FROM ( #{lhs[0].sub(/\s*;\s*\z/, "")} ) " \
|
423
|
+
"UNION #{rhs[0].sub(/\s*;\s*\z/, "")} " \
|
424
|
+
"EXCEPT SELECT host_id FROM ( #{lhs[0].sub(/\s*;\s*\z/, "")} ) " \
|
425
|
+
"INTERSECT #{rhs[0].sub(/\s*;\s*\z/, "")};"
|
426
|
+
QueryExpressionNode.new(q, lhs[1] + rhs[1], fallback: self)
|
427
|
+
else
|
428
|
+
self
|
429
|
+
end
|
430
|
+
else
|
431
|
+
self
|
432
|
+
end
|
433
|
+
else
|
434
|
+
self
|
435
|
+
end
|
436
|
+
end
|
295
437
|
end
|
296
438
|
|
297
439
|
class UnaryExpressionNode < ExpressionNode
|
@@ -310,8 +452,9 @@ module Hotdog
|
|
310
452
|
def evaluate(environment, options={})
|
311
453
|
case @op
|
312
454
|
when :NOT
|
313
|
-
values = @expression.evaluate(environment).
|
314
|
-
|
455
|
+
values = @expression.evaluate(environment, options).tap do |values|
|
456
|
+
environment.logger.debug("expr: #{values.length} value(s)")
|
457
|
+
end
|
315
458
|
if values.empty?
|
316
459
|
environment.execute("SELECT id FROM hosts").map { |row| row.first }.tap do |values|
|
317
460
|
environment.logger.debug("NOT expr: #{values.length} value(s)")
|
@@ -319,8 +462,8 @@ module Hotdog
|
|
319
462
|
else
|
320
463
|
# workaround for "too many terms in compound SELECT"
|
321
464
|
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
322
|
-
(min / SQLITE_LIMIT_COMPOUND_SELECT).upto(max / SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |i|
|
323
|
-
range = (SQLITE_LIMIT_COMPOUND_SELECT*i)...(SQLITE_LIMIT_COMPOUND_SELECT*(i+1))
|
465
|
+
(min / (SQLITE_LIMIT_COMPOUND_SELECT - 2)).upto(max / (SQLITE_LIMIT_COMPOUND_SELECT - 2)).flat_map { |i|
|
466
|
+
range = ((SQLITE_LIMIT_COMPOUND_SELECT - 2) * i)...((SQLITE_LIMIT_COMPOUND_SELECT - 2) * (i + 1))
|
324
467
|
selected = values.select { |n| range === n }
|
325
468
|
q = "SELECT id FROM hosts " \
|
326
469
|
"WHERE ? <= id AND id < ? AND id NOT IN (%s);"
|
@@ -336,12 +479,9 @@ module Hotdog
|
|
336
479
|
|
337
480
|
def optimize(options={})
|
338
481
|
@expression = @expression.optimize(options)
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
else
|
343
|
-
self
|
344
|
-
end
|
482
|
+
case op
|
483
|
+
when :NOT
|
484
|
+
optimize1(options)
|
345
485
|
else
|
346
486
|
self
|
347
487
|
end
|
@@ -350,15 +490,69 @@ module Hotdog
|
|
350
490
|
def ==(other)
|
351
491
|
self.class === other and @op == other.op and @expression == other.expression
|
352
492
|
end
|
493
|
+
|
494
|
+
def dump(options={})
|
495
|
+
{op: @op.to_s, expression: @expression.dump(options)}
|
496
|
+
end
|
497
|
+
|
498
|
+
private
|
499
|
+
def optimize1(options={})
|
500
|
+
case op
|
501
|
+
when :NOT
|
502
|
+
if UnaryExpressionNode === expression and expression.op == :NOT
|
503
|
+
expression.expression
|
504
|
+
else
|
505
|
+
if TagExpressionNode === expression
|
506
|
+
expr = expression.plan(options)
|
507
|
+
if expr and expr[1].length <= SQLITE_LIMIT_COMPOUND_SELECT
|
508
|
+
q = "SELECT id AS host_id FROM hosts " \
|
509
|
+
"EXCEPT #{expr[0].sub(/\s*;\s*\z/, "")};"
|
510
|
+
QueryExpressionNode.new(q, expr[1])
|
511
|
+
else
|
512
|
+
self
|
513
|
+
end
|
514
|
+
else
|
515
|
+
self
|
516
|
+
end
|
517
|
+
end
|
518
|
+
else
|
519
|
+
self
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
class QueryExpressionNode < ExpressionNode
|
525
|
+
def initialize(query, args=[], options={})
|
526
|
+
@query = query
|
527
|
+
@args = args
|
528
|
+
@fallback = options[:fallback]
|
529
|
+
end
|
530
|
+
|
531
|
+
def evaluate(environment, options={})
|
532
|
+
values = environment.execute(@query, @args).map { |row| row.first }
|
533
|
+
if values.empty? and @fallback
|
534
|
+
@fallback.evaluate(environment, options)
|
535
|
+
else
|
536
|
+
values
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def dump(options={})
|
541
|
+
data = {query: @query, arguments: @args}
|
542
|
+
data[:fallback] = @fallback.dump(options) if @fallback
|
543
|
+
data
|
544
|
+
end
|
353
545
|
end
|
354
546
|
|
355
547
|
class TagExpressionNode < ExpressionNode
|
356
|
-
def initialize(identifier, attribute)
|
548
|
+
def initialize(identifier, attribute, separator=nil)
|
357
549
|
@identifier = identifier
|
358
550
|
@attribute = attribute
|
551
|
+
@separator = separator
|
359
552
|
end
|
360
553
|
attr_reader :identifier
|
361
554
|
attr_reader :attribute
|
555
|
+
attr_reader :separator
|
362
556
|
|
363
557
|
def identifier?
|
364
558
|
!(identifier.nil? or identifier.to_s.empty?)
|
@@ -368,41 +562,60 @@ module Hotdog
|
|
368
562
|
!(attribute.nil? or attribute.to_s.empty?)
|
369
563
|
end
|
370
564
|
|
371
|
-
def
|
565
|
+
def separator?
|
566
|
+
!(separator.nil? or separator.to_s.empty?)
|
567
|
+
end
|
568
|
+
|
569
|
+
def plan(options={})
|
372
570
|
if identifier?
|
373
571
|
if attribute?
|
374
572
|
case identifier
|
375
573
|
when /\Ahost\z/i
|
376
|
-
q = "SELECT hosts.id FROM hosts " \
|
574
|
+
q = "SELECT hosts.id AS host_id FROM hosts " \
|
377
575
|
"WHERE hosts.name = ?;"
|
378
|
-
|
576
|
+
[q, [attribute]]
|
379
577
|
else
|
380
578
|
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
381
579
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
382
580
|
"WHERE tags.name = ? AND tags.value = ?;"
|
383
|
-
|
581
|
+
[q, [identifier, attribute]]
|
384
582
|
end
|
385
583
|
else
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
584
|
+
if separator?
|
585
|
+
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
586
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
587
|
+
"WHERE tags.name = ?;"
|
588
|
+
[q, [identifier]]
|
589
|
+
else
|
590
|
+
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
591
|
+
"INNER JOIN hosts ON hosts_tags.host_id = hosts.id " \
|
592
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
593
|
+
"WHERE hosts.name = ? OR tags.name = ? OR tags.value = ?;"
|
594
|
+
[q, [identifier, identifier, identifier]]
|
595
|
+
end
|
391
596
|
end
|
392
597
|
else
|
393
598
|
if attribute?
|
394
599
|
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
395
600
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
396
601
|
"WHERE tags.value = ?;"
|
397
|
-
|
602
|
+
[q, [attribute]]
|
398
603
|
else
|
399
|
-
|
604
|
+
nil
|
400
605
|
end
|
401
606
|
end
|
402
|
-
|
403
|
-
|
607
|
+
end
|
608
|
+
|
609
|
+
def evaluate(environment, options={})
|
610
|
+
if q = plan(options)
|
611
|
+
values = environment.execute(*q).map { |row| row.first }
|
612
|
+
if values.empty?
|
613
|
+
fallback(environment, options)
|
614
|
+
else
|
615
|
+
values
|
616
|
+
end
|
404
617
|
else
|
405
|
-
|
618
|
+
return []
|
406
619
|
end
|
407
620
|
end
|
408
621
|
|
@@ -415,11 +628,11 @@ module Hotdog
|
|
415
628
|
[]
|
416
629
|
else
|
417
630
|
# fallback to glob expression
|
418
|
-
identifier_glob = identifier.gsub(/[-.\/_]/, "?") if identifier?
|
419
|
-
attribute_glob = attribute.gsub(/[-.\/_]/, "?") if attribute?
|
631
|
+
identifier_glob = "*#{identifier.gsub(/[-.\/_]/, "?")}*" if identifier?
|
632
|
+
attribute_glob = "*#{attribute.gsub(/[-.\/_]/, "?")}*" if attribute?
|
420
633
|
if (identifier? and identifier != identifier_glob) or (attribute? and attribute != attribute_glob)
|
421
634
|
environment.logger.info("fallback to glob expression: %s:%s" % [identifier_glob, attribute_glob])
|
422
|
-
values = TagGlobExpressionNode.new(identifier_glob, attribute_glob).evaluate(environment, options)
|
635
|
+
values = TagGlobExpressionNode.new(identifier_glob, attribute_glob, separator).evaluate(environment, options)
|
423
636
|
if values.empty?
|
424
637
|
reload(environment, options)
|
425
638
|
else
|
@@ -436,97 +649,138 @@ module Hotdog
|
|
436
649
|
if 0 < ttl
|
437
650
|
environment.logger.info("force reloading all hosts and tags.")
|
438
651
|
environment.reload(force: true)
|
439
|
-
self.class.new(identifier, attribute).evaluate(environment, options.merge(ttl: ttl-1))
|
652
|
+
self.class.new(identifier, attribute, separator).evaluate(environment, options.merge(ttl: ttl-1))
|
440
653
|
else
|
441
654
|
[]
|
442
655
|
end
|
443
656
|
end
|
657
|
+
|
658
|
+
def dump(options={})
|
659
|
+
data = {}
|
660
|
+
data[:identifier] = @identifier if @identifier
|
661
|
+
data[:separator] = @separator if @separator
|
662
|
+
data[:attribute] = @attribute if @attribute
|
663
|
+
data
|
664
|
+
end
|
444
665
|
end
|
445
666
|
|
446
667
|
class TagGlobExpressionNode < TagExpressionNode
|
447
|
-
def
|
668
|
+
def plan(options={})
|
448
669
|
if identifier?
|
449
670
|
if attribute?
|
450
671
|
case identifier
|
451
672
|
when /\Ahost\z/i
|
452
|
-
q = "SELECT hosts.id FROM hosts " \
|
673
|
+
q = "SELECT hosts.id AS host_id FROM hosts " \
|
453
674
|
"WHERE hosts.name GLOB ?;"
|
454
|
-
|
675
|
+
[q, [attribute]]
|
455
676
|
else
|
456
677
|
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
457
678
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
458
679
|
"WHERE tags.name GLOB ? AND tags.value GLOB ?;"
|
459
|
-
|
680
|
+
[q, [identifier, attribute]]
|
460
681
|
end
|
461
682
|
else
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
683
|
+
if separator?
|
684
|
+
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
685
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
686
|
+
"WHERE tags.name GLOB ?;"
|
687
|
+
[q, [identifier]]
|
688
|
+
else
|
689
|
+
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
690
|
+
"INNER JOIN hosts ON hosts_tags.host_id = hosts.id " \
|
691
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
692
|
+
"WHERE hosts.name GLOB ? OR tags.name GLOB ? OR tags.value GLOB ?;"
|
693
|
+
[q, [identifier, identifier, identifier]]
|
694
|
+
end
|
467
695
|
end
|
468
696
|
else
|
469
697
|
if attribute?
|
470
698
|
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
471
699
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
472
700
|
"WHERE tags.value GLOB ?;"
|
473
|
-
|
701
|
+
[q, [attribute]]
|
474
702
|
else
|
475
|
-
|
703
|
+
nil
|
476
704
|
end
|
477
705
|
end
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
706
|
+
end
|
707
|
+
|
708
|
+
def dump(options={})
|
709
|
+
data = {}
|
710
|
+
data[:identifier_glob] = @identifier if @identifier
|
711
|
+
data[:separator] = @separator if @separator
|
712
|
+
data[:attribute_glob] = @attribute if @attribute
|
713
|
+
data
|
483
714
|
end
|
484
715
|
end
|
485
716
|
|
486
717
|
class TagRegexpExpressionNode < TagExpressionNode
|
487
|
-
def initialize(identifier, attribute)
|
718
|
+
def initialize(identifier, attribute, separator=nil)
|
488
719
|
identifier = identifier.sub(%r{\A/(.*)/\z}) { $1 } if identifier
|
489
720
|
attribute = attribute.sub(%r{\A/(.*)/\z}) { $1 } if attribute
|
490
|
-
super(identifier, attribute)
|
721
|
+
super(identifier, attribute, separator)
|
491
722
|
end
|
492
723
|
|
493
|
-
def
|
724
|
+
def plan(options={})
|
494
725
|
if identifier?
|
495
726
|
if attribute?
|
496
727
|
case identifier
|
497
728
|
when /\Ahost\z/i
|
498
|
-
q = "SELECT hosts.id FROM hosts " \
|
729
|
+
q = "SELECT hosts.id AS host_id FROM hosts " \
|
499
730
|
"WHERE hosts.name REGEXP ?;"
|
500
|
-
|
731
|
+
[q, [attribute]]
|
501
732
|
else
|
502
733
|
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
503
734
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
504
735
|
"WHERE tags.name REGEXP ? AND tags.value REGEXP ?;"
|
505
|
-
|
736
|
+
[q, [identifier, attribute]]
|
506
737
|
end
|
507
738
|
else
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
739
|
+
if separator?
|
740
|
+
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
741
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
742
|
+
"WHERE tags.name REGEXP ?;"
|
743
|
+
[q, [identifier]]
|
744
|
+
else
|
745
|
+
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
746
|
+
"INNER JOIN hosts ON hosts_tags.host_id = hosts.id " \
|
747
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
748
|
+
"WHERE hosts.name REGEXP ? OR tags.name REGEXP ? OR tags.value REGEXP ?;"
|
749
|
+
[q, [identifier, identifier, identifier]]
|
750
|
+
end
|
513
751
|
end
|
514
752
|
else
|
515
753
|
if attribute?
|
516
754
|
q = "SELECT DISTINCT hosts_tags.host_id FROM hosts_tags " \
|
517
755
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
518
756
|
"WHERE tags.value REGEXP ?;"
|
519
|
-
|
757
|
+
[q, [attribute]]
|
520
758
|
else
|
521
|
-
|
759
|
+
nil
|
522
760
|
end
|
523
761
|
end
|
524
|
-
|
525
|
-
|
762
|
+
end
|
763
|
+
|
764
|
+
def evaluate(environment, options={})
|
765
|
+
if q = plan(options)
|
766
|
+
values = environment.execute(*q).map { |row| row.first }
|
767
|
+
if values.empty?
|
768
|
+
reload(environment)
|
769
|
+
else
|
770
|
+
values
|
771
|
+
end
|
526
772
|
else
|
527
|
-
|
773
|
+
return []
|
528
774
|
end
|
529
775
|
end
|
776
|
+
|
777
|
+
def dump(options={})
|
778
|
+
data = {}
|
779
|
+
data[:identifier_regexp] = @identifier if @identifier
|
780
|
+
data[:separator] = @separator if @separator
|
781
|
+
data[:attribute_regexp] = @attribute if @attribute
|
782
|
+
data
|
783
|
+
end
|
530
784
|
end
|
531
785
|
end
|
532
786
|
end
|
data/lib/hotdog/commands/ssh.rb
CHANGED
data/lib/hotdog/version.rb
CHANGED
data/spec/parser/parser_spec.rb
CHANGED
@@ -15,15 +15,15 @@ describe "parser" do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it "parses ':foo'" do
|
18
|
-
expect(cmd.parse(":foo")).to eq({attribute: "foo"})
|
18
|
+
expect(cmd.parse(":foo")).to eq({separator: ":", attribute: "foo"})
|
19
19
|
end
|
20
20
|
|
21
21
|
it "parses ':foo*'" do
|
22
|
-
expect(cmd.parse(":foo*")).to eq({attribute_glob: "foo*"})
|
22
|
+
expect(cmd.parse(":foo*")).to eq({separator: ":", attribute_glob: "foo*"})
|
23
23
|
end
|
24
24
|
|
25
25
|
it "parses ':/foo/'" do
|
26
|
-
expect(cmd.parse(":/foo/")).to eq({attribute_regexp: "/foo/"})
|
26
|
+
expect(cmd.parse(":/foo/")).to eq({separator: ":", attribute_regexp: "/foo/"})
|
27
27
|
end
|
28
28
|
|
29
29
|
it "parses 'foo'" do
|
@@ -31,23 +31,23 @@ describe "parser" do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
it "parses 'foo:bar'" do
|
34
|
-
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", attribute: "bar"})
|
34
|
+
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", separator: ":", attribute: "bar"})
|
35
35
|
end
|
36
36
|
|
37
37
|
it "parses 'foo: bar'" do
|
38
|
-
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", attribute: "bar"})
|
38
|
+
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", separator: ":", attribute: "bar"})
|
39
39
|
end
|
40
40
|
|
41
41
|
it "parses 'foo :bar'" do
|
42
|
-
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", attribute: "bar"})
|
42
|
+
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", separator: ":", attribute: "bar"})
|
43
43
|
end
|
44
44
|
|
45
45
|
it "parses 'foo : bar'" do
|
46
|
-
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", attribute: "bar"})
|
46
|
+
expect(cmd.parse("foo:bar")).to eq({identifier: "foo", separator: ":", attribute: "bar"})
|
47
47
|
end
|
48
48
|
|
49
49
|
it "parses 'foo:bar*'" do
|
50
|
-
expect(cmd.parse("foo:bar*")).to eq({identifier: "foo", attribute_glob: "bar*"})
|
50
|
+
expect(cmd.parse("foo:bar*")).to eq({identifier: "foo", separator: ":", attribute_glob: "bar*"})
|
51
51
|
end
|
52
52
|
|
53
53
|
it "parses 'foo*'" do
|
@@ -55,11 +55,11 @@ describe "parser" do
|
|
55
55
|
end
|
56
56
|
|
57
57
|
it "parses 'foo*:bar'" do
|
58
|
-
expect(cmd.parse("foo*:bar")).to eq({identifier_glob: "foo*", attribute: "bar"})
|
58
|
+
expect(cmd.parse("foo*:bar")).to eq({identifier_glob: "foo*", separator: ":", attribute: "bar"})
|
59
59
|
end
|
60
60
|
|
61
61
|
it "parses 'foo*:bar*'" do
|
62
|
-
expect(cmd.parse("foo*:bar*")).to eq({identifier_glob: "foo*", attribute_glob: "bar*"})
|
62
|
+
expect(cmd.parse("foo*:bar*")).to eq({identifier_glob: "foo*", separator: ":", attribute_glob: "bar*"})
|
63
63
|
end
|
64
64
|
|
65
65
|
it "parses '/foo/'" do
|
@@ -67,7 +67,7 @@ describe "parser" do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
it "parses '/foo/:/bar/'" do
|
70
|
-
expect(cmd.parse("/foo/:/bar/")).to eq({identifier_regexp: "/foo/", attribute_regexp: "/bar/"})
|
70
|
+
expect(cmd.parse("/foo/:/bar/")).to eq({identifier_regexp: "/foo/", separator: ":", attribute_regexp: "/bar/"})
|
71
71
|
end
|
72
72
|
|
73
73
|
it "parses '(foo)'" do
|
@@ -10,19 +10,20 @@ describe "tag expression" do
|
|
10
10
|
}
|
11
11
|
|
12
12
|
it "interprets tag with host" do
|
13
|
-
expr = Hotdog::Commands::Search::TagExpressionNode.new("host", "foo")
|
13
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new("host", "foo", ":")
|
14
14
|
q = [
|
15
|
-
"SELECT hosts.id FROM hosts",
|
15
|
+
"SELECT hosts.id AS host_id FROM hosts",
|
16
16
|
"WHERE hosts.name = ?;",
|
17
17
|
]
|
18
18
|
allow(cmd).to receive(:execute).with(q.join(" "), ["foo"]) {
|
19
19
|
[[1], [2], [3]]
|
20
20
|
}
|
21
21
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
22
|
+
expect(expr.dump).to eq({identifier: "host", separator: ":", attribute: "foo"})
|
22
23
|
end
|
23
24
|
|
24
25
|
it "interprets tag with identifier and attribute" do
|
25
|
-
expr = Hotdog::Commands::Search::TagExpressionNode.new("foo", "bar")
|
26
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new("foo", "bar", ":")
|
26
27
|
q = [
|
27
28
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
28
29
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
@@ -32,10 +33,25 @@ describe "tag expression" do
|
|
32
33
|
[[1], [2], [3]]
|
33
34
|
}
|
34
35
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
36
|
+
expect(expr.dump).to eq({identifier: "foo", separator: ":", attribute: "bar"})
|
35
37
|
end
|
36
38
|
|
37
|
-
it "interprets tag with identifier" do
|
38
|
-
expr = Hotdog::Commands::Search::TagExpressionNode.new("foo", nil)
|
39
|
+
it "interprets tag with identifier with separator" do
|
40
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new("foo", nil, ":")
|
41
|
+
q = [
|
42
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
43
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
44
|
+
"WHERE tags.name = ?;",
|
45
|
+
]
|
46
|
+
allow(cmd).to receive(:execute).with(q.join(" "), ["foo"]) {
|
47
|
+
[[1], [2], [3]]
|
48
|
+
}
|
49
|
+
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
50
|
+
expect(expr.dump).to eq({identifier: "foo", separator: ":"})
|
51
|
+
end
|
52
|
+
|
53
|
+
it "interprets tag with identifier without separator" do
|
54
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new("foo", nil, nil)
|
39
55
|
q = [
|
40
56
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
41
57
|
"INNER JOIN hosts ON hosts_tags.host_id = hosts.id",
|
@@ -46,10 +62,11 @@ describe "tag expression" do
|
|
46
62
|
[[1], [2], [3]]
|
47
63
|
}
|
48
64
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
65
|
+
expect(expr.dump).to eq({identifier: "foo"})
|
49
66
|
end
|
50
67
|
|
51
|
-
it "interprets tag with attribute" do
|
52
|
-
expr = Hotdog::Commands::Search::TagExpressionNode.new(nil, "foo")
|
68
|
+
it "interprets tag with attribute with separator" do
|
69
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new(nil, "foo", ":")
|
53
70
|
q = [
|
54
71
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
55
72
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
@@ -59,5 +76,26 @@ describe "tag expression" do
|
|
59
76
|
[[1], [2], [3]]
|
60
77
|
}
|
61
78
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
79
|
+
expect(expr.dump).to eq({separator: ":", attribute: "foo"})
|
80
|
+
end
|
81
|
+
|
82
|
+
it "interprets tag with attribute without separator" do
|
83
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new(nil, "foo", nil)
|
84
|
+
q = [
|
85
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
86
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
87
|
+
"WHERE tags.value = ?;",
|
88
|
+
]
|
89
|
+
allow(cmd).to receive(:execute).with(q.join(" "), ["foo"]) {
|
90
|
+
[[1], [2], [3]]
|
91
|
+
}
|
92
|
+
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
93
|
+
expect(expr.dump).to eq({attribute: "foo"})
|
94
|
+
end
|
95
|
+
|
96
|
+
it "empty tag" do
|
97
|
+
expr = Hotdog::Commands::Search::TagExpressionNode.new(nil, nil, nil)
|
98
|
+
expect(expr.evaluate(cmd)).to eq([])
|
99
|
+
expect(expr.dump).to eq({})
|
62
100
|
end
|
63
101
|
end
|
@@ -10,19 +10,20 @@ describe "tag glob expression" do
|
|
10
10
|
}
|
11
11
|
|
12
12
|
it "interprets tag glob with host" do
|
13
|
-
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("host", "foo*")
|
13
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("host", "foo*", ":")
|
14
14
|
q = [
|
15
|
-
"SELECT hosts.id FROM hosts",
|
15
|
+
"SELECT hosts.id AS host_id FROM hosts",
|
16
16
|
"WHERE hosts.name GLOB ?;",
|
17
17
|
]
|
18
18
|
allow(cmd).to receive(:execute).with(q.join(" "), ["foo*"]) {
|
19
19
|
[[1], [2], [3]]
|
20
20
|
}
|
21
21
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
22
|
+
expect(expr.dump).to eq({identifier_glob: "host", separator: ":", attribute_glob: "foo*"})
|
22
23
|
end
|
23
24
|
|
24
25
|
it "interprets tag glob with identifier and attribute" do
|
25
|
-
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("foo*", "bar*")
|
26
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("foo*", "bar*", ":")
|
26
27
|
q = [
|
27
28
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
28
29
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
@@ -32,10 +33,25 @@ describe "tag glob expression" do
|
|
32
33
|
[[1], [2], [3]]
|
33
34
|
}
|
34
35
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
36
|
+
expect(expr.dump).to eq({identifier_glob: "foo*", separator: ":", attribute_glob: "bar*"})
|
35
37
|
end
|
36
38
|
|
37
|
-
it "interprets tag glob with identifier" do
|
38
|
-
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("foo*", nil)
|
39
|
+
it "interprets tag glob with identifier with separator" do
|
40
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("foo*", nil, ":")
|
41
|
+
q = [
|
42
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
43
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
44
|
+
"WHERE tags.name GLOB ?;",
|
45
|
+
]
|
46
|
+
allow(cmd).to receive(:execute).with(q.join(" "), ["foo*"]) {
|
47
|
+
[[1], [2], [3]]
|
48
|
+
}
|
49
|
+
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
50
|
+
expect(expr.dump).to eq({identifier_glob: "foo*", separator: ":"})
|
51
|
+
end
|
52
|
+
|
53
|
+
it "interprets tag glob with identifier without separator" do
|
54
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new("foo*", nil, nil)
|
39
55
|
q = [
|
40
56
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
41
57
|
"INNER JOIN hosts ON hosts_tags.host_id = hosts.id",
|
@@ -46,10 +62,11 @@ describe "tag glob expression" do
|
|
46
62
|
[[1], [2], [3]]
|
47
63
|
}
|
48
64
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
65
|
+
expect(expr.dump).to eq({identifier_glob: "foo*"})
|
49
66
|
end
|
50
67
|
|
51
|
-
it "interprets tag glob with attribute" do
|
52
|
-
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new(nil, "foo*")
|
68
|
+
it "interprets tag glob with attribute with separator" do
|
69
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new(nil, "foo*", ":")
|
53
70
|
q = [
|
54
71
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
55
72
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
@@ -59,5 +76,26 @@ describe "tag glob expression" do
|
|
59
76
|
[[1], [2], [3]]
|
60
77
|
}
|
61
78
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
79
|
+
expect(expr.dump).to eq({separator: ":", attribute_glob: "foo*"})
|
80
|
+
end
|
81
|
+
|
82
|
+
it "interprets tag glob with attribute without separator" do
|
83
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new(nil, "foo*", nil)
|
84
|
+
q = [
|
85
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
86
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
87
|
+
"WHERE tags.value GLOB ?;",
|
88
|
+
]
|
89
|
+
allow(cmd).to receive(:execute).with(q.join(" "), ["foo*"]) {
|
90
|
+
[[1], [2], [3]]
|
91
|
+
}
|
92
|
+
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
93
|
+
expect(expr.dump).to eq({attribute_glob: "foo*"})
|
94
|
+
end
|
95
|
+
|
96
|
+
it "empty tag glob" do
|
97
|
+
expr = Hotdog::Commands::Search::TagGlobExpressionNode.new(nil, nil, nil)
|
98
|
+
expect(expr.evaluate(cmd)).to eq([])
|
99
|
+
expect(expr.dump).to eq({})
|
62
100
|
end
|
63
101
|
end
|
@@ -10,19 +10,20 @@ describe "tag regexp expression" do
|
|
10
10
|
}
|
11
11
|
|
12
12
|
it "interprets tag regexp with host" do
|
13
|
-
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("host", "/foo/")
|
13
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("host", "/foo/", ":")
|
14
14
|
q = [
|
15
|
-
"SELECT hosts.id FROM hosts",
|
15
|
+
"SELECT hosts.id AS host_id FROM hosts",
|
16
16
|
"WHERE hosts.name REGEXP ?;",
|
17
17
|
]
|
18
18
|
allow(cmd).to receive(:execute).with(q.join(" "), ["foo"]) {
|
19
19
|
[[1], [2], [3]]
|
20
20
|
}
|
21
21
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
22
|
+
expect(expr.dump).to eq({identifier_regexp: "host", separator: ":", attribute_regexp: "foo"})
|
22
23
|
end
|
23
24
|
|
24
25
|
it "interprets tag regexp with identifier and attribute" do
|
25
|
-
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("/foo/", "/bar/")
|
26
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("/foo/", "/bar/", ":")
|
26
27
|
q = [
|
27
28
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
28
29
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
@@ -32,10 +33,25 @@ describe "tag regexp expression" do
|
|
32
33
|
[[1], [2], [3]]
|
33
34
|
}
|
34
35
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
36
|
+
expect(expr.dump).to eq({identifier_regexp: "foo", separator: ":", attribute_regexp: "bar"})
|
35
37
|
end
|
36
38
|
|
37
|
-
it "interprets tag regexp with identifier" do
|
38
|
-
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("/foo/", nil)
|
39
|
+
it "interprets tag regexp with identifier with separator" do
|
40
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("/foo/", nil, ":")
|
41
|
+
q = [
|
42
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
43
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
44
|
+
"WHERE tags.name REGEXP ?;",
|
45
|
+
]
|
46
|
+
allow(cmd).to receive(:execute).with(q.join(" "), ["foo"]) {
|
47
|
+
[[1], [2], [3]]
|
48
|
+
}
|
49
|
+
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
50
|
+
expect(expr.dump).to eq({identifier_regexp: "foo", separator: ":"})
|
51
|
+
end
|
52
|
+
|
53
|
+
it "interprets tag regexp with identifier without separator" do
|
54
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new("/foo/", nil, nil)
|
39
55
|
q = [
|
40
56
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
41
57
|
"INNER JOIN hosts ON hosts_tags.host_id = hosts.id",
|
@@ -46,10 +62,11 @@ describe "tag regexp expression" do
|
|
46
62
|
[[1], [2], [3]]
|
47
63
|
}
|
48
64
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
65
|
+
expect(expr.dump).to eq({identifier_regexp: "foo"})
|
49
66
|
end
|
50
67
|
|
51
|
-
it "interprets tag regexp with attribute" do
|
52
|
-
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new(nil, "/foo/")
|
68
|
+
it "interprets tag regexp with attribute with separator" do
|
69
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new(nil, "/foo/", ":")
|
53
70
|
q = [
|
54
71
|
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
55
72
|
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
@@ -59,5 +76,26 @@ describe "tag regexp expression" do
|
|
59
76
|
[[1], [2], [3]]
|
60
77
|
}
|
61
78
|
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
79
|
+
expect(expr.dump).to eq({separator: ":", attribute_regexp: "foo"})
|
80
|
+
end
|
81
|
+
|
82
|
+
it "interprets tag regexp with attribute without separator" do
|
83
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new(nil, "/foo/", nil)
|
84
|
+
q = [
|
85
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags",
|
86
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id",
|
87
|
+
"WHERE tags.value REGEXP ?;",
|
88
|
+
]
|
89
|
+
allow(cmd).to receive(:execute).with(q.join(" "), ["foo"]) {
|
90
|
+
[[1], [2], [3]]
|
91
|
+
}
|
92
|
+
expect(expr.evaluate(cmd)).to eq([1, 2, 3])
|
93
|
+
expect(expr.dump).to eq({attribute_regexp: "foo"})
|
94
|
+
end
|
95
|
+
|
96
|
+
it "empty tag regexp" do
|
97
|
+
expr = Hotdog::Commands::Search::TagRegexpExpressionNode.new(nil, nil, nil)
|
98
|
+
expect(expr.evaluate(cmd)).to eq([])
|
99
|
+
expect(expr.dump).to eq({})
|
62
100
|
end
|
63
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hotdog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yamashita Yuu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|