bud 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/History.txt +41 -0
- data/README.md +5 -4
- data/docs/cheat.md +108 -9
- data/lib/bud/aggs.rb +4 -2
- data/lib/bud/bud_meta.rb +1 -4
- data/lib/bud/collections.rb +55 -27
- data/lib/bud/executor/README.rescan +2 -1
- data/lib/bud/executor/elements.rb +33 -37
- data/lib/bud/executor/join.rb +88 -110
- data/lib/bud/lattice-core.rb +21 -13
- data/lib/bud/lattice-lib.rb +73 -41
- data/lib/bud/monkeypatch.rb +16 -17
- data/lib/bud/rewrite.rb +13 -10
- data/lib/bud/source.rb +3 -1
- data/lib/bud.rb +85 -46
- metadata +41 -27
@@ -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
|
-
#
|
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
|
-
#
|
162
|
-
#
|
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
|
-
#
|
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
|
-
#
|
174
|
-
# in rescan, it may invalidate
|
175
|
-
# the
|
176
|
-
# enable a refill of that table at run-time
|
177
|
-
@outputs.each do |
|
178
|
-
|
179
|
-
o.
|
180
|
-
|
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(
|
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
|
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
|
-
|
301
|
-
|
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,
|
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
|
-
#
|
469
|
-
#
|
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
|
-
#
|
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
|
-
#
|
481
|
-
|
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
|
data/lib/bud/executor/join.rb
CHANGED
@@ -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.
|
10
|
+
@relnames = @rels.map{|r| r.qualified_tabname}
|
11
11
|
@cols = []
|
12
12
|
@bud_instance = bud_instance
|
13
13
|
@origpreds = preds
|
14
|
-
@localpreds =
|
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
|
-
|
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.
|
33
|
-
memo[r.
|
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
|
-
|
47
|
-
memo[
|
48
|
-
newstr =
|
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[
|
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.
|
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
|
-
|
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
|
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
|
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 (
|
99
|
-
p[1][0] != @rels[1].
|
100
|
-
or (p[0][0] != @rels[1].
|
101
|
-
and p[1][0] == @rels[1].
|
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.
|
112
|
-
p
|
113
|
-
end
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@keys
|
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 "#{
|
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.
|
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
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
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
|
-
|
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].
|
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.
|
254
|
+
if @selfjoins.include? source.qualified_tabname
|
273
255
|
offsets = []
|
274
|
-
@relnames.each_with_index{|r,i| offsets << i if r == source.
|
256
|
+
@relnames.each_with_index{|r,i| offsets << i if r == source.qualified_tabname}
|
275
257
|
else
|
276
|
-
offsets = [@relnames.index(source.
|
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
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
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].
|
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].
|
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
|
-
|
463
|
-
|
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
|
-
|
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
|
-
|
502
|
-
push_out([t,
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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.
|
613
|
+
puts "#{tabname} rel:#{@rhs.qualified_tabname} invalidated" if $BUD_DEBUG
|
636
614
|
@hash_tables[1] = {}
|
637
615
|
end
|
638
616
|
end
|
data/lib/bud/lattice-core.rb
CHANGED
@@ -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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
197
|
-
# morphisms to just produce Enumerable
|
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
|