hotdog 0.12.0 → 0.13.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/README.md +27 -8
- data/lib/hotdog/application.rb +7 -1
- data/lib/hotdog/commands.rb +0 -2
- data/lib/hotdog/commands/search.rb +16 -1239
- data/lib/hotdog/commands/ssh.rb +7 -0
- data/lib/hotdog/expression.rb +165 -0
- data/lib/hotdog/expression/semantics.rb +1098 -0
- data/lib/hotdog/expression/syntax.rb +176 -0
- data/lib/hotdog/version.rb +1 -1
- data/spec/parser/glob_expression_spec.rb +18 -18
- data/spec/parser/parser_spec.rb +113 -77
- data/spec/parser/regexp_expression_spec.rb +18 -18
- data/spec/parser/string_expression_spec.rb +18 -18
- metadata +5 -2
data/lib/hotdog/commands/ssh.rb
CHANGED
@@ -16,6 +16,7 @@ module Hotdog
|
|
16
16
|
default_option(options, :forward_agent, false)
|
17
17
|
default_option(options, :color, :auto)
|
18
18
|
default_option(options, :max_parallelism, Parallel.processor_count)
|
19
|
+
default_option(options, :shuffle, false)
|
19
20
|
optparse.on("-o SSH_OPTION", "Passes this string to ssh command through shell. This option may be given multiple times") do |option|
|
20
21
|
options[:options] += [option]
|
21
22
|
end
|
@@ -43,6 +44,9 @@ module Hotdog
|
|
43
44
|
optparse.on("--color=WHEN", "--colour=WHEN", "Enable colors") do |color|
|
44
45
|
options[:color] = color
|
45
46
|
end
|
47
|
+
optparse.on("--shuffle", "Shuffle result") do |v|
|
48
|
+
options[:shuffle] = v
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def run(args=[], options={})
|
@@ -61,6 +65,9 @@ module Hotdog
|
|
61
65
|
|
62
66
|
result0 = evaluate(node, self)
|
63
67
|
tuples, fields = get_hosts_with_search_tags(result0, node)
|
68
|
+
if options[:shuffle]
|
69
|
+
tuples = tuples.shuffle()
|
70
|
+
end
|
64
71
|
tuples = filter_hosts(tuples)
|
65
72
|
validate_hosts!(tuples, fields)
|
66
73
|
run_main(tuples.map {|tuple| tuple.first }, options)
|
@@ -0,0 +1,165 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "parslet"
|
4
|
+
|
5
|
+
# Monkey patch to prevent `NoMethodError` after some parse error in parselet
|
6
|
+
module Parslet
|
7
|
+
class Cause
|
8
|
+
def cause
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def backtrace
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require "hotdog/expression/semantics"
|
19
|
+
require "hotdog/expression/syntax"
|
20
|
+
|
21
|
+
module Hotdog
|
22
|
+
module Expression
|
23
|
+
class ExpressionTransformer < Parslet::Transform
|
24
|
+
rule(float: simple(:float)) {
|
25
|
+
float.to_f
|
26
|
+
}
|
27
|
+
rule(integer: simple(:integer)) {
|
28
|
+
integer.to_i
|
29
|
+
}
|
30
|
+
rule(string: simple(:string)) {
|
31
|
+
case string
|
32
|
+
when /\A"(.*)"\z/
|
33
|
+
$1
|
34
|
+
when /\A'(.*)'\z/
|
35
|
+
$1
|
36
|
+
else
|
37
|
+
string
|
38
|
+
end
|
39
|
+
}
|
40
|
+
rule(regexp: simple(:regexp)) {
|
41
|
+
case regexp
|
42
|
+
when /\A\/(.*)\/\z/
|
43
|
+
$1
|
44
|
+
else
|
45
|
+
regexp
|
46
|
+
end
|
47
|
+
}
|
48
|
+
rule(funcall_args_head: simple(:funcall_args_head), funcall_args_tail: sequence(:funcall_args_tail)) {
|
49
|
+
[funcall_args_head] + funcall_args_tail
|
50
|
+
}
|
51
|
+
rule(funcall_args_head: simple(:funcall_args_head)) {
|
52
|
+
[funcall_args_head]
|
53
|
+
}
|
54
|
+
rule(funcall: simple(:funcall), funcall_args: sequence(:funcall_args)) {
|
55
|
+
FuncallNode.new(funcall, funcall_args)
|
56
|
+
}
|
57
|
+
rule(funcall: simple(:funcall)) {
|
58
|
+
FuncallNode.new(funcall, [])
|
59
|
+
}
|
60
|
+
rule(binary_op: simple(:binary_op), left: simple(:left), right: simple(:right)) {
|
61
|
+
BinaryExpressionNode.new(binary_op, left, right)
|
62
|
+
}
|
63
|
+
rule(unary_op: simple(:unary_op), expression: simple(:expression)) {
|
64
|
+
UnaryExpressionNode.new(unary_op, expression)
|
65
|
+
}
|
66
|
+
rule(tag_name_regexp: simple(:tag_name_regexp), separator: simple(:separator), tag_value_regexp: simple(:tag_value_regexp)) {
|
67
|
+
if "/host/" == tag_name_regexp
|
68
|
+
RegexpHostNode.new(tag_value_regexp, separator)
|
69
|
+
else
|
70
|
+
RegexpTagNode.new(tag_name_regexp, tag_value_regexp, separator)
|
71
|
+
end
|
72
|
+
}
|
73
|
+
rule(tag_name_regexp: simple(:tag_name_regexp), separator: simple(:separator)) {
|
74
|
+
if "/host/" == tag_name_regexp
|
75
|
+
EverythingNode.new()
|
76
|
+
else
|
77
|
+
RegexpTagNameNode.new(tag_name_regexp, separator)
|
78
|
+
end
|
79
|
+
}
|
80
|
+
rule(tag_name_regexp: simple(:tag_name_regexp)) {
|
81
|
+
if "/host/" == tag_name_regexp
|
82
|
+
EverythingNode.new()
|
83
|
+
else
|
84
|
+
RegexpNode.new(tag_name_regexp)
|
85
|
+
end
|
86
|
+
}
|
87
|
+
rule(tag_name_glob: simple(:tag_name_glob), separator: simple(:separator), tag_value_glob: simple(:tag_value_glob)) {
|
88
|
+
if "host" == tag_name_glob
|
89
|
+
GlobHostNode.new(tag_value_glob, separator)
|
90
|
+
else
|
91
|
+
GlobTagNode.new(tag_name_glob, tag_value_glob, separator)
|
92
|
+
end
|
93
|
+
}
|
94
|
+
rule(tag_name_glob: simple(:tag_name_glob), separator: simple(:separator), tag_value: simple(:tag_value)) {
|
95
|
+
if "host" == tag_name_glob
|
96
|
+
GlobHostNode.new(tag_value, separator)
|
97
|
+
else
|
98
|
+
GlobTagNode.new(tag_name_glob, tag_value, separator)
|
99
|
+
end
|
100
|
+
}
|
101
|
+
rule(tag_name_glob: simple(:tag_name_glob), separator: simple(:separator)) {
|
102
|
+
if "host" == tag_name_glob
|
103
|
+
EverythingNode.new()
|
104
|
+
else
|
105
|
+
GlobTagNameNode.new(tag_name_glob, separator)
|
106
|
+
end
|
107
|
+
}
|
108
|
+
rule(tag_name_glob: simple(:tag_name_glob)) {
|
109
|
+
if "host" == tag_name_glob
|
110
|
+
EverythingNode.new()
|
111
|
+
else
|
112
|
+
GlobNode.new(tag_name_glob)
|
113
|
+
end
|
114
|
+
}
|
115
|
+
rule(tag_name: simple(:tag_name), separator: simple(:separator), tag_value_glob: simple(:tag_value_glob)) {
|
116
|
+
if "host" == tag_name
|
117
|
+
GlobHostNode.new(tag_value_glob, separator)
|
118
|
+
else
|
119
|
+
GlobTagNode.new(tag_name, tag_value_glob, separator)
|
120
|
+
end
|
121
|
+
}
|
122
|
+
rule(tag_name: simple(:tag_name), separator: simple(:separator), tag_value: simple(:tag_value)) {
|
123
|
+
if "host" == tag_name
|
124
|
+
StringHostNode.new(tag_value, separator)
|
125
|
+
else
|
126
|
+
StringTagNode.new(tag_name, tag_value, separator)
|
127
|
+
end
|
128
|
+
}
|
129
|
+
rule(tag_name: simple(:tag_name), separator: simple(:separator)) {
|
130
|
+
if "host" == tag_name
|
131
|
+
EverythingNode.new()
|
132
|
+
else
|
133
|
+
StringTagNameNode.new(tag_name, separator)
|
134
|
+
end
|
135
|
+
}
|
136
|
+
rule(tag_name: simple(:tag_name)) {
|
137
|
+
if "host" == tag_name
|
138
|
+
EverythingNode.new()
|
139
|
+
else
|
140
|
+
StringNode.new(tag_name)
|
141
|
+
end
|
142
|
+
}
|
143
|
+
rule(separator: simple(:separator), tag_value_regexp: simple(:tag_value_regexp)) {
|
144
|
+
RegexpTagValueNode.new(tag_value_regexp, separator)
|
145
|
+
}
|
146
|
+
rule(tag_value_regexp: simple(:tag_value_regexp)) {
|
147
|
+
RegexpTagValueNode.new(tag_value_regexp)
|
148
|
+
}
|
149
|
+
rule(separator: simple(:separator), tag_value_glob: simple(:tag_value_glob)) {
|
150
|
+
GlobTagValueNode.new(tag_value_glob, separator)
|
151
|
+
}
|
152
|
+
rule(tag_value_glob: simple(:tag_value_glob)) {
|
153
|
+
GlobTagValueNode.new(tag_value_glob)
|
154
|
+
}
|
155
|
+
rule(separator: simple(:separator), tag_value: simple(:tag_value)) {
|
156
|
+
StringTagValueNode.new(tag_value, separator)
|
157
|
+
}
|
158
|
+
rule(tag_value: simple(:tag_value)) {
|
159
|
+
StringTagValueNode.new(tag_value)
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# vim:set ft=ruby :
|
@@ -0,0 +1,1098 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Hotdog
|
4
|
+
module Expression
|
5
|
+
class ExpressionNode
|
6
|
+
def evaluate(environment, options={})
|
7
|
+
raise(NotImplementedError.new("must be overridden"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def optimize(options={})
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def dump(options={})
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class UnaryExpressionNode < ExpressionNode
|
20
|
+
attr_reader :op, :expression
|
21
|
+
|
22
|
+
def initialize(op, expression)
|
23
|
+
case (op || "not").to_s
|
24
|
+
when "!", "~", "NOT", "not"
|
25
|
+
@op = :NOT
|
26
|
+
else
|
27
|
+
raise(SyntaxError.new("unknown unary operator: #{op.inspect}"))
|
28
|
+
end
|
29
|
+
@expression = expression
|
30
|
+
end
|
31
|
+
|
32
|
+
def evaluate(environment, options={})
|
33
|
+
case @op
|
34
|
+
when :NOT
|
35
|
+
values = @expression.evaluate(environment, options).tap do |values|
|
36
|
+
environment.logger.debug("expr: #{values.length} value(s)")
|
37
|
+
end
|
38
|
+
if values.empty?
|
39
|
+
EverythingNode.new().evaluate(environment, options).tap do |values|
|
40
|
+
environment.logger.debug("NOT expr: #{values.length} value(s)")
|
41
|
+
end
|
42
|
+
else
|
43
|
+
# workaround for "too many terms in compound SELECT"
|
44
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
45
|
+
(min / (SQLITE_LIMIT_COMPOUND_SELECT - 2)).upto(max / (SQLITE_LIMIT_COMPOUND_SELECT - 2)).flat_map { |i|
|
46
|
+
range = ((SQLITE_LIMIT_COMPOUND_SELECT - 2) * i)...((SQLITE_LIMIT_COMPOUND_SELECT - 2) * (i + 1))
|
47
|
+
selected = values.select { |n| range === n }
|
48
|
+
q = "SELECT id FROM hosts " \
|
49
|
+
"WHERE ? <= id AND id < ? AND id NOT IN (%s);"
|
50
|
+
environment.execute(q % selected.map { "?" }.join(", "), [range.first, range.last] + selected).map { |row| row.first }
|
51
|
+
}.tap do |values|
|
52
|
+
environment.logger.debug("NOT expr: #{values.length} value(s)")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
else
|
56
|
+
[]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def optimize(options={})
|
61
|
+
@expression = @expression.optimize(options)
|
62
|
+
case op
|
63
|
+
when :NOT
|
64
|
+
case expression
|
65
|
+
when EverythingNode
|
66
|
+
NothingNode.new(options)
|
67
|
+
when NothingNode
|
68
|
+
EverythingNode.new(options)
|
69
|
+
else
|
70
|
+
optimize1(options)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def ==(other)
|
78
|
+
self.class === other and @op == other.op and @expression == other.expression
|
79
|
+
end
|
80
|
+
|
81
|
+
def dump(options={})
|
82
|
+
{unary_op: @op.to_s, expression: @expression.dump(options)}
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def optimize1(options={})
|
87
|
+
case op
|
88
|
+
when :NOT
|
89
|
+
if UnaryExpressionNode === expression and expression.op == :NOT
|
90
|
+
expression.expression
|
91
|
+
else
|
92
|
+
case expression
|
93
|
+
when QueryExpressionNode
|
94
|
+
q = expression.query
|
95
|
+
v = expression.values
|
96
|
+
if q and v.length <= SQLITE_LIMIT_COMPOUND_SELECT
|
97
|
+
QueryExpressionNode.new("SELECT id AS host_id FROM hosts EXCEPT #{q.sub(/\s*;\s*\z/, "")};", v)
|
98
|
+
else
|
99
|
+
self
|
100
|
+
end
|
101
|
+
when TagExpressionNode
|
102
|
+
q = expression.maybe_query(options)
|
103
|
+
v = expression.condition_values(options)
|
104
|
+
if q and v.length <= SQLITE_LIMIT_COMPOUND_SELECT
|
105
|
+
QueryExpressionNode.new("SELECT id AS host_id FROM hosts EXCEPT #{q.sub(/\s*;\s*\z/, "")};", v)
|
106
|
+
else
|
107
|
+
self
|
108
|
+
end
|
109
|
+
else
|
110
|
+
self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
self
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class BinaryExpressionNode < ExpressionNode
|
120
|
+
attr_reader :op, :left, :right
|
121
|
+
|
122
|
+
def initialize(op, left, right)
|
123
|
+
case (op || "or").to_s
|
124
|
+
when "&&", "&", "AND", "and"
|
125
|
+
@op = :AND
|
126
|
+
when ",", "||", "|", "OR", "or"
|
127
|
+
@op = :OR
|
128
|
+
when "^", "XOR", "xor"
|
129
|
+
@op = :XOR
|
130
|
+
else
|
131
|
+
raise(SyntaxError.new("unknown binary operator: #{op.inspect}"))
|
132
|
+
end
|
133
|
+
@left = left
|
134
|
+
@right = right
|
135
|
+
end
|
136
|
+
|
137
|
+
def evaluate(environment, options={})
|
138
|
+
case @op
|
139
|
+
when :AND
|
140
|
+
left_values = @left.evaluate(environment, options).tap do |values|
|
141
|
+
environment.logger.debug("lhs: #{values.length} value(s)")
|
142
|
+
end
|
143
|
+
if left_values.empty?
|
144
|
+
[]
|
145
|
+
else
|
146
|
+
right_values = @right.evaluate(environment, options).tap do |values|
|
147
|
+
environment.logger.debug("rhs: #{values.length} value(s)")
|
148
|
+
end
|
149
|
+
if right_values.empty?
|
150
|
+
[]
|
151
|
+
else
|
152
|
+
# workaround for "too many terms in compound SELECT"
|
153
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
154
|
+
(min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).flat_map { |i|
|
155
|
+
range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * (i + 1))
|
156
|
+
left_selected = left_values.select { |n| range === n }
|
157
|
+
right_selected = right_values.select { |n| range === n }
|
158
|
+
q = "SELECT id FROM hosts " \
|
159
|
+
"WHERE ? <= id AND id < ? AND ( id IN (%s) AND id IN (%s) );"
|
160
|
+
environment.execute(q % [left_selected.map { "?" }.join(", "), right_selected.map { "?" }.join(", ")], [range.first, range.last] + left_selected + right_selected).map { |row| row.first }
|
161
|
+
}.tap do |values|
|
162
|
+
environment.logger.debug("lhs AND rhs: #{values.length} value(s)")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
when :OR
|
167
|
+
left_values = @left.evaluate(environment, options).tap do |values|
|
168
|
+
environment.logger.debug("lhs: #{values.length} value(s)")
|
169
|
+
end
|
170
|
+
right_values = @right.evaluate(environment, options).tap do |values|
|
171
|
+
environment.logger.debug("rhs: #{values.length} value(s)")
|
172
|
+
end
|
173
|
+
if left_values.empty?
|
174
|
+
right_values
|
175
|
+
else
|
176
|
+
if right_values.empty?
|
177
|
+
[]
|
178
|
+
else
|
179
|
+
# workaround for "too many terms in compound SELECT"
|
180
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
181
|
+
(min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2)).flat_map { |i|
|
182
|
+
range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 2) * (i + 1))
|
183
|
+
left_selected = left_values.select { |n| range === n }
|
184
|
+
right_selected = right_values.select { |n| range === n }
|
185
|
+
q = "SELECT id FROM hosts " \
|
186
|
+
"WHERE ? <= id AND id < ? AND ( id IN (%s) OR id IN (%s) );"
|
187
|
+
environment.execute(q % [left_selected.map { "?" }.join(", "), right_selected.map { "?" }.join(", ")], [range.first, range.last] + left_selected + right_selected).map { |row| row.first }
|
188
|
+
}.tap do |values|
|
189
|
+
environment.logger.debug("lhs OR rhs: #{values.length} value(s)")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
when :XOR
|
194
|
+
left_values = @left.evaluate(environment, options).tap do |values|
|
195
|
+
environment.logger.debug("lhs: #{values.length} value(s)")
|
196
|
+
end
|
197
|
+
right_values = @right.evaluate(environment, options).tap do |values|
|
198
|
+
environment.logger.debug("rhs: #{values.length} value(s)")
|
199
|
+
end
|
200
|
+
if left_values.empty?
|
201
|
+
right_values
|
202
|
+
else
|
203
|
+
if right_values.empty?
|
204
|
+
[]
|
205
|
+
else
|
206
|
+
# workaround for "too many terms in compound SELECT"
|
207
|
+
min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts ORDER BY id LIMIT 1").first.to_a
|
208
|
+
(min / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4)).upto(max / ((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4)).flat_map { |i|
|
209
|
+
range = (((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4) * i)...(((SQLITE_LIMIT_COMPOUND_SELECT - 2) / 4) * (i + 1))
|
210
|
+
left_selected = left_values.select { |n| range === n }
|
211
|
+
right_selected = right_values.select { |n| range === n }
|
212
|
+
q = "SELECT id FROM hosts " \
|
213
|
+
"WHERE ? <= id AND id < ? AND NOT (id IN (%s) AND id IN (%s)) AND ( id IN (%s) OR id IN (%s) );"
|
214
|
+
lq = left_selected.map { "?" }.join(", ")
|
215
|
+
rq = right_selected.map { "?" }.join(", ")
|
216
|
+
environment.execute(q % [lq, rq, lq, rq], [range.first, range.last] + left_selected + right_selected + left_selected + right_selected).map { |row| row.first }
|
217
|
+
}.tap do |values|
|
218
|
+
environment.logger.debug("lhs XOR rhs: #{values.length} value(s)")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
else
|
223
|
+
[]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def optimize(options={})
|
228
|
+
@left = @left.optimize(options)
|
229
|
+
@right = @right.optimize(options)
|
230
|
+
case op
|
231
|
+
when :AND
|
232
|
+
case left
|
233
|
+
when EverythingNode
|
234
|
+
right
|
235
|
+
when NothingNode
|
236
|
+
left
|
237
|
+
else
|
238
|
+
if left == right
|
239
|
+
left
|
240
|
+
else
|
241
|
+
optimize1(options)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
when :OR
|
245
|
+
case left
|
246
|
+
when EverythingNode
|
247
|
+
left
|
248
|
+
when NothingNode
|
249
|
+
right
|
250
|
+
else
|
251
|
+
if left == right
|
252
|
+
left
|
253
|
+
else
|
254
|
+
if MultinaryExpressionNode === left
|
255
|
+
if left.op == op
|
256
|
+
left.merge(right, fallback: self)
|
257
|
+
else
|
258
|
+
optimize1(options)
|
259
|
+
end
|
260
|
+
else
|
261
|
+
if MultinaryExpressionNode === right
|
262
|
+
if right.op == op
|
263
|
+
right.merge(left, fallback: self)
|
264
|
+
else
|
265
|
+
optimize1(options)
|
266
|
+
end
|
267
|
+
else
|
268
|
+
MultinaryExpressionNode.new(op, [left, right], fallback: self)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
when :XOR
|
274
|
+
if left == right
|
275
|
+
[]
|
276
|
+
else
|
277
|
+
optimize1(options)
|
278
|
+
end
|
279
|
+
else
|
280
|
+
self
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def ==(other)
|
285
|
+
self.class === other and @op == other.op and @left == other.left and @right == other.right
|
286
|
+
end
|
287
|
+
|
288
|
+
def dump(options={})
|
289
|
+
{left: @left.dump(options), binary_op: @op.to_s, right: @right.dump(options)}
|
290
|
+
end
|
291
|
+
|
292
|
+
private
|
293
|
+
def optimize1(options)
|
294
|
+
if TagExpressionNode === left and TagExpressionNode === right
|
295
|
+
lq = left.maybe_query(options)
|
296
|
+
lv = left.condition_values(options)
|
297
|
+
rq = right.maybe_query(options)
|
298
|
+
rv = right.condition_values(options)
|
299
|
+
if lq and rq and lv.length + rv.length <= SQLITE_LIMIT_COMPOUND_SELECT
|
300
|
+
case op
|
301
|
+
when :AND
|
302
|
+
q = "#{lq.sub(/\s*;\s*\z/, "")} INTERSECT #{rq.sub(/\s*;\s*\z/, "")};"
|
303
|
+
QueryExpressionNode.new(q, lv + rv, fallback: self)
|
304
|
+
when :OR
|
305
|
+
q = "#{lq.sub(/\s*;\s*\z/, "")} UNION #{rq.sub(/\s*;\s*\z/, "")};"
|
306
|
+
QueryExpressionNode.new(q, lv + rv, fallback: self)
|
307
|
+
when :XOR
|
308
|
+
q = "#{lq.sub(/\s*;\s*\z/, "")} UNION #{rq.sub(/\s*;\s*\z/, "")} " \
|
309
|
+
"EXCEPT #{lq.sub(/\s*;\s*\z/, "")} " \
|
310
|
+
"INTERSECT #{rq.sub(/\s*;\s*\z/, "")};"
|
311
|
+
QueryExpressionNode.new(q, lv + rv, fallback: self)
|
312
|
+
else
|
313
|
+
self
|
314
|
+
end
|
315
|
+
else
|
316
|
+
self
|
317
|
+
end
|
318
|
+
else
|
319
|
+
self
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class MultinaryExpressionNode < ExpressionNode
|
325
|
+
attr_reader :op, :expressions
|
326
|
+
|
327
|
+
def initialize(op, expressions, options={})
|
328
|
+
case (op || "or").to_s
|
329
|
+
when ",", "||", "|", "OR", "or"
|
330
|
+
@op = :OR
|
331
|
+
else
|
332
|
+
raise(SyntaxError.new("unknown multinary operator: #{op.inspect}"))
|
333
|
+
end
|
334
|
+
if SQLITE_LIMIT_COMPOUND_SELECT < expressions.length
|
335
|
+
raise(ArgumentError.new("expressions limit exceeded: #{expressions.length} for #{SQLITE_LIMIT_COMPOUND_SELECT}"))
|
336
|
+
end
|
337
|
+
@expressions = expressions
|
338
|
+
@fallback = options[:fallback]
|
339
|
+
end
|
340
|
+
|
341
|
+
def merge(other, options={})
|
342
|
+
if MultinaryExpressionNode === other and op == other.op
|
343
|
+
MultinaryExpressionNode.new(op, expressions + other.expressions, options)
|
344
|
+
else
|
345
|
+
MultinaryExpressionNode.new(op, expressions + [other], options)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def evaluate(environment, options={})
|
350
|
+
case @op
|
351
|
+
when :OR
|
352
|
+
if expressions.all? { |expression| TagExpressionNode === expression }
|
353
|
+
values = expressions.group_by { |expression| expression.class }.values.flat_map { |expressions|
|
354
|
+
query_without_condition = expressions.first.maybe_query_without_condition(options)
|
355
|
+
if query_without_condition
|
356
|
+
condition_length = expressions.map { |expression| expression.condition_values(options).length }.max
|
357
|
+
expressions.each_slice(SQLITE_LIMIT_COMPOUND_SELECT / condition_length).flat_map { |expressions|
|
358
|
+
q = query_without_condition.sub(/\s*;\s*\z/, "") + " WHERE " + expressions.map { |expression| "( %s )" % expression.condition(options) }.join(" OR ") + ";"
|
359
|
+
environment.execute(q, expressions.flat_map { |expression| expression.condition_values(options) }).map { |row| row.first }
|
360
|
+
}
|
361
|
+
else
|
362
|
+
[]
|
363
|
+
end
|
364
|
+
}
|
365
|
+
else
|
366
|
+
values = []
|
367
|
+
end
|
368
|
+
else
|
369
|
+
values = []
|
370
|
+
end
|
371
|
+
if values.empty?
|
372
|
+
if @fallback
|
373
|
+
@fallback.evaluate(environment, options={})
|
374
|
+
else
|
375
|
+
[]
|
376
|
+
end
|
377
|
+
else
|
378
|
+
values
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def dump(options={})
|
383
|
+
{multinary_op: @op.to_s, expressions: expressions.map { |expression| expression.dump(options) }}
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
class QueryExpressionNode < ExpressionNode
|
388
|
+
def initialize(query, values=[], options={})
|
389
|
+
@query = query
|
390
|
+
@values = values
|
391
|
+
@fallback = options[:fallback]
|
392
|
+
end
|
393
|
+
attr_reader :query
|
394
|
+
attr_reader :values
|
395
|
+
|
396
|
+
def evaluate(environment, options={})
|
397
|
+
values = environment.execute(@query, @values).map { |row| row.first }
|
398
|
+
if values.empty? and @fallback
|
399
|
+
@fallback.evaluate(environment, options)
|
400
|
+
else
|
401
|
+
values
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def dump(options={})
|
406
|
+
data = {query: @query, values: @values}
|
407
|
+
data[:fallback] = @fallback.dump(options) if @fallback
|
408
|
+
data
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
class FuncallNode < ExpressionNode
|
413
|
+
attr_reader :function, :args
|
414
|
+
|
415
|
+
def initialize(function, args)
|
416
|
+
# FIXME: smart argument handling (e.g. arity & type checking)
|
417
|
+
case function.to_s
|
418
|
+
when "HEAD", "head"
|
419
|
+
@function = :HEAD
|
420
|
+
when "GROUP_BY", "group_by"
|
421
|
+
@function = :GROUP_BY
|
422
|
+
when "LIMIT", "limit"
|
423
|
+
@function = :HEAD
|
424
|
+
when "ORDER_BY", "order_by"
|
425
|
+
@function = :ORDER_BY
|
426
|
+
when "REVERSE", "reverse"
|
427
|
+
@function = :REVERSE
|
428
|
+
when "SHUFFLE", "shuffle"
|
429
|
+
@function = :SHUFFLE
|
430
|
+
when "SORT", "sort"
|
431
|
+
@function = :ORDER_BY
|
432
|
+
when "TAIL", "tail"
|
433
|
+
@function = :TAIL
|
434
|
+
else
|
435
|
+
raise(SyntaxError.new("unknown function call: #{function}"))
|
436
|
+
end
|
437
|
+
@args = args
|
438
|
+
end
|
439
|
+
|
440
|
+
def optimize(options={})
|
441
|
+
self
|
442
|
+
end
|
443
|
+
|
444
|
+
def dump(options={})
|
445
|
+
args = @args.map { |arg|
|
446
|
+
if ExpressionNode === arg
|
447
|
+
arg.dump(options)
|
448
|
+
else
|
449
|
+
arg
|
450
|
+
end
|
451
|
+
}
|
452
|
+
{funcall: @function.to_s, args: args}
|
453
|
+
end
|
454
|
+
|
455
|
+
def evaluate(environment, options={})
|
456
|
+
case function
|
457
|
+
when :HEAD
|
458
|
+
args[0].evaluate(environment, options).take(args[1] || 1)
|
459
|
+
when :GROUP_BY
|
460
|
+
intermediate = args[0].evaluate(environment, options)
|
461
|
+
q = "SELECT hosts_tags.host_id FROM hosts_tags " \
|
462
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
463
|
+
"WHERE tags.name = ? AND hosts_tags.host_id IN (%s) " \
|
464
|
+
"GROUP BY tags.value;" % intermediate.map { "?" }.join(", ")
|
465
|
+
QueryExpressionNode.new(q, [args[1]] + intermediate, fallback: nil).evaluate(environment, options)
|
466
|
+
when :ORDER_BY
|
467
|
+
intermediate = args[0].evaluate(environment, options)
|
468
|
+
if args[1]
|
469
|
+
q = "SELECT hosts_tags.host_id FROM hosts_tags " \
|
470
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
471
|
+
"WHERE tags.name = ? AND hosts_tags.host_id IN (%s) " \
|
472
|
+
"ORDER BY tags.value;" % intermediate.map { "?" }.join(", ")
|
473
|
+
QueryExpressionNode.new(q, [args[1]] + intermediate, fallback: nil).evaluate(environment, options)
|
474
|
+
else
|
475
|
+
q = "SELECT hosts_tags.host_id FROM hosts_tags " \
|
476
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
477
|
+
"WHERE hosts_tags.host_id IN (%s) " \
|
478
|
+
"ORDER BY hosts_tags.host_id;" % intermediate.map { "?" }.join(", ")
|
479
|
+
QueryExpressionNode.new(q, intermediate, fallback: nil).evaluate(environment, options)
|
480
|
+
end
|
481
|
+
when :REVERSE
|
482
|
+
args[0].evaluate(environment, options).reverse()
|
483
|
+
when :SHUFFLE
|
484
|
+
args[0].evaluate(environment, options).shuffle()
|
485
|
+
when :TAIL
|
486
|
+
args[0].evaluate(environment, options).last(args[1] || 1)
|
487
|
+
else
|
488
|
+
[]
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
class EverythingNode < QueryExpressionNode
|
494
|
+
def initialize(options={})
|
495
|
+
super("SELECT id AS host_id FROM hosts", [], options)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
class NothingNode < QueryExpressionNode
|
500
|
+
def initialize(options={})
|
501
|
+
super("SELECT NULL AS host_id WHERE host_id NOT NULL", [], options)
|
502
|
+
end
|
503
|
+
|
504
|
+
def evaluate(environment, options={})
|
505
|
+
if @fallback
|
506
|
+
@fallback.evaluate(environment, options)
|
507
|
+
else
|
508
|
+
[]
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
class TagExpressionNode < ExpressionNode
|
514
|
+
def initialize(tag_name, tag_value, separator=nil)
|
515
|
+
@tag_name = tag_name
|
516
|
+
@tag_value = tag_value
|
517
|
+
@separator = separator
|
518
|
+
@fallback = nil
|
519
|
+
end
|
520
|
+
attr_reader :tag_name
|
521
|
+
attr_reader :tag_value
|
522
|
+
attr_reader :separator
|
523
|
+
|
524
|
+
def tag_name?
|
525
|
+
!(tag_name.nil? or tag_name.to_s.empty?)
|
526
|
+
end
|
527
|
+
|
528
|
+
def tag_value?
|
529
|
+
!(tag_value.nil? or tag_value.to_s.empty?)
|
530
|
+
end
|
531
|
+
|
532
|
+
def separator?
|
533
|
+
!(separator.nil? or separator.to_s.empty?)
|
534
|
+
end
|
535
|
+
|
536
|
+
def maybe_query(options={})
|
537
|
+
query_without_condition = maybe_query_without_condition(options)
|
538
|
+
if query_without_condition
|
539
|
+
query_without_condition.sub(/\s*;\s*\z/, "") + " WHERE " + condition(options) + ";"
|
540
|
+
else
|
541
|
+
nil
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def maybe_query_without_condition(options={})
|
546
|
+
tables = condition_tables(options)
|
547
|
+
if tables.empty?
|
548
|
+
nil
|
549
|
+
else
|
550
|
+
case tables
|
551
|
+
when [:hosts]
|
552
|
+
"SELECT hosts.id AS host_id FROM hosts;"
|
553
|
+
when [:hosts, :tags]
|
554
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags INNER JOIN hosts ON hosts_tags.host_id = hosts.id INNER JOIN tags ON hosts_tags.tag_id = tags.id;"
|
555
|
+
when [:tags]
|
556
|
+
"SELECT DISTINCT hosts_tags.host_id FROM hosts_tags INNER JOIN tags ON hosts_tags.tag_id = tags.id;"
|
557
|
+
else
|
558
|
+
raise(NotImplementedError.new("unknown tables: #{tables.join(", ")}"))
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
def condition(options={})
|
564
|
+
raise(NotImplementedError.new("must be overridden"))
|
565
|
+
end
|
566
|
+
|
567
|
+
def condition_tables(options={})
|
568
|
+
raise(NotImplementedError.new("must be overridden"))
|
569
|
+
end
|
570
|
+
|
571
|
+
def condition_values(options={})
|
572
|
+
raise(NotImplementedError.new("must be overridden"))
|
573
|
+
end
|
574
|
+
|
575
|
+
def evaluate(environment, options={})
|
576
|
+
q = maybe_query(options)
|
577
|
+
if q
|
578
|
+
values = environment.execute(q, condition_values(options)).map { |row| row.first }
|
579
|
+
if values.empty?
|
580
|
+
if options[:did_fallback]
|
581
|
+
[]
|
582
|
+
else
|
583
|
+
if not environment.fixed_string? and @fallback
|
584
|
+
# avoid optimizing @fallback to prevent infinite recursion
|
585
|
+
values = @fallback.evaluate(environment, options.merge(did_fallback: true))
|
586
|
+
if values.empty?
|
587
|
+
if reload(environment, options)
|
588
|
+
evaluate(environment, options).tap do |values|
|
589
|
+
if values.empty?
|
590
|
+
environment.logger.info("no result: #{self.dump.inspect}")
|
591
|
+
end
|
592
|
+
end
|
593
|
+
else
|
594
|
+
[]
|
595
|
+
end
|
596
|
+
else
|
597
|
+
values
|
598
|
+
end
|
599
|
+
else
|
600
|
+
if reload(environment, options)
|
601
|
+
evaluate(environment, options).tap do |values|
|
602
|
+
if values.empty?
|
603
|
+
environment.logger.info("no result: #{self.dump.inspect}")
|
604
|
+
end
|
605
|
+
end
|
606
|
+
else
|
607
|
+
[]
|
608
|
+
end
|
609
|
+
end
|
610
|
+
end
|
611
|
+
else
|
612
|
+
values
|
613
|
+
end
|
614
|
+
else
|
615
|
+
[]
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
def ==(other)
|
620
|
+
self.class == other.class and @tag_name == other.tag_name and @tag_value == other.tag_value
|
621
|
+
end
|
622
|
+
|
623
|
+
def optimize(options={})
|
624
|
+
# fallback to glob expression
|
625
|
+
@fallback = maybe_fallback(options)
|
626
|
+
self
|
627
|
+
end
|
628
|
+
|
629
|
+
def to_glob(s)
|
630
|
+
(s.start_with?("*") ? "" : "*") + s.gsub(/[-.\/_]/, "?") + (s.end_with?("*") ? "" : "*")
|
631
|
+
end
|
632
|
+
|
633
|
+
def maybe_glob(s)
|
634
|
+
s ? to_glob(s.to_s) : nil
|
635
|
+
end
|
636
|
+
|
637
|
+
def reload(environment, options={})
|
638
|
+
$did_reload ||= false
|
639
|
+
if $did_reload
|
640
|
+
false
|
641
|
+
else
|
642
|
+
$did_reload = true
|
643
|
+
environment.logger.info("force reloading all hosts and tags.")
|
644
|
+
environment.reload(force: true)
|
645
|
+
true
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
def dump(options={})
|
650
|
+
data = {}
|
651
|
+
data[:tag_name] = tag_name.to_s if tag_name
|
652
|
+
data[:separator] = separator.to_s if separator
|
653
|
+
data[:tag_value] = tag_value.to_s if tag_value
|
654
|
+
data[:fallback ] = @fallback.dump(options) if @fallback
|
655
|
+
data
|
656
|
+
end
|
657
|
+
|
658
|
+
def maybe_fallback(options={})
|
659
|
+
nil
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
class AnyHostNode < TagExpressionNode
|
664
|
+
def initialize(separator=nil)
|
665
|
+
super("host", nil, separator)
|
666
|
+
end
|
667
|
+
|
668
|
+
def condition(options={})
|
669
|
+
"1"
|
670
|
+
end
|
671
|
+
|
672
|
+
def condition_tables(options={})
|
673
|
+
[:hosts]
|
674
|
+
end
|
675
|
+
|
676
|
+
def condition_values(options={})
|
677
|
+
[]
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
class StringExpressionNode < TagExpressionNode
|
682
|
+
end
|
683
|
+
|
684
|
+
class StringHostNode < StringExpressionNode
|
685
|
+
def initialize(tag_value, separator=nil)
|
686
|
+
super("host", tag_value.to_s, separator)
|
687
|
+
end
|
688
|
+
|
689
|
+
def condition(options={})
|
690
|
+
"hosts.name = ?"
|
691
|
+
end
|
692
|
+
|
693
|
+
def condition_tables(options={})
|
694
|
+
[:hosts]
|
695
|
+
end
|
696
|
+
|
697
|
+
def condition_values(options={})
|
698
|
+
[tag_value]
|
699
|
+
end
|
700
|
+
|
701
|
+
def maybe_fallback(options={})
|
702
|
+
fallback = GlobHostNode.new(to_glob(tag_value), separator)
|
703
|
+
query = fallback.maybe_query(options)
|
704
|
+
if query
|
705
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
706
|
+
else
|
707
|
+
nil
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
class StringTagNode < StringExpressionNode
|
713
|
+
def initialize(tag_name, tag_value, separator=nil)
|
714
|
+
super(tag_name.to_s, tag_value.to_s, separator)
|
715
|
+
end
|
716
|
+
|
717
|
+
def condition(options={})
|
718
|
+
"tags.name = ? AND tags.value = ?"
|
719
|
+
end
|
720
|
+
|
721
|
+
def condition_tables(options={})
|
722
|
+
[:tags]
|
723
|
+
end
|
724
|
+
|
725
|
+
def condition_values(options={})
|
726
|
+
[tag_name, tag_value]
|
727
|
+
end
|
728
|
+
|
729
|
+
def maybe_fallback(options={})
|
730
|
+
fallback = GlobTagNode.new(to_glob(tag_name), to_glob(tag_value), separator)
|
731
|
+
query = fallback.maybe_query(options)
|
732
|
+
if query
|
733
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
734
|
+
else
|
735
|
+
nil
|
736
|
+
end
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
class StringTagNameNode < StringExpressionNode
|
741
|
+
def initialize(tag_name, separator=nil)
|
742
|
+
super(tag_name.to_s, nil, separator)
|
743
|
+
end
|
744
|
+
|
745
|
+
def condition(options={})
|
746
|
+
"tags.name = ?"
|
747
|
+
end
|
748
|
+
|
749
|
+
def condition_tables(options={})
|
750
|
+
[:tags]
|
751
|
+
end
|
752
|
+
|
753
|
+
def condition_values(options={})
|
754
|
+
[tag_name]
|
755
|
+
end
|
756
|
+
|
757
|
+
def maybe_fallback(options={})
|
758
|
+
fallback = GlobTagNameNode.new(to_glob(tag_name), separator)
|
759
|
+
query = fallback.maybe_query(options)
|
760
|
+
if query
|
761
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
762
|
+
else
|
763
|
+
nil
|
764
|
+
end
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
class StringTagValueNode < StringExpressionNode
|
769
|
+
def initialize(tag_value, separator=nil)
|
770
|
+
super(nil, tag_value.to_s, separator)
|
771
|
+
end
|
772
|
+
|
773
|
+
def condition(options={})
|
774
|
+
"hosts.name = ? OR tags.value = ?"
|
775
|
+
end
|
776
|
+
|
777
|
+
def condition_tables(options={})
|
778
|
+
[:hosts, :tags]
|
779
|
+
end
|
780
|
+
|
781
|
+
def condition_values(options={})
|
782
|
+
[tag_value, tag_value]
|
783
|
+
end
|
784
|
+
|
785
|
+
def maybe_fallback(options={})
|
786
|
+
fallback = GlobTagValueNode.new(to_glob(tag_value), separator)
|
787
|
+
query = fallback.maybe_query(options)
|
788
|
+
if query
|
789
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
790
|
+
else
|
791
|
+
nil
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
class StringNode < StringExpressionNode
|
797
|
+
def initialize(tag_name, separator=nil)
|
798
|
+
super(tag_name.to_s, nil, separator)
|
799
|
+
end
|
800
|
+
|
801
|
+
def condition(options={})
|
802
|
+
"hosts.name = ? OR tags.name = ? OR tags.value = ?"
|
803
|
+
end
|
804
|
+
|
805
|
+
def condition_tables(options={})
|
806
|
+
[:hosts, :tags]
|
807
|
+
end
|
808
|
+
|
809
|
+
def condition_values(options={})
|
810
|
+
[tag_name, tag_name, tag_name]
|
811
|
+
end
|
812
|
+
|
813
|
+
def maybe_fallback(options={})
|
814
|
+
fallback = GlobNode.new(to_glob(tag_name), separator)
|
815
|
+
query = fallback.maybe_query(options)
|
816
|
+
if query
|
817
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
818
|
+
else
|
819
|
+
nil
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
class GlobExpressionNode < TagExpressionNode
|
825
|
+
def dump(options={})
|
826
|
+
data = {}
|
827
|
+
data[:tag_name_glob] = tag_name.to_s if tag_name
|
828
|
+
data[:separator] = separator.to_s if separator
|
829
|
+
data[:tag_value_glob] = tag_value.to_s if tag_value
|
830
|
+
data[:fallback] = @fallback.dump(options) if @fallback
|
831
|
+
data
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
class GlobHostNode < GlobExpressionNode
|
836
|
+
def initialize(tag_value, separator=nil)
|
837
|
+
super("host", tag_value.to_s, separator)
|
838
|
+
end
|
839
|
+
|
840
|
+
def condition(options={})
|
841
|
+
"LOWER(hosts.name) GLOB LOWER(?)"
|
842
|
+
end
|
843
|
+
|
844
|
+
def condition_tables(options={})
|
845
|
+
[:hosts]
|
846
|
+
end
|
847
|
+
|
848
|
+
def condition_values(options={})
|
849
|
+
[tag_value]
|
850
|
+
end
|
851
|
+
|
852
|
+
def maybe_fallback(options={})
|
853
|
+
fallback = GlobHostNode.new(to_glob(tag_value), separator)
|
854
|
+
query = fallback.maybe_query(options)
|
855
|
+
if query
|
856
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
857
|
+
else
|
858
|
+
nil
|
859
|
+
end
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
class GlobTagNode < GlobExpressionNode
|
864
|
+
def initialize(tag_name, tag_value, separator=nil)
|
865
|
+
super(tag_name.to_s, tag_value.to_s, separator)
|
866
|
+
end
|
867
|
+
|
868
|
+
def condition(options={})
|
869
|
+
"LOWER(tags.name) GLOB LOWER(?) AND LOWER(tags.value) GLOB LOWER(?)"
|
870
|
+
end
|
871
|
+
|
872
|
+
def condition_tables(options={})
|
873
|
+
[:tags]
|
874
|
+
end
|
875
|
+
|
876
|
+
def condition_values(options={})
|
877
|
+
[tag_name, tag_value]
|
878
|
+
end
|
879
|
+
|
880
|
+
def maybe_fallback(options={})
|
881
|
+
fallback = GlobTagNode.new(to_glob(tag_name), to_glob(tag_value), separator)
|
882
|
+
query = fallback.maybe_query(options)
|
883
|
+
if query
|
884
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
885
|
+
else
|
886
|
+
nil
|
887
|
+
end
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
class GlobTagNameNode < GlobExpressionNode
|
892
|
+
def initialize(tag_name, separator=nil)
|
893
|
+
super(tag_name.to_s, nil, separator)
|
894
|
+
end
|
895
|
+
|
896
|
+
def condition(options={})
|
897
|
+
"LOWER(tags.name) GLOB LOWER(?)"
|
898
|
+
end
|
899
|
+
|
900
|
+
def condition_tables(options={})
|
901
|
+
[:tags]
|
902
|
+
end
|
903
|
+
|
904
|
+
def condition_values(options={})
|
905
|
+
[tag_name]
|
906
|
+
end
|
907
|
+
|
908
|
+
def maybe_fallback(options={})
|
909
|
+
fallback = GlobTagNameNode.new(to_glob(tag_name), separator)
|
910
|
+
query = fallback.maybe_query(options)
|
911
|
+
if query
|
912
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
913
|
+
else
|
914
|
+
nil
|
915
|
+
end
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
class GlobTagValueNode < GlobExpressionNode
|
920
|
+
def initialize(tag_value, separator=nil)
|
921
|
+
super(nil, tag_value.to_s, separator)
|
922
|
+
end
|
923
|
+
|
924
|
+
def condition(options={})
|
925
|
+
"LOWER(hosts.name) GLOB LOWER(?) OR LOWER(tags.value) GLOB LOWER(?)"
|
926
|
+
end
|
927
|
+
|
928
|
+
def condition_tables(options={})
|
929
|
+
[:hosts, :tags]
|
930
|
+
end
|
931
|
+
|
932
|
+
def condition_values(options={})
|
933
|
+
[tag_value, tag_value]
|
934
|
+
end
|
935
|
+
|
936
|
+
def maybe_fallback(options={})
|
937
|
+
fallback = GlobTagValueNode.new(to_glob(tag_value), separator)
|
938
|
+
query = fallback.maybe_query(options)
|
939
|
+
if query
|
940
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
941
|
+
else
|
942
|
+
nil
|
943
|
+
end
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
class GlobNode < GlobExpressionNode
|
948
|
+
def initialize(tag_name, separator=nil)
|
949
|
+
super(tag_name.to_s, nil, separator)
|
950
|
+
end
|
951
|
+
|
952
|
+
def condition(options={})
|
953
|
+
"LOWER(hosts.name) GLOB LOWER(?) OR LOWER(tags.name) GLOB LOWER(?) OR LOWER(tags.value) GLOB LOWER(?)"
|
954
|
+
end
|
955
|
+
|
956
|
+
def condition_tables(options={})
|
957
|
+
[:hosts, :tags]
|
958
|
+
end
|
959
|
+
|
960
|
+
def condition_values(options={})
|
961
|
+
[tag_name, tag_name, tag_name]
|
962
|
+
end
|
963
|
+
|
964
|
+
def maybe_fallback(options={})
|
965
|
+
fallback = GlobNode.new(to_glob(tag_name), separator)
|
966
|
+
query = fallback.maybe_query(options)
|
967
|
+
if query
|
968
|
+
QueryExpressionNode.new(query, fallback.condition_values(options))
|
969
|
+
else
|
970
|
+
nil
|
971
|
+
end
|
972
|
+
end
|
973
|
+
end
|
974
|
+
|
975
|
+
class RegexpExpressionNode < TagExpressionNode
|
976
|
+
def dump(options={})
|
977
|
+
data = {}
|
978
|
+
data[:tag_name_regexp] = tag_name.to_s if tag_name
|
979
|
+
data[:separator] = separator.to_s if separator
|
980
|
+
data[:tag_value_regexp] = tag_value.to_s if tag_value
|
981
|
+
data[:fallback] = @fallback.dump(options) if @fallback
|
982
|
+
data
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
class RegexpHostNode < RegexpExpressionNode
|
987
|
+
def initialize(tag_value, separator=nil)
|
988
|
+
case tag_value
|
989
|
+
when /\A\/(.*)\/\z/
|
990
|
+
tag_value = $1
|
991
|
+
end
|
992
|
+
super("host", tag_value, separator)
|
993
|
+
end
|
994
|
+
|
995
|
+
def condition(options={})
|
996
|
+
"hosts.name REGEXP ?"
|
997
|
+
end
|
998
|
+
|
999
|
+
def condition_tables(options={})
|
1000
|
+
[:hosts]
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
def condition_values(options={})
|
1004
|
+
[tag_value]
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
class RegexpTagNode < RegexpExpressionNode
|
1009
|
+
def initialize(tag_name, tag_value, separator=nil)
|
1010
|
+
case tag_name
|
1011
|
+
when /\A\/(.*)\/\z/
|
1012
|
+
tag_name = $1
|
1013
|
+
end
|
1014
|
+
case tag_value
|
1015
|
+
when /\A\/(.*)\/\z/
|
1016
|
+
tag_value = $1
|
1017
|
+
end
|
1018
|
+
super(tag_name, tag_value, separator)
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
def condition(options={})
|
1022
|
+
"tags.name REGEXP ? AND tags.value REGEXP ?"
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
def condition_tables(options={})
|
1026
|
+
[:tags]
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
def condition_values(options={})
|
1030
|
+
[tag_name, tag_value]
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
class RegexpTagNameNode < RegexpExpressionNode
|
1035
|
+
def initialize(tag_name, separator=nil)
|
1036
|
+
case tag_name
|
1037
|
+
when /\A\/(.*)\/\z/
|
1038
|
+
tag_name = $1
|
1039
|
+
end
|
1040
|
+
super(tag_name.to_s, nil, separator)
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def condition(options={})
|
1044
|
+
"tags.name REGEXP ?"
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
def condition_tables(options={})
|
1048
|
+
[:tags]
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
def condition_values(options={})
|
1052
|
+
[tag_name]
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
class RegexpTagValueNode < RegexpExpressionNode
|
1057
|
+
def initialize(tag_value, separator=nil)
|
1058
|
+
case tag_value
|
1059
|
+
when /\A\/(.*)\/\z/
|
1060
|
+
tag_value = $1
|
1061
|
+
end
|
1062
|
+
super(nil, tag_value.to_s, separator)
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def condition(options={})
|
1066
|
+
"hosts.name REGEXP ? OR tags.value REGEXP ?"
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def condition_tables(options={})
|
1070
|
+
[:hosts, :tags]
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
def condition_values(options={})
|
1074
|
+
[tag_value, tag_value]
|
1075
|
+
end
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
class RegexpNode < RegexpExpressionNode
|
1079
|
+
def initialize(tag_name, separator=nil)
|
1080
|
+
super(tag_name, separator)
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
def condition(options={})
|
1084
|
+
"hosts.name REGEXP ? OR tags.name REGEXP ? OR tags.value REGEXP ?"
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
def condition_tables(options={})
|
1088
|
+
[:hosts, :tags]
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
def condition_values(options={})
|
1092
|
+
[tag_name, tag_name, tag_name]
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
# vim:set ft=ruby :
|