bud 0.0.8 → 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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