flor 0.16.1 → 0.16.2
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/CHANGELOG.md +8 -0
- data/CREDITS.md +1 -0
- data/Makefile +1 -1
- data/README.md +82 -6
- data/lib/flor.rb +1 -1
- data/lib/flor/conf.rb +19 -6
- data/lib/flor/core/executor.rb +45 -16
- data/lib/flor/core/node.rb +4 -4
- data/lib/flor/core/procedure.rb +40 -0
- data/lib/flor/djan.rb +5 -2
- data/lib/flor/flor.rb +92 -7
- data/lib/flor/id.rb +19 -0
- data/lib/flor/migrations/0001_tables.rb +6 -6
- data/lib/flor/migrations/0005_pointer_content.rb +20 -0
- data/lib/flor/pcore/_apply.rb +103 -57
- data/lib/flor/pcore/_att.rb +15 -1
- data/lib/flor/pcore/_ref.rb +2 -1
- data/lib/flor/pcore/arith.rb +46 -9
- data/lib/flor/pcore/break.rb +1 -1
- data/lib/flor/pcore/case.rb +41 -0
- data/lib/flor/pcore/collect.rb +1 -1
- data/lib/flor/pcore/cursor.rb +1 -1
- data/lib/flor/pcore/define.rb +32 -6
- data/lib/flor/pcore/iterator.rb +12 -0
- data/lib/flor/pcore/on_cancel.rb +1 -1
- data/lib/flor/pcore/set.rb +14 -4
- data/lib/flor/punit/{ccollect.rb → c_collect.rb} +2 -2
- data/lib/flor/punit/c_each.rb +11 -0
- data/lib/flor/punit/c_for_each.rb +41 -0
- data/lib/flor/punit/c_iterator.rb +160 -0
- data/lib/flor/punit/c_map.rb +43 -0
- data/lib/flor/punit/concurrence.rb +43 -200
- data/lib/flor/punit/graft.rb +3 -2
- data/lib/flor/punit/m_ram.rb +281 -0
- data/lib/flor/unit.rb +1 -0
- data/lib/flor/unit/caller.rb +6 -1
- data/lib/flor/unit/executor.rb +17 -4
- data/lib/flor/unit/ganger.rb +12 -1
- data/lib/flor/unit/hloader.rb +251 -0
- data/lib/flor/unit/hook.rb +74 -15
- data/lib/flor/unit/hooker.rb +9 -12
- data/lib/flor/unit/loader.rb +41 -17
- data/lib/flor/unit/models.rb +54 -18
- data/lib/flor/unit/models/execution.rb +15 -4
- data/lib/flor/unit/models/pointer.rb +11 -0
- data/lib/flor/unit/scheduler.rb +126 -30
- data/lib/flor/unit/spooler.rb +5 -3
- data/lib/flor/unit/storage.rb +40 -13
- data/lib/flor/unit/waiter.rb +165 -26
- data/lib/flor/unit/wlist.rb +98 -5
- metadata +10 -4
- data/lib/flor/punit/cmap.rb +0 -112
data/lib/flor/unit/spooler.rb
CHANGED
@@ -43,9 +43,11 @@ module Flor
|
|
43
43
|
|
44
44
|
def determine_spool_dir
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
r = @unit.conf['root']
|
47
|
+
return nil unless r
|
48
|
+
|
49
|
+
d = File.join(r, @unit.conf['spo_dir'] || 'var/spool')
|
50
|
+
return nil unless File.directory?(d)
|
49
51
|
|
50
52
|
d
|
51
53
|
end
|
data/lib/flor/unit/storage.rb
CHANGED
@@ -80,14 +80,25 @@ module Flor
|
|
80
80
|
opts[:current] ||= from if from.is_a?(Integer)
|
81
81
|
#
|
82
82
|
# defaults for the migration version table:
|
83
|
-
|
84
|
-
|
83
|
+
# { table: :schema_info,
|
84
|
+
# column: :version }
|
85
|
+
|
86
|
+
skip =
|
87
|
+
@unit.conf['db_sparse_migrations'] ||
|
88
|
+
@unit.conf['sto_sparse_migrations'] ||
|
89
|
+
opts[:sparse_migrations]
|
90
|
+
if skip && ! opts.has_key?(:allow_missing_migration_files)
|
91
|
+
opts[:allow_missing_migration_files] = true
|
92
|
+
end
|
85
93
|
|
86
94
|
dir =
|
87
95
|
@unit.conf['db_migrations'] ||
|
88
|
-
|
89
|
-
|
90
|
-
|
96
|
+
@unit.conf['db_migration_dir'] ||
|
97
|
+
@unit.conf['sto_migrations'] ||
|
98
|
+
@unit.conf['sto_migration_dir'] ||
|
99
|
+
opts[:migrations] ||
|
100
|
+
opts[:migration_dir] ||
|
101
|
+
Flor.migration_dir
|
91
102
|
|
92
103
|
synchronize do
|
93
104
|
|
@@ -664,7 +675,9 @@ module Flor
|
|
664
675
|
|
665
676
|
def update_pointers(exe, status, now)
|
666
677
|
|
667
|
-
#
|
678
|
+
# Q Should we archive old pointers?
|
679
|
+
# Well, it might be better to only archive the execution and leave
|
680
|
+
# in there enough information...
|
668
681
|
|
669
682
|
exid = exe['exid']
|
670
683
|
|
@@ -694,9 +707,12 @@ module Flor
|
|
694
707
|
|
695
708
|
vs = nid == '0' ? node['vars'] : nil
|
696
709
|
vs.each { |k, v|
|
697
|
-
case v; when
|
710
|
+
case v; when Numeric, String, TrueClass, FalseClass, NilClass
|
698
711
|
a << [ dom, exid, '0', 'var', k, v.to_s, now, u ]
|
699
|
-
when
|
712
|
+
when Array, Hash
|
713
|
+
s = '(array)'; s = '(object)' if v.is_a?(Hash)
|
714
|
+
a << [ dom, exid, '0', 'var', k, s, now, u ]
|
715
|
+
else
|
700
716
|
a << [ dom, exid, '0', 'var', k, nil, now, u ]
|
701
717
|
end } if vs
|
702
718
|
|
@@ -793,19 +809,30 @@ module Flor
|
|
793
809
|
@db.loggers << @db_logger
|
794
810
|
end
|
795
811
|
|
796
|
-
|
812
|
+
class << self
|
813
|
+
|
814
|
+
def to_blob(h)
|
797
815
|
|
798
|
-
|
816
|
+
h ? Sequel.blob(Zlib::Deflate.deflate(JSON.dump(h))) : nil
|
799
817
|
#rescue => e; pp h; raise e
|
800
|
-
|
818
|
+
end
|
801
819
|
|
802
|
-
|
820
|
+
def from_blob(content)
|
803
821
|
|
804
|
-
|
822
|
+
content ? JSON.parse(Zlib::Inflate.inflate(content)) : nil
|
823
|
+
end
|
805
824
|
end
|
806
825
|
|
807
826
|
def to_blob(h); self.class.to_blob(h); end
|
808
827
|
def from_blob(content); self.class.from_blob(content); end
|
809
828
|
end
|
829
|
+
|
830
|
+
# module Flor
|
831
|
+
|
832
|
+
class << self
|
833
|
+
|
834
|
+
def to_blob(h); ::Flor::Storage.to_blob(h); end
|
835
|
+
def from_blob(content); ::Flor::Storage.from_blob(content); end
|
836
|
+
end
|
810
837
|
end
|
811
838
|
|
data/lib/flor/unit/waiter.rb
CHANGED
@@ -5,11 +5,12 @@ module Flor
|
|
5
5
|
|
6
6
|
def initialize(exid, opts)
|
7
7
|
|
8
|
-
serie, timeout, on_timeout
|
8
|
+
serie, timeout, on_timeout =
|
9
9
|
expand_args(opts)
|
10
10
|
|
11
|
+
# TODO fail if the serie mixes msg_waiting with row_waiting...
|
12
|
+
|
11
13
|
@exid = exid
|
12
|
-
@original_serie = repeat ? Flor.dup(serie) : nil
|
13
14
|
@serie = serie
|
14
15
|
@timeout = timeout
|
15
16
|
@on_timeout = on_timeout
|
@@ -17,39 +18,81 @@ module Flor
|
|
17
18
|
@queue = []
|
18
19
|
@mutex = Mutex.new
|
19
20
|
@var = ConditionVariable.new
|
21
|
+
|
22
|
+
@executor = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
ROW_PSEUDO_POINTS = %w[ status tag tasker var variable ]
|
26
|
+
# "tasker", not "task", since "task" is already a message point
|
27
|
+
|
28
|
+
def row_waiter?
|
29
|
+
|
30
|
+
@serie.find { |_, points|
|
31
|
+
points.find { |po|
|
32
|
+
pos = po.split(':')
|
33
|
+
pos.length > 1 && ROW_PSEUDO_POINTS.include?(pos[0]) } }
|
34
|
+
end
|
35
|
+
|
36
|
+
def msg_waiter?
|
37
|
+
|
38
|
+
@serie.find { |_, points|
|
39
|
+
points.find { |po|
|
40
|
+
! ROW_PSEUDO_POINTS.include?(po.split(':').first) } }
|
20
41
|
end
|
21
42
|
|
22
43
|
def to_s
|
23
44
|
|
24
|
-
"#{super[0..-2]}#{
|
25
|
-
{ exid: @exid,
|
26
|
-
original_serie: @original_serie,
|
27
|
-
timeout: @timeout }.inspect
|
28
|
-
}>"
|
45
|
+
"#{super[0..-2]}#{{ exid: @exid, timeout: @timeout }.inspect}>"
|
29
46
|
end
|
30
47
|
|
31
48
|
def notify(executor, message)
|
32
49
|
|
50
|
+
@executor = executor
|
51
|
+
# could be handy
|
52
|
+
|
33
53
|
@mutex.synchronize do
|
34
54
|
|
35
55
|
return false unless match?(message)
|
36
56
|
|
37
57
|
@serie.shift
|
38
|
-
return false
|
58
|
+
return false if @serie.any?
|
39
59
|
|
40
60
|
@queue << [ executor, message ]
|
41
61
|
@var.signal
|
42
62
|
end
|
43
63
|
|
44
|
-
#
|
45
|
-
|
46
|
-
|
64
|
+
true # serie over, remove me
|
65
|
+
end
|
66
|
+
|
67
|
+
def check(unit, rs)
|
68
|
+
|
69
|
+
@mutex.synchronize do
|
47
70
|
|
48
|
-
|
71
|
+
row = nil
|
49
72
|
|
50
|
-
|
73
|
+
loop do
|
51
74
|
|
52
|
-
|
75
|
+
break if @serie.empty?
|
76
|
+
|
77
|
+
row = row_match?(unit, rs)
|
78
|
+
return false unless row
|
79
|
+
|
80
|
+
@serie.shift
|
81
|
+
end
|
82
|
+
|
83
|
+
@queue << [ unit, row ]
|
84
|
+
@var.signal
|
85
|
+
end
|
86
|
+
|
87
|
+
true # serie over, remove me
|
88
|
+
|
89
|
+
rescue => err
|
90
|
+
|
91
|
+
#puts "!" * 80; p err
|
92
|
+
unit.logger.warn(
|
93
|
+
"#{self.class}#check()", err, '(returning true, aka remove me)')
|
94
|
+
|
95
|
+
true # remove me
|
53
96
|
end
|
54
97
|
|
55
98
|
def wait
|
@@ -63,7 +106,8 @@ module Flor
|
|
63
106
|
|
64
107
|
if @queue.empty?
|
65
108
|
fail RuntimeError.new(
|
66
|
-
"timeout for #{self.to_s}"
|
109
|
+
"timeout for #{self.to_s}, " +
|
110
|
+
"msg_waiter? #{ !! msg_waiter?}, row_waiter? #{ !! row_waiter?}"
|
67
111
|
) if @on_timeout == 'fail'
|
68
112
|
return { 'exid' => @exid, 'timed_out' => @on_timeout }
|
69
113
|
end
|
@@ -73,6 +117,37 @@ module Flor
|
|
73
117
|
end
|
74
118
|
end
|
75
119
|
|
120
|
+
def to_query_hashes
|
121
|
+
|
122
|
+
@serie
|
123
|
+
.inject([ [], [] ]) { |a, (nid, points)|
|
124
|
+
|
125
|
+
points.each do |point|
|
126
|
+
|
127
|
+
ss = point.split(':')
|
128
|
+
|
129
|
+
h = {}
|
130
|
+
h[:exid] = @exid if @exid
|
131
|
+
h[:nid] = nid if nid
|
132
|
+
|
133
|
+
case ss[0]
|
134
|
+
when 'status'
|
135
|
+
h[:status] = ss[1]
|
136
|
+
a[0] << h
|
137
|
+
when 'tag', 'tasker', 'var', 'variable'
|
138
|
+
t = ss[0]; t = 'var' if t == 'variable'
|
139
|
+
h[:type] = t
|
140
|
+
h[:name] = ss[1]
|
141
|
+
h[:value] = ss[2] if ss[2]
|
142
|
+
a[1] << h
|
143
|
+
else
|
144
|
+
fail ArgumentError, "cannot turn to query_hash, #{self.inspect}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
a }
|
149
|
+
end
|
150
|
+
|
76
151
|
protected
|
77
152
|
|
78
153
|
def match?(message)
|
@@ -85,15 +160,70 @@ module Flor
|
|
85
160
|
mnid = message['nid']
|
86
161
|
|
87
162
|
return false if nid && mnid && nid != mnid
|
88
|
-
|
163
|
+
|
164
|
+
return false unless points.find { |point|
|
165
|
+
ps = point.split(':')
|
166
|
+
next false if ps[0] != mpoint
|
167
|
+
next false if ps[1] && ! message['tags'].include?(ps[1])
|
168
|
+
true }
|
89
169
|
|
90
170
|
true
|
91
171
|
end
|
92
172
|
|
173
|
+
def row_match?(unit, rs)
|
174
|
+
|
175
|
+
nid, points = @serie.first
|
176
|
+
|
177
|
+
row = nil
|
178
|
+
|
179
|
+
points.find { |point|
|
180
|
+
ps = point.split(':')
|
181
|
+
row = send("row_match_#{ps[0]}?", unit, rs, nid, ps[1..-1]) }
|
182
|
+
|
183
|
+
row
|
184
|
+
end
|
185
|
+
|
186
|
+
def row_match_status?(unit, rs, _, cdr)
|
187
|
+
|
188
|
+
rs[0].find { |exe|
|
189
|
+
(@exid == nil || exe.exid == @exid) &&
|
190
|
+
exe.status == cdr.first }
|
191
|
+
end
|
192
|
+
|
193
|
+
def row_match_tag?(unit, rs, nid, (name, value))
|
194
|
+
|
195
|
+
rs[1].find { |ptr|
|
196
|
+
ptr.type == 'tag' &&
|
197
|
+
(@exid == nil || ptr.exid == @exid) &&
|
198
|
+
(nid == nil || ptr.nid == nid) &&
|
199
|
+
(name == nil || ptr.name == name) &&
|
200
|
+
(value == nil || ptr.value == value) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def row_match_var?(unit, rs, nid, (name, value))
|
204
|
+
|
205
|
+
rs[1].find { |ptr|
|
206
|
+
ptr.type == 'var' &&
|
207
|
+
(@exid == nil || ptr.exid == @exid) &&
|
208
|
+
(nid == nil || ptr.nid == nid) &&
|
209
|
+
(name == nil || ptr.name == name) &&
|
210
|
+
(value == nil || ptr.value == value.to_s) }
|
211
|
+
end
|
212
|
+
alias row_match_variable? row_match_var?
|
213
|
+
|
214
|
+
def row_match_tasker?(unit, rs, nid, (name, value))
|
215
|
+
|
216
|
+
rs[1].find { |ptr|
|
217
|
+
ptr.type == 'tasker' &&
|
218
|
+
(@exid == nil || ptr.exid == @exid) &&
|
219
|
+
(nid == nil || ptr.nid == nid) &&
|
220
|
+
(name == nil || ptr.name == name) &&
|
221
|
+
(value == nil || ptr.value == value) }
|
222
|
+
end
|
223
|
+
|
93
224
|
def expand_args(opts)
|
94
225
|
|
95
226
|
owait = opts[:wait]
|
96
|
-
orepeat = opts[:repeat] || false
|
97
227
|
otimeout = opts[:timeout]
|
98
228
|
oontimeout = opts[:on_timeout] || opts[:ontimeout] || 'fail'
|
99
229
|
|
@@ -101,33 +231,42 @@ module Flor
|
|
101
231
|
when nil, true
|
102
232
|
[ [ [ nil, %w[ failed terminated ] ] ], # serie
|
103
233
|
otimeout,
|
104
|
-
oontimeout
|
105
|
-
orepeat ]
|
234
|
+
oontimeout ]
|
106
235
|
when Numeric
|
107
236
|
[ [ [ nil, %w[ failed terminated ] ] ], # serie
|
108
237
|
owait, # timeout
|
109
|
-
oontimeout
|
110
|
-
orepeat ]
|
238
|
+
oontimeout ]
|
111
239
|
when String, Array
|
112
240
|
[ parse_serie(owait), # serie
|
113
241
|
otimeout,
|
114
|
-
oontimeout
|
115
|
-
orepeat ]
|
242
|
+
oontimeout ]
|
116
243
|
else
|
117
244
|
fail ArgumentError.new(
|
118
245
|
"don't know how to deal with #{owait.inspect} (#{owait.class})")
|
119
246
|
end
|
120
247
|
end
|
121
248
|
|
249
|
+
WAIT_REX =
|
250
|
+
%r{
|
251
|
+
\A
|
252
|
+
([0-9_\-]+)?[ ]*
|
253
|
+
(
|
254
|
+
[a-z]+(?::[^:|;,\s]+){0,2}
|
255
|
+
(?:[|, ][a-z]+(:[^:|;,\s]+){0,2})*
|
256
|
+
)\z
|
257
|
+
}x
|
258
|
+
|
122
259
|
def parse_serie(s)
|
123
260
|
|
124
261
|
return s if s.is_a?(Array) && s.collect(&:class).uniq == [ Array ]
|
125
262
|
|
126
263
|
(s.is_a?(String) ? s.split(';') : s)
|
127
264
|
.collect { |ss|
|
128
|
-
|
129
|
-
|
130
|
-
|
265
|
+
m = ss.strip.match(WAIT_REX)
|
266
|
+
fail ArgumentError.new(
|
267
|
+
"cannot parse #{ss.strip.inspect} wait directive") unless m
|
268
|
+
ni, pt = m[1, 2]
|
269
|
+
[ ni, pt.split(/[|,]/).collect(&:strip) ] }
|
131
270
|
end
|
132
271
|
end
|
133
272
|
end
|
data/lib/flor/unit/wlist.rb
CHANGED
@@ -7,6 +7,9 @@ module Flor
|
|
7
7
|
#
|
8
8
|
# `wtl_default_timeout`:
|
9
9
|
# when #launch ing or #wait ing, set the default timeout, in seconds
|
10
|
+
#
|
11
|
+
# `wtl_row_frequency`:
|
12
|
+
# sleep time between row waiter checks, defaults to 1
|
10
13
|
|
11
14
|
DEFAULT_TIMEOUT = Flor.env_i('FLOR_DEFAULT_TIMEOUT')
|
12
15
|
|
@@ -16,16 +19,25 @@ module Flor
|
|
16
19
|
@unit.hooker.add('wlist', self)
|
17
20
|
|
18
21
|
@mutex = Mutex.new
|
19
|
-
@
|
22
|
+
@msg_waiters = []
|
23
|
+
@row_waiters = []
|
20
24
|
|
21
25
|
@unit.instance_eval do
|
22
26
|
def wait(exid, opts=true, more=nil)
|
23
27
|
@hooker['wlist'].wait(exid, opts, more)
|
24
28
|
end
|
25
29
|
end
|
30
|
+
|
31
|
+
@row_thread = nil
|
32
|
+
@row_thread_status = nil
|
33
|
+
@row_frequency = @unit.conf['wtl_row_frequency'] || 1
|
26
34
|
end
|
27
35
|
|
28
36
|
def shutdown
|
37
|
+
|
38
|
+
@row_thread_status = :shutdown
|
39
|
+
|
40
|
+
nil
|
29
41
|
end
|
30
42
|
|
31
43
|
def notify(executor, message)
|
@@ -34,12 +46,12 @@ module Flor
|
|
34
46
|
|
35
47
|
to_remove = []
|
36
48
|
|
37
|
-
@
|
49
|
+
@msg_waiters.each do |w|
|
38
50
|
remove = w.notify(executor, message)
|
39
51
|
to_remove << w if remove
|
40
52
|
end
|
41
53
|
|
42
|
-
@
|
54
|
+
@msg_waiters -= to_remove
|
43
55
|
|
44
56
|
end if message['consumed']
|
45
57
|
|
@@ -49,7 +61,7 @@ module Flor
|
|
49
61
|
def wait(exid, opts, more)
|
50
62
|
|
51
63
|
exid, opts =
|
52
|
-
if opts == true && exid
|
64
|
+
if opts == true && ! Flor.is_exid?(exid)
|
53
65
|
[ nil, { wait: exid } ]
|
54
66
|
elsif opts == true || opts.is_a?(String)
|
55
67
|
[ exid, { wait: opts } ]
|
@@ -65,11 +77,92 @@ module Flor
|
|
65
77
|
|
66
78
|
@mutex.synchronize do
|
67
79
|
|
68
|
-
|
80
|
+
waiter = Waiter.new(exid, opts)
|
81
|
+
|
82
|
+
fail ArgumentError.new(
|
83
|
+
"unit is stopped, it cannot wait for #{[ exid, opts ].inspect}"
|
84
|
+
) if waiter.msg_waiter? && @unit.stopped?
|
85
|
+
|
86
|
+
waiters = [ @msg_waiters, @row_waiters ]
|
87
|
+
ts = [ 'msg', 'row' ]
|
88
|
+
if waiter.row_waiter?; waiters.reverse!; ts.reverse!; end
|
89
|
+
|
90
|
+
fail ArgumentError.new(
|
91
|
+
"cannot add a #{ts[0]} waiter, since there are already #{ts[1]} ones"
|
92
|
+
) if waiters[1].any?
|
93
|
+
|
94
|
+
waiters.first << waiter
|
95
|
+
|
96
|
+
start_row_thread if @row_waiters.any?
|
97
|
+
|
98
|
+
waiter
|
69
99
|
|
70
100
|
end.wait
|
71
101
|
# returns the response message
|
72
102
|
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def start_row_thread
|
107
|
+
|
108
|
+
return if @row_thread_status == :shutdown
|
109
|
+
|
110
|
+
@row_thread = nil if @row_thread && ! @row_thread.alive?
|
111
|
+
@row_thread_status = :running
|
112
|
+
|
113
|
+
@row_thread ||=
|
114
|
+
Thread.new do
|
115
|
+
loop do
|
116
|
+
sleep(@row_frequency)
|
117
|
+
break if [ :stop, :shutdown ].include?(@row_thread_status)
|
118
|
+
break if @row_waiters.empty?
|
119
|
+
check
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def check
|
125
|
+
|
126
|
+
@mutex.synchronize do
|
127
|
+
|
128
|
+
to_remove = []
|
129
|
+
|
130
|
+
rs = row_query_all
|
131
|
+
|
132
|
+
@row_waiters.each do |w|
|
133
|
+
remove = w.check(@unit, rs)
|
134
|
+
to_remove << w if remove
|
135
|
+
end
|
136
|
+
|
137
|
+
@row_waiters -= to_remove
|
138
|
+
end
|
139
|
+
|
140
|
+
rescue => err
|
141
|
+
|
142
|
+
@unit.logger.warn("#{self.class}#check()", err)
|
143
|
+
end
|
144
|
+
|
145
|
+
def row_query_all
|
146
|
+
|
147
|
+
exes, ptrs =
|
148
|
+
@row_waiters.inject([ [], [] ]) { |a, w|
|
149
|
+
es, ps = w.to_query_hashes
|
150
|
+
a[0].concat(es)
|
151
|
+
a[1].concat(ps)
|
152
|
+
a }
|
153
|
+
|
154
|
+
[ row_query(:executions, exes), row_query(:pointers, ptrs) ]
|
155
|
+
end
|
156
|
+
|
157
|
+
def row_query(klass, hs)
|
158
|
+
|
159
|
+
return [] if hs.empty?
|
160
|
+
|
161
|
+
q = @unit.send(klass).where(hs.shift)
|
162
|
+
hs.each { |h| q = q.or(h) }
|
163
|
+
|
164
|
+
q.all
|
165
|
+
end
|
73
166
|
end
|
74
167
|
end
|
75
168
|
|