hotdog 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|