bud 0.1.0.pre1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +23 -0
- data/{README → README.md} +6 -2
- data/docs/cheat.md +1 -8
- data/docs/intro.md +1 -1
- data/lib/bud/aggs.rb +16 -16
- data/lib/bud/bud_meta.rb +8 -15
- data/lib/bud/collections.rb +85 -172
- data/lib/bud/errors.rb +5 -1
- data/lib/bud/executor/elements.rb +133 -118
- data/lib/bud/executor/group.rb +6 -6
- data/lib/bud/executor/join.rb +25 -22
- data/lib/bud/metrics.rb +1 -1
- data/lib/bud/monkeypatch.rb +18 -29
- data/lib/bud/rebl.rb +5 -4
- data/lib/bud/rewrite.rb +21 -160
- data/lib/bud/source.rb +5 -5
- data/lib/bud/state.rb +13 -12
- data/lib/bud/storage/dbm.rb +13 -23
- data/lib/bud/storage/zookeeper.rb +0 -4
- data/lib/bud.rb +184 -162
- metadata +144 -216
- data/docs/deploy.md +0 -96
- data/lib/bud/deploy/countatomicdelivery.rb +0 -38
- data/lib/bud/joins.rb +0 -526
data/lib/bud/joins.rb
DELETED
@@ -1,526 +0,0 @@
|
|
1
|
-
$EMPTY = []
|
2
|
-
module Bud
|
3
|
-
class BudJoin < BudCollection
|
4
|
-
attr_accessor :rels, :origrels, :origpreds # :nodoc: all
|
5
|
-
attr_reader :hash_tables # :nodoc: all
|
6
|
-
|
7
|
-
def initialize(rellist, bud_instance, preds=[]) # :nodoc: all
|
8
|
-
@schema = []
|
9
|
-
@origpreds = preds
|
10
|
-
@bud_instance = bud_instance
|
11
|
-
@localpreds = nil
|
12
|
-
@hashpreds = nil
|
13
|
-
@selfjoins = []
|
14
|
-
|
15
|
-
# if any elements on rellist are BudJoins, suck up their contents
|
16
|
-
tmprels = []
|
17
|
-
rellist.each do |r|
|
18
|
-
if r.class <= BudJoin
|
19
|
-
tmprels += r.origrels
|
20
|
-
preds += r.origpreds
|
21
|
-
else
|
22
|
-
tmprels << r
|
23
|
-
end
|
24
|
-
end
|
25
|
-
rellist = tmprels
|
26
|
-
@origrels = rellist
|
27
|
-
|
28
|
-
# check for self-joins: we currently only handle 2 instances of the same table per rule
|
29
|
-
counts = @origrels.reduce({}) do |memo, r|
|
30
|
-
memo[r.tabname] ||= 0
|
31
|
-
memo[r.tabname] += 1
|
32
|
-
memo
|
33
|
-
end
|
34
|
-
counts.each do |name, cnt|
|
35
|
-
raise Bud::CompileError, "#{cnt} instances of #{name} in rule; only one self-join currently allowed per rule" if cnt > 2
|
36
|
-
@selfjoins << name if cnt == 2
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
# recurse to form a tree of binary BudJoins
|
41
|
-
@rels = [rellist[0]]
|
42
|
-
@rels << (rellist.length == 2 ? rellist[1] : BudJoin.new(rellist[1..rellist.length-1], @bud_instance))
|
43
|
-
# derive schema: one column for each table.
|
44
|
-
# duplicated inputs get distinguishing numeral
|
45
|
-
@schema = []
|
46
|
-
index = 0
|
47
|
-
retval = rellist.reduce({}) do |memo, r|
|
48
|
-
index += 1
|
49
|
-
memo[r.tabname.to_s] ||= 0
|
50
|
-
newstr = r.tabname.to_s + ((memo[r.tabname.to_s] > 0) ? ("_" + memo[r.tabname.to_s].to_s) : "")
|
51
|
-
@schema << newstr.to_sym
|
52
|
-
memo[r.tabname.to_s] += 1
|
53
|
-
memo
|
54
|
-
end
|
55
|
-
|
56
|
-
setup_preds(preds)
|
57
|
-
setup_state
|
58
|
-
end
|
59
|
-
|
60
|
-
public
|
61
|
-
def state_id # :nodoc: all
|
62
|
-
Marshal.dump([@rels.map{|r| r.tabname}, @localpreds]).hash
|
63
|
-
end
|
64
|
-
|
65
|
-
# initialize the state for this join to be carried across iterations within a fixpoint
|
66
|
-
private
|
67
|
-
def setup_state
|
68
|
-
sid = state_id
|
69
|
-
@tabname = ("temp_join"+state_id.to_s).to_sym
|
70
|
-
@bud_instance.joinstate[sid] ||= [{:storage => {}, :delta => {}}, {:storage => {}, :delta => {}}]
|
71
|
-
@hash_tables = @bud_instance.joinstate[sid]
|
72
|
-
end
|
73
|
-
|
74
|
-
private_class_method
|
75
|
-
def self.natural_preds(bud_instance, rels)
|
76
|
-
preds = []
|
77
|
-
rels.each do |r|
|
78
|
-
rels.each do |s|
|
79
|
-
matches = r.cols & s.cols
|
80
|
-
matches.each do |c|
|
81
|
-
preds << [bud_instance.send(r.tabname).send(c), bud_instance.send(s.tabname).send(c)] unless r.tabname.to_s >= s.tabname.to_s
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
preds.uniq
|
86
|
-
end
|
87
|
-
|
88
|
-
private_class_method
|
89
|
-
def self.positionwise_preds(bud_instance, rels)
|
90
|
-
preds = []
|
91
|
-
rels.each do |r|
|
92
|
-
rels.each do |s|
|
93
|
-
[r.cols.length, s.cols.length].min.times do |c|
|
94
|
-
preds << [bud_instance.send(r.tabname).send(r.cols[c]), bud_instance.send(s.tabname).send(s.cols[c])] unless r.tabname.to_s >= s.tabname.to_s
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
preds.uniq
|
99
|
-
end
|
100
|
-
|
101
|
-
# flatten joined items into arrays, with attribute accessors inherited
|
102
|
-
# from the input collections, disambiguated via suffix indexes as needed.
|
103
|
-
# similar to <tt>SELECT * FROM ... WHERE...</tt> block in SQL.
|
104
|
-
public
|
105
|
-
def flatten(*preds)
|
106
|
-
setup_preds(preds)
|
107
|
-
flat_schema = @rels.map{|r| r.cols}.flatten(1)
|
108
|
-
dupfree_schema = []
|
109
|
-
# while loop here (inefficiently) ensures no collisions
|
110
|
-
while dupfree_schema == $EMPTY or dupfree_schema.uniq.length < dupfree_schema.length
|
111
|
-
dupfree_schema = []
|
112
|
-
flat_schema.reduce({}) do |memo, r|
|
113
|
-
if r.to_s.include?("_") and ((r.to_s.rpartition("_")[2] =~ /^\d+$/) == 0)
|
114
|
-
r = r.to_s.rpartition("_")[0].to_sym
|
115
|
-
end
|
116
|
-
memo[r] ||= 0
|
117
|
-
if memo[r] == 0
|
118
|
-
dupfree_schema << r.to_s.to_sym
|
119
|
-
else
|
120
|
-
dupfree_schema << (r.to_s + "_" + (memo[r]).to_s).to_sym
|
121
|
-
end
|
122
|
-
memo[r] += 1
|
123
|
-
memo
|
124
|
-
end
|
125
|
-
flat_schema = dupfree_schema
|
126
|
-
end
|
127
|
-
retval = BudScratch.new('temp_flatten', bud_instance, dupfree_schema)
|
128
|
-
retval.uniquify_tabname
|
129
|
-
retval.merge(self.map{|r,s| r + s}, retval.storage)
|
130
|
-
end
|
131
|
-
|
132
|
-
undef do_insert
|
133
|
-
|
134
|
-
public
|
135
|
-
# map each (nested) item in the collection into a string, suitable for placement in stdio
|
136
|
-
def inspected
|
137
|
-
raise Bud::Error, "join left unconverted to binary" if @rels.length > 2
|
138
|
-
tabnames = @origrels.map {|r| r.tabname.to_s}.join " * "
|
139
|
-
[["(#{tabnames}): [#{self.map{|r1, r2| "\n (#{r1.inspect}, #{r2.inspect})"}}]"]]
|
140
|
-
end
|
141
|
-
|
142
|
-
public
|
143
|
-
def pro(&blk) # :nodoc: all
|
144
|
-
pairs(&blk)
|
145
|
-
end
|
146
|
-
|
147
|
-
public
|
148
|
-
def each(mode=:both, &block) # :nodoc: all
|
149
|
-
mode = :storage if @bud_instance.stratum_first_iter
|
150
|
-
if mode == :storage
|
151
|
-
methods = [:storage]
|
152
|
-
else
|
153
|
-
methods = [:delta, :storage]
|
154
|
-
end
|
155
|
-
|
156
|
-
methods.each do |left_rel|
|
157
|
-
methods.each do |right_rel|
|
158
|
-
next if (mode == :both and left_rel == :storage and right_rel == :storage)
|
159
|
-
if @hashpreds.nil? or @hashpreds.empty?
|
160
|
-
nestloop_join(left_rel, right_rel, &block)
|
161
|
-
else
|
162
|
-
hash_join(left_rel, right_rel, &block)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
tick_hash_deltas
|
167
|
-
end
|
168
|
-
|
169
|
-
# given a * expression over n collections, form all combinations of items
|
170
|
-
# subject to an array of predicates, pred
|
171
|
-
# currently supports two options for equijoin predicates:
|
172
|
-
# general form: an array of arrays capturing a conjunction of equiv. classes
|
173
|
-
# [[table1.col1, table2.col2, table3.col3], [table1.col2, table2.col3]]
|
174
|
-
# common form: a hash capturing equality of a column on left with one on right.
|
175
|
-
# :col1 => :col2 (same as lefttable.col1 => righttable.col2)
|
176
|
-
public
|
177
|
-
def pairs(*preds, &blk)
|
178
|
-
@origpreds = preds
|
179
|
-
setup_preds(preds)
|
180
|
-
# given new preds, the state for the join will be different. set it up again.
|
181
|
-
setup_state if self.class <= Bud::BudJoin
|
182
|
-
blk.nil? ? self : map(&blk)
|
183
|
-
end
|
184
|
-
|
185
|
-
alias combos pairs
|
186
|
-
|
187
|
-
# the natural join: given a * expression over n collections, form all
|
188
|
-
# combinations of items that have the same values in matching fields
|
189
|
-
public
|
190
|
-
def matches(&blk)
|
191
|
-
preds = BudJoin::natural_preds(@bud_instance, @origrels)
|
192
|
-
pairs(*preds, &blk)
|
193
|
-
end
|
194
|
-
|
195
|
-
# given a * expression over 2 collections, form all combinations of items
|
196
|
-
# that satisfy the predicates +preds+, and project only onto the attributes
|
197
|
-
# of the first collection
|
198
|
-
public
|
199
|
-
def lefts(*preds, &blk)
|
200
|
-
setup_preds(preds)
|
201
|
-
# given new preds, the state for the join will be different. set it up again.
|
202
|
-
setup_state if self.class <= Bud::BudJoin
|
203
|
-
map{ |l,r| blk.nil? ? l : blk.call(l) }
|
204
|
-
end
|
205
|
-
|
206
|
-
# given a * expression over 2 collections, form all combinations of items
|
207
|
-
# that satisfy the predicates +preds+, and project only onto the attributes
|
208
|
-
# of the second item
|
209
|
-
public
|
210
|
-
def rights(*preds, &blk)
|
211
|
-
setup_preds(preds)
|
212
|
-
# given new preds, the state for the join will be different. set it up again.
|
213
|
-
setup_state if self.class <= Bud::BudJoin
|
214
|
-
map{ |l,r| blk.nil? ? r : blk.call(r) }
|
215
|
-
end
|
216
|
-
|
217
|
-
# given a * expression over 2 collections, form all combos of items that
|
218
|
-
# satisfy +preds+, and for any item from the 1st collection that has no
|
219
|
-
# matches in the 2nd, nil-pad it and include it in the output.
|
220
|
-
public
|
221
|
-
def outer(*preds, &blk)
|
222
|
-
@origpreds = preds
|
223
|
-
setup_preds(preds)
|
224
|
-
self.extend(Bud::BudOuterJoin)
|
225
|
-
blk.nil? ? self : map(&blk)
|
226
|
-
end
|
227
|
-
|
228
|
-
# AntiJoin
|
229
|
-
# note: unlike other join methods (e.g. lefts) all we do with the return value
|
230
|
-
# of block is check whether it's nil. Putting "projection" logic in the block
|
231
|
-
# has no effect on the output.
|
232
|
-
public
|
233
|
-
def anti(*preds, &blk)
|
234
|
-
return [] unless @bud_instance.stratum_first_iter
|
235
|
-
@origpreds = preds
|
236
|
-
# no projection involved here, so we can propagate the schema
|
237
|
-
@cols = @rels[0].cols
|
238
|
-
if preds == [] and blk.nil? and @cols.length == @rels[1].cols.length
|
239
|
-
preds = BudJoin::positionwise_preds(@bud_instance, rels)
|
240
|
-
end
|
241
|
-
setup_preds(preds)
|
242
|
-
setup_state if self.class <= Bud::BudJoin
|
243
|
-
if blk.nil?
|
244
|
-
if preds == [] # mismatched schemas -- no matches to be excluded
|
245
|
-
@exclude = []
|
246
|
-
else
|
247
|
-
# exclude those tuples of r that have a match
|
248
|
-
@exclude = map { |r, s| r }
|
249
|
-
end
|
250
|
-
else
|
251
|
-
# exclude tuples of r that pass the blk call
|
252
|
-
@exclude = map { |r, s| r unless blk.call(r, s).nil? }.compact
|
253
|
-
end
|
254
|
-
# XXX: @exclude is an Array, which makes include? O(n)
|
255
|
-
@rels[0].map {|r| (@exclude.include? r) ? nil : r}
|
256
|
-
end
|
257
|
-
|
258
|
-
private
|
259
|
-
def check_join_pred(pred, join_rels)
|
260
|
-
unless join_rels.include? pred[0]
|
261
|
-
raise Bud::CompileError, "illegal predicate: collection #{pred[0]} is not being joined"
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
# extract predicates on rellist[0] and recurse to right side with remainder
|
266
|
-
protected
|
267
|
-
def setup_preds(preds) # :nodoc: all
|
268
|
-
return if preds.empty?
|
269
|
-
allpreds = disambiguate_preds(preds)
|
270
|
-
allpreds = canonicalize_localpreds(@rels, allpreds)
|
271
|
-
# check for refs to collections that aren't being joined, Issue 191
|
272
|
-
unless @rels[1].class <= Bud::BudJoin
|
273
|
-
tabnames = @rels.map{ |r| r.tabname }
|
274
|
-
allpreds.each do |p|
|
275
|
-
check_join_pred(p[0], tabnames)
|
276
|
-
check_join_pred(p[1], tabnames)
|
277
|
-
end
|
278
|
-
end
|
279
|
-
@hashpreds = allpreds.reject {|p| p[0][0] != @rels[0].tabname}
|
280
|
-
@localpreds = @hashpreds
|
281
|
-
|
282
|
-
# only allow preds on the same table name if they're on a self-joined table
|
283
|
-
@localpreds.each do |p|
|
284
|
-
if p[0][0] == p[1][0] and not @selfjoins.include? p[0][0]
|
285
|
-
raise Bud::CompileError, "single-table predicate on #{p[0][0]} disallowed in joins"
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
@localpreds += allpreds.map do |p|
|
290
|
-
p if p[0][0] == p[1][0] and (p[0][0] == @rels[0].tabname or p[0][0] == @rels[1].tabname)
|
291
|
-
end.compact
|
292
|
-
otherpreds = allpreds - @localpreds
|
293
|
-
unless otherpreds.empty?
|
294
|
-
unless @rels[1].class <= Bud::BudJoin
|
295
|
-
raise Bud::CompileError, "join predicates don't match collections being joined: #{otherpreds.inspect}"
|
296
|
-
end
|
297
|
-
@rels[1].setup_preds(otherpreds)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
protected
|
302
|
-
def disambiguate_preds(preds) # :nodoc: all
|
303
|
-
if preds.size == 1 and preds[0].class <= Hash
|
304
|
-
predarray = preds[0].map do |k,v|
|
305
|
-
if k.class != v.class
|
306
|
-
raise Bud::CompileError, "inconsistent attribute ref style #{k.inspect} => #{v.inspect}"
|
307
|
-
elsif k.class <= Array
|
308
|
-
[k,v]
|
309
|
-
elsif k.class <= Symbol
|
310
|
-
if @origrels and @origrels.length == 2
|
311
|
-
[find_attr_match(k, @origrels[0]), find_attr_match(v, @origrels[1])]
|
312
|
-
else
|
313
|
-
[find_attr_match(k), find_attr_match(v)]
|
314
|
-
end
|
315
|
-
else
|
316
|
-
raise Bud::CompileError, "invalid attribute ref in #{k.inspect} => #{v.inspect}"
|
317
|
-
end
|
318
|
-
end
|
319
|
-
return decomp_preds(*predarray)
|
320
|
-
else
|
321
|
-
return decomp_preds(*preds)
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
# find element in @origrels that contains this +aname+ method
|
326
|
-
# if +rel+ is non-nil, only check that collection.
|
327
|
-
# after found, return the result of invoking +aname+ from chosen collection
|
328
|
-
protected
|
329
|
-
def find_attr_match(aname, rel=nil) # :nodoc: all
|
330
|
-
dorels = (rel.nil? ? @origrels : [rel])
|
331
|
-
match = nil
|
332
|
-
dorels.each do |r|
|
333
|
-
match ||= r if r.respond_to?(aname)
|
334
|
-
if r.respond_to?(aname) and match != r
|
335
|
-
raise Bud::CompileError, "ambiguous attribute :#{aname} in both #{match.tabname} and #{r.tabname}"
|
336
|
-
end
|
337
|
-
end
|
338
|
-
if match.nil?
|
339
|
-
raise Bud::CompileError, "attribute :#{aname} not found in any of #{dorels.map{|t| t.tabname}.inspect}"
|
340
|
-
end
|
341
|
-
match.send(aname)
|
342
|
-
end
|
343
|
-
|
344
|
-
protected
|
345
|
-
def decomp_preds(*preds) # :nodoc:all
|
346
|
-
# decompose each pred into a binary pred
|
347
|
-
return nil if preds.empty? or preds == [nil]
|
348
|
-
newpreds = []
|
349
|
-
preds.each do |p|
|
350
|
-
p.each_with_index do |c, i|
|
351
|
-
newpreds << [p[i], p[i+1]] unless p[i+1].nil?
|
352
|
-
end
|
353
|
-
end
|
354
|
-
newpreds
|
355
|
-
end
|
356
|
-
|
357
|
-
protected
|
358
|
-
def canonicalize_localpreds(rel_list, preds) # :nodoc:all
|
359
|
-
retval = preds.map do |p|
|
360
|
-
# reverse if rhs is rel_list[0], *unless* it's a self-join!
|
361
|
-
(p[1][0] == rel_list[0].tabname and p[1][0] != p[0][0]) ? p.reverse : p
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
public
|
366
|
-
def each_from_sym(buf_syms, &block) # :nodoc: all
|
367
|
-
buf_syms.each do |s|
|
368
|
-
each(s, &block)
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
private
|
373
|
-
# r is a tuple
|
374
|
-
# s is an array (combo) of joined tuples
|
375
|
-
def test_locals(r, s, *skips)
|
376
|
-
retval = true
|
377
|
-
if (@localpreds and skips and @localpreds.length > skips.length)
|
378
|
-
# check remainder of the predicates
|
379
|
-
@localpreds.each do |pred|
|
380
|
-
# skip skips, and self-join preds
|
381
|
-
next if (skips.include? pred or pred[0][0] == pred[1][0])
|
382
|
-
vals = []
|
383
|
-
(0..1).each do |i|
|
384
|
-
if pred[i][0] == @rels[0].tabname
|
385
|
-
vals[i] = r[pred[i][1] ]
|
386
|
-
else
|
387
|
-
ix, off = join_offset(pred[i])
|
388
|
-
vals[i] = s[ix][off]
|
389
|
-
end
|
390
|
-
end
|
391
|
-
if vals[0] != vals[1]
|
392
|
-
retval = false
|
393
|
-
break
|
394
|
-
end
|
395
|
-
end
|
396
|
-
end
|
397
|
-
return retval
|
398
|
-
end
|
399
|
-
|
400
|
-
private
|
401
|
-
def nestloop_join(left_rel, right_rel, &block)
|
402
|
-
@rels[0].each_from_sym([left_rel]) do |r|
|
403
|
-
@rels[1].each_from_sym([right_rel]) do |s|
|
404
|
-
s = [s] if origrels.length == 2
|
405
|
-
if test_locals(r, s)
|
406
|
-
yield([r] + s)
|
407
|
-
end
|
408
|
-
end
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
private
|
413
|
-
# calculate the position for a field in the result of a join:
|
414
|
-
# the tuple offset ("subtuple") and the attribute position within it
|
415
|
-
# ("offset")
|
416
|
-
def join_offset(entry)
|
417
|
-
name, offset = entry[0], entry[1]
|
418
|
-
|
419
|
-
# determine which subtuple of the collection contains the table
|
420
|
-
# referenced in entry.
|
421
|
-
subtuple = 0
|
422
|
-
origrels[1..origrels.length].each_with_index do |t,i|
|
423
|
-
if t.tabname == entry[0]
|
424
|
-
subtuple = i
|
425
|
-
break
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
return subtuple, offset
|
430
|
-
end
|
431
|
-
|
432
|
-
def tick_hash_deltas
|
433
|
-
# for hash_join, move old delta hashtables into storage hashtables
|
434
|
-
return if @hash_tables.nil?
|
435
|
-
(0..1).each do |i|
|
436
|
-
@hash_tables[i][:storage].merge!(@hash_tables[i][:delta]) do |k,l,r|
|
437
|
-
l+r
|
438
|
-
end
|
439
|
-
@hash_tables[i][:delta] = {}
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
# semi-naive symmetric hash join on first predicate
|
444
|
-
private
|
445
|
-
def hash_join(left_sym, right_sym, &block)
|
446
|
-
# we know that a hashpred has been canonicalized with @rels[0] in left offset
|
447
|
-
left_offset = @hashpreds.first[0][1]
|
448
|
-
right_subtuple, right_offset = join_offset(@hashpreds.first[1])
|
449
|
-
|
450
|
-
syms = [left_sym, right_sym]
|
451
|
-
|
452
|
-
syms.each_with_index do |probe_sym, probe_ix|
|
453
|
-
other_ix = 1 - probe_ix # bit-flip
|
454
|
-
other_sym = syms[other_ix]
|
455
|
-
probe_offset = (probe_ix == 0) ? left_offset : right_offset
|
456
|
-
|
457
|
-
# in a delta/storage join we do traditional one-sided hash join
|
458
|
-
# so don't probe from the storage side.
|
459
|
-
# the other side should have been built already!
|
460
|
-
if probe_sym == :storage and probe_sym != other_sym
|
461
|
-
next
|
462
|
-
end
|
463
|
-
|
464
|
-
# ready to do the symmetric hash join
|
465
|
-
rels[probe_ix].each_from_sym([probe_sym]) do |r|
|
466
|
-
r = [r] unless probe_ix == 1 and origrels.length > 2
|
467
|
-
attrval = (probe_ix == 0) ? r[0][left_offset] : r[right_subtuple][right_offset]
|
468
|
-
|
469
|
-
# insert into the prober's hashtable only if symmetric
|
470
|
-
if probe_sym == other_sym
|
471
|
-
@hash_tables[probe_ix][probe_sym][attrval] ||= []
|
472
|
-
@hash_tables[probe_ix][probe_sym][attrval] << r
|
473
|
-
end
|
474
|
-
|
475
|
-
# ...and probe the other hashtable
|
476
|
-
if @hash_tables[other_ix][other_sym][attrval].nil?
|
477
|
-
next
|
478
|
-
else
|
479
|
-
@hash_tables[other_ix][other_sym][attrval].each do |s_tup|
|
480
|
-
if probe_ix == 0
|
481
|
-
left = r; right = s_tup
|
482
|
-
else
|
483
|
-
left = s_tup; right = r
|
484
|
-
end
|
485
|
-
retval = left + right
|
486
|
-
yield retval if test_locals(left[0], right, @hashpreds.first)
|
487
|
-
end
|
488
|
-
end
|
489
|
-
end
|
490
|
-
end
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
# intended to be used to extend a BudJoin instance
|
495
|
-
module BudOuterJoin
|
496
|
-
public
|
497
|
-
def each(&block) # :nodoc:all
|
498
|
-
super(&block)
|
499
|
-
# Previous line finds all the matches. Now its time to ``preserve'' the
|
500
|
-
# outer tuples with no matches. Our trick: for each tuple of the outer,
|
501
|
-
# generate a singleton relation and join with inner. If result is empty,
|
502
|
-
# preserve tuple.
|
503
|
-
# XXX: This is totally inefficient: we should fold the identification of
|
504
|
-
# non-matches into the join algorithms. Another day.
|
505
|
-
@rels[0].each do |r|
|
506
|
-
t = @origrels[0].clone_empty
|
507
|
-
# need to uniquify the tablename here to avoid sharing join state with original
|
508
|
-
t.uniquify_tabname
|
509
|
-
t << r
|
510
|
-
j = BudJoin.new([t, @origrels[1]], @bud_instance, @origpreds)
|
511
|
-
|
512
|
-
# the following is "next if j.any?" on storage tuples *only*
|
513
|
-
any = false
|
514
|
-
j.each(:storage) do |j|
|
515
|
-
any = true
|
516
|
-
break
|
517
|
-
end
|
518
|
-
next if any
|
519
|
-
|
520
|
-
nulltup = @origrels[1].null_tuple
|
521
|
-
yield [r, nulltup]
|
522
|
-
end
|
523
|
-
end
|
524
|
-
end
|
525
|
-
end
|
526
|
-
|