bud 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,10 +17,10 @@ module Bud
17
17
  def initialize(name_in, bud_instance, collection_name=nil, given_schema=nil, defer_schema=false, &blk)
18
18
  super(name_in, bud_instance, given_schema, defer_schema)
19
19
  @blk = blk
20
- @outputs = []
21
- @pendings = []
22
- @deletes = []
23
- @delete_keys = []
20
+ @outputs = Set.new
21
+ @pendings = Set.new
22
+ @deletes = Set.new
23
+ @delete_keys = Set.new
24
24
  @wired_by = []
25
25
  @elem_name = name_in
26
26
  @found_delta = false
@@ -114,8 +114,6 @@ module Bud
114
114
  end
115
115
 
116
116
  def push_out(item, do_block=true)
117
- return if item.nil?
118
-
119
117
  if do_block && @blk
120
118
  item = item.to_a if @blk.arity > 1
121
119
  item = @blk.call item
@@ -151,18 +149,17 @@ module Bud
151
149
  # default for stateless elements
152
150
  public
153
151
  def add_rescan_invalidate(rescan, invalidate)
154
- # if any of the source elements are in rescan mode, then put this node in
155
- # rescan.
152
+ # If any sources are in rescan mode, then put this node in rescan
156
153
  srcs = non_temporal_predecessors
157
154
  if srcs.any?{|p| rescan.member? p}
158
155
  rescan << self
159
156
  end
160
157
 
161
- # pass the current state to the non-element outputs, and see if they end
162
- # up marking this node for rescan
158
+ # Pass the current state to each output collection and see if they end up
159
+ # marking this node for rescan
163
160
  invalidate_tables(rescan, invalidate)
164
161
 
165
- # finally, if this node is in rescan, pass the request on to all source
162
+ # Finally, if this node is in rescan, pass the request on to all source
166
163
  # elements
167
164
  if rescan.member? self
168
165
  rescan.merge(srcs)
@@ -170,14 +167,16 @@ module Bud
170
167
  end
171
168
 
172
169
  def invalidate_tables(rescan, invalidate)
173
- # exchange rescan and invalidate information with tables. If this node is
174
- # in rescan, it may invalidate a target table (if it is a scratch). And if
175
- # the target node is invalidated, this node marks itself for rescan to
176
- # enable a refill of that table at run-time
177
- @outputs.each do |o|
178
- unless o.class <= PushElement
179
- o.add_rescan_invalidate(rescan, invalidate)
180
- rescan << self if invalidate.member? o
170
+ # Exchange rescan and invalidate information with tables. If this node is
171
+ # in rescan, it may invalidate an output table (if it is a scratch). And
172
+ # if the output table is going to be invalidated, this node marks itself
173
+ # for rescan to enable a refill of that table at run-time.
174
+ [@outputs, @pendings].each do |v|
175
+ v.each do |o|
176
+ unless o.class <= PushElement
177
+ o.add_rescan_invalidate(rescan, invalidate)
178
+ rescan << self if invalidate.member? o
179
+ end
181
180
  end
182
181
  end
183
182
  end
@@ -211,9 +210,8 @@ module Bud
211
210
 
212
211
  alias each pro
213
212
 
214
- # XXX: "the_name" & "the_schema" parameters are unused
215
213
  public
216
- def each_with_index(the_name=elem_name, the_schema=schema, &blk)
214
+ def each_with_index(&blk)
217
215
  toplevel = @bud_instance.toplevel
218
216
  elem = Bud::PushEachWithIndex.new("each_with_index#{object_id}",
219
217
  toplevel.this_rule_context,
@@ -255,6 +253,7 @@ module Bud
255
253
  end
256
254
  end
257
255
  alias <= merge
256
+
258
257
  superator "<~" do |o|
259
258
  raise Bud::Error, "illegal use of <~ with pusher '#{tabname}' on left"
260
259
  end
@@ -269,7 +268,7 @@ module Bud
269
268
 
270
269
  def group(keycols, *aggpairs, &blk)
271
270
  # establish schema
272
- keycols = [] if keycols.nil?
271
+ keycols ||= []
273
272
  keycols = keycols.map{|c| canonicalize_col(c)}
274
273
  keynames = keycols.map{|k| k[2]}
275
274
  aggcolsdups = aggpairs.map{|ap| ap[0].class.name.split("::").last}
@@ -293,18 +292,15 @@ module Bud
293
292
  end
294
293
 
295
294
  def argagg(aggname, gbkey_cols, collection, &blk)
295
+ gbkey_cols ||= []
296
296
  gbkey_cols = gbkey_cols.map{|c| canonicalize_col(c)}
297
297
  collection = canonicalize_col(collection)
298
298
  toplevel = @bud_instance.toplevel
299
299
  agg = toplevel.send(aggname, collection)[0]
300
- raise Bud::Error, "#{aggname} not declared exemplary" unless agg.class <= Bud::ArgExemplary
301
- keynames = gbkey_cols.map do |k|
302
- if k.class == Symbol
303
- k.to_s
304
- else
305
- k[2]
306
- end
300
+ unless agg.class <= Bud::ArgExemplary
301
+ raise Bud::Error, "#{aggname} not declared exemplary"
307
302
  end
303
+
308
304
  aggpairs = [[agg, collection]]
309
305
  aa = Bud::PushArgAgg.new('argagg'+Time.new.tv_usec.to_s, toplevel.this_rule_context,
310
306
  @collection_name, gbkey_cols, aggpairs, schema, &blk)
@@ -393,7 +389,8 @@ module Bud
393
389
  end
394
390
 
395
391
  class PushPredicate < PushStatefulElement
396
- def initialize(pred_symbol, elem_name=nil, collection_name=nil, bud_instance=nil, schema_in=nil, &blk)
392
+ def initialize(pred_symbol, elem_name=nil, collection_name=nil,
393
+ bud_instance=nil, schema_in=nil, &blk)
397
394
  @pred_symbol = pred_symbol
398
395
  @in_buf = []
399
396
  super(elem_name, bud_instance, collection_name, schema_in, &blk)
@@ -465,20 +462,21 @@ module Bud
465
462
  @collection.invalidate_at_tick # need to scan afresh if collection invalidated.
466
463
  end
467
464
 
468
- # collection of others to rescan/invalidate if this scanner's collection
469
- # were to be invalidated.
465
+ # What should be rescanned/invalidated if this scanner's collection were to
466
+ # be invalidated.
470
467
  def invalidate_at_tick(rescan, invalidate)
471
468
  @rescan_set = rescan
472
469
  @invalidate_set = invalidate
473
470
  end
474
471
 
475
472
  def add_rescan_invalidate(rescan, invalidate)
476
- # if the collection is to be invalidated, the scanner needs to be in
473
+ # If the collection is to be invalidated, the scanner needs to be in
477
474
  # rescan mode
478
475
  rescan << self if invalidate.member? @collection
479
476
 
480
- # in addition, default PushElement rescan/invalidate logic applies
481
- super
477
+ # Pass the current state to each output collection and see if they end up
478
+ # marking this node for rescan
479
+ invalidate_tables(rescan, invalidate)
482
480
 
483
481
  # Note also that this node can be nominated for rescan by a target node;
484
482
  # in other words, a scanner element can be set to rescan even if the
@@ -487,12 +485,10 @@ module Bud
487
485
 
488
486
  def scan(first_iter)
489
487
  if @force_rescan
490
- # Scan entire storage
491
488
  @collection.each_raw {|item| push_out(item)}
492
489
  @force_rescan = false
493
490
  elsif first_iter
494
491
  if rescan
495
- # Scan entire storage
496
492
  @collection.each_raw {|item| push_out(item)}
497
493
  else
498
494
  # In the first iteration, tick_delta would be non-null IFF the
@@ -7,17 +7,19 @@ module Bud
7
7
 
8
8
  def initialize(rellist, bud_instance, preds=nil) # :nodoc: all
9
9
  @rels = rellist
10
- @relnames = @rels.map{|r| r.elem_name}
10
+ @relnames = @rels.map{|r| r.qualified_tabname}
11
11
  @cols = []
12
12
  @bud_instance = bud_instance
13
13
  @origpreds = preds
14
- @localpreds = nil
14
+ @localpreds = []
15
15
  @selfjoins = []
16
+ @keys = []
17
+ @key_attnos = [[], []]
16
18
  @missing_keys = Set.new
17
19
 
18
20
  # if any elements on rellist are PushSHJoins, suck up their contents
19
21
  @all_rels_below = []
20
- rellist.each do |r|
22
+ @rels.each do |r|
21
23
  if r.class <= PushSHJoin
22
24
  @all_rels_below += r.all_rels_below
23
25
  preds += r.origpreds
@@ -25,12 +27,13 @@ module Bud
25
27
  @all_rels_below << r
26
28
  end
27
29
  end
30
+ @left_is_array = @all_rels_below.length > 2
28
31
 
29
32
  # check for self-joins: we currently only handle 2 instances of the same
30
33
  # table per rule
31
34
  counts = @all_rels_below.reduce({}) do |memo, r|
32
- memo[r.elem_name] ||= 0
33
- memo[r.elem_name] += 1
35
+ memo[r.qualified_tabname] ||= 0
36
+ memo[r.qualified_tabname] += 1
34
37
  memo
35
38
  end
36
39
  counts.each do |name, cnt|
@@ -41,13 +44,12 @@ module Bud
41
44
  # derive schema: one column for each table.
42
45
  # duplicated inputs get distinguishing numeral
43
46
  @cols = []
44
- index = 0
45
47
  retval = @all_rels_below.reduce({}) do |memo, r|
46
- index += 1
47
- memo[r.tabname.to_s] ||= 0
48
- newstr = r.tabname.to_s + ((memo[r.tabname.to_s] > 0) ? ("_" + memo[r.tabname.to_s].to_s) : "")
48
+ r_name = r.qualified_tabname.to_s
49
+ memo[r_name] ||= 0
50
+ newstr = r_name + (memo[r_name] > 0 ? "_#{memo[r_name]}" : "")
49
51
  @cols << newstr.to_sym
50
- memo[r.tabname.to_s] += 1
52
+ memo[r_name] += 1
51
53
  memo
52
54
  end
53
55
 
@@ -70,7 +72,7 @@ module Bud
70
72
  private
71
73
  def setup_state
72
74
  sid = state_id
73
- @tabname = ("(" + @all_rels_below.map{|r| r.tabname}.join('*') +"):"+sid.to_s).to_sym
75
+ @tabname = ("(" + @all_rels_below.map{|r| r.qualified_tabname}.join('*') +"):"+sid.to_s).to_sym
74
76
  @hash_tables = [{}, {}]
75
77
  end
76
78
 
@@ -80,25 +82,26 @@ module Bud
80
82
  # print "setting up preds for #{@relnames.inspect}(#{self.object_id}): "
81
83
  allpreds = disambiguate_preds(preds)
82
84
  allpreds = canonicalize_localpreds(@rels, allpreds)
83
- # check for refs to collections that aren't being joined, Issue 191
85
+
86
+ # check for refs to collections that aren't being joined
84
87
  unless @rels[0].class <= Bud::PushSHJoin
85
- tabnames = @rels.map{ |r| r.tabname }
86
88
  allpreds.each do |p|
87
- unless tabnames.include? p[0][0]
89
+ unless @relnames.include? p[0][0]
88
90
  raise Bud::CompileError, "illegal predicate: collection #{p[0][0]} is not being joined"
89
91
  end
90
- unless tabnames.include? p[1][0]
92
+ unless @relnames.include? p[1][0]
91
93
  raise Bud::CompileError, "illegal predicate: collection #{p[1][0]} is not being joined"
92
94
  end
93
95
  end
94
96
  end
97
+
95
98
  @localpreds = allpreds.reject do |p|
96
99
  # reject if it doesn't match the right (leaf node) of the join
97
100
  # or reject if it does match, but it can be evaluated by a lower join
98
- # i.e. one that also has this table on the right (lead node)
99
- p[1][0] != @rels[1].tabname \
100
- or (p[0][0] != @rels[1].tabname \
101
- and p[1][0] == @rels[1].tabname and @selfjoins.include? @rels[1].tabname)
101
+ # i.e. one that also has this table on the right (leaf node)
102
+ p[1][0] != @rels[1].qualified_tabname \
103
+ or (p[0][0] != @rels[1].qualified_tabname \
104
+ and p[1][0] == @rels[1].qualified_tabname and @selfjoins.include? @rels[1].qualified_tabname)
102
105
  end
103
106
 
104
107
  # only allow preds on the same table name if they're on a self-joined table
@@ -108,9 +111,9 @@ module Bud
108
111
  end
109
112
  end
110
113
 
111
- @localpreds += allpreds.map do |p|
112
- p if p[0][0] == p[1][0] and (p[1][0] == @rels[0].tabname or p[1][0] == @rels[1].tabname)
113
- end.compact
114
+ @localpreds += allpreds.select do |p|
115
+ p[0][0] == p[1][0] and (p[1][0] == @rels[0].qualified_tabname or p[1][0] == @rels[1].qualified_tabname)
116
+ end
114
117
  otherpreds = allpreds - @localpreds
115
118
  unless otherpreds.empty?
116
119
  unless @rels[0].class <= Bud::PushSHJoin
@@ -119,20 +122,26 @@ module Bud
119
122
  @rels[0].setup_preds(otherpreds)
120
123
  end
121
124
 
122
- if @localpreds.length > 0
123
- @right_offset = @localpreds.first[1][1]
124
- @left_subtuple, @left_offset = join_offset(@localpreds.first[0])
125
- @keys = [[@left_subtuple, @left_offset], [1, @right_offset]]
126
- else
127
- @keys = []
125
+ @localpreds.each do |lp|
126
+ right_offset = lp[1][1]
127
+ left_subtuple, left_offset = join_offset(lp[0])
128
+ @keys << [[left_subtuple, left_offset], [1, right_offset]]
128
129
  end
130
+
131
+ # Optimize for a common case. When we're just fetching key values from
132
+ # an input tuple, lookup the column offsets we need to fetch for each
133
+ # input. This doesn't apply when we're computing the key for the left
134
+ # input and @left_is_array is true.
135
+ @key_attnos = []
136
+ @key_attnos[0] = @keys.map {|k| k[0][1]}
137
+ @key_attnos[1] = @keys.map {|k| k[1][1]}
129
138
  end
130
139
 
131
140
  public
132
141
  def invalidate_cache
133
142
  @rels.each_with_index do |source_elem, i|
134
143
  if source_elem.rescan
135
- puts "#{tabname} rel:#{i}(#{source_elem.tabname}) invalidated" if $BUD_DEBUG
144
+ puts "#{qualified_tabname} rel:#{i}(#{source_elem.qualified_tabname}) invalidated" if $BUD_DEBUG
136
145
  @hash_tables[i] = {}
137
146
  if i == 0
138
147
  # Only if i == 0 because outer joins in Bloom are left outer joins.
@@ -156,7 +165,7 @@ module Bud
156
165
  # referenced in entry.
157
166
  subtuple = 0
158
167
  all_rels_below[0..all_rels_below.length-1].each_with_index do |t,i|
159
- if t.tabname == entry[0]
168
+ if t.qualified_tabname == entry[0]
160
169
  subtuple = i
161
170
  break
162
171
  end
@@ -197,20 +206,23 @@ module Bud
197
206
  dorels = (rel.nil? ? @all_rels_below : [rel])
198
207
  match = nil
199
208
  dorels.each do |r|
200
- match ||= r if bud_instance.tables[r.elem_name].respond_to?(aname)
201
- if bud_instance.tables[r.elem_name].respond_to?(aname) and match != r
202
- raise Bud::CompileError, "ambiguous attribute :#{aname} in both #{match.tabname} and #{r.tabname}"
209
+ r_name = r.qualified_tabname
210
+ tbl = bud_instance.toplevel.tables[r_name]
211
+ match ||= r if tbl.respond_to?(aname)
212
+ if tbl.respond_to?(aname) and match != r
213
+ raise Bud::CompileError, "ambiguous attribute :#{aname} in both #{match.qualified_tabname} and #{r_name}"
203
214
  end
204
215
  end
205
216
  if match.nil?
206
- raise Bud::CompileError, "attribute :#{aname} not found in any of #{dorels.map{|t| t.tabname}.inspect}"
217
+ rel_names = dorels.map{|t| t.qualified_tabname.to_s}.to_s
218
+ raise Bud::CompileError, "attribute :#{aname} not found in any of #{rel_names}"
207
219
  end
208
- bud_instance.tables[match.elem_name].send(aname)
220
+ match.send(aname)
209
221
  end
210
222
 
223
+ # decompose each pred into a binary pred
211
224
  protected
212
225
  def decomp_preds(*preds) # :nodoc:all
213
- # decompose each pred into a binary pred
214
226
  return nil if preds.empty? or preds == [nil]
215
227
  newpreds = []
216
228
  preds.each do |p|
@@ -225,38 +237,8 @@ module Bud
225
237
  def canonicalize_localpreds(rel_list, preds) # :nodoc:all
226
238
  retval = preds.map do |p|
227
239
  # reverse if lhs is rel_list[1], *unless* it's a self-join!
228
- (p[0][0] == rel_list[1].tabname and p[0][0] != p[1][0]) ? p.reverse : p
229
- end
230
- end
231
-
232
- private
233
- # right is a tuple
234
- # left is a tuple or an array (combo) of joined tuples.
235
- def test_locals(left, left_is_array, right, *skips)
236
- retval = true
237
- if (skips and @localpreds.length > skips.length)
238
- # check remainder of the predicates
239
- @localpreds.each do |pred|
240
- # skip skips
241
- next if (skips.include? pred)
242
- # assumption of left-deep joins here
243
- if pred[1][0] != @rels[1].tabname
244
- raise Bud::Error, "expected rhs table to be #{@rels[1].tabname}, not #{pred[1][0]}"
245
- end
246
- rfield = right[pred[1][1]]
247
- if left_is_array
248
- ix, off = join_offset(pred[0])
249
- lfield = left[ix][off]
250
- else
251
- lfield = left[pred[0][1]]
252
- end
253
- if lfield != rfield
254
- retval = false
255
- break
256
- end
257
- end
240
+ (p[0][0] == rel_list[1].qualified_tabname and p[0][0] != p[1][0]) ? p.reverse : p
258
241
  end
259
- return retval
260
242
  end
261
243
 
262
244
  undef do_insert
@@ -269,11 +251,11 @@ module Bud
269
251
  # again if we didn't rescan now.
270
252
  replay_join if @rescan
271
253
 
272
- if @selfjoins.include? source.elem_name
254
+ if @selfjoins.include? source.qualified_tabname
273
255
  offsets = []
274
- @relnames.each_with_index{|r,i| offsets << i if r == source.elem_name}
256
+ @relnames.each_with_index{|r,i| offsets << i if r == source.qualified_tabname}
275
257
  else
276
- offsets = [@relnames.index(source.elem_name)]
258
+ offsets = [@relnames.index(source.qualified_tabname)]
277
259
  end
278
260
  raise Bud::Error, "item #{item.inspect} inserted into join from unknown source #{source.elem_name}" if offsets == $EMPTY
279
261
  offsets.each do |offset|
@@ -283,16 +265,16 @@ module Bud
283
265
 
284
266
  protected
285
267
  def insert_item(item, offset)
286
- if @keys.nil? or @keys.empty?
287
- the_key = nil
288
- else
289
- # assumes left-deep trees
290
- if all_rels_below.length > 2 and offset == 0
291
- the_key = item[@keys[0][0]][@keys[0][1]]
292
- else
293
- the_key = item[@keys[offset][1]]
268
+ # assumes left-deep trees
269
+ if @left_is_array and offset == 0
270
+ the_key = @keys.map do |k|
271
+ left_subtuple, left_offset = k.first
272
+ item[left_subtuple][left_offset]
294
273
  end
274
+ else
275
+ the_key = item.values_at(*@key_attnos[offset])
295
276
  end
277
+
296
278
  #build
297
279
  # puts "building #{item.inspect} into @source[#{offset}] on key #{the_key.inspect}"
298
280
  if (@hash_tables[offset][the_key] ||= Set.new).add? item
@@ -340,11 +322,10 @@ module Bud
340
322
  left = m
341
323
  right = item
342
324
  end
343
- left_is_array = all_rels_below.length > 2
344
- if @localpreds.nil? or @localpreds.length == 1 or test_locals(left, left_is_array, right, @localpreds.first)
345
- result = left_is_array ? left + [right] : [left, right] # FIX: reduce arrays being created.
346
- push_out(result)
347
- end
325
+
326
+ # FIX: reduce arrays being created
327
+ result = @left_is_array ? left + [right] : [left, right]
328
+ push_out(result)
348
329
  end
349
330
  end
350
331
 
@@ -372,13 +353,16 @@ module Bud
372
353
  # matches in the 2nd, nil-pad it and include it in the output.
373
354
  public
374
355
  def outer(*preds, &blk)
356
+ if @all_rels_below.length > 2
357
+ raise Bud::Error, "outer joins cannot be used with more than 2 join relations"
358
+ end
375
359
  pairs(*preds, &blk)
376
360
  self.extend(Bud::PushSHOuterJoin)
377
361
  end
378
362
 
379
363
  public
380
364
  def rights(*preds, &blk)
381
- @cols = blk.nil? ? @bud_instance.tables[@rels[1].tabname].cols : nil
365
+ @cols = blk.nil? ? @bud_instance.toplevel.tables[@rels[1].qualified_tabname].cols : nil
382
366
  setup_accessors if blk.nil?
383
367
  pairs(*preds) do |x,y|
384
368
  blk.nil? ? y : blk.call(y)
@@ -387,7 +371,7 @@ module Bud
387
371
 
388
372
  public
389
373
  def lefts(*preds, &blk)
390
- @cols = blk.nil? ? @bud_instance.tables[@rels[0].tabname].cols : nil
374
+ @cols = blk.nil? ? @bud_instance.toplevel.tables[@rels[0].qualified_tabname].cols : nil
391
375
  setup_accessors if blk.nil?
392
376
  pairs(*preds) do |x,y|
393
377
  blk.nil? ? x : blk.call(x)
@@ -457,17 +441,11 @@ module Bud
457
441
  end
458
442
 
459
443
  module PushSHOuterJoin
444
+ # XXX: duplicates code from PushSHJoin
460
445
  private
461
446
  def insert_item(item, offset)
462
- if @keys.nil? or @keys.empty?
463
- the_key = nil
464
- else
465
- if all_rels_below.length > 2 and offset == 1
466
- the_key = item[@keys[1][0]][@keys[1][1]]
467
- else
468
- the_key = item[@keys[offset][1]]
469
- end
470
- end
447
+ the_key = item.values_at(*@key_attnos[offset])
448
+
471
449
  #build
472
450
  # puts "building #{item.inspect} into @source[#{offset}] on key #{the_key.inspect}"
473
451
  if (@hash_tables[offset][the_key] ||= Set.new).add? item
@@ -478,7 +456,8 @@ module Bud
478
456
  if the_matches.nil? and offset == 0 # only doing Left Outer Join right now
479
457
  @missing_keys << the_key
480
458
  else
481
- @missing_keys.delete(the_key) # no longer missing no matter which side this tuple is
459
+ # no longer missing no matter which side this tuple is
460
+ @missing_keys.delete(the_key)
482
461
  process_matches(item, the_matches, offset) unless the_matches.nil?
483
462
  end
484
463
  end
@@ -497,9 +476,11 @@ module Bud
497
476
 
498
477
  private
499
478
  def push_missing
479
+ left_hash = @hash_tables[0]
480
+ null_tuple = @rels[1].null_tuple
500
481
  @missing_keys.each do |key|
501
- @hash_tables[0][key].each do |t|
502
- push_out([t, @rels[1].null_tuple])
482
+ left_hash[key].each do |t|
483
+ push_out([t, null_tuple])
503
484
  end
504
485
  end
505
486
  end
@@ -520,8 +501,8 @@ module Bud
520
501
  @lhs, @rhs = rellist
521
502
  @lhs_keycols = nil
522
503
  @rhs_keycols = nil
523
- name_in = "#{@lhs.tabname}_notin_#{@rhs.tabname}"
524
- super(name_in, bud_instance)
504
+ name_in = "#{@lhs.qualified_tabname}_notin_#{@rhs.qualified_tabname}"
505
+ super(name_in, bud_instance, nil, @lhs.schema)
525
506
  setup_preds(preds) unless preds.empty?
526
507
  @rhs_rcvd = false
527
508
  @hash_tables = [{},{}]
@@ -553,7 +534,7 @@ module Bud
553
534
  def find_col(colspec, rel)
554
535
  if colspec.is_a? Symbol
555
536
  unless rel.respond_to? colspec
556
- raise Bud::Error, "attribute :#{colspec} not found in #{rel.tabname}"
537
+ raise Bud::Error, "attribute :#{colspec} not found in #{rel.qualified_tabname}"
557
538
  end
558
539
  col_desc = rel.send(colspec)
559
540
  elsif colspec.is_a? Array
@@ -561,7 +542,7 @@ module Bud
561
542
  else
562
543
  raise Bud::Error, "symbol or column spec expected. Got #{colspec}"
563
544
  end
564
- col_desc[1] # col_desc is of the form [tabname, colnum, colname]
545
+ col_desc[1] # col_desc is of the form [tabname, colnum, colname, seqno]
565
546
  end
566
547
 
567
548
  def get_key(item, offset)
@@ -588,7 +569,8 @@ module Bud
588
569
  key = get_key(item, offset)
589
570
  (@hash_tables[offset][key] ||= Set.new).add item
590
571
  if @rhs_rcvd and offset == 0
591
- push_lhs(key, item)
572
+ rhs_values = @hash_tables[1][key]
573
+ process_match(item, rhs_values)
592
574
  end
593
575
  end
594
576
 
@@ -599,16 +581,12 @@ module Bud
599
581
  unless @rhs_rcvd
600
582
  @rhs_rcvd = true
601
583
  @hash_tables[0].each do |key,values|
602
- values.each {|item| push_lhs(key, item)}
584
+ rhs_values = @hash_tables[1][key]
585
+ values.each {|item| process_match(item, rhs_values)}
603
586
  end
604
587
  end
605
588
  end
606
589
 
607
- def push_lhs(key, lhs_item)
608
- rhs_values = @hash_tables[1][key]
609
- process_match(lhs_item, rhs_values)
610
- end
611
-
612
590
  def process_match(lhs_item, rhs_values)
613
591
  if rhs_values.nil?
614
592
  # no corresponding rhs. Include in output
@@ -628,11 +606,11 @@ module Bud
628
606
  raise Bud::Error if @rhs_rcvd # sanity check; should already be reset
629
607
 
630
608
  if @lhs.rescan
631
- puts "#{tabname} rel:#{@lhs.tabname} invalidated" if $BUD_DEBUG
609
+ puts "#{tabname} rel:#{@lhs.qualified_tabname} invalidated" if $BUD_DEBUG
632
610
  @hash_tables[0] = {}
633
611
  end
634
612
  if @rhs.rescan
635
- puts "#{tabname} rel:#{@rhs.tabname} invalidated" if $BUD_DEBUG
613
+ puts "#{tabname} rel:#{@rhs.qualified_tabname} invalidated" if $BUD_DEBUG
636
614
  @hash_tables[1] = {}
637
615
  end
638
616
  end
@@ -112,8 +112,6 @@ class Bud::Lattice
112
112
  end
113
113
  end
114
114
 
115
- # TODO:
116
- # * merge logic for set-oriented collections
117
115
  class Bud::LatticePushElement
118
116
  attr_reader :wired_by, :outputs
119
117
  attr_accessor :invalidated, :rescan
@@ -123,6 +121,7 @@ class Bud::LatticePushElement
123
121
  @wired_by = []
124
122
  @outputs = []
125
123
  @pendings = []
124
+ @deletes = []
126
125
  @invalidated = true
127
126
  @rescan = true
128
127
  end
@@ -133,6 +132,8 @@ class Bud::LatticePushElement
133
132
  @outputs << element
134
133
  when :pending
135
134
  @pendings << element
135
+ when :delete
136
+ @deletes << element
136
137
  else
137
138
  raise Bud::Error, "unrecognized wiring kind: #{kind}"
138
139
  end
@@ -141,7 +142,7 @@ class Bud::LatticePushElement
141
142
  end
142
143
 
143
144
  def check_wiring
144
- if @outputs.empty? and @pendings.empty?
145
+ if @outputs.empty? and @pendings.empty? and @deletes.empty?
145
146
  raise Bud::Error, "no output specified for #{inspect}"
146
147
  end
147
148
  end
@@ -149,12 +150,15 @@ class Bud::LatticePushElement
149
150
  def print_wiring(depth=0, accum="")
150
151
  puts "#{' ' * depth}#{accum} #{inspect}"
151
152
 
152
- [@outputs, @pendings].each do |buf|
153
- if buf == @outputs
154
- next_accum = "=> "
155
- else
156
- next_accum = "+> "
157
- end
153
+ [@outputs, @pendings, @deletes].each do |buf|
154
+ next_accum = case buf
155
+ when @outputs
156
+ "=> "
157
+ when @pendings
158
+ "+> "
159
+ when @deletes
160
+ "-> "
161
+ end
158
162
 
159
163
  buf.each do |o|
160
164
  if o.respond_to? :print_wiring
@@ -171,7 +175,7 @@ class Bud::LatticePushElement
171
175
  end
172
176
 
173
177
  def wirings
174
- @outputs + @pendings
178
+ @outputs + @pendings + @deletes
175
179
  end
176
180
 
177
181
  def method_missing(meth, *args, &blk)
@@ -193,9 +197,9 @@ class Bud::LatticePushElement
193
197
  # operators (e.g., <=, <+) take a collection of tuples, so we need to
194
198
  # convert the lattice value into a collection of tuple-like values. For
195
199
  # now, we hardcode a single way to do this: we simply assume the value
196
- # embedded inside the lattice is Enumerable. We also allow lattice
197
- # morphisms to just produce Enumerable values directly, so we don't call
198
- # reveal in that case.
200
+ # embedded inside the lattice is an Enumerable that contains tuple-like
201
+ # values. We also allow lattice morphisms to just produce Enumerable
202
+ # values directly, so we don't call reveal in that case.
199
203
  # XXX: rethink this.
200
204
  if o.class <= Bud::BudCollection
201
205
  o <= (v.class <= Bud::Lattice ? v.reveal : v)
@@ -210,6 +214,10 @@ class Bud::LatticePushElement
210
214
  o <+ v
211
215
  end
212
216
  end
217
+ @deletes.each do |o|
218
+ raise Bud::Error unless o.class <= Bud::BudCollection
219
+ o.pending_delete(v.class <= Bud::Lattice ? v.reveal : v)
220
+ end
213
221
  end
214
222
 
215
223
  def flush