bud 0.9.6 → 0.9.7
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.
- 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
|