bud 0.0.8 → 0.1.0.pre1

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.
@@ -5,41 +5,66 @@ class DepAnalysis #:nodoc: all
5
5
  include Bud
6
6
 
7
7
  state do
8
- table :providing, [:pred, :input]
9
- table :depends_tc, [:head, :body, :via, :neg, :temporal]
10
- table :underspecified, [:pred, :input]
11
-
8
+ # Data inserted by client, usually from t_depends and t_provides
9
+ scratch :depends, [:lhs, :op, :body, :neg]
10
+ scratch :providing, [:pred, :input]
12
11
 
13
- table :source, [:pred]
14
- table :sink, [:pred]
12
+ # Intermediate state
13
+ scratch :depends_clean, [:lhs, :body, :neg, :temporal]
14
+
15
+ scratch :depends_tc, [:lhs, :body, :via, :neg, :temporal]
16
+ scratch :cycle, [:pred, :via, :neg, :temporal]
17
+ scratch :underspecified, [:pred, :input]
18
+ scratch :source, [:pred]
19
+ scratch :sink, [:pred]
15
20
  end
16
21
 
17
- def declaration
18
- strata[0] = lambda {
19
- source <= providing do |p|
20
- if p.input and !depends_tc.map{|d| d.head}.include? p.pred
21
- [p.pred]
22
+ bloom :analysis do
23
+ depends_clean <= depends do |d|
24
+ is_temporal = (d.op.to_s =~ /<[\+\-\~]/)
25
+ [d.lhs, d.body, d.neg, is_temporal]
26
+ end
27
+
28
+ # Compute the transitive closure of "depends_clean" to detect cycles in
29
+ # the deductive fragment of the program.
30
+ depends_tc <= depends_clean do |d|
31
+ [d.lhs, d.body, d.body, d.neg, d.temporal]
32
+ end
33
+ depends_tc <= (depends_clean * depends_tc).pairs(:body => :lhs) do |b, r|
34
+ [b.lhs, r.body, b.body, (b.neg or r.neg), (b.temporal or r.temporal)]
35
+ end
36
+
37
+ cycle <= depends_tc do |d|
38
+ if d.lhs == d.body
39
+ unless d.neg and !d.temporal
40
+ [d.lhs, d.via, d.neg, d.temporal]
22
41
  end
23
42
  end
43
+ end
24
44
 
25
- sink <= providing do |p|
26
- if !p.input and !depends_tc.map{|d| d.body}.include? p.pred
27
- [p.pred]
28
- end
45
+ source <= providing do |p|
46
+ if p.input and !depends_tc.map{|d| d.lhs}.include? p.pred
47
+ [p.pred]
29
48
  end
49
+ end
30
50
 
31
- underspecified <= providing do |p|
32
- if p.input
33
- unless depends_tc.map{|d| d.body if d.head != d.body}.include? p.pred
34
- [p.pred, true]
35
- end
36
- else
37
- unless depends_tc.map{|d| d.head if d.head != d.body}.include? p.pred
38
- [p.pred, false]
39
- end
51
+ sink <= providing do |p|
52
+ if !p.input and !depends_tc.map{|d| d.body}.include? p.pred
53
+ [p.pred]
54
+ end
55
+ end
56
+
57
+ underspecified <= providing do |p|
58
+ if p.input
59
+ unless depends_tc.map{|d| d.body if d.lhs != d.body}.include? p.pred
60
+ [p.pred, true]
61
+ end
62
+ else
63
+ unless depends_tc.map{|d| d.lhs if d.lhs != d.body}.include? p.pred
64
+ [p.pred, false]
40
65
  end
41
66
  end
42
- }
67
+ end
43
68
  end
44
69
  end
45
70
 
@@ -0,0 +1,592 @@
1
+ require "set"
2
+ require 'bud/collections'
3
+ ELEMENT_BUFSIZE = 1
4
+
5
+ module Bud
6
+ # Usage example:
7
+ # p = PushElement.new(:r) do |inp|
8
+ # puts "in block"
9
+ # [inp] if inp.class <= Numeric and inp%2 == 0
10
+ # end
11
+
12
+ # p.insert(1)
13
+ # p.insert(nil)
14
+ class PushElement < BudCollection
15
+ attr_accessor :elem_name
16
+ attr_accessor :rescan, :invalidated
17
+ attr_reader :arity, :inputs, :found_delta, :refcount, :wired_by, :outputs
18
+
19
+ def initialize(name_in, bud_instance, collection_name=nil, given_schema=nil, defer_schema=false, &blk)
20
+ super(name_in, bud_instance, given_schema, defer_schema)
21
+ @blk = blk
22
+ @outputs = []
23
+ @pendings = []
24
+ @deletes = []
25
+ @delete_keys = []
26
+ @wired_by = []
27
+ @elem_name = name_in
28
+ @found_delta = false
29
+ @refcount = 1
30
+ @each_index = 0
31
+ @collection_name = collection_name
32
+ @invalidated = true
33
+ @rescan = true
34
+ end
35
+
36
+ def wiring?
37
+ @bud_instance.toplevel.done_wiring == false
38
+ end
39
+
40
+ def wirings
41
+ @wirings ||= @outputs + @pendings + @deletes + @delete_keys
42
+ end
43
+
44
+ public
45
+ def print_wiring(depth=0, accum = "")
46
+ depth.times {print " "}
47
+ puts "#{accum} #{(self.object_id*2).to_s(16)}: #{qualified_tabname} (#{self.class})"
48
+
49
+ [@outputs, @pendings, @deletes, @delete_keys].each do |kind|
50
+ case kind.object_id
51
+ when @outputs.object_id
52
+ next_accum = "=> "
53
+ when @pendings.object_id
54
+ next_accum = "+> "
55
+ when @deletes.object_id, @delete_keys.object_id
56
+ next_accum = "-> "
57
+ end
58
+
59
+ kind.each do |o|
60
+ if o.respond_to?(:print_wiring)
61
+ o.print_wiring(depth+1, next_accum)
62
+ else
63
+ (depth+1).times {print " "}
64
+ print "#{next_accum} "
65
+ if o.class <= Bud::BudCollection
66
+ puts "#{(o.object_id*2).to_s(16)}: #{o.qualified_tabname} (#{o.class})"
67
+ else
68
+ puts "#{(o.object_id*2).to_s(16)}: (#{o.class.name})"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def check_wiring
76
+ if @blk.nil? and @outputs.empty? and @pendings.empty? and @deletes.empty? and @delete_keys.empty?
77
+ raise "no output specified for PushElement #{@qualified_tabname}"
78
+ end
79
+ end
80
+
81
+ def set_block(&blk)
82
+ @blk = blk
83
+ end
84
+ def wire_to(element)
85
+ unless element.methods.include? :insert or element.methods.include? "insert"
86
+ raise Bud::Error, "attempt to wire_to element without insert method"
87
+ end
88
+ # elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
89
+ # puts "wiring #{self.elem_name} to #{elem_name}"
90
+ @outputs << element
91
+ element.wired_by << self if element.respond_to? :wired_by
92
+ end
93
+ def wire_to_pending(element)
94
+ raise Bud::Error, "attempt to wire_to_pending element without pending_merge method" unless element.methods.include? "pending_merge" or element.methods.include? :pending_merge
95
+ elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
96
+ # puts "wiring #{self.elem_name} to #{elem_name}(pending)"
97
+ @pendings << element
98
+ element.wired_by << self if element.respond_to? :wired_by
99
+ end
100
+ def wire_to_delete(element)
101
+ raise Bud::Error, "attempt to wire_to_delete element without pending_delete method" unless element.methods.include? "pending_delete" or element.methods.include? :pending_delete
102
+ elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
103
+ # puts "wiring #{self.elem_name} to #{elem_name}(delete)"
104
+ @deletes << element
105
+ element.wired_by << self if element.respond_to? :wired_by
106
+ end
107
+ def wire_to_delete_by_key(element)
108
+ raise Bud::Error, "attempt to wire_to_delete_by_key element without pending_delete_keys method" unless element.methods.include? "pending_delete_keys" or element.methods.include? :pending_delete_keys
109
+ elem_name = element.respond_to?(:tabname) ? element.tabname : element.elem_name
110
+ # puts "wiring #{self.elem_name} to #{elem_name}(delete)"
111
+ @delete_keys << element
112
+ element.wired_by << self if element.respond_to? :wired_by
113
+ end
114
+
115
+ def rescan_at_tick
116
+ false
117
+ end
118
+
119
+
120
+ def insert(item, source=nil)
121
+ push_out(item)
122
+ end
123
+
124
+ def tick
125
+ invalidate_cache if @invalidated
126
+ end
127
+
128
+ def tick_deltas
129
+ @found_delta = false
130
+ end
131
+
132
+ def push_out(item, do_block=true)
133
+ if item
134
+ blk = @blk if do_block
135
+ if blk
136
+ item = item.to_a if blk.arity > 1
137
+ begin
138
+ item = blk.call item
139
+ rescue Exception
140
+ raise
141
+ end
142
+ end
143
+ @outputs.each do |ou|
144
+ if ou.class <= Bud::PushElement
145
+ #the_name = ou.elem_name
146
+ # puts "#{self.object_id%10000} (#{elem_name}) -> #{ou.object_id%10000} (#{the_name}): #{item.inspect}"
147
+ ou.insert(item,self)
148
+ elsif ou.class <= Bud::BudCollection
149
+ # the_name = ou.tabname
150
+ # puts "#{self.object_id%10000} (#{elem_name}) -> #{ou.object_id%10000} (#{the_name}): #{item.inspect}"
151
+ ou.do_insert(item,ou.new_delta)
152
+ else
153
+ raise "Expected either a PushElement or a BudCollection"
154
+ end
155
+ end unless item.nil?
156
+ # for all the following, o is a BudCollection
157
+ @deletes.each{|o| o.pending_delete([item])} unless item.nil?
158
+ @delete_keys.each{|o| o.pending_delete_keys([item])} unless item.nil?
159
+ @pendings.each{|o| o.pending_merge([item])} unless item.nil?
160
+ end
161
+ end
162
+
163
+
164
+ # default for stateless elements
165
+ public
166
+ def add_rescan_invalidate(rescan, invalidate)
167
+ # if any of the source elements are in rescan mode, then put this node in rescan.
168
+ srcs = non_temporal_predecessors
169
+ if srcs.any?{|p| rescan.member? p}
170
+ rescan << self
171
+ end
172
+
173
+ # pass the current state to the non-element outputs, and see if they end up marking this node for rescan
174
+ invalidate_tables(rescan, invalidate)
175
+
176
+ # finally, if this node is in rescan, pass the request on to all source elements
177
+ if rescan.member? self
178
+ srcs.each{|e| rescan << e} # propagate a rescan request to all sources.
179
+ end
180
+ end
181
+
182
+ def invalidate_tables(rescan, invalidate)
183
+ # exchange rescan and invalidate information with tables. If this node is in rescan, it may invalidate a target
184
+ # table (if it is a scratch). And if the target node is invalidated, this node marks itself for rescan to
185
+ # enable a refill of that table at run-time
186
+
187
+ @outputs.each do |o|
188
+ unless o.class <= PushElement
189
+ o.add_rescan_invalidate(rescan, invalidate) unless o.class <= PushElement
190
+ rescan << self if invalidate.member? o
191
+ end
192
+ end
193
+ end
194
+
195
+ def <<(i)
196
+ insert(i, nil)
197
+ end
198
+ public
199
+ def flush
200
+ end
201
+
202
+ def invalidate_cache
203
+ #override to get rid of cached information.
204
+ end
205
+ public
206
+ def stratum_end
207
+ end
208
+ #public
209
+ #def set_schema(schema)
210
+ # @schema=schema
211
+ # setup_accessors
212
+ #end
213
+
214
+
215
+ ####
216
+ # and now, the Bloom-facing methods
217
+ public
218
+ def pro(the_name = @elem_name, the_schema = schema, &blk)
219
+ toplevel = @bud_instance.toplevel
220
+ elem = Bud::PushElement.new('project' + object_id.to_s, toplevel.this_rule_context, @collection_name, the_schema)
221
+ #elem.init_schema(the_schema) unless the_schema.nil?
222
+ self.wire_to(elem)
223
+ elem.set_block(&blk)
224
+ toplevel.push_elems[[self.object_id,:pro,blk]] = elem
225
+ return elem
226
+ end
227
+
228
+ alias each pro
229
+
230
+ public
231
+ def each_with_index(the_name = elem_name, the_schema = schema, &blk)
232
+ toplevel = @bud_instance.toplevel
233
+ elem = Bud::PushEachWithIndex.new('each_with_index' + object_id.to_s, toplevel.this_rule_context, @collection_name)
234
+ elem.set_block(&blk)
235
+ self.wire_to(elem)
236
+ toplevel.push_elems[[self.object_id,:each,blk]] = elem
237
+ end
238
+
239
+ def join(elem2, &blk)
240
+ # cached = @bud_instance.push_elems[[self.object_id,:join,[self,elem2], @bud_instance, blk]]
241
+ # if cached.nil?
242
+ elem2 = elem2.to_push_elem unless elem2.class <= PushElement
243
+ toplevel = @bud_instance.toplevel
244
+ join = Bud::PushSHJoin.new([self,elem2], toplevel.this_rule_context, [])
245
+ self.wire_to(join)
246
+ elem2.wire_to(join)
247
+ toplevel.push_elems[[self.object_id,:join,[self,elem2], toplevel, blk]] = join
248
+ toplevel.push_joins[toplevel.this_stratum] << join
249
+ # else
250
+ # cached.refcount += 1
251
+ # end
252
+ return toplevel.push_elems[[self.object_id,:join,[self,elem2], toplevel, blk]]
253
+ end
254
+ def *(elem2, &blk)
255
+ join(elem2, &blk)
256
+ end
257
+
258
+ def notin(elem2, preds=nil, &blk)
259
+ toplevel = @bud_instance.toplevel
260
+ notin_elem = Bud::PushNotIn.new([self, elem2], toplevel.this_rule_context, preds, &blk)
261
+ self.wire_to(notin_elem)
262
+ elem2.wire_to(notin_elem)
263
+ toplevel.push_elems[[self.object_id, :notin, collection, toplevel, blk]] == notin_elem
264
+ return notin_elem
265
+ end
266
+
267
+ def merge(source)
268
+ if source.class <= PushElement and wiring?
269
+ source.wire_to(self)
270
+ else
271
+ source.each{|i| self << i}
272
+ end
273
+ end
274
+ alias <= merge
275
+ superator "<~" do |o|
276
+ raise Bud::Error, "Illegal use of <~ with pusher '#{tabname}' on left"
277
+ end
278
+
279
+ superator "<-" do |o|
280
+ raise Bud::Error, "Illegal use of <- with pusher '#{tabname}' on left"
281
+ end
282
+
283
+ superator "<+" do |o|
284
+ raise Bud::Error, "Illegal use of <+ with pusher '#{tabname}' on left"
285
+ end
286
+
287
+ def group(keycols, *aggpairs, &blk)
288
+ # establish schema
289
+ keycols = [] if keycols.nil?
290
+ keycols = keycols.map{|c| canonicalize_col(c)}
291
+ keynames = keycols.map{|k| k[2]}
292
+ aggcolsdups = aggpairs.map{|ap| ap[0].class.name.split("::").last}
293
+ aggcols = []
294
+ aggcolsdups.each_with_index do |n, i|
295
+ aggcols << "#{n.downcase}_#{i}".to_sym
296
+ end
297
+ if aggcols.empty?
298
+ the_schema = keynames
299
+ else
300
+ the_schema = { keynames => aggcols }
301
+ end
302
+
303
+ aggpairs = aggpairs.map{|ap| ap[1].nil? ? [ap[0]] : [ap[0], canonicalize_col(ap[1])]}
304
+ toplevel = @bud_instance.toplevel
305
+ # if @bud_instance.push_elems[[self.object_id, :group, keycols, aggpairs, blk]].nil?
306
+ g = Bud::PushGroup.new('grp'+Time.new.tv_usec.to_s, toplevel.this_rule_context, @collection_name, keycols, aggpairs, the_schema, &blk)
307
+ self.wire_to(g)
308
+ toplevel.push_elems[[self.object_id, :group, keycols, aggpairs, blk]] = g
309
+ # end
310
+ # toplevel.push_elems[[self.object_id, :group, keycols, aggpairs, blk]]
311
+ return g
312
+ end
313
+
314
+
315
+ def argagg(aggname, gbkey_cols, collection, &blk)
316
+ gbkey_cols = gbkey_cols.map{|c| canonicalize_col(c)}
317
+ collection = canonicalize_col(collection)
318
+ toplevel = @bud_instance.toplevel
319
+ agg = toplevel.send(aggname, collection)[0]
320
+ raise Bud::Error, "#{aggname} not declared exemplary" unless agg.class <= Bud::ArgExemplary
321
+ keynames = gbkey_cols.map do |k|
322
+ if k.class == Symbol
323
+ k.to_s
324
+ else
325
+ k[2]
326
+ end
327
+ end
328
+ aggpairs = [[agg,collection]]
329
+ # if toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]].nil?
330
+ aa = Bud::PushArgAgg.new('argagg'+Time.new.tv_usec.to_s, toplevel.this_rule_context, @collection_name, gbkey_cols, aggpairs, schema, &blk)
331
+ self.wire_to(aa)
332
+ toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]] = aa
333
+ # end
334
+ # return toplevel.push_elems[[self.object_id,:argagg, gbkey_cols, aggpairs, blk]]
335
+ return aa
336
+ end
337
+ def argmax(gbcols, col, &blk)
338
+ argagg(gbcols, Bud::max(col), blk)
339
+ end
340
+ def argmin(gbcols, col, &blk)
341
+ argagg(gbcols, Bud::min(col), blk)
342
+ end
343
+ def sort(name=nil, bud_instance=nil, the_schema=nil, &blk)
344
+ elem = Bud::PushSort.new(name, bud_instance, the_schema, &blk)
345
+ wire_to(elem)
346
+ elem
347
+ end
348
+ def push_predicate(pred_symbol, name=nil, bud_instance=nil, the_schema=nil, &blk)
349
+ elem = Bud::PushPredicate.new(pred_symbol, name, bud_instance, the_schema, &blk)
350
+ wire_to(elem)
351
+ elem
352
+ end
353
+ def all?(name=nil, bud_instance=nil, the_schema=nil, &blk)
354
+ push_predicate(:all?, name, bud_instance, the_schema, &blk)
355
+ end
356
+ def any?(name=nil, bud_instance=nil, the_schema=nil, &blk)
357
+ push_predicate(:any?, name, bud_instance, the_schema, &blk)
358
+ end
359
+ def include?(name=nil, bud_instance=nil, the_schema=nil, &blk)
360
+ push_predicate(:include?, name, bud_instance, the_schema, &blk)
361
+ end
362
+ def member?(name=nil, bud_instance=nil, the_schema=nil, &blk)
363
+ push_predicate(:member?, name, bud_instance, the_schema, &blk)
364
+ end
365
+ def none?(name=nil, bud_instance=nil, the_schema=nil, &blk)
366
+ push_predicate(:none?, name, bud_instance, the_schema, &blk)
367
+ end
368
+ def one?(name=nil, bud_instance=nil, the_schema=nil, &blk)
369
+ push_predicate(:one?, name, bud_instance, the_schema, &blk)
370
+ end
371
+
372
+ def reduce(initial, &blk)
373
+ @memo = initial
374
+ retval = Bud::PushReduce.new('reduce'+Time.new.tv_usec.to_s, @bud_instance, @collection_name, schema, initial, &blk)
375
+ self.wire_to(retval)
376
+ retval
377
+ end
378
+
379
+ alias on_exists? pro
380
+ def on_include?(item, &blk)
381
+ toplevel = @bud_instance.toplevel
382
+ if toplevel.push_elems[[self.object_id,:on_include?, item, blk]].nil?
383
+ inc = pro{|i| blk.call(item) if i == item and not blk.nil?}
384
+ wire_to(inc)
385
+ toplevel.push_elems[[self.object_id,:on_include?, item, blk]] = inc
386
+ end
387
+ toplevel.push_elems[[self.object_id,:on_include?, item, blk]]
388
+ end
389
+ def inspected
390
+ toplevel = @bud_instance.toplevel
391
+ if toplevel.push_elems[[self.object_id,:inspected]].nil?
392
+ ins = pro{|i| [i.inspect]}
393
+ self.wire_to(ins)
394
+ toplevel.push_elems[[self.object_id,:inspected]] = ins
395
+ end
396
+ toplevel.push_elems[[self.object_id,:inspected]]
397
+ end
398
+
399
+ def to_enum
400
+ # scr = @bud_instance.scratch(("scratch_" + Process.pid.to_s + "_" + object_id.to_s + "_" + rand(10000).to_s).to_sym, schema)
401
+ scr = []
402
+ self.wire_to(scr)
403
+ scr
404
+ end
405
+ end
406
+
407
+ class PushStatefulElement < PushElement
408
+
409
+ def rescan_at_tick
410
+ true
411
+ end
412
+
413
+ def rescan
414
+ true # always gives an entire dump of its contents
415
+ end
416
+
417
+ def add_rescan_invalidate(rescan, invalidate)
418
+ # If an upstream node is set to rescan, a stateful node invalidates its cache
419
+ # In addition, a stateful node always rescans its own contents (doesn't need to pass a rescan request to its
420
+ # its source nodes
421
+
422
+ rescan << self
423
+ srcs = non_temporal_predecessors
424
+ if srcs.any? {|p| rescan.member? p}
425
+ invalidate << self
426
+ end
427
+
428
+ invalidate_tables(rescan, invalidate)
429
+ end
430
+ end
431
+
432
+ class PushPredicate < PushStatefulElement
433
+ def initialize(pred_symbol, elem_name=nil, collection_name=nil, bud_instance=nil, schema_in=nil, &blk)
434
+ @pred_symbol = pred_symbol
435
+ @in_buf = []
436
+ super(elem_name, bud_instance, collection_name, schema_in, &blk)
437
+ end
438
+
439
+ def insert(item, source)
440
+ @in_buf << item
441
+ end
442
+
443
+ public
444
+ def flush
445
+ # always rescans
446
+ @in_buf.send(@pred_symbol, @blk)
447
+ end
448
+
449
+ def invalidate_cache
450
+ @in_buf.clear
451
+ end
452
+ end
453
+
454
+ class PushSort < PushStatefulElement
455
+ def initialize(elem_name=nil, bud_instance=nil, collection_name=nil, schema_in=nil, &blk)
456
+ @sortbuf = []
457
+ super(elem_name, bud_instance, collection_name, schema_in, &blk)
458
+ end
459
+
460
+ def insert(item, source)
461
+ @sortbuf << item
462
+ end
463
+
464
+ def flush
465
+ unless @sortbuf.empty?
466
+ @sortbuf.sort!(&@blk)
467
+ @sortbuf.each do |t|
468
+ push_out(t, false)
469
+ end
470
+ @sortbuf = []
471
+ end
472
+ nil
473
+ end
474
+
475
+ def invalidate_cache
476
+ @sortbuf = []
477
+ end
478
+ end
479
+
480
+ class ScannerElement < PushElement
481
+ attr_reader :collection
482
+ attr_reader :rescan_set, :invalidate_set
483
+ def initialize(elem_name, bud_instance, collection_in, the_schema=collection_in.schema, &blk)
484
+ # puts self.class
485
+ super(elem_name, bud_instance, collection_in.qualified_tabname, the_schema)
486
+ @collection = collection_in
487
+ @rescan_set = []
488
+ @invalidate_set = []
489
+ end
490
+
491
+ def rescan
492
+ @rescan || @collection.invalidated
493
+ end
494
+
495
+ def rescan_at_tick
496
+ @collection.invalidate_at_tick # need to scan afresh if collection invalidated.
497
+ end
498
+
499
+ def invalidate_at_tick(rescan, invalidate)
500
+ # collection of others to rescan/invalidate if this scanner's collection were to be invalidated.
501
+ @rescan_set = rescan
502
+ @invalidate_set = invalidate
503
+ end
504
+
505
+ public
506
+ def add_rescan_invalidate(rescan, invalidate)
507
+ # scanner elements are never directly connected to tables.
508
+ rescan << self if invalidate.member? @collection
509
+
510
+ # Note also that this node can be nominated for rescan by a target node; in other words, a scanner element
511
+ # can be set to rescan even if the collection is not invalidated.
512
+ end
513
+
514
+ def scan(first_iter)
515
+ if (first_iter)
516
+ if rescan
517
+ # scan entire storage
518
+ @collection.each_raw {|item|
519
+ push_out(item)
520
+ }
521
+ else
522
+ # In the first iteration, tick_delta would be non-null IFF the collection has grown in an earlier stratum
523
+ @collection.tick_delta.each {|item| push_out(item)}
524
+ end
525
+ end
526
+
527
+ # send deltas out in all cases
528
+ @collection.delta.each_value {|item| push_out(item)}
529
+ end
530
+ end
531
+
532
+ class PushReduce < PushStatefulElement
533
+ def initialize(elem_name, bud_instance, collection_name, schema_in, initial, &blk)
534
+ @memo = initial
535
+ @blk = blk
536
+ super(elem_name, bud_instance, collection_name, schema)
537
+ end
538
+
539
+ def insert(i, source=nil)
540
+ @memo = @blk.call(@memo,i)
541
+ end
542
+
543
+ def invalidate_cache
544
+ @memo.clear
545
+ end
546
+
547
+ public
548
+ def flush
549
+ @memo.each do |k,v|
550
+ push_out([k,v], false)
551
+ end
552
+ end
553
+ end
554
+
555
+ class PushEachWithIndex < PushStatefulElement
556
+ def initialize(elem_name, bud_instance, collection_name)
557
+ super(elem_name, bud_instance, collection_name)
558
+ @each_index = 0
559
+ end
560
+
561
+ def add_rescan_invalidate(rescan, invalidate)
562
+ srcs = non_temporal_predecessors
563
+ if srcs.any? {|p| rescan.member? p}
564
+ invalidate << self
565
+ rescan << self
566
+ end
567
+
568
+ invalidate_tables(rescan, invalidate)
569
+
570
+ # This node has some state (@each_index), but not the tuples. If it is in rescan mode, then it must ask its
571
+ # sources to rescan, and restart its index.
572
+ if rescan.member? self
573
+ invalidate << self
574
+ srcs.each {|e| rescan << e}
575
+ end
576
+ end
577
+
578
+ def invalidate_cache
579
+ @each_index = 0
580
+ end
581
+
582
+ def stratum_end
583
+ @each_index = 0
584
+ end
585
+
586
+ def insert(item, source=nil)
587
+ ix = @each_index
588
+ @each_index = ix + 1
589
+ push_out([item, ix])
590
+ end
591
+ end
592
+ end