bud 0.9.4 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.txt +106 -0
- data/README.md +6 -4
- data/Rakefile +91 -0
- data/bin/budlabel +63 -0
- data/bin/budplot +18 -8
- data/bin/budtimelines +2 -2
- data/bin/budvis +7 -1
- data/docs/README.md +8 -17
- data/docs/cheat.md +112 -13
- data/docs/getstarted.md +97 -84
- data/docs/operational.md +3 -3
- data/examples/basics/paths.rb +2 -2
- data/examples/chat/README.md +2 -0
- data/examples/chat/chat.rb +3 -2
- data/examples/chat/chat_protocol.rb +2 -2
- data/examples/chat/chat_server.rb +3 -2
- data/lib/bud.rb +229 -114
- data/lib/bud/aggs.rb +20 -4
- data/lib/bud/bud_meta.rb +83 -73
- data/lib/bud/collections.rb +306 -120
- data/lib/bud/depanalysis.rb +3 -4
- data/lib/bud/executor/README.rescan +2 -1
- data/lib/bud/executor/elements.rb +96 -95
- data/lib/bud/executor/group.rb +35 -32
- data/lib/bud/executor/join.rb +164 -183
- data/lib/bud/graphs.rb +3 -3
- data/lib/bud/labeling/bloomgraph.rb +47 -0
- data/lib/bud/labeling/budplot_style.rb +53 -0
- data/lib/bud/labeling/labeling.rb +288 -0
- data/lib/bud/lattice-core.rb +595 -0
- data/lib/bud/lattice-lib.rb +422 -0
- data/lib/bud/monkeypatch.rb +68 -32
- data/lib/bud/rebl.rb +28 -10
- data/lib/bud/rewrite.rb +361 -152
- data/lib/bud/server.rb +16 -8
- data/lib/bud/source.rb +21 -18
- data/lib/bud/state.rb +93 -4
- data/lib/bud/storage/zookeeper.rb +45 -33
- data/lib/bud/version.rb +3 -0
- data/lib/bud/viz.rb +10 -12
- data/lib/bud/viz_util.rb +8 -3
- metadata +107 -108
data/lib/bud/depanalysis.rb
CHANGED
@@ -6,7 +6,7 @@ class DepAnalysis #:nodoc: all
|
|
6
6
|
|
7
7
|
state do
|
8
8
|
# Data inserted by client, usually from t_depends and t_provides
|
9
|
-
scratch :depends, [:lhs, :op, :body, :neg]
|
9
|
+
scratch :depends, [:lhs, :op, :body, :neg, :in_body]
|
10
10
|
scratch :providing, [:pred, :input]
|
11
11
|
|
12
12
|
# Intermediate state
|
@@ -36,7 +36,7 @@ class DepAnalysis #:nodoc: all
|
|
36
36
|
|
37
37
|
cycle <= depends_tc do |d|
|
38
38
|
if d.lhs == d.body
|
39
|
-
unless
|
39
|
+
unless d.neg and !d.temporal
|
40
40
|
[d.lhs, d.via, d.neg, d.temporal]
|
41
41
|
end
|
42
42
|
end
|
@@ -60,11 +60,10 @@ class DepAnalysis #:nodoc: all
|
|
60
60
|
[p.pred, true]
|
61
61
|
end
|
62
62
|
else
|
63
|
-
unless depends_tc.map{|
|
63
|
+
unless depends_tc.map{|dt| dt.lhs if dt.lhs != dt.body}.include? p.pred
|
64
64
|
[p.pred, false]
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
@@ -3,7 +3,8 @@ Notes on Invalidate and Rescan in Bud
|
|
3
3
|
|
4
4
|
(I'll use 'downstream' to mean rhs to lhs (like in budplot). In every stratum,
|
5
5
|
data originates at scanned sources at the "top", winds its way through various
|
6
|
-
PushElements and ends up in a collection at the "bottom".
|
6
|
+
PushElements and ends up in a collection at the "bottom". That is, data flows
|
7
|
+
from "upstream" producers to "downstream" consumers. I'll also the term
|
7
8
|
"elements" to mean both dataflow nodes (PushElements) and collections).
|
8
9
|
|
9
10
|
Invalidation strategy works through two flags/signals, rescan and
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'set'
|
2
1
|
require 'bud/collections'
|
3
2
|
|
4
3
|
module Bud
|
@@ -13,19 +12,18 @@ module Bud
|
|
13
12
|
class PushElement < BudCollection
|
14
13
|
attr_accessor :rescan, :invalidated
|
15
14
|
attr_accessor :elem_name
|
16
|
-
attr_reader :found_delta, :
|
15
|
+
attr_reader :found_delta, :wired_by, :outputs, :pendings
|
17
16
|
|
18
17
|
def initialize(name_in, bud_instance, collection_name=nil, given_schema=nil, defer_schema=false, &blk)
|
19
18
|
super(name_in, bud_instance, given_schema, defer_schema)
|
20
19
|
@blk = blk
|
21
|
-
@outputs =
|
22
|
-
@pendings =
|
23
|
-
@deletes =
|
24
|
-
@delete_keys =
|
20
|
+
@outputs = Set.new
|
21
|
+
@pendings = Set.new
|
22
|
+
@deletes = Set.new
|
23
|
+
@delete_keys = Set.new
|
25
24
|
@wired_by = []
|
26
25
|
@elem_name = name_in
|
27
26
|
@found_delta = false
|
28
|
-
@refcount = 1
|
29
27
|
@collection_name = collection_name
|
30
28
|
@invalidated = true
|
31
29
|
@rescan = true
|
@@ -58,6 +56,8 @@ module Bud
|
|
58
56
|
print "#{next_accum} "
|
59
57
|
if o.class <= Bud::BudCollection
|
60
58
|
puts "#{(o.object_id*2).to_s(16)}: #{o.qualified_tabname} (#{o.class})"
|
59
|
+
elsif o.class <= Bud::LatticeWrapper
|
60
|
+
puts "#{o.inspect}"
|
61
61
|
else
|
62
62
|
puts "#{(o.object_id*2).to_s(16)}: (#{o.class.name})"
|
63
63
|
end
|
@@ -83,17 +83,15 @@ module Bud
|
|
83
83
|
|
84
84
|
case kind
|
85
85
|
when :output
|
86
|
-
raise Bud::Error unless element.respond_to? :insert
|
87
86
|
@outputs << element
|
88
87
|
when :pending
|
89
|
-
raise Bud::Error unless element.respond_to? :pending_merge
|
90
88
|
@pendings << element
|
91
89
|
when :delete
|
92
|
-
raise Bud::Error unless element.respond_to? :pending_delete
|
93
90
|
@deletes << element
|
94
91
|
when :delete_by_key
|
95
|
-
raise Bud::Error unless element.respond_to? :pending_delete_keys
|
96
92
|
@delete_keys << element
|
93
|
+
else
|
94
|
+
raise Bud::Error, "unrecognized wiring kind: #{kind}"
|
97
95
|
end
|
98
96
|
|
99
97
|
element.wired_by << self if element.respond_to? :wired_by
|
@@ -116,27 +114,34 @@ module Bud
|
|
116
114
|
end
|
117
115
|
|
118
116
|
def push_out(item, do_block=true)
|
119
|
-
if
|
120
|
-
|
121
|
-
|
122
|
-
|
117
|
+
if do_block && @blk
|
118
|
+
item = item.to_a if @blk.arity > 1
|
119
|
+
item = @blk.call item
|
120
|
+
return if item.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
@outputs.each do |ou|
|
124
|
+
if ou.class <= Bud::PushElement
|
125
|
+
ou.insert(item, self)
|
126
|
+
elsif ou.class <= Bud::BudCollection
|
127
|
+
ou.do_insert(item, ou.new_delta)
|
128
|
+
elsif ou.class <= Bud::LatticeWrapper
|
129
|
+
ou.insert(item, self)
|
130
|
+
else
|
131
|
+
raise Bud::Error, "unexpected output target: #{ou.class}"
|
123
132
|
end
|
133
|
+
end
|
124
134
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
ou.insert(item, self)
|
129
|
-
elsif ou.class <= Bud::BudCollection
|
130
|
-
ou.do_insert(item, ou.new_delta)
|
131
|
-
else
|
132
|
-
raise Bud::Error, "expected either a PushElement or a BudCollection"
|
133
|
-
end
|
134
|
-
end
|
135
|
+
# for the following, o is a BudCollection
|
136
|
+
@deletes.each{|o| o.pending_delete([item])}
|
137
|
+
@delete_keys.each{|o| o.pending_delete_keys([item])}
|
135
138
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
139
|
+
# o is a LatticeWrapper or a BudCollection
|
140
|
+
@pendings.each do |o|
|
141
|
+
if o.class <= Bud::LatticeWrapper
|
142
|
+
o <+ item
|
143
|
+
else
|
144
|
+
o.pending_merge([item])
|
140
145
|
end
|
141
146
|
end
|
142
147
|
end
|
@@ -144,18 +149,17 @@ module Bud
|
|
144
149
|
# default for stateless elements
|
145
150
|
public
|
146
151
|
def add_rescan_invalidate(rescan, invalidate)
|
147
|
-
#
|
148
|
-
# rescan.
|
152
|
+
# If any sources are in rescan mode, then put this node in rescan
|
149
153
|
srcs = non_temporal_predecessors
|
150
154
|
if srcs.any?{|p| rescan.member? p}
|
151
155
|
rescan << self
|
152
156
|
end
|
153
157
|
|
154
|
-
#
|
155
|
-
#
|
158
|
+
# Pass the current state to each output collection and see if they end up
|
159
|
+
# marking this node for rescan
|
156
160
|
invalidate_tables(rescan, invalidate)
|
157
161
|
|
158
|
-
#
|
162
|
+
# Finally, if this node is in rescan, pass the request on to all source
|
159
163
|
# elements
|
160
164
|
if rescan.member? self
|
161
165
|
rescan.merge(srcs)
|
@@ -163,14 +167,16 @@ module Bud
|
|
163
167
|
end
|
164
168
|
|
165
169
|
def invalidate_tables(rescan, invalidate)
|
166
|
-
#
|
167
|
-
# in rescan, it may invalidate
|
168
|
-
# the
|
169
|
-
# enable a refill of that table at run-time
|
170
|
-
@outputs.each do |
|
171
|
-
|
172
|
-
o.
|
173
|
-
|
170
|
+
# Exchange rescan and invalidate information with tables. If this node is
|
171
|
+
# in rescan, it may invalidate an output table (if it is a scratch). And
|
172
|
+
# if the output table is going to be invalidated, this node marks itself
|
173
|
+
# for rescan to enable a refill of that table at run-time.
|
174
|
+
[@outputs, @pendings].each do |v|
|
175
|
+
v.each do |o|
|
176
|
+
unless o.class <= PushElement
|
177
|
+
o.add_rescan_invalidate(rescan, invalidate)
|
178
|
+
rescan << self if invalidate.member? o
|
179
|
+
end
|
174
180
|
end
|
175
181
|
end
|
176
182
|
end
|
@@ -193,7 +199,7 @@ module Bud
|
|
193
199
|
public
|
194
200
|
def pro(the_name=elem_name, the_schema=schema, &blk)
|
195
201
|
toplevel = @bud_instance.toplevel
|
196
|
-
elem = Bud::PushElement.new("project#{object_id}",
|
202
|
+
elem = Bud::PushElement.new("project#{object_id}".to_sym,
|
197
203
|
toplevel.this_rule_context,
|
198
204
|
@collection_name, the_schema)
|
199
205
|
self.wire_to(elem)
|
@@ -204,43 +210,38 @@ module Bud
|
|
204
210
|
|
205
211
|
alias each pro
|
206
212
|
|
207
|
-
# XXX: "the_name" & "the_schema" parameters are unused
|
208
213
|
public
|
209
|
-
def each_with_index(
|
214
|
+
def each_with_index(&blk)
|
210
215
|
toplevel = @bud_instance.toplevel
|
211
|
-
elem = Bud::PushEachWithIndex.new("each_with_index#{object_id}",
|
216
|
+
elem = Bud::PushEachWithIndex.new("each_with_index#{object_id}".to_sym,
|
212
217
|
toplevel.this_rule_context,
|
213
218
|
@collection_name)
|
214
219
|
elem.set_block(&blk)
|
215
220
|
self.wire_to(elem)
|
216
|
-
toplevel.push_elems[[self.object_id, :
|
221
|
+
toplevel.push_elems[[self.object_id, :each_with_index, blk]] = elem
|
217
222
|
end
|
218
223
|
|
219
224
|
def join(elem2, &blk)
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
toplevel.push_joins[toplevel.this_stratum] << join
|
229
|
-
# else
|
230
|
-
# cached.refcount += 1
|
231
|
-
# end
|
232
|
-
return toplevel.push_elems[[self.object_id, :join, [self, elem2], toplevel, blk]]
|
225
|
+
elem2 = elem2.to_push_elem unless elem2.kind_of? PushElement
|
226
|
+
toplevel = @bud_instance.toplevel
|
227
|
+
join = Bud::PushSHJoin.new([self, elem2], toplevel.this_rule_context, [])
|
228
|
+
self.wire_to(join)
|
229
|
+
elem2.wire_to(join)
|
230
|
+
toplevel.push_elems[[self.object_id, :join, [self, elem2], toplevel, blk]] = join
|
231
|
+
toplevel.push_joins[toplevel.this_stratum] << join
|
232
|
+
return join
|
233
233
|
end
|
234
234
|
def *(elem2, &blk)
|
235
235
|
join(elem2, &blk)
|
236
236
|
end
|
237
237
|
|
238
|
-
def notin(elem2, preds
|
238
|
+
def notin(elem2, *preds, &blk)
|
239
|
+
elem2 = elem2.to_push_elem unless elem2.kind_of? PushElement
|
239
240
|
toplevel = @bud_instance.toplevel
|
240
241
|
notin_elem = Bud::PushNotIn.new([self, elem2], toplevel.this_rule_context, preds, &blk)
|
241
242
|
self.wire_to(notin_elem)
|
242
243
|
elem2.wire_to(notin_elem)
|
243
|
-
toplevel.push_elems[[self.object_id, :notin,
|
244
|
+
toplevel.push_elems[[self.object_id, :notin, [self, elem2], toplevel, blk]] = notin_elem
|
244
245
|
return notin_elem
|
245
246
|
end
|
246
247
|
|
@@ -252,6 +253,7 @@ module Bud
|
|
252
253
|
end
|
253
254
|
end
|
254
255
|
alias <= merge
|
256
|
+
|
255
257
|
superator "<~" do |o|
|
256
258
|
raise Bud::Error, "illegal use of <~ with pusher '#{tabname}' on left"
|
257
259
|
end
|
@@ -266,7 +268,7 @@ module Bud
|
|
266
268
|
|
267
269
|
def group(keycols, *aggpairs, &blk)
|
268
270
|
# establish schema
|
269
|
-
keycols
|
271
|
+
keycols ||= []
|
270
272
|
keycols = keycols.map{|c| canonicalize_col(c)}
|
271
273
|
keynames = keycols.map{|k| k[2]}
|
272
274
|
aggcolsdups = aggpairs.map{|ap| ap[0].class.name.split("::").last}
|
@@ -280,44 +282,37 @@ module Bud
|
|
280
282
|
the_schema = { keynames => aggcols }
|
281
283
|
end
|
282
284
|
|
283
|
-
aggpairs = aggpairs
|
285
|
+
aggpairs = prep_aggpairs(aggpairs)
|
284
286
|
toplevel = @bud_instance.toplevel
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
# end
|
290
|
-
# toplevel.push_elems[[self.object_id, :group, keycols, aggpairs, blk]]
|
287
|
+
g = Bud::PushGroup.new("grp#{Time.new.tv_usec}".to_sym, toplevel.this_rule_context,
|
288
|
+
@collection_name, keycols, aggpairs, the_schema, &blk)
|
289
|
+
self.wire_to(g)
|
290
|
+
toplevel.push_elems[[self.object_id, :group, keycols, aggpairs, blk]] = g
|
291
291
|
return g
|
292
292
|
end
|
293
293
|
|
294
294
|
def argagg(aggname, gbkey_cols, collection, &blk)
|
295
|
+
gbkey_cols ||= []
|
295
296
|
gbkey_cols = gbkey_cols.map{|c| canonicalize_col(c)}
|
296
297
|
collection = canonicalize_col(collection)
|
297
298
|
toplevel = @bud_instance.toplevel
|
298
299
|
agg = toplevel.send(aggname, collection)[0]
|
299
|
-
|
300
|
-
|
301
|
-
if k.class == Symbol
|
302
|
-
k.to_s
|
303
|
-
else
|
304
|
-
k[2]
|
305
|
-
end
|
300
|
+
unless agg.class <= Bud::ArgExemplary
|
301
|
+
raise Bud::Error, "#{aggname} not declared exemplary"
|
306
302
|
end
|
303
|
+
|
307
304
|
aggpairs = [[agg, collection]]
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
# end
|
313
|
-
# return toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]]
|
305
|
+
aa = Bud::PushArgAgg.new("argagg#{Time.new.tv_usec}".to_sym, toplevel.this_rule_context,
|
306
|
+
@collection_name, gbkey_cols, aggpairs, schema, &blk)
|
307
|
+
self.wire_to(aa)
|
308
|
+
toplevel.push_elems[[self.object_id, :argagg, gbkey_cols, aggpairs, blk]] = aa
|
314
309
|
return aa
|
315
310
|
end
|
316
311
|
def argmax(gbcols, col, &blk)
|
317
|
-
argagg(gbcols,
|
312
|
+
argagg(:max, gbcols, col, &blk)
|
318
313
|
end
|
319
314
|
def argmin(gbcols, col, &blk)
|
320
|
-
argagg(gbcols,
|
315
|
+
argagg(:min, gbcols, col, &blk)
|
321
316
|
end
|
322
317
|
def sort(name=nil, bud_instance=nil, the_schema=nil, &blk)
|
323
318
|
elem = Bud::PushSort.new(name, bud_instance, the_schema, &blk)
|
@@ -351,7 +346,7 @@ module Bud
|
|
351
346
|
end
|
352
347
|
|
353
348
|
def reduce(initial, &blk)
|
354
|
-
retval = Bud::PushReduce.new("reduce#{Time.new.tv_usec}",
|
349
|
+
retval = Bud::PushReduce.new("reduce#{Time.new.tv_usec}".to_sym,
|
355
350
|
@bud_instance, @collection_name,
|
356
351
|
schema, initial, &blk)
|
357
352
|
self.wire_to(retval)
|
@@ -394,7 +389,8 @@ module Bud
|
|
394
389
|
end
|
395
390
|
|
396
391
|
class PushPredicate < PushStatefulElement
|
397
|
-
def initialize(pred_symbol, elem_name=nil, collection_name=nil,
|
392
|
+
def initialize(pred_symbol, elem_name=nil, collection_name=nil,
|
393
|
+
bud_instance=nil, schema_in=nil, &blk)
|
398
394
|
@pred_symbol = pred_symbol
|
399
395
|
@in_buf = []
|
400
396
|
super(elem_name, bud_instance, collection_name, schema_in, &blk)
|
@@ -447,6 +443,7 @@ module Bud
|
|
447
443
|
class ScannerElement < PushElement
|
448
444
|
attr_reader :collection
|
449
445
|
attr_reader :rescan_set, :invalidate_set
|
446
|
+
attr_accessor :force_rescan
|
450
447
|
|
451
448
|
def initialize(elem_name, bud_instance, collection_in,
|
452
449
|
the_schema=collection_in.schema, &blk)
|
@@ -454,6 +451,7 @@ module Bud
|
|
454
451
|
@collection = collection_in
|
455
452
|
@rescan_set = []
|
456
453
|
@invalidate_set = []
|
454
|
+
@force_rescan = false
|
457
455
|
end
|
458
456
|
|
459
457
|
def rescan
|
@@ -464,20 +462,21 @@ module Bud
|
|
464
462
|
@collection.invalidate_at_tick # need to scan afresh if collection invalidated.
|
465
463
|
end
|
466
464
|
|
467
|
-
#
|
468
|
-
#
|
465
|
+
# What should be rescanned/invalidated if this scanner's collection were to
|
466
|
+
# be invalidated.
|
469
467
|
def invalidate_at_tick(rescan, invalidate)
|
470
468
|
@rescan_set = rescan
|
471
469
|
@invalidate_set = invalidate
|
472
470
|
end
|
473
471
|
|
474
472
|
def add_rescan_invalidate(rescan, invalidate)
|
475
|
-
#
|
473
|
+
# If the collection is to be invalidated, the scanner needs to be in
|
476
474
|
# rescan mode
|
477
475
|
rescan << self if invalidate.member? @collection
|
478
476
|
|
479
|
-
#
|
480
|
-
|
477
|
+
# Pass the current state to each output collection and see if they end up
|
478
|
+
# marking this node for rescan
|
479
|
+
invalidate_tables(rescan, invalidate)
|
481
480
|
|
482
481
|
# Note also that this node can be nominated for rescan by a target node;
|
483
482
|
# in other words, a scanner element can be set to rescan even if the
|
@@ -485,9 +484,11 @@ module Bud
|
|
485
484
|
end
|
486
485
|
|
487
486
|
def scan(first_iter)
|
488
|
-
if
|
487
|
+
if @force_rescan
|
488
|
+
@collection.each_raw {|item| push_out(item)}
|
489
|
+
@force_rescan = false
|
490
|
+
elsif first_iter
|
489
491
|
if rescan
|
490
|
-
# Scan entire storage
|
491
492
|
@collection.each_raw {|item| push_out(item)}
|
492
493
|
else
|
493
494
|
# In the first iteration, tick_delta would be non-null IFF the
|
@@ -497,7 +498,7 @@ module Bud
|
|
497
498
|
end
|
498
499
|
|
499
500
|
# send deltas out in all cases
|
500
|
-
@collection.
|
501
|
+
@collection.each_delta {|item| push_out(item)}
|
501
502
|
end
|
502
503
|
end
|
503
504
|
|
data/lib/bud/executor/group.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'bud/executor/elements'
|
2
|
-
require 'set'
|
3
2
|
|
4
3
|
module Bud
|
5
4
|
class PushGroup < PushStatefulElement
|
@@ -10,21 +9,25 @@ module Bud
|
|
10
9
|
else
|
11
10
|
@keys = keys_in.map{|k| k[1]}
|
12
11
|
end
|
13
|
-
# An aggpair is an array: [agg class instance,
|
14
|
-
#
|
15
|
-
@aggpairs = aggpairs_in.map
|
12
|
+
# An aggpair is an array: [agg class instance, array of indexes of input
|
13
|
+
# agg input columns]. The second field is nil for Count.
|
14
|
+
@aggpairs = aggpairs_in.map do |ap|
|
15
|
+
agg, *rest = ap
|
16
|
+
if rest.empty?
|
17
|
+
[agg, nil]
|
18
|
+
else
|
19
|
+
[agg, rest.map {|r| r[1]}]
|
20
|
+
end
|
21
|
+
end
|
16
22
|
@groups = {}
|
17
23
|
|
18
24
|
# Check whether we need to eliminate duplicates from our input (we might
|
19
25
|
# see duplicates because of the rescan/invalidation logic, as well as
|
20
26
|
# because we don't do duplicate elimination on the output of a projection
|
21
27
|
# operator). We don't need to dupelim if all the args are exemplary.
|
22
|
-
@elim_dups = @aggpairs.any? {|
|
23
|
-
if @elim_dups
|
24
|
-
@input_cache = Set.new
|
25
|
-
end
|
28
|
+
@elim_dups = @aggpairs.any? {|ap| not ap[0].kind_of? ArgExemplary}
|
29
|
+
@input_cache = Set.new if @elim_dups
|
26
30
|
|
27
|
-
@seen_new_data = false
|
28
31
|
super(elem_name, bud_instance, collection_name, schema_in, &blk)
|
29
32
|
end
|
30
33
|
|
@@ -34,19 +37,25 @@ module Bud
|
|
34
37
|
@input_cache << item
|
35
38
|
end
|
36
39
|
|
37
|
-
@seen_new_data = true
|
38
40
|
key = item.values_at(*@keys)
|
39
41
|
group_state = @groups[key]
|
40
42
|
if group_state.nil?
|
41
43
|
@groups[key] = @aggpairs.map do |ap|
|
42
|
-
|
43
|
-
|
44
|
+
if ap[1].nil?
|
45
|
+
ap[0].init(item)
|
46
|
+
else
|
47
|
+
ap[0].init(*item.values_at(*ap[1]))
|
48
|
+
end
|
44
49
|
end
|
45
50
|
else
|
46
51
|
@aggpairs.each_with_index do |ap, agg_ix|
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
state_val = group_state[agg_ix]
|
53
|
+
if ap[1].nil?
|
54
|
+
trans_rv = ap[0].trans(state_val, item)
|
55
|
+
else
|
56
|
+
trans_rv = ap[0].trans(state_val, *item.values_at(*ap[1]))
|
57
|
+
end
|
58
|
+
group_state[agg_ix] = trans_rv[0]
|
50
59
|
end
|
51
60
|
end
|
52
61
|
end
|
@@ -62,14 +71,12 @@ module Bud
|
|
62
71
|
puts "#{self.class}/#{self.tabname} invalidated" if $BUD_DEBUG
|
63
72
|
@groups.clear
|
64
73
|
@input_cache.clear if @elim_dups
|
65
|
-
@seen_new_data = false
|
66
74
|
end
|
67
75
|
|
68
76
|
def flush
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
@seen_new_data = false
|
77
|
+
# Don't emit fresh output unless a rescan is needed
|
78
|
+
return unless @rescan
|
79
|
+
@rescan = false
|
73
80
|
|
74
81
|
@groups.each do |key, group_state|
|
75
82
|
rv = key.clone
|
@@ -87,7 +94,6 @@ module Bud
|
|
87
94
|
raise Bud::Error, "multiple aggpairs #{aggpairs_in.map{|a| a.class.name}} in ArgAgg; only one allowed"
|
88
95
|
end
|
89
96
|
super(elem_name, bud_instance, collection_name, keys_in, aggpairs_in, schema_in, &blk)
|
90
|
-
@agg, @aggcol = @aggpairs[0]
|
91
97
|
@winners = {}
|
92
98
|
end
|
93
99
|
|
@@ -101,18 +107,16 @@ module Bud
|
|
101
107
|
key = @keys.map{|k| item[k]}
|
102
108
|
group_state = @groups[key]
|
103
109
|
if group_state.nil?
|
104
|
-
@seen_new_data = true
|
105
110
|
@groups[key] = @aggpairs.map do |ap|
|
106
111
|
@winners[key] = [item]
|
107
|
-
|
108
|
-
ap[0].init(
|
112
|
+
input_vals = item.values_at(*ap[1])
|
113
|
+
ap[0].init(*input_vals)
|
109
114
|
end
|
110
115
|
else
|
111
116
|
@aggpairs.each_with_index do |ap, agg_ix|
|
112
|
-
|
113
|
-
state_val, flag, *rest = ap[0].trans(group_state[agg_ix],
|
117
|
+
input_vals = item.values_at(*ap[1])
|
118
|
+
state_val, flag, *rest = ap[0].trans(group_state[agg_ix], *input_vals)
|
114
119
|
group_state[agg_ix] = state_val
|
115
|
-
@seen_new_data = true unless flag == :ignore
|
116
120
|
|
117
121
|
case flag
|
118
122
|
when :ignore
|
@@ -133,14 +137,13 @@ module Bud
|
|
133
137
|
end
|
134
138
|
|
135
139
|
def flush
|
136
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
@seen_new_data = false
|
140
|
+
# Don't emit fresh output unless a rescan is needed
|
141
|
+
return unless @rescan
|
142
|
+
@rescan = false
|
140
143
|
|
141
144
|
@groups.each_key do |g|
|
142
145
|
@winners[g].each do |t|
|
143
|
-
push_out(t
|
146
|
+
push_out(t)
|
144
147
|
end
|
145
148
|
end
|
146
149
|
end
|