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.
@@ -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