bud 0.9.4 → 0.9.9
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 +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
|