bud 0.9.4 → 0.9.9

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,24 +1,24 @@
1
1
  require 'bud/executor/elements'
2
- require 'set'
3
2
 
4
- $EMPTY = []
5
3
  module Bud
6
4
  class PushSHJoin < PushStatefulElement
7
5
  attr_reader :all_rels_below, :origpreds, :relnames, :keys, :localpreds
8
6
 
9
7
  def initialize(rellist, bud_instance, preds=nil) # :nodoc: all
10
8
  @rels = rellist
11
- @relnames = @rels.map{|r| r.elem_name}
9
+ @relnames = @rels.map{|r| r.qualified_tabname}
12
10
  @cols = []
13
11
  @bud_instance = bud_instance
14
12
  @origpreds = preds
15
- @localpreds = nil
13
+ @localpreds = []
16
14
  @selfjoins = []
15
+ @keys = []
16
+ @key_attnos = [[], []]
17
17
  @missing_keys = Set.new
18
18
 
19
19
  # if any elements on rellist are PushSHJoins, suck up their contents
20
20
  @all_rels_below = []
21
- rellist.each do |r|
21
+ @rels.each do |r|
22
22
  if r.class <= PushSHJoin
23
23
  @all_rels_below += r.all_rels_below
24
24
  preds += r.origpreds
@@ -26,12 +26,13 @@ module Bud
26
26
  @all_rels_below << r
27
27
  end
28
28
  end
29
+ @left_is_array = @all_rels_below.length > 2
29
30
 
30
31
  # check for self-joins: we currently only handle 2 instances of the same
31
32
  # table per rule
32
33
  counts = @all_rels_below.reduce({}) do |memo, r|
33
- memo[r.elem_name] ||= 0
34
- memo[r.elem_name] += 1
34
+ memo[r.qualified_tabname] ||= 0
35
+ memo[r.qualified_tabname] += 1
35
36
  memo
36
37
  end
37
38
  counts.each do |name, cnt|
@@ -39,31 +40,12 @@ module Bud
39
40
  @selfjoins << name if cnt == 2
40
41
  end
41
42
 
42
- # derive schema: one column for each table.
43
- # duplicated inputs get distinguishing numeral
44
- @cols = []
45
- index = 0
46
- retval = @all_rels_below.reduce({}) do |memo, r|
47
- index += 1
48
- memo[r.tabname.to_s] ||= 0
49
- newstr = r.tabname.to_s + ((memo[r.tabname.to_s] > 0) ? ("_" + memo[r.tabname.to_s].to_s) : "")
50
- @cols << newstr.to_sym
51
- memo[r.tabname.to_s] += 1
52
- memo
53
- end
54
-
55
43
  setup_preds(preds) unless preds.empty?
56
44
  setup_state
57
45
 
58
46
  super(@tabname, @bud_instance, nil, @cols)
59
47
  end
60
48
 
61
- public
62
- def copy_on_write
63
- @refcount -= 1
64
- return Bud::PushSHJoin.new(@all_rels_below, @bud_instance, [])
65
- end
66
-
67
49
  public
68
50
  def state_id # :nodoc: all
69
51
  object_id
@@ -77,7 +59,7 @@ module Bud
77
59
  private
78
60
  def setup_state
79
61
  sid = state_id
80
- @tabname = ("(" + @all_rels_below.map{|r| r.tabname}.join('*') +"):"+sid.to_s).to_sym
62
+ @tabname = ("(" + @all_rels_below.map{|r| r.qualified_tabname}.join('*') +"):"+sid.to_s).to_sym
81
63
  @hash_tables = [{}, {}]
82
64
  end
83
65
 
@@ -87,25 +69,26 @@ module Bud
87
69
  # print "setting up preds for #{@relnames.inspect}(#{self.object_id}): "
88
70
  allpreds = disambiguate_preds(preds)
89
71
  allpreds = canonicalize_localpreds(@rels, allpreds)
90
- # check for refs to collections that aren't being joined, Issue 191
72
+
73
+ # check for refs to collections that aren't being joined
91
74
  unless @rels[0].class <= Bud::PushSHJoin
92
- tabnames = @rels.map{ |r| r.tabname }
93
75
  allpreds.each do |p|
94
- unless tabnames.include? p[0][0]
76
+ unless @relnames.include? p[0][0]
95
77
  raise Bud::CompileError, "illegal predicate: collection #{p[0][0]} is not being joined"
96
78
  end
97
- unless tabnames.include? p[1][0]
79
+ unless @relnames.include? p[1][0]
98
80
  raise Bud::CompileError, "illegal predicate: collection #{p[1][0]} is not being joined"
99
81
  end
100
82
  end
101
83
  end
84
+
102
85
  @localpreds = allpreds.reject do |p|
103
86
  # reject if it doesn't match the right (leaf node) of the join
104
87
  # or reject if it does match, but it can be evaluated by a lower join
105
- # i.e. one that also has this table on the right (lead node)
106
- p[1][0] != @rels[1].tabname \
107
- or (p[0][0] != @rels[1].tabname \
108
- and p[1][0] == @rels[1].tabname and @selfjoins.include? @rels[1].tabname)
88
+ # i.e. one that also has this table on the right (leaf node)
89
+ p[1][0] != @rels[1].qualified_tabname \
90
+ or (p[0][0] != @rels[1].qualified_tabname \
91
+ and p[1][0] == @rels[1].qualified_tabname and @selfjoins.include? @rels[1].qualified_tabname)
109
92
  end
110
93
 
111
94
  # only allow preds on the same table name if they're on a self-joined table
@@ -115,9 +98,9 @@ module Bud
115
98
  end
116
99
  end
117
100
 
118
- @localpreds += allpreds.map do |p|
119
- p if p[0][0] == p[1][0] and (p[1][0] == @rels[0].tabname or p[1][0] == @rels[1].tabname)
120
- end.compact
101
+ @localpreds += allpreds.select do |p|
102
+ p[0][0] == p[1][0] and (p[1][0] == @rels[0].qualified_tabname or p[1][0] == @rels[1].qualified_tabname)
103
+ end
121
104
  otherpreds = allpreds - @localpreds
122
105
  unless otherpreds.empty?
123
106
  unless @rels[0].class <= Bud::PushSHJoin
@@ -126,29 +109,27 @@ module Bud
126
109
  @rels[0].setup_preds(otherpreds)
127
110
  end
128
111
 
129
- if @localpreds.length > 0
130
- @right_offset = @localpreds.first[1][1]
131
- @left_subtuple, @left_offset = join_offset(@localpreds.first[0])
132
- @keys = [[@left_subtuple, @left_offset], [1, @right_offset]]
133
- else
134
- @keys = []
112
+ @localpreds.each do |lp|
113
+ right_offset = lp[1][1]
114
+ left_subtuple, left_offset = join_offset(lp[0])
115
+ @keys << [[left_subtuple, left_offset], [1, right_offset]]
135
116
  end
117
+
118
+ # Optimize for a common case. When we're just fetching key values from
119
+ # an input tuple, lookup the column offsets we need to fetch for each
120
+ # input. This doesn't apply when we're computing the key for the left
121
+ # input and @left_is_array is true.
122
+ @key_attnos = []
123
+ @key_attnos[0] = @keys.map {|k| k[0][1]}
124
+ @key_attnos[1] = @keys.map {|k| k[1][1]}
136
125
  end
137
126
 
138
127
  public
139
128
  def invalidate_cache
140
129
  @rels.each_with_index do |source_elem, i|
141
130
  if source_elem.rescan
142
- puts "#{tabname} rel:#{i}(#{source_elem.tabname}) invalidated" if $BUD_DEBUG
131
+ puts "#{qualified_tabname} rel:#{i}(#{source_elem.qualified_tabname}) invalidated" if $BUD_DEBUG
143
132
  @hash_tables[i] = {}
144
- if i == 0
145
- # Only if i == 0 because outer joins in Bloom are left outer joins.
146
- # If i == 1, missing_keys will be corrected when items are populated
147
- # in the rhs fork.
148
- # XXX This is not modular. We are doing invalidation work for outer
149
- # joins, which is part of a separate module PushSHOuterJoin.
150
- @missing_keys.clear
151
- end
152
133
  end
153
134
  end
154
135
  end
@@ -163,7 +144,7 @@ module Bud
163
144
  # referenced in entry.
164
145
  subtuple = 0
165
146
  all_rels_below[0..all_rels_below.length-1].each_with_index do |t,i|
166
- if t.tabname == entry[0]
147
+ if t.qualified_tabname == name
167
148
  subtuple = i
168
149
  break
169
150
  end
@@ -181,7 +162,7 @@ module Bud
181
162
  elsif k.class <= Array
182
163
  [k,v]
183
164
  elsif k.class <= Symbol
184
- if @all_rels_below and @all_rels_below.length == 2
165
+ if @all_rels_below.length == 2
185
166
  [find_attr_match(k, @all_rels_below[0]), find_attr_match(v, @all_rels_below[1])]
186
167
  else
187
168
  [find_attr_match(k), find_attr_match(v)]
@@ -204,20 +185,23 @@ module Bud
204
185
  dorels = (rel.nil? ? @all_rels_below : [rel])
205
186
  match = nil
206
187
  dorels.each do |r|
207
- match ||= r if bud_instance.tables[r.elem_name].respond_to?(aname)
208
- if bud_instance.tables[r.elem_name].respond_to?(aname) and match != r
209
- raise Bud::CompileError, "ambiguous attribute :#{aname} in both #{match.tabname} and #{r.tabname}"
188
+ r_name = r.qualified_tabname
189
+ tbl = bud_instance.toplevel.tables[r_name]
190
+ match ||= r if tbl.respond_to?(aname)
191
+ if tbl.respond_to?(aname) and match != r
192
+ raise Bud::CompileError, "ambiguous attribute :#{aname} in both #{match.qualified_tabname} and #{r_name}"
210
193
  end
211
194
  end
212
195
  if match.nil?
213
- raise Bud::CompileError, "attribute :#{aname} not found in any of #{dorels.map{|t| t.tabname}.inspect}"
196
+ rel_names = dorels.map{|t| t.qualified_tabname.to_s}.to_s
197
+ raise Bud::CompileError, "attribute :#{aname} not found in any of #{rel_names}"
214
198
  end
215
- bud_instance.tables[match.elem_name].send(aname)
199
+ match.send(aname)
216
200
  end
217
201
 
202
+ # decompose each pred into a binary pred
218
203
  protected
219
204
  def decomp_preds(*preds) # :nodoc:all
220
- # decompose each pred into a binary pred
221
205
  return nil if preds.empty? or preds == [nil]
222
206
  newpreds = []
223
207
  preds.each do |p|
@@ -230,40 +214,11 @@ module Bud
230
214
 
231
215
  protected
232
216
  def canonicalize_localpreds(rel_list, preds) # :nodoc:all
233
- retval = preds.map do |p|
234
- # reverse if lhs is rel_list[1], *unless* it's a self-join!
235
- (p[0][0] == rel_list[1].tabname and p[0][0] != p[1][0]) ? p.reverse : p
236
- end
237
- end
238
-
239
- private
240
- # right is a tuple
241
- # left is a tuple or an array (combo) of joined tuples.
242
- def test_locals(left, left_is_array, right, *skips)
243
- retval = true
244
- if (skips and @localpreds.length > skips.length)
245
- # check remainder of the predicates
246
- @localpreds.each do |pred|
247
- # skip skips
248
- next if (skips.include? pred)
249
- # assumption of left-deep joins here
250
- if pred[1][0] != @rels[1].tabname
251
- raise Bud::Error, "expected rhs table to be #{@rels[1].tabname}, not #{pred[1][0]}"
252
- end
253
- rfield = right[pred[1][1]]
254
- if left_is_array
255
- ix, off = join_offset(pred[0])
256
- lfield = left[ix][off]
257
- else
258
- lfield = left[pred[0][1]]
259
- end
260
- if lfield != rfield
261
- retval = false
262
- break
263
- end
264
- end
217
+ second_rel = rel_list[1].qualified_tabname
218
+ preds.map do |p|
219
+ # reverse if lhs is second_rel *unless* it's a self-join!
220
+ (p[0][0] == second_rel and p[0][0] != p[1][0]) ? p.reverse : p
265
221
  end
266
- return retval
267
222
  end
268
223
 
269
224
  undef do_insert
@@ -276,30 +231,29 @@ module Bud
276
231
  # again if we didn't rescan now.
277
232
  replay_join if @rescan
278
233
 
279
- if @selfjoins.include? source.elem_name
234
+ source_tbl = source.qualified_tabname
235
+ if @selfjoins.include? source_tbl
280
236
  offsets = []
281
- @relnames.each_with_index{|r,i| offsets << i if r == source.elem_name}
237
+ @relnames.each_with_index{|r,i| offsets << i if r == source_tbl}
282
238
  else
283
- offsets = [@relnames.index(source.elem_name)]
284
- end
285
- raise Bud::Error, "item #{item.inspect} inserted into join from unknown source #{source.elem_name}" if offsets == $EMPTY
286
- offsets.each do |offset|
287
- insert_item(item, offset)
239
+ offsets = [@relnames.index(source_tbl)]
288
240
  end
241
+
242
+ offsets.each {|offset| insert_item(item, offset)}
289
243
  end
290
244
 
291
245
  protected
292
246
  def insert_item(item, offset)
293
- if @keys.nil? or @keys.empty?
294
- the_key = nil
295
- else
296
- # assumes left-deep trees
297
- if all_rels_below.length > 2 and offset == 0
298
- the_key = item[@keys[0][0]][@keys[0][1]]
299
- else
300
- the_key = item[@keys[offset][1]]
247
+ # assumes left-deep trees
248
+ if @left_is_array and offset == 0
249
+ the_key = @keys.map do |k|
250
+ left_subtuple, left_offset = k.first
251
+ item[left_subtuple][left_offset]
301
252
  end
253
+ else
254
+ the_key = item.values_at(*@key_attnos[offset])
302
255
  end
256
+
303
257
  #build
304
258
  # puts "building #{item.inspect} into @source[#{offset}] on key #{the_key.inspect}"
305
259
  if (@hash_tables[offset][the_key] ||= Set.new).add? item
@@ -347,29 +301,42 @@ module Bud
347
301
  left = m
348
302
  right = item
349
303
  end
350
- left_is_array = all_rels_below.length > 2
351
- if @localpreds.nil? or @localpreds.length == 1 or test_locals(left, left_is_array, right, @localpreds.first)
352
- result = left_is_array ? left + [right] : [left, right] # FIX: reduce arrays being created.
353
- push_out(result)
354
- end
304
+
305
+ # FIX: reduce arrays being created
306
+ result = @left_is_array ? left + [right] : [left, right]
307
+ push_out(result)
355
308
  end
356
309
  end
357
310
 
358
311
  ####
359
312
  # and now, the Bloom-facing methods
360
313
  # given a * expression over n collections, form all combinations of items
361
- # subject to an array of predicates, pred
362
- # currently supports two options for equijoin predicates:
314
+ # subject to an array of predicates, +preds+.
315
+ # currently supports two syntax options for equijoin predicates:
363
316
  # general form: an array of arrays capturing a conjunction of equiv. classes
364
317
  # [[table1.col1, table2.col2, table3.col3], [table1.col2, table2.col3]]
365
318
  # common form: a hash capturing equality of a column on left with one on right.
366
319
  # :col1 => :col2 (same as lefttable.col1 => righttable.col2)
367
320
  public
368
321
  def pairs(*preds, &blk)
369
- ## XXX Need to do this for all the join modifiers
370
- unless @refcount == 1
371
- return self.copy_on_write.pairs(preds, blk)
322
+ if @cols.nil?
323
+ # derive schema if needed: one column for each table. duplicated inputs
324
+ # get distinguishing numeral.
325
+ #
326
+ # XXX: actually, this seems completely bogus. The schema for the output
327
+ # of the join should depend on the join's *targetlist*.
328
+ @cols = []
329
+ retval = @all_rels_below.reduce({}) do |memo, r|
330
+ r_name = r.qualified_tabname.to_s
331
+ memo[r_name] ||= 0
332
+ newstr = r_name + (memo[r_name] > 0 ? "_#{memo[r_name]}" : "")
333
+ @cols << newstr.to_sym
334
+ memo[r_name] += 1
335
+ memo
336
+ end
337
+ setup_accessors
372
338
  end
339
+
373
340
  @origpreds = preds
374
341
  setup_preds(preds) unless preds.empty?
375
342
  # given new preds, the state for the join will be different. set it up again.
@@ -383,25 +350,32 @@ module Bud
383
350
  # matches in the 2nd, nil-pad it and include it in the output.
384
351
  public
385
352
  def outer(*preds, &blk)
353
+ if @all_rels_below.length > 2
354
+ raise Bud::Error, "outer joins cannot be used with more than 2 join relations"
355
+ end
386
356
  pairs(*preds, &blk)
387
357
  self.extend(Bud::PushSHOuterJoin)
388
358
  end
389
359
 
390
360
  public
391
- def rights(*preds, &blk)
392
- @cols = blk.nil? ? @bud_instance.tables[@rels[1].tabname].cols : nil
393
- setup_accessors if blk.nil?
361
+ def lefts(*preds, &blk)
362
+ if blk.nil?
363
+ @cols = @bud_instance.toplevel.tables[@rels[0].qualified_tabname].cols
364
+ setup_accessors
365
+ end
394
366
  pairs(*preds) do |x,y|
395
- blk.nil? ? y : blk.call(y)
367
+ blk.nil? ? x : blk.call(x)
396
368
  end
397
369
  end
398
370
 
399
371
  public
400
- def lefts(*preds, &blk)
401
- @cols = blk.nil? ? @bud_instance.tables[@rels[0].tabname].cols : nil
402
- setup_accessors if blk.nil?
372
+ def rights(*preds, &blk)
373
+ if blk.nil?
374
+ @cols = @bud_instance.toplevel.tables[@rels[1].qualified_tabname].cols
375
+ setup_accessors
376
+ end
403
377
  pairs(*preds) do |x,y|
404
- blk.nil? ? x : blk.call(x)
378
+ blk.nil? ? y : blk.call(y)
405
379
  end
406
380
  end
407
381
 
@@ -432,13 +406,13 @@ module Bud
432
406
  public
433
407
  def flatten(*preds, &blk)
434
408
  if blk.nil?
435
- @cols = dupfree_schema(@bud_instance.tables[@cols[0]].cols + @bud_instance.tables[@cols[1]].cols)
409
+ @cols = dupfree_schema(@rels[0].cols + @rels[1].cols)
436
410
  else
437
411
  @cols = []
438
412
  end
439
413
  setup_accessors
440
414
  pairs(*preds) do |x,y|
441
- blk.nil? ? x.to_a + y.to_a : blk.call(x.to_a + y.to_a)
415
+ blk.nil? ? x + y : blk.call(x + y)
442
416
  end
443
417
  end
444
418
 
@@ -468,17 +442,11 @@ module Bud
468
442
  end
469
443
 
470
444
  module PushSHOuterJoin
445
+ # XXX: duplicates code from PushSHJoin
471
446
  private
472
447
  def insert_item(item, offset)
473
- if @keys.nil? or @keys.empty?
474
- the_key = nil
475
- else
476
- if all_rels_below.length > 2 and offset == 1
477
- the_key = item[@keys[1][0]][@keys[1][1]]
478
- else
479
- the_key = item[@keys[offset][1]]
480
- end
481
- end
448
+ the_key = item.values_at(*@key_attnos[offset])
449
+
482
450
  #build
483
451
  # puts "building #{item.inspect} into @source[#{offset}] on key #{the_key.inspect}"
484
452
  if (@hash_tables[offset][the_key] ||= Set.new).add? item
@@ -489,7 +457,8 @@ module Bud
489
457
  if the_matches.nil? and offset == 0 # only doing Left Outer Join right now
490
458
  @missing_keys << the_key
491
459
  else
492
- @missing_keys.delete(the_key) # no longer missing no matter which side this tuple is
460
+ # no longer missing no matter which side this tuple is
461
+ @missing_keys.delete(the_key)
493
462
  process_matches(item, the_matches, offset) unless the_matches.nil?
494
463
  end
495
464
  end
@@ -508,12 +477,22 @@ module Bud
508
477
 
509
478
  private
510
479
  def push_missing
480
+ left_hash = @hash_tables[0]
481
+ null_tuple = @rels[1].null_tuple
511
482
  @missing_keys.each do |key|
512
- @hash_tables[0][key].each do |t|
513
- push_out([t, @rels[1].null_tuple])
483
+ left_hash[key].each do |t|
484
+ push_out([t, null_tuple])
514
485
  end
515
486
  end
516
487
  end
488
+
489
+ public
490
+ def invalidate_cache
491
+ super
492
+ # Only if need to check left join rel because outer joins in Bloom are
493
+ # left outer joins.
494
+ @missing_keys.clear if @rels.first.rescan
495
+ end
517
496
  end
518
497
 
519
498
 
@@ -527,17 +506,18 @@ module Bud
527
506
  # first flush, at which point we are sure to have seen all the t-side tuples
528
507
  # in this tick.
529
508
  class PushNotIn < PushStatefulElement
530
- def initialize(rellist, bud_instance, preds=nil, &blk) # :nodoc: all
509
+ def initialize(rellist, bud_instance, preds, &blk) # :nodoc: all
531
510
  @lhs, @rhs = rellist
532
511
  @lhs_keycols = nil
533
512
  @rhs_keycols = nil
534
- name_in = "#{@lhs.tabname}_notin_#{@rhs.tabname}"
535
- super(name_in, bud_instance)
513
+ name_in = "#{@lhs.qualified_tabname}_notin_#{@rhs.qualified_tabname}".to_sym
514
+ super(name_in, bud_instance, nil, @lhs.schema)
536
515
  setup_preds(preds) unless preds.empty?
537
516
  @rhs_rcvd = false
538
517
  @hash_tables = [{},{}]
539
518
  if @lhs_keycols.nil? and blk.nil?
540
- # pointwise comparison. Could use zip, but it creates an array for each field pair
519
+ # Pointwise comparison. Could use zip, but it creates an array for each
520
+ # field pair.
541
521
  blk = lambda {|lhs, rhs|
542
522
  lhs.to_a == rhs.to_a
543
523
  }
@@ -547,7 +527,7 @@ module Bud
547
527
 
548
528
  def setup_preds(preds)
549
529
  # This is simpler than PushSHJoin's setup_preds, because notin is a binary
550
- # operator where both lhs and rhs are collections. preds an array of
530
+ # operator where both lhs and rhs are collections. preds is an array of
551
531
  # hash_pairs. For now assume that the attributes are in the same order as
552
532
  # the tables.
553
533
  @lhs_keycols, @rhs_keycols = preds.reduce([[], []]) do |memo, item|
@@ -559,21 +539,25 @@ module Bud
559
539
  memo
560
540
  end
561
541
  end
542
+
562
543
  def find_col(colspec, rel)
563
- if colspec.is_a? Symbol
544
+ case colspec
545
+ when Symbol
546
+ unless rel.respond_to? colspec
547
+ raise Bud::Error, "attribute :#{colspec} not found in #{rel.qualified_tabname}"
548
+ end
564
549
  col_desc = rel.send(colspec)
565
- raise Bud::Error, "unknown column #{colspec} in #{@rel.tabname}" if col_desc.nil?
566
- elsif colspec.is_a? Array
550
+ when Array
567
551
  col_desc = colspec
568
552
  else
569
553
  raise Bud::Error, "symbol or column spec expected. Got #{colspec}"
570
554
  end
571
- col_desc[1] # col_desc is of the form [tabname, colnum, colname]
555
+ col_desc[1] # col_desc is of the form [tabname, colnum, colname, seqno]
572
556
  end
573
557
 
574
558
  def get_key(item, offset)
575
- keycols = offset == 0 ? @lhs_keycols : @rhs_keycols
576
- keycols.nil? ? $EMPTY : keycols.map{|col| item[col]}
559
+ keycols = (offset == 0 ? @lhs_keycols : @rhs_keycols)
560
+ keycols.nil? ? [] : item.values_at(*keycols)
577
561
  end
578
562
 
579
563
  public
@@ -582,11 +566,21 @@ module Bud
582
566
  end
583
567
 
584
568
  def insert(item, source)
585
- offset = source == @lhs ? 0 : 1
569
+ if source == @lhs && source == @rhs # Self join
570
+ do_insert(item, 0)
571
+ do_insert(item, 1)
572
+ else
573
+ offset = source == @lhs ? 0 : 1
574
+ do_insert(item, offset)
575
+ end
576
+ end
577
+
578
+ def do_insert(item, offset)
586
579
  key = get_key(item, offset)
587
580
  (@hash_tables[offset][key] ||= Set.new).add item
588
581
  if @rhs_rcvd and offset == 0
589
- push_lhs(key, item)
582
+ rhs_values = @hash_tables[1][key]
583
+ process_match(item, rhs_values)
590
584
  end
591
585
  end
592
586
 
@@ -596,19 +590,15 @@ module Bud
596
590
  # growing any more, until the next tick.
597
591
  unless @rhs_rcvd
598
592
  @rhs_rcvd = true
593
+ rhs_hash = @hash_tables[1]
599
594
  @hash_tables[0].each do |key,values|
600
- values.each {|item| push_lhs(key, item)}
595
+ rhs_values = rhs_hash[key]
596
+ values.each {|item| process_match(item, rhs_values)}
601
597
  end
602
598
  end
603
599
  end
604
600
 
605
- def push_lhs(key, lhs_item)
606
- rhs_values = @hash_tables[1][key]
607
- process_match(lhs_item, rhs_values)
608
- end
609
-
610
601
  def process_match(lhs_item, rhs_values)
611
- exclude = true
612
602
  if rhs_values.nil?
613
603
  # no corresponding rhs. Include in output
614
604
  exclude = false
@@ -616,33 +606,24 @@ module Bud
616
606
  # for any lhs * rhs pair, if block returns true, do not push lhs. lhs is pushed
617
607
  # only if there is no match (anti-join)
618
608
  exclude = rhs_values.any?{|rhs_item| @blk.call(lhs_item, rhs_item)}
609
+ else
610
+ exclude = true
619
611
  end
620
- unless exclude
621
- push_out(lhs_item)
622
- end
623
- end
624
612
 
625
- public
626
- def push_out(item)
627
- @outputs.each do |ou|
628
- if ou.class <= Bud::PushElement
629
- ou.insert(item, self)
630
- elsif ou.class <= Bud::BudCollection
631
- ou.do_insert(item, ou.new_delta)
632
- else
633
- raise Bud::Error, "expected either a PushElement or a BudCollection"
634
- end
635
- end
636
- # for all the following, o is a BudCollection
637
- @deletes.each{|o| o.pending_delete([item])}
638
- @delete_keys.each{|o| o.pending_delete_keys([item])}
639
- @pendings.each{|o| o.pending_merge([item])}
613
+ push_out(lhs_item, false) unless exclude
640
614
  end
641
615
 
642
616
  def invalidate_cache
643
- puts "#{self.class}/#{self.tabname} invalidated" if $BUD_DEBUG
644
- @hash_tables = [{},{}]
645
- @rhs_rcvd = false
617
+ raise Bud::Error if @rhs_rcvd # sanity check; should already be reset
618
+
619
+ if @lhs.rescan
620
+ puts "#{tabname} rel:#{@lhs.qualified_tabname} invalidated" if $BUD_DEBUG
621
+ @hash_tables[0] = {}
622
+ end
623
+ if @rhs.rescan
624
+ puts "#{tabname} rel:#{@rhs.qualified_tabname} invalidated" if $BUD_DEBUG
625
+ @hash_tables[1] = {}
626
+ end
646
627
  end
647
628
 
648
629
  def stratum_end