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.
@@ -1,28 +1,37 @@
1
1
  require 'msgpack'
2
2
 
3
+ $struct_classes = {}
4
+ $EMPTY_HASH = {}
3
5
  module Bud
4
6
  ########
5
7
  #--
6
- # the collection types
8
+ # the collection types ``
7
9
  # each collection is partitioned into 4:
8
10
  # - pending holds tuples deferred til the next tick
9
11
  # - storage holds the "normal" tuples
10
12
  # - delta holds the delta for rhs's of rules during semi-naive
11
13
  # - new_delta will hold the lhs tuples currently being produced during s-n
14
+ # - tick_delta holds \Union(delta_i) for each delta_i processed in fixpoint iteration i.
12
15
  #++
13
16
 
14
17
  class BudCollection
15
18
  include Enumerable
16
19
 
17
- # This needs to be an accessor to allow REBL to update it after cloning a
18
- # Bud instance.
19
- attr_accessor :bud_instance # :nodoc: all
20
- attr_reader :cols, :key_cols, :tabname # :nodoc: all
21
- attr_reader :storage, :delta, :new_delta, :pending # :nodoc: all
20
+ attr_accessor :bud_instance, :locspec_idx, :tabname # :nodoc: all
21
+ attr_reader :cols, :key_cols # :nodoc: all
22
+ attr_reader :struct
23
+ attr_reader :storage, :delta, :new_delta, :pending, :tick_delta # :nodoc: all
24
+ attr_accessor :qualified_tabname
25
+ attr_accessor :invalidated, :to_delete, :rescan
26
+ attr_accessor :is_source
27
+ attr_accessor :wired_by
22
28
 
23
29
  def initialize(name, bud_instance, given_schema=nil, defer_schema=false) # :nodoc: all
24
30
  @tabname = name
25
31
  @bud_instance = bud_instance
32
+ @invalidated = true
33
+ @is_source = true # unless it shows up on the lhs of some rule
34
+ @wired_by = []
26
35
  init_schema(given_schema) unless given_schema.nil? and defer_schema
27
36
  init_buffers
28
37
  end
@@ -34,31 +43,42 @@ module Bud
34
43
  init_deltas
35
44
  end
36
45
 
37
- private
46
+ public
38
47
  def init_schema(given_schema)
39
48
  given_schema ||= {[:key]=>[:val]}
40
49
 
50
+
51
+ @given_schema = given_schema
52
+ @cols, @key_cols = BudCollection.parse_schema(given_schema)
41
53
  # Check that no location specifiers appear in the schema. In the case of
42
54
  # channels, the location specifier has already been stripped from the
43
55
  # user-specified schema.
44
- given_schema.each do |s|
56
+ @cols.each do |s|
45
57
  if s.to_s.start_with? "@"
46
58
  raise Bud::Error, "illegal use of location specifier (@) in column #{s} of non-channel collection #{tabname}"
47
59
  end
48
60
  end
49
61
 
50
- @given_schema = given_schema
51
- @cols, @key_cols = parse_schema(given_schema)
62
+ if @cols.size == 0
63
+ @cols = nil
64
+ else
65
+ @struct = ($struct_classes[@cols] ||= Struct.new(*@cols))
66
+ @structlen = @struct.members.length
67
+ end
52
68
  @key_colnums = key_cols.map {|k| @cols.index(k)}
53
69
  setup_accessors
54
70
  end
55
71
 
72
+ def qualified_tabname
73
+ @qualified_tabname ||= @bud_instance.toplevel? ? tabname : (@bud_instance.qualified_name + "." + tabname.to_s).to_sym
74
+ end
75
+
56
76
  # The user-specified schema might come in two forms: a hash of Array =>
57
77
  # Array (key_cols => remaining columns), or simply an Array of columns (if
58
78
  # no key_cols were specified). Return a pair: [list of (all) columns, list
59
79
  # of key columns]
60
80
  private
61
- def parse_schema(given_schema)
81
+ def self.parse_schema(given_schema)
62
82
  if given_schema.respond_to? :keys
63
83
  raise Bud::Error, "invalid schema for #{tabname}" if given_schema.length != 1
64
84
  key_cols = given_schema.keys.first
@@ -71,16 +91,20 @@ module Bud
71
91
  cols = key_cols + val_cols
72
92
  cols.each do |c|
73
93
  if c.class != Symbol
74
- raise Bud::Error, "invalid schema element \"#{c}\", type \"#{c.class}\""
94
+ raise Bud::Error, "Invalid column name \"#{c}\", type \"#{c.class}\""
75
95
  end
76
96
  end
77
97
  if cols.uniq.length < cols.length
78
- raise Bud::Error, "schema for #{tabname} contains duplicate names"
98
+ raise Bud::Error, "schema #{given_schema.inspect} contains duplicate names"
79
99
  end
80
100
 
81
101
  return [cols, key_cols]
82
102
  end
83
103
 
104
+ def inspect
105
+ "#{self.class}:#{self.object_id.to_s(16)} [#{qualified_tabname}]"
106
+ end
107
+
84
108
  public
85
109
  def clone_empty #:nodoc: all
86
110
  self.class.new(tabname, bud_instance, @given_schema)
@@ -105,28 +129,27 @@ module Bud
105
129
  # j = join link, path, {link.to => path.from}
106
130
  private
107
131
  def setup_accessors
108
- s = @cols
109
- s.each do |colname|
110
- reserved = eval "defined?(#{colname})"
111
- unless (reserved.nil? or
112
- (reserved == "method" and method(colname).arity == -1 and (eval(colname))[0] == self.tabname))
132
+ sc = @cols
133
+ return if sc.nil?
134
+ sc.each do |colname|
135
+ if name_reserved? colname
113
136
  raise Bud::Error, "symbol :#{colname} reserved, cannot be used as column name for #{tabname}"
114
137
  end
115
138
  end
116
139
 
117
140
  # set up schema accessors, which are class methods
118
- m = Module.new do
119
- s.each_with_index do |c, i|
120
- define_method c do
141
+ @cols_access = Module.new do
142
+ sc.each_with_index do |c, i|
143
+ m = define_method c do
121
144
  [@tabname, i, c]
122
145
  end
123
146
  end
124
147
  end
125
- self.extend m
148
+ self.extend @cols_access
126
149
 
127
150
  # now set up a Module for tuple accessors, which are instance methods
128
151
  @tupaccess = Module.new do
129
- s.each_with_index do |colname, offset|
152
+ sc.each_with_index do |colname, offset|
130
153
  define_method colname do
131
154
  self[offset]
132
155
  end
@@ -134,51 +157,120 @@ module Bud
134
157
  end
135
158
  end
136
159
 
137
- # define methods to access tuple attributes by column name
160
+
138
161
  private
162
+ def name_reserved?(colname)
163
+ reserved = eval "defined?(#{colname})"
164
+ return false if reserved.nil?
165
+ if reserved == "method" and (method(colname).arity == 0 or method(colname).arity == -1)
166
+ begin
167
+ ret = eval("#{colname}")
168
+ if ret.kind_of? Array and ret.size == 3 and ret[0] == tabname
169
+ return false # schema redefinition (see tupaccess above), so name is not considered reserved
170
+ end
171
+ rescue # in case calling method throws an error
172
+ end
173
+ end
174
+ return true
175
+ end
176
+
177
+ # define methods to access tuple attributes by column name
178
+ public
139
179
  def tuple_accessors(tup)
140
- tup.extend @tupaccess
180
+ tup # XXX remove tuple_acessors everywhere.
141
181
  end
142
182
 
143
183
  # generate a tuple with the schema of this collection and nil values in each attribute
144
184
  public
145
185
  def null_tuple
146
- tuple_accessors(Array.new(@cols.length))
186
+ @struct.new
147
187
  end
148
188
 
149
189
  # project the collection to its key attributes
150
190
  public
151
191
  def keys
152
- self.map{|t| get_key_vals(t)}
192
+ self.pro{|t| @key_colnums.map {|i| t[i]}}
153
193
  end
154
194
 
155
195
  # project the collection to its non-key attributes
156
196
  public
157
197
  def values
158
- self.map{|t| (self.key_cols.length..self.cols.length-1).map{|i| t[i]}}
198
+ self.pro{|t| (self.key_cols.length..self.cols.length-1).map{|i| t[i]}}
159
199
  end
160
200
 
161
201
  # map each item in the collection into a string, suitable for placement in stdio
162
202
  public
163
203
  def inspected
164
- [["#{@tabname}: [#{self.map{|t| "\n (#{t.map{|v| v.inspect}.join ", "})"}}]"]]
204
+ self.pro{|t| [t.inspect]}
205
+ # how about when this is called outside wiring?
206
+ # [["#{@tabname}: [#{self.map{|t| "\n (#{t.map{|v| v.inspect}.join ", "})"}}]"]]
207
+ end
208
+
209
+ # projection
210
+ public
211
+ def pro(the_name = tabname, the_schema = schema, &blk)
212
+ pusher = to_push_elem(the_name, the_schema)
213
+ pusher_pro = pusher.pro(&blk)
214
+ pusher_pro.elem_name = the_name
215
+ pusher_pro.tabname = the_name
216
+ pusher_pro
165
217
  end
166
218
 
167
- # akin to map, but modified for efficiency in Bloom statements
168
219
  public
169
- def pro(&blk)
170
- if @bud_instance.stratum_first_iter
171
- return map(&blk)
220
+ def each_with_index(the_name = tabname, the_schema = schema, &blk)
221
+ toplevel = @bud_instance.toplevel
222
+ if not toplevel.done_wiring
223
+ proj = pro(the_name, the_schema)
224
+ elem = Bud::PushEachWithIndex.new('each_with_index' + object_id.to_s, toplevel.this_rule_context, tabname)
225
+ elem.set_block(&blk)
226
+ proj.wire_to(elem)
227
+ toplevel.push_elems[[self.object_id,:each,blk]] = elem
228
+ elem
172
229
  else
173
- retval = []
174
- each_from([@delta]) do |t|
175
- newitem = blk.call(t)
176
- retval << newitem unless newitem.nil?
230
+ storage.each_with_index
231
+ end
232
+ end
233
+
234
+
235
+ # ruby 1.9 defines flat_map to return "a new array with the concatenated results of running
236
+ # <em>block</em> once for every element". So we wire the input to a pro(&blk), and wire the output
237
+ # of that pro to a group that does accum.
238
+ public
239
+ def flat_map(&blk)
240
+ pusher = self.pro(&blk)
241
+ toplevel = @bud_instance.toplevel
242
+ elem = Bud::PushElement.new(tabname, toplevel.this_rule_context, tabname)
243
+ pusher.wire_to(elem)
244
+ f = Proc.new do |t|
245
+ t.each do |i|
246
+ elem.push_out(i,false)
177
247
  end
178
- return retval
248
+ nil
179
249
  end
250
+ elem.set_block(&f)
251
+ toplevel.push_elems[[self.object_id,:flatten]] = elem
252
+ return elem
253
+ end
254
+
255
+ public
256
+ def sort(&blk)
257
+ pusher = self.pro
258
+ pusher.sort(@name, @bud_instance, @cols, &blk)
180
259
  end
181
260
 
261
+ def rename(the_name, the_schema=nil)
262
+ # a scratch with this name should have been defined during rewriting
263
+ raise(Bud::Error, "rename failed to define a scratch named #{the_name}") unless @bud_instance.respond_to? the_name
264
+ retval = pro(the_name, the_schema)
265
+ #retval.init_schema(the_schema)
266
+ retval
267
+ end
268
+
269
+ # def to_enum
270
+ # pusher = self.pro
271
+ # pusher.to_enum
272
+ # end
273
+
182
274
  # By default, all tuples in any rhs are in storage or delta. Tuples in
183
275
  # new_delta will get transitioned to delta in the next iteration of the
184
276
  # evaluator (but within the current time tick).
@@ -187,6 +279,21 @@ module Bud
187
279
  each_from([@storage, @delta], &block)
188
280
  end
189
281
 
282
+ public
283
+ def each_raw(&block)
284
+ @storage.each_value(&block)
285
+ end
286
+
287
+ public
288
+ def invalidate_at_tick
289
+ true # being conservative here as a default.
290
+ end
291
+
292
+ public
293
+ def non_temporal_predecessors
294
+ @wired_by.map {|elem| elem if elem.outputs.include? self}
295
+ end
296
+
190
297
  public
191
298
  def tick_metrics
192
299
  strat_num = bud_instance.this_stratum
@@ -195,8 +302,8 @@ module Bud
195
302
  addr = bud_instance.ip_port unless bud_instance.port.nil?
196
303
  rule_txt = nil
197
304
  bud_instance.metrics[:collections] ||= {}
198
- bud_instance.metrics[:collections][{:addr=>addr, :tabname=>tabname, :strat_num=>strat_num, :rule_num=>rule_num}] ||= 0
199
- bud_instance.metrics[:collections][{:addr=>addr, :tabname=>tabname, :strat_num=>strat_num, :rule_num=>rule_num}] += 1
305
+ bud_instance.metrics[:collections][{:addr=>addr, :tabname=>qualified_tabname, :strat_num=>strat_num, :rule_num=>rule_num}] ||= 0
306
+ bud_instance.metrics[:collections][{:addr=>addr, :tabname=>qualified_tabname, :strat_num=>strat_num, :rule_num=>rule_num}] += 1
200
307
  end
201
308
 
202
309
  private
@@ -204,7 +311,7 @@ module Bud
204
311
  bufs.each do |b|
205
312
  b.each_value do |v|
206
313
  tick_metrics if bud_instance and bud_instance.options[:metrics]
207
- yield v
314
+ yield tuple_accessors(v)
208
315
  end
209
316
  end
210
317
  end
@@ -236,6 +343,7 @@ module Bud
236
343
  def init_deltas
237
344
  @delta = {}
238
345
  @new_delta = {}
346
+ @tick_delta = []
239
347
  end
240
348
 
241
349
  public
@@ -257,18 +365,26 @@ module Bud
257
365
  # is this enforced in do_insert?
258
366
  check_enumerable(k)
259
367
  t = @storage[k]
260
- return t.nil? ? @delta[k] : t
368
+ return t.nil? ? @delta[k] : tuple_accessors(t)
261
369
  end
262
370
 
263
371
  # checks for +item+ in the collection
264
372
  public
265
373
  def include?(item)
266
374
  return true if key_cols.nil? or (key_cols.empty? and length > 0)
267
- return false if item.nil? or item.empty?
375
+ return false if item.nil?
268
376
  key = get_key_vals(item)
269
377
  return (item == self[key])
270
378
  end
271
379
 
380
+ def length
381
+ @storage.length + @delta.length
382
+ end
383
+
384
+ def empty?
385
+ length == 0
386
+ end
387
+
272
388
  # checks for an item for which +block+ produces a match
273
389
  public
274
390
  def exists?(&block)
@@ -289,24 +405,20 @@ module Bud
289
405
 
290
406
  private
291
407
  def prep_tuple(o)
292
- unless o.respond_to?(:length) and o.respond_to?(:[])
293
- raise Bud::TypeError, "non-indexable type inserted into \"#{tabname}\": #{o.inspect}"
294
- end
295
- if o.class <= String
296
- raise Bud::TypeError, "String value used as a fact inserted into \"#{tabname}\": #{o.inspect}"
297
- end
298
-
299
- if o.length < cols.length then
300
- # if this tuple has too few fields, pad with nil's
301
- old = o.clone
302
- (o.length..cols.length-1).each{|i| o << nil}
303
- # puts "in #{@tabname}, converted #{old.inspect} to #{o.inspect}"
304
- elsif o.length > cols.length then
305
- # if this tuple has more fields than usual, bundle up the
306
- # extras into an array
307
- o = (0..(cols.length - 1)).map{|c| o[c]} << (cols.length..(o.length - 1)).map{|c| o[c]}
408
+ return o if o.class == @struct
409
+ if o.class == Array
410
+ if @struct.nil?
411
+ sch = (1 .. o.length).map{|i| ("c"+i.to_s).to_sym}
412
+ init_schema(sch)
413
+ end
414
+ o = o.take(@structlen) if o.length > @structlen
415
+ elsif o.kind_of? Struct
416
+ init_schema(o.members.map{|m| m.to_sym}) if @struct.nil?
417
+ o = o.take(@structlen)
418
+ else
419
+ raise TypeError, "Array or struct type expected in \"#{qualified_tabname}\": #{o.inspect}"
308
420
  end
309
- return o
421
+ return @struct.new(*o)
310
422
  end
311
423
 
312
424
  private
@@ -316,8 +428,17 @@ module Bud
316
428
  end
317
429
  end
318
430
 
319
- private
431
+ public
320
432
  def do_insert(o, store)
433
+ if $BUD_DEBUG
434
+ storetype = case store.object_id
435
+ when @storage.object_id; "storage"
436
+ when @pending.object_id; "pending"
437
+ when @delta.object_id; "delta"
438
+ when @new_delta.object_id; "new_delta"
439
+ end
440
+ puts "#{qualified_tabname}.#{storetype} ==> #{o}"
441
+ end
321
442
  return if o.nil? # silently ignore nils resulting from map predicates failing
322
443
  o = prep_tuple(o)
323
444
  key = get_key_vals(o)
@@ -331,8 +452,8 @@ module Bud
331
452
  end
332
453
 
333
454
  public
334
- def insert(o) # :nodoc: all
335
- # puts "insert: #{o.inspect} into #{tabname}"
455
+ def insert(o, source=nil) # :nodoc: all
456
+ # puts "insert: #{o} into #{qualified_tabname}"
336
457
  do_insert(o, @storage)
337
458
  end
338
459
 
@@ -343,8 +464,8 @@ module Bud
343
464
 
344
465
  private
345
466
  def check_enumerable(o)
346
- unless o.nil? or o.class < Enumerable
347
- raise Bud::TypeError, "collection #{tabname} expected Enumerable value, not #{o.inspect} (class = #{o.class})"
467
+ unless o.nil? or o.class < Enumerable or o.class <= Proc
468
+ raise TypeError, "Collection #{qualified_tabname} expected Enumerable value, not #{o.inspect} (class = #{o.class})"
348
469
  end
349
470
  end
350
471
 
@@ -397,22 +518,47 @@ module Bud
397
518
  end
398
519
 
399
520
  public
400
- def merge(o, buf=@new_delta) # :nodoc: all
401
- unless o.nil?
402
- check_enumerable(o)
403
- establish_schema(o) if @cols.nil?
404
-
405
- # it's a pity that we are massaging tuples that may be dups
406
- o.each do |t|
407
- next if t.nil? or t == []
408
- t = prep_tuple(t)
409
- key = get_key_vals(t)
410
- buf[key] = tuple_accessors(t) unless include_any_buf?(t, key)
521
+ def merge(o, buf=@delta) # :nodoc: all
522
+ toplevel = @bud_instance.toplevel
523
+ if o.class <= Bud::PushElement
524
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
525
+ deduce_schema(o) if @cols.nil?
526
+ o.wire_to self
527
+ elsif o.class <= Bud::BudCollection
528
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
529
+ deduce_schema(o) if @cols.nil?
530
+ o.pro.wire_to self
531
+ elsif o.class <= Proc and toplevel.done_bootstrap and not toplevel.done_wiring and not o.nil?
532
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
533
+ tbl = register_coll_expr(o)
534
+ tbl.pro.wire_to self
535
+ else
536
+ unless o.nil?
537
+ o = o.uniq.compact if o.respond_to?(:uniq)
538
+ check_enumerable(o)
539
+ establish_schema(o) if @cols.nil?
540
+ o.each {|i| do_insert(i, buf)}
411
541
  end
412
542
  end
413
543
  return self
414
544
  end
415
545
 
546
+ # def prep_coll_expr(o)
547
+ # o = o.uniq.compact if o.respond_to?(:uniq)
548
+ # check_enumerable(o)
549
+ # establish_schema(o) if @cols.nil?
550
+ # o
551
+ # end
552
+
553
+ def register_coll_expr(expr)
554
+ # require 'ruby-debug'; debugger
555
+ coll_name = ("expr_"+expr.object_id.to_s)
556
+ cols = (1..@cols.length).map{|i| ("c"+i.to_s).to_sym} unless @cols.nil?
557
+ @bud_instance.coll_expr(coll_name.to_sym, expr, cols)
558
+ coll = @bud_instance.send(coll_name)
559
+ coll
560
+ end
561
+
416
562
  public
417
563
  # instantaneously merge items from collection +o+ into +buf+
418
564
  def <=(collection)
@@ -422,60 +568,121 @@ module Bud
422
568
  # buffer items to be merged atomically at end of this timestep
423
569
  public
424
570
  def pending_merge(o) # :nodoc: all
425
- check_enumerable(o)
426
- establish_schema(o) if @cols.nil?
427
-
428
- o.each {|i| do_insert(i, @pending)}
571
+ toplevel = @bud_instance.toplevel
572
+ if o.class <= Bud::PushElement
573
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
574
+ o.wire_to_pending self
575
+ elsif o.class <= Bud::BudCollection
576
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
577
+ o.pro.wire_to_pending self
578
+ elsif o.class <= Proc and toplevel.done_bootstrap and not toplevel.done_wiring
579
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
580
+ tbl = register_coll_expr(o) unless o.nil?
581
+ tbl.pro.wire_to_pending self
582
+ else
583
+ unless o.nil?
584
+ o = o.uniq.compact if o.respond_to?(:uniq)
585
+ check_enumerable(o)
586
+ establish_schema(o) if @cols.nil?
587
+ o.each{|i| self.do_insert(i, @pending)}
588
+ end
589
+ end
429
590
  return self
430
591
  end
431
592
 
593
+ public
594
+ def flush ; end
595
+
432
596
  public
433
597
  superator "<+" do |o|
434
598
  pending_merge o
435
599
  end
436
600
 
601
+ def tick
602
+ raise "tick must be overriden in #{self.class}"
603
+ end
604
+
605
+ # move deltas to storage, and new_deltas to deltas.
606
+ # return true if new deltas were found
437
607
  public
438
- superator "<+-" do |o|
439
- self <+ o
440
- self <- o.map do |t|
441
- unless t.nil?
442
- self[get_key_vals(t)]
608
+ def tick_deltas # :nodoc: all
609
+ unless @delta.empty?
610
+ puts "#{qualified_tabname}.tick_delta delta --> storage (#{@delta.size} elems)" if $BUD_DEBUG
611
+ @storage.merge!(@delta)
612
+ @tick_delta += @delta.values
613
+ @delta.clear
614
+ end
615
+
616
+ unless @new_delta.empty?
617
+ puts "#{qualified_tabname}.tick_delta new_delta --> delta (#{@new_delta.size} elems)" if $BUD_DEBUG
618
+ @new_delta.each_pair do |k, v|
619
+ sv = @storage[k]
620
+ if sv.nil?
621
+ @delta[k] = v
622
+ else
623
+ raise_pk_error(v, sv) unless v == sv
624
+ end
443
625
  end
626
+ @new_delta.clear
627
+ return !(@delta.empty?)
444
628
  end
629
+ return false # delta empty; another fixpoint iter not required.
445
630
  end
446
631
 
447
632
  public
448
- superator "<-+" do |o|
449
- self <+- o
633
+ def add_rescan_invalidate(rescan, invalidate)
634
+ # No change. Most collections don't need to rescan on every tick (only do so on negate). Also, there's no cache
635
+ # to invalidate by default. Scratches and PushElements override this method.
450
636
  end
451
637
 
452
- # Called at the end of each timestep: prepare the collection for the next
453
- # timestep.
454
- public
455
- def tick # :nodoc: all
456
- @storage = @pending
457
- @pending = {}
458
- raise Bud::Error, "orphaned tuples in @delta for #{@tabname}" unless @delta.empty?
459
- raise Bud::Error, "orphaned tuples in @new_delta for #{@tabname}" unless @new_delta.empty?
638
+ def bootstrap
639
+ unless @pending.empty?
640
+ @delta = @pending
641
+ @pending = {}
642
+ end
460
643
  end
461
644
 
462
- # move deltas to storage, and new_deltas to deltas.
463
645
  public
464
- def tick_deltas # :nodoc: all
465
- # assertion: intersect(@storage, @delta) == nil
466
- @storage.merge!(@delta)
467
- @delta = @new_delta
468
- @new_delta = {}
646
+ def flush_deltas
647
+ if $BUD_DEBUG
648
+ puts "#{qualified_tabname}.flush delta --> storage" unless @delta.empty?
649
+ puts "#{qualified_tabname}.flush new_delta --> storage" unless @new_delta.empty?
650
+ end
651
+ unless (@delta.empty?)
652
+ @storage.merge!(@delta)
653
+ @tick_delta += @delta.values
654
+ @delta.clear
655
+ end
656
+ unless @new_delta.empty?
657
+ @storage.merge!(@new_delta)
658
+ @new_delta.clear
659
+ end
660
+ # @tick_delta kept around for higher strata.
469
661
  end
470
662
 
471
663
  public
472
- def length
473
- @storage.length
664
+ def to_push_elem(the_name=tabname, the_schema=schema)
665
+ # if no push source yet, set one up
666
+ toplevel = @bud_instance.toplevel
667
+ #rule_context = toplevel.this_rule_context
668
+ this_stratum = toplevel.this_stratum
669
+ oid = self.object_id
670
+ unless toplevel.scanners[this_stratum][[oid, the_name]]
671
+ toplevel.scanners[this_stratum][[oid, the_name]] = Bud::ScannerElement.new(the_name, self.bud_instance, self, the_schema)
672
+ toplevel.push_sources[this_stratum][[oid, the_name]] = toplevel.scanners[this_stratum][[oid, the_name]]
673
+ end
674
+ return toplevel.scanners[this_stratum][[oid, the_name]]
474
675
  end
475
676
 
476
- public
477
- def empty?
478
- @storage.empty?
677
+ private
678
+ def method_missing(sym, *args, &block)
679
+ begin
680
+ @storage.send sym, *args, &block
681
+ rescue Exception => e
682
+ err = NoMethodError.new("no method :#{sym} in class #{self.class.name}")
683
+ err.set_backtrace(e.backtrace)
684
+ raise err
685
+ end
479
686
  end
480
687
 
481
688
  ######## aggs
@@ -485,73 +692,26 @@ module Bud
485
692
  # never deal with deltas. This assumes that stratification is done right, and it will
486
693
  # be sensitive to bugs in the stratification!
487
694
  def agg_in
488
- if not respond_to?(:bud_instance) or bud_instance.nil? or bud_instance.stratum_first_iter
695
+ if not respond_to?(:bud_instance) or bud_instance.nil?
489
696
  return self
490
697
  else
491
698
  return []
492
699
  end
493
700
  end
494
701
 
702
+
495
703
  # a generalization of argmin/argmax to arbitrary exemplary aggregates.
496
704
  # for each distinct value of the grouping key columns, return the items in that group
497
705
  # that have the value of the exemplary aggregate +aggname+
498
706
  public
499
707
  def argagg(aggname, gbkey_cols, collection)
500
- agg = bud_instance.send(aggname, nil)[0]
501
- raise Bud::Error, "#{aggname} not declared exemplary" unless agg.class <= Bud::ArgExemplary
502
- keynames = gbkey_cols.map do |k|
503
- if k.class == Symbol
504
- k.to_s
505
- else
506
- k[2]
507
- end
508
- end
509
- if collection.class == Symbol
510
- colnum = self.send(collection.to_s)[1]
511
- else
512
- colnum = collection[1]
513
- end
514
- tups = agg_in.inject({}) do |memo,p|
515
- pkey_cols = keynames.map{|n| p.send(n.to_sym)}
516
- if memo[pkey_cols].nil?
517
- memo[pkey_cols] = {:agg=>agg.send(:init, p[colnum]), :tups => [p]}
518
- else
519
- memo[pkey_cols][:agg], argflag = \
520
- agg.send(:trans, memo[pkey_cols][:agg], p[colnum])
521
- if argflag == :keep or agg.send(:tie, memo[pkey_cols][:agg], p[colnum])
522
- memo[pkey_cols][:tups] << p
523
- elsif argflag == :replace
524
- memo[pkey_cols][:tups] = [p]
525
- elsif argflag.class <= Array and argflag[0] == :delete
526
- memo[pkey_cols][:tups] -= argflag[1..-1]
527
- end
528
- end
529
- memo
530
- end
531
-
532
- # now we need to finalize the agg per group
533
- finalaggs = {}
534
- finals = []
535
- tups.each do |k,v|
536
- finalaggs[k] = agg.send(:final, v[:agg])
537
- end
538
-
539
- # and winnow the tups to match
540
- finalaggs.each do |k,v|
541
- tups[k][:tups].each do |t|
542
- finals << t if (t[colnum] == v)
543
- end
544
- end
545
-
546
- if block_given?
547
- finals.map{|r| yield r}
548
- else
549
- # merge directly into retval.storage, so that the temp tuples get picked up
550
- # by the lhs of the rule
551
- retval = BudScratch.new('argagg_temp', bud_instance, @given_schema)
552
- retval.uniquify_tabname
553
- retval.merge(finals, retval.storage)
554
- end
708
+ elem = to_push_elem
709
+ elem.schema
710
+ gbkey_cols = gbkey_cols.map{|k| canonicalize_col(k)} unless gbkey_cols.nil?
711
+ retval = elem.argagg(aggname,gbkey_cols,canonicalize_col(collection))
712
+ # PushElement inherits the schema accessors from this Collection
713
+ retval.extend @cols_access
714
+ retval
555
715
  end
556
716
 
557
717
  # for each distinct value of the grouping key columns, return the items in
@@ -579,92 +739,54 @@ module Bud
579
739
  end
580
740
  end
581
741
 
582
- def join(collections, *preds, &blk)
583
- # since joins are stateful, we want to allocate them once and store in this Bud instance
584
- # we ID them on their tablenames, preds, and block
585
- return wrap_map(BudJoin.new(collections, @bud_instance, preds), &blk)
586
- end
742
+ # def join(collections, *preds, &blk)
743
+ # # since joins are stateful, we want to allocate them once and store in this Bud instance
744
+ # # we ID them on their tablenames, preds, and block
745
+ # return wrap_map(BudJoin.new(collections, @bud_instance, preds), &blk)
746
+ # end
587
747
 
588
748
  # form a collection containing all pairs of items in +self+ and items in
589
749
  # +collection+
590
750
  public
591
751
  def *(collection)
592
- join([self, collection])
752
+ elem1 = to_push_elem
753
+ j = elem1.join(collection)
754
+ return j
755
+ # join([self, collection])
593
756
  end
594
757
 
595
- # AntiJoin
596
- public
597
- def notin(coll, *preds, &blk)
598
- return BudJoin.new([self, coll], @bud_instance).anti(*preds, &blk)
758
+ def group(key_cols, *aggpairs, &blk)
759
+ elem = to_push_elem
760
+ key_cols = key_cols.map{|k| canonicalize_col(k)} unless key_cols.nil?
761
+ aggpairs = aggpairs.map{|ap| [ap[0], canonicalize_col(ap[1])].compact} unless aggpairs.nil?
762
+ g = elem.group(key_cols, *aggpairs, &blk)
763
+ return g
599
764
  end
600
765
 
601
- # SQL-style grouping. first argument is an array of attributes to group by.
602
- # Followed by a variable-length list of aggregates over attributes (e.g. +min(:x)+)
603
- # Attributes can be referenced as symbols, or as +collection_name.attribute_name+
604
- public
605
- def group(key_cols, *aggpairs)
606
- key_cols ||= []
607
- keynames = key_cols.map do |k|
608
- if k.class == Symbol
609
- k
610
- elsif k[2] and k[2].class == Symbol
611
- k[2]
612
- else
613
- raise Bud::CompileError, "invalid grouping key"
614
- end
615
- end
616
- aggcolsdups = aggpairs.map{|ap| ap[0].class.name.split("::").last}
617
- aggcols = []
618
- aggcolsdups.each_with_index do |n, i|
619
- aggcols << "#{n.downcase}_#{i}".to_sym
620
- end
621
- aggpairs = aggpairs.map do |ap|
622
- if ap[1].class == Symbol
623
- colnum = ap[1].nil? ? nil : self.send(ap[1].to_s)[1]
624
- else
625
- colnum = ap[1].nil? ? nil : ap[1][1]
626
- end
627
- [ap[0], colnum]
628
- end
629
- tups = agg_in.inject({}) do |memo, p|
630
- pkey_cols = keynames.map{|n| p.send(n)}
631
- memo[pkey_cols] = [] if memo[pkey_cols].nil?
632
- aggpairs.each_with_index do |ap, i|
633
- agg = ap[0]
634
- colval = ap[1].nil? ? nil : p[ap[1]]
635
- if memo[pkey_cols][i].nil?
636
- memo[pkey_cols][i] = agg.send(:init, colval)
637
- else
638
- memo[pkey_cols][i], ignore = agg.send(:trans, memo[pkey_cols][i], colval)
639
- end
640
- end
641
- memo
642
- end
766
+ def notin(collection, *preds, &blk)
767
+ elem1 = to_push_elem
768
+ elem2 = collection.to_push_elem
769
+ return elem1.notin(elem2, preds, &blk)
770
+ end
643
771
 
644
- result = tups.inject([]) do |memo, t|
645
- finals = []
646
- aggpairs.each_with_index do |ap, i|
647
- finals << ap[0].send(:final, t[1][i])
648
- end
649
- memo << t[0] + finals
650
- end
651
- if block_given?
652
- result.map{|r| yield r}
653
- else
654
- # merge directly into retval.storage, so that the temp tuples get picked up
655
- # by the lhs of the rule
656
- if aggcols.empty?
657
- schema = keynames
658
- else
659
- schema = { keynames => aggcols }
660
- end
661
- retval = BudScratch.new('temp_group', bud_instance, schema)
662
- retval.uniquify_tabname
663
- retval.merge(result, retval.storage)
664
- end
772
+ def canonicalize_col(col)
773
+ col.class <= Symbol ? self.send(col) : col
665
774
  end
666
775
 
667
- alias reduce inject
776
+ # alias reduce inject
777
+ def reduce(initial, &blk)
778
+ elem1 = to_push_elem
779
+ red_elem = elem1.reduce(initial, &blk)
780
+ return red_elem
781
+ end
782
+
783
+ public
784
+ def pretty_print_instance_variables
785
+ # list of attributes (in order) to print when pretty_print is called.
786
+ important = ["@tabname", "@storage", "@delta", "@new_delta", "@pending"]
787
+ # everything except bud_instance
788
+ important + (self.instance_variables - important - ["@bud_instance"])
789
+ end
668
790
 
669
791
  public
670
792
  def uniquify_tabname # :nodoc: all
@@ -674,11 +796,56 @@ module Bud
674
796
  end
675
797
 
676
798
  class BudScratch < BudCollection # :nodoc: all
799
+ public
800
+ def tick # :nodoc: all
801
+ @tick_delta.clear
802
+ @delta.clear
803
+ if not @pending.empty?
804
+ invalidate_cache
805
+ @delta = @pending
806
+ @pending = {}
807
+ elsif is_source
808
+ invalidate_cache
809
+ end
810
+ raise Bud::Error, "orphaned tuples in @new_delta for #{qualified_tabname}" unless @new_delta.empty?
811
+ end
812
+
813
+ public
814
+ def invalidate_at_tick
815
+ is_source # rescan always only if this scratch is a source.
816
+ end
817
+
818
+
819
+ public
820
+ def add_rescan_invalidate(rescan, invalidate)
821
+ srcs = non_temporal_predecessors
822
+ if srcs.any? {|e| rescan.member? e}
823
+ invalidate << self
824
+ srcs.each{|e| rescan << e}
825
+ end
826
+ end
827
+
828
+ public
829
+ def invalidate_cache
830
+ puts "#{qualified_tabname} invalidated" if $BUD_DEBUG
831
+ #for scratches, storage is a cached value.
832
+ @invalidated = true
833
+ @storage.clear
834
+ end
835
+ end
836
+
837
+ class BudInputInterface < BudScratch
677
838
  end
678
839
 
679
- class BudTemp < BudCollection # :nodoc: all
840
+ class BudOutputInterface < BudScratch
680
841
  end
681
842
 
843
+ class BudTemp < BudScratch # :nodoc: all
844
+ end
845
+
846
+ # Channels are a different type of collection in that they represent two distinct collections, one each for
847
+ # incoming and outgoing. The incoming side makes use of @storage and @delta, whereas the outgoing side only deals
848
+ # with @pending. XXX Maybe we should be using aliases instead.
682
849
  class BudChannel < BudCollection
683
850
  attr_reader :locspec_idx # :nodoc: all
684
851
 
@@ -694,7 +861,7 @@ module Bud
694
861
  given_schema = Marshal.load(Marshal.dump(given_schema))
695
862
 
696
863
  unless @is_loopback
697
- the_cols, the_key_cols = parse_schema(given_schema)
864
+ the_cols, the_key_cols = BudCollection.parse_schema(given_schema)
698
865
  spec_count = the_cols.count {|c| c.to_s.start_with? "@"}
699
866
  if spec_count == 0
700
867
  raise Bud::Error, "missing location specifier for channel '#{name}'"
@@ -720,6 +887,11 @@ module Bud
720
887
  super(name, bud_instance, given_schema)
721
888
  end
722
889
 
890
+ def bootstrap
891
+ # override BudCollection; pending should not be moved into delta.
892
+ end
893
+
894
+
723
895
  private
724
896
  def remove_at_sign!(cols)
725
897
  i = cols.find_index {|c| c.to_s.start_with? "@"}
@@ -736,7 +908,7 @@ module Bud
736
908
  lsplit[1] = lsplit[1].to_i
737
909
  return lsplit
738
910
  rescue Exception => e
739
- raise Bud::Error, "illegal location specifier in tuple #{t.inspect} for channel \"#{tabname}\": #{e.to_s}"
911
+ raise Bud::Error, "Illegal location specifier in tuple #{t.inspect} for channel \"#{qualified_tabname}\": #{e.to_s}"
740
912
  end
741
913
  end
742
914
 
@@ -747,24 +919,31 @@ module Bud
747
919
 
748
920
  public
749
921
  def tick # :nodoc: all
750
- @storage = {}
922
+ @storage.clear
923
+ @invalidated = true
751
924
  # Note that we do not clear @pending here: if the user inserted into the
752
925
  # channel manually (e.g., via <~ from inside a sync_do block), we send the
753
926
  # message at the end of the current tick.
754
927
  end
755
928
 
929
+ public
930
+ def invalidate_cache
931
+ end
932
+
756
933
  public
757
934
  def flush # :nodoc: all
758
- ip = @bud_instance.ip
759
- port = @bud_instance.port
760
- each_from([@pending]) do |t|
935
+ toplevel = @bud_instance.toplevel
936
+ ip = toplevel.ip
937
+ port = toplevel.port
938
+ @pending.each_value do |t|
761
939
  if @is_loopback
762
940
  the_locspec = [ip, port]
763
941
  else
764
942
  the_locspec = split_locspec(t, @locspec_idx)
765
943
  raise Bud::Error, "'#{t[@locspec_idx]}', channel '#{@tabname}'" if the_locspec[0].nil? or the_locspec[1].nil? or the_locspec[0] == '' or the_locspec[1] == ''
766
944
  end
767
- @bud_instance.dsock.send_datagram([@tabname, t].to_msgpack, the_locspec[0], the_locspec[1])
945
+ puts "channel #{qualified_tabname}.send: #{t}" if $BUD_DEBUG
946
+ toplevel.dsock.send_datagram([qualified_tabname.to_s, t].to_msgpack, the_locspec[0], the_locspec[1])
768
947
  end
769
948
  @pending.clear
770
949
  end
@@ -777,9 +956,9 @@ module Bud
777
956
  if cols.size > 2
778
957
  # bundle up each tuple's non-locspec fields into an array
779
958
  retval = case @locspec_idx
780
- when 0 then self.pro{|t| t[1..(t.size-1)]}
781
- when (cols.size - 1) then self.pro{|t| t[0..(t.size-2)]}
782
- else self.pro{|t| t[0..(@locspec_idx-1)] + t[@locspec_idx+1..(t.size-1)]}
959
+ when 0 then self.pro{|t| t.values_at(1..(t.size-1))}
960
+ when (schema.size - 1) then self.pro{|t| t.values_at(0..(t.size-2))}
961
+ else self.pro{|t| t.values_at(0..(@locspec_idx-1), @locspec_idx+1..(t.size-1))}
783
962
  end
784
963
  else
785
964
  # just return each tuple's non-locspec field value
@@ -789,7 +968,11 @@ module Bud
789
968
  end
790
969
 
791
970
  superator "<~" do |o|
792
- pending_merge o
971
+ if o.class <= PushElement
972
+ o.wire_to_pending self
973
+ else
974
+ pending_merge(o)
975
+ end
793
976
  end
794
977
 
795
978
  superator "<+" do |o|
@@ -803,7 +986,7 @@ module Bud
803
986
  end
804
987
  end
805
988
 
806
- class BudTerminal < BudCollection # :nodoc: all
989
+ class BudTerminal < BudScratch # :nodoc: all
807
990
  def initialize(name, given_schema, bud_instance, prompt=false) # :nodoc: all
808
991
  super(name, bud_instance, given_schema)
809
992
  @prompt = prompt
@@ -815,18 +998,19 @@ module Bud
815
998
  # we should add the terminal file descriptor to the EM event loop.
816
999
  @reader = Thread.new do
817
1000
  begin
1001
+ toplevel = @bud_instance.toplevel
818
1002
  while true
819
1003
  out_io = get_out_io
820
1004
  out_io.print("#{tabname} > ") if @prompt
821
1005
 
822
- in_io = @bud_instance.options[:stdin]
1006
+ in_io = toplevel.options[:stdin]
823
1007
  s = in_io.gets
824
1008
  break if s.nil? # Hit EOF
825
1009
  s = s.chomp if s
826
1010
  tup = [s]
827
1011
 
828
- ip = @bud_instance.ip
829
- port = @bud_instance.port
1012
+ ip = toplevel.ip
1013
+ port = toplevel.port
830
1014
  EventMachine::schedule do
831
1015
  socket = EventMachine::open_datagram_socket("127.0.0.1", 0)
832
1016
  socket.send_datagram([tabname, tup].to_msgpack, ip, port)
@@ -843,19 +1027,37 @@ module Bud
843
1027
  public
844
1028
  def flush #:nodoc: all
845
1029
  out_io = get_out_io
846
- @pending.each do |p|
1030
+ @pending.each_value do |p|
847
1031
  out_io.puts p[0]
848
1032
  out_io.flush
849
1033
  end
850
- @pending = {}
1034
+ @pending.clear
1035
+ end
1036
+
1037
+ public
1038
+ def invalidate_at_tick
1039
+ true
851
1040
  end
852
1041
 
853
1042
  public
854
1043
  def tick #:nodoc: all
855
- @storage = {}
1044
+ unless @pending.empty?
1045
+ @delta = @pending # pending used for input tuples in this case.
1046
+ @tick_delta = @pending.values
1047
+ @pending.clear
1048
+ else
1049
+ @storage.clear
1050
+ @delta.clear
1051
+ @tick_delta.clear
1052
+ end
1053
+ @invalidated = true # channels and terminals are always invalidated.
856
1054
  raise Bud::Error, "orphaned pending tuples in terminal" unless @pending.empty?
857
1055
  end
858
1056
 
1057
+ public
1058
+ def invalidate_cache
1059
+ end
1060
+
859
1061
  undef merge
860
1062
 
861
1063
  public
@@ -864,19 +1066,23 @@ module Bud
864
1066
  end
865
1067
 
866
1068
  superator "<~" do |o|
867
- pending_merge(o)
1069
+ if o.class <= PushElement
1070
+ o.wire_to_pending self
1071
+ else
1072
+ pending_merge(o)
1073
+ end
868
1074
  end
869
1075
 
870
1076
  private
871
1077
  def get_out_io
872
- rv = @bud_instance.options[:stdout]
1078
+ rv = @bud_instance.toplevel.options[:stdout]
873
1079
  rv ||= $stdout
874
1080
  raise Bud::Error, "attempting to write to terminal #{tabname} that was already closed" if rv.closed?
875
1081
  rv
876
1082
  end
877
1083
  end
878
1084
 
879
- class BudPeriodic < BudCollection # :nodoc: all
1085
+ class BudPeriodic < BudScratch # :nodoc: all
880
1086
  def <=(o)
881
1087
  raise Bud::Error, "illegal use of <= with periodic '#{tabname}' on left"
882
1088
  end
@@ -892,43 +1098,142 @@ module Bud
892
1098
  superator "<+" do |o|
893
1099
  raise Bud::Error, "illegal use of <+ with periodic '#{tabname}' on left"
894
1100
  end
1101
+
1102
+ def tick
1103
+ @tick_delta.clear
1104
+ @delta.clear
1105
+ @invalidated = true
1106
+ unless pending.empty?
1107
+ @delta = @pending
1108
+ @pending = {}
1109
+ end
1110
+ end
1111
+ end
1112
+
1113
+ class BudPersistentCollection < BudCollection
1114
+ public
1115
+ def invalidate_at_tick
1116
+ false # rescan required only when negated.
1117
+ end
1118
+
1119
+ public
1120
+ def invalidate_cache
1121
+ raise "Abstract method not implemented by derived class #{self.class}"
1122
+ end
895
1123
  end
896
1124
 
897
- class BudTable < BudCollection # :nodoc: all
1125
+ class BudTable < BudPersistentCollection # :nodoc: all
898
1126
  def initialize(name, bud_instance, given_schema) # :nodoc: all
899
1127
  super(name, bud_instance, given_schema)
900
1128
  @to_delete = []
1129
+ @to_delete_by_key = []
901
1130
  end
902
1131
 
903
1132
  public
904
1133
  def tick #:nodoc: all
1134
+ if $BUD_DEBUG
1135
+ puts "#{tabname}. storage -= pending deletes" unless @to_delete.empty? and @to_delete_by_key.empty?
1136
+ puts "#{tabname}. delta += pending" unless @pending.empty?
1137
+ end
1138
+ @tick_delta.clear
1139
+ deleted = nil
905
1140
  @to_delete.each do |tuple|
906
- key = get_key_vals(tuple)
907
- if @storage[key] == tuple
908
- @storage.delete key
1141
+ keycols = @key_colnums.map{|k| tuple[k]}
1142
+ if @storage[keycols] == tuple
1143
+ v = @storage.delete keycols
1144
+ deleted ||= v
909
1145
  end
910
1146
  end
911
- @pending.each do |key, tuple|
912
- old = @storage[key]
1147
+ @to_delete_by_key.each do |tuple|
1148
+ v = @storage.delete @key_colnums.map{|k| tuple[k]}
1149
+ deleted ||= v
1150
+ end
1151
+
1152
+ @invalidated = (not deleted.nil?)
1153
+ puts "table #{qualified_tabname} invalidated" if $BUD_DEBUG and @invalidated
1154
+
1155
+ @pending.each do |keycols, tuple|
1156
+ old = @storage[keycols]
913
1157
  if old.nil?
914
- @storage[key] = tuple
1158
+ @delta[keycols] = tuple #
915
1159
  else
916
1160
  raise_pk_error(tuple, old) unless tuple == old
917
1161
  end
918
1162
  end
919
1163
  @to_delete = []
1164
+ @to_delete_by_key = []
920
1165
  @pending = {}
921
1166
  end
922
1167
 
1168
+ def invalidated=(val)
1169
+ raise "Internal error: nust not set invalidate on tables"
1170
+ end
1171
+
1172
+ def pending_delete(o)
1173
+ toplevel = @bud_instance.toplevel
1174
+ if o.class <= Bud::PushElement
1175
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
1176
+ o.wire_to_delete self
1177
+ elsif o.class <= Bud::BudCollection
1178
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
1179
+ o.pro.wire_to_delete self
1180
+ elsif o.class <= Proc and @bud_instance.toplevel.done_bootstrap and not toplevel.done_wiring
1181
+ toplevel.merge_targets[toplevel.this_stratum][self] = true if toplevel.done_bootstrap
1182
+ tbl = register_coll_expr(o)
1183
+ tbl.pro.wire_to_delete self
1184
+ else
1185
+ unless o.nil?
1186
+ o = o.uniq.compact if o.respond_to?(:uniq)
1187
+ check_enumerable(o)
1188
+ establish_schema(o) if @cols.nil?
1189
+ o.each{|i| @to_delete << prep_tuple(i)}
1190
+ end
1191
+ end
1192
+ end
923
1193
  superator "<-" do |o|
924
- o.each do |t|
925
- next if t.nil?
926
- @to_delete << prep_tuple(t)
1194
+ pending_delete(o)
1195
+ end
1196
+
1197
+ public
1198
+ def pending_delete_keys(o)
1199
+ toplevel = @bud_instance.toplevel
1200
+ if o.class <= Bud::PushElement
1201
+ o.wire_to_delete_by_key self
1202
+ elsif o.class <= Bud::BudCollection
1203
+ o.pro.wire_to_delete_by_key self
1204
+ elsif o.class <= Proc and @bud_instance.toplevel.done_bootstrap and not @bud_instance.toplevel.done_wiring
1205
+ tbl = register_coll_expr(o)
1206
+ tbl.pro.wire_to_delete_by_key self
1207
+ else
1208
+ unless o.nil?
1209
+ o = o.uniq.compact if o.respond_to?(:uniq)
1210
+ check_enumerable(o)
1211
+ establish_schema(o) if @cols.nil?
1212
+ o.each{|i| @to_delete_by_key << prep_tuple(i)}
1213
+ end
927
1214
  end
1215
+ o
1216
+ end
1217
+
1218
+ public
1219
+ def invalidate_cache
1220
+ # no cache to invalidate. Also, tables do not invalidate dependents, because their own state is not considered
1221
+ # invalidated; that happens only if there were pending deletes at the beginning of a tick (see tick())
1222
+ puts "******** invalidate_cache called on BudTable"
1223
+ end
1224
+
1225
+ public
1226
+ superator "<+-" do |o|
1227
+ pending_delete_keys(o)
1228
+ self <+ o
1229
+ end
1230
+ public
1231
+ superator "<-+" do |o|
1232
+ self <+- o
928
1233
  end
929
1234
  end
930
1235
 
931
- class BudReadOnly < BudScratch # :nodoc: all
1236
+ class BudReadOnly < BudCollection # :nodoc: all
932
1237
  superator "<+" do |o|
933
1238
  raise CompileError, "illegal use of <+ with read-only collection '#{@tabname}' on left"
934
1239
  end
@@ -936,6 +1241,50 @@ module Bud
936
1241
  def merge(o) #:nodoc: all
937
1242
  raise CompileError, "illegal use of <= with read-only collection '#{@tabname}' on left"
938
1243
  end
1244
+ public
1245
+ def invalidate_cache
1246
+ end
1247
+
1248
+ public
1249
+ def invalidate_at_tick
1250
+ true
1251
+ end
1252
+ end
1253
+
1254
+ class BudSignal < BudReadOnly
1255
+ def invalidate_at_tick
1256
+ true
1257
+ end
1258
+ def tick
1259
+ @invalidated = true
1260
+ @storage.clear
1261
+ unless @pending.empty?
1262
+ @delta = @pending
1263
+ @pending = {}
1264
+ end
1265
+ end
1266
+ end
1267
+
1268
+ class BudCollExpr < BudReadOnly # :nodoc: all
1269
+ def initialize(name, bud_instance, expr, given_schema=nil, defer_schema=false)
1270
+ super(name, bud_instance, given_schema, defer_schema)
1271
+ @expr = expr
1272
+ @invalidated = true
1273
+ end
1274
+
1275
+ def tick
1276
+ @invalidated = true
1277
+ end
1278
+
1279
+ public
1280
+ def each(&block)
1281
+ @expr.call.each {|i| yield i}
1282
+ end
1283
+
1284
+ public
1285
+ def each_raw(&block)
1286
+ each(&block)
1287
+ end
939
1288
  end
940
1289
 
941
1290
  class BudFileReader < BudReadOnly # :nodoc: all
@@ -949,22 +1298,36 @@ module Bud
949
1298
  end
950
1299
 
951
1300
  public
952
- def pro(&blk)
953
- if @bud_instance.stratum_first_iter
954
- return map(&blk)
955
- else
956
- return []
957
- end
958
- end
959
-
960
- public
961
- def each(&block) # :nodoc: all
1301
+ def each_raw(&block) # :nodoc: all
962
1302
  while (l = @fd.gets)
963
- t = tuple_accessors([@linenum, l.strip])
1303
+ t = [@linenum, l.strip]
964
1304
  @linenum += 1
965
1305
  tick_metrics if bud_instance.options[:metrics]
966
1306
  yield t
967
1307
  end
968
1308
  end
1309
+
1310
+ public
1311
+ def each(&blk)
1312
+ each_raw {|l| tuple_accessors(blk.call(l))}
1313
+ end
1314
+ end
1315
+ end
1316
+
1317
+ module Enumerable
1318
+ # public
1319
+ # # monkeypatch to Enumerable to rename collections and their schemas
1320
+ # def rename(new_tabname, new_schema=nil)
1321
+ # scr = Bud::BudScratch.new(new_tabname.to_s, nil, new_schema)
1322
+ # scr.merge(self, scr.storage)
1323
+ # scr
1324
+ # end
1325
+
1326
+ public
1327
+ # We rewrite "map" calls in Bloom blocks to invoke the "pro" method
1328
+ # instead. This is fine when applied to a BudCollection; when applied to a
1329
+ # normal Enumerable, just treat pro as an alias for map.
1330
+ def pro(&blk)
1331
+ map(&blk)
969
1332
  end
970
1333
  end