bud 0.0.2

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.
Files changed (62) hide show
  1. data/LICENSE +9 -0
  2. data/README +30 -0
  3. data/bin/budplot +134 -0
  4. data/bin/budvis +201 -0
  5. data/bin/rebl +4 -0
  6. data/docs/README.md +13 -0
  7. data/docs/bfs.md +379 -0
  8. data/docs/bfs.raw +251 -0
  9. data/docs/bfs_arch.png +0 -0
  10. data/docs/bloom-loop.png +0 -0
  11. data/docs/bust.md +83 -0
  12. data/docs/cheat.md +291 -0
  13. data/docs/deploy.md +96 -0
  14. data/docs/diffs +181 -0
  15. data/docs/getstarted.md +296 -0
  16. data/docs/intro.md +36 -0
  17. data/docs/modules.md +112 -0
  18. data/docs/operational.md +96 -0
  19. data/docs/rebl.md +99 -0
  20. data/docs/ruby_hooks.md +19 -0
  21. data/docs/visualizations.md +75 -0
  22. data/examples/README +1 -0
  23. data/examples/basics/hello.rb +12 -0
  24. data/examples/basics/out +1103 -0
  25. data/examples/basics/out.new +856 -0
  26. data/examples/basics/paths.rb +51 -0
  27. data/examples/bust/README.md +9 -0
  28. data/examples/bust/bustclient-example.rb +23 -0
  29. data/examples/bust/bustinspector.html +135 -0
  30. data/examples/bust/bustserver-example.rb +18 -0
  31. data/examples/chat/README.md +9 -0
  32. data/examples/chat/chat.rb +45 -0
  33. data/examples/chat/chat_protocol.rb +8 -0
  34. data/examples/chat/chat_server.rb +29 -0
  35. data/examples/deploy/tokenring-ec2.rb +26 -0
  36. data/examples/deploy/tokenring-local.rb +17 -0
  37. data/examples/deploy/tokenring.rb +39 -0
  38. data/lib/bud/aggs.rb +126 -0
  39. data/lib/bud/bud_meta.rb +185 -0
  40. data/lib/bud/bust/bust.rb +126 -0
  41. data/lib/bud/bust/client/idempotence.rb +10 -0
  42. data/lib/bud/bust/client/restclient.rb +49 -0
  43. data/lib/bud/collections.rb +937 -0
  44. data/lib/bud/depanalysis.rb +44 -0
  45. data/lib/bud/deploy/countatomicdelivery.rb +50 -0
  46. data/lib/bud/deploy/deployer.rb +67 -0
  47. data/lib/bud/deploy/ec2deploy.rb +200 -0
  48. data/lib/bud/deploy/localdeploy.rb +41 -0
  49. data/lib/bud/errors.rb +15 -0
  50. data/lib/bud/graphs.rb +405 -0
  51. data/lib/bud/joins.rb +300 -0
  52. data/lib/bud/rebl.rb +314 -0
  53. data/lib/bud/rewrite.rb +523 -0
  54. data/lib/bud/rtrace.rb +27 -0
  55. data/lib/bud/server.rb +43 -0
  56. data/lib/bud/state.rb +108 -0
  57. data/lib/bud/storage/tokyocabinet.rb +170 -0
  58. data/lib/bud/storage/zookeeper.rb +178 -0
  59. data/lib/bud/stratify.rb +83 -0
  60. data/lib/bud/viz.rb +65 -0
  61. data/lib/bud.rb +797 -0
  62. metadata +330 -0
data/lib/bud/graphs.rb ADDED
@@ -0,0 +1,405 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'graphviz'
4
+
5
+ class GraphGen #:nodoc: all
6
+
7
+ def initialize(mapping, tableinfo, cycle, name, budtime, vizlevel, pics_dir, collapse=false, depanalysis=nil, cardinalities={})
8
+ #@graph = GraphViz.new(:G, :type => :digraph, :label => "", :ratio => 0.85 )
9
+ @graph = GraphViz.new(:G, :type => :digraph, :label => "")
10
+ @graph.node[:fontname] = "Times-Roman"
11
+ @graph.node[:fontsize] = 18
12
+ @graph.edge[:fontname] = "Times-Roman"
13
+ @graph.edge[:fontsize] = 18
14
+ @tiers = []
15
+ @cards = cardinalities
16
+ @name = name
17
+ @collapse = collapse
18
+ @depanalysis = depanalysis
19
+ @budtime = budtime
20
+ @vizlevel = vizlevel
21
+ @pics_dir = pics_dir
22
+ #@internals = {'count' => 1, 'localtick' => 1, 'stdio' => 1, 't_rules' => 1, 't_depends' => 1, 't_depends_tc' => 1, 't_provides' => 1, 't_cycle' => 1}
23
+ @internals = {'count' => 1, 'localtick' => 1, 'stdio' => 1} #, 't_rules' => 1, 't_depends' => 1, 't_depends_tc' => 1, 't_provides' => 1, 't_cycle' => 1}
24
+
25
+ # map: table -> stratum
26
+ @t2s = {}
27
+ mapping.each do |m|
28
+ @t2s[m[0]] = m[1].to_i
29
+ end
30
+
31
+ # map: table -> type
32
+ @tabinf = {}
33
+ tableinfo.each do |ti|
34
+ @tabinf[ti[0].to_s] = ti[1]
35
+ end
36
+
37
+ @redcycle = {}
38
+ cycle.each do |c|
39
+ puts "CYCLE: #{c.inspect}"
40
+ if c[2] and c[3]
41
+ if !@redcycle[c[0]]
42
+ @redcycle[c[0]] = []
43
+ end
44
+ @redcycle[c[0]] << c[1]
45
+ end
46
+ end
47
+
48
+ @nodes = {}
49
+ @edges = {}
50
+ @labels = {}
51
+ end
52
+
53
+ def safe_t2s(tab)
54
+ if @t2s[tab]
55
+ @t2s[tab]
56
+ else
57
+ words = tab.split(",")
58
+ maxs = 0
59
+ words.each do |w|
60
+ if @t2s[w] and @t2s[w] > maxs
61
+ maxs = @t2s[w]
62
+ end
63
+ end
64
+ return maxs
65
+ end
66
+ end
67
+
68
+ def name_bag(predicate, bag)
69
+ if bag[predicate]
70
+ return bag
71
+ else
72
+ bag[predicate] = true
73
+ res = bag
74
+ if @redcycle[predicate].nil?
75
+ return res
76
+ end
77
+ @redcycle[predicate].each do |rp|
78
+ res = name_bag(rp, res)
79
+ end
80
+ end
81
+
82
+ return res
83
+ end
84
+
85
+ def name_of(predicate)
86
+ # consider doing this in bud
87
+ # PAA
88
+ if @redcycle[predicate] and @collapse
89
+ puts "collapse #{predicate}, redcycle #{@redcycle[predicate].inspect}"
90
+ via = @redcycle[predicate]
91
+ bag = name_bag(predicate, {})
92
+ #str = bag.key_cols.sort.join(", ")
93
+ str = bag.keys.sort.join(", ")
94
+ return str
95
+ else
96
+ return predicate
97
+ end
98
+ end
99
+
100
+ def process(depends)
101
+
102
+ # collapsing NEG/+ cycles.
103
+ # we want to create a function from any predicate to (cycle_name or bottom)
104
+ # bottom if the predicate is not in a NEG/+ cycle. otherwise,
105
+ # its name is "CYC" + concat(sort(predicate names))
106
+
107
+ depends.each do |d|
108
+ #puts "DEP: #{d.inspect}"
109
+ head = d[1]
110
+ body = d[3]
111
+
112
+ # hack attack
113
+ if @internals[head] or @internals[body]
114
+ next
115
+ end
116
+
117
+ head = name_of(head)
118
+ body = name_of(body)
119
+ addonce(head, (head != d[1]))
120
+ addonce(body, (body != d[3]))
121
+ addedge(body, head, d[2], d[4], (head != d[1]), d[0])
122
+ end
123
+ end
124
+
125
+ def addonce(node, negcluster)
126
+ #puts "ADD NODE #{node}"
127
+ if !@nodes[node]
128
+ @nodes[node] = @graph.add_node(node)
129
+ if @cards and @cards[node]
130
+ @nodes[node].label = node +"\n (#{@cards[node].to_s})"
131
+ puts "IMAGE IS #{@cards[node]}"
132
+ #@nodes[node].image = @cards[node]
133
+ end
134
+
135
+ if @vizlevel >= 3
136
+ @nodes[node].URL = "javascript:openWin(\"#{node}\", #{@budtime})"
137
+ else
138
+ @nodes[node].URL = "#{node}.html"
139
+ end
140
+ end
141
+
142
+ if negcluster
143
+ # cleaning
144
+ res = node
145
+ node.split(", ").each_with_index do |p, i|
146
+ if i == 0
147
+ res = p
148
+ elsif i % 4 == 0
149
+ res = res + ",\n" + p
150
+ else
151
+ res = res + ", " + p
152
+ end
153
+ end
154
+ #@nodes[node].label = "<b>" + res + "</b>"
155
+ @nodes[node].label = res
156
+ @nodes[node].color = "red"
157
+ @nodes[node].shape = "octagon"
158
+ @nodes[node].penwidth = 3
159
+ @nodes[node].URL = "#{File.basename(@name)}_expanded.svg"
160
+ elsif @tabinf[node] and (@tabinf[node] == "Bud::BudTable")
161
+ @nodes[node].shape = "rect"
162
+ end
163
+ end
164
+
165
+ def addedge(body, head, op, nm, negcluster, rule_id=nil)
166
+ return if body.nil? or head.nil?
167
+ body = body.to_s
168
+ head = head.to_s
169
+ return if negcluster and body == head
170
+
171
+ ekey = body + head
172
+ if !@edges[ekey]
173
+ @edges[ekey] = @graph.add_edge(@nodes[body], @nodes[head], :penwidth => 5)
174
+ @edges[ekey].arrowsize = 2
175
+
176
+ @edges[ekey].URL = "#{rule_id}.html" unless rule_id.nil?
177
+ if head =~ /_msg\z/
178
+ @edges[ekey].minlen = 2
179
+ else
180
+ @edges[ekey].minlen = 1.5
181
+ end
182
+ @labels[ekey] = {}
183
+
184
+ end
185
+
186
+ #@edges[ekey].minlen = 5 if negcluster and body == head
187
+
188
+ if op == '<+'
189
+ @labels[ekey][' +/-'] = true
190
+ elsif op == "<~"
191
+ @edges[ekey].style = 'dashed'
192
+ elsif op == "<-"
193
+ #@labels[ekey] = @labels[ekey] + 'NEG(del)'
194
+ @labels[ekey][' +/-'] = true
195
+ @edges[ekey].arrowhead = 'veeodot'
196
+ end
197
+ if nm and head != "T"
198
+ # hm, nonmono
199
+ @edges[ekey].arrowhead = 'veeodot'
200
+ end
201
+ end
202
+
203
+ def finish
204
+ @labels.each_key do |k|
205
+ #@edges[k].label = @labels[k].key_cols.join(" ")
206
+ @edges[k].label = @labels[k].keys.join(" ")
207
+ end
208
+
209
+ addonce("S", false)
210
+ addonce("T", false)
211
+
212
+ @nodes["T"].URL = "javascript:advanceTo(#{@budtime+1})"
213
+ @nodes["S"].URL = "javascript:advanceTo(#{@budtime-1})"
214
+
215
+ @nodes["S"].color = "blue"
216
+ @nodes["T"].color = "blue"
217
+ @nodes["S"].shape = "diamond"
218
+ @nodes["T"].shape = "diamond"
219
+
220
+ @nodes["S"].penwidth = 3
221
+ @nodes["T"].penwidth = 3
222
+
223
+ @tabinf.each_pair do |k, v|
224
+
225
+ unless @nodes[name_of(k.to_s)] or k.to_s =~ /_tbl/ or @internals[k.to_s] or (k.to_s =~ /^t_/ and @budtime != 0)
226
+ addonce(k.to_s, false)
227
+ end
228
+ if v == "Bud::BudPeriodic"
229
+ puts "adding edge S -> #{@nodes[k.to_s]}"
230
+ addedge("S", k.to_s, false, false, false)
231
+ end
232
+ end
233
+
234
+ unless @depanalysis.nil?
235
+ @depanalysis.source.each {|s| addedge("S", s.pred, false, false, false) }
236
+ @depanalysis.sink.each {|s| addedge(s.pred, "T", false, false, false) }
237
+
238
+ unless @depanalysis.underspecified.empty?
239
+ addonce("??", false)
240
+ @nodes["??"].color = "red"
241
+ @nodes["??"].shape = "diamond"
242
+ @nodes["??"].penwidth = 2
243
+ end
244
+
245
+ @depanalysis.underspecified.each do |u|
246
+ if u.input
247
+ addedge(u.pred, "??", false, false, false)
248
+ else
249
+ addedge("??", u.pred, false, false, false)
250
+ end
251
+ end
252
+ end
253
+
254
+ suffix = @collapse ? "collapsed" : "expanded"
255
+ fn = "#{@name}_#{suffix}.svg"
256
+ puts "fn is #{fn}"
257
+ staging = "#{fn}_staging"
258
+ @graph.output(:svg => staging)
259
+ @graph.output(:dot => "#{fn}.dot")
260
+ @graph.output(:png => "#{fn}.png")
261
+ fin = File.open(staging, "r")
262
+ fout = File.open(fn, "w")
263
+ while line = fin.gets
264
+ fout.puts line.gsub("<title>G</title>", svg_javascript())
265
+ end
266
+ fin.close
267
+ fout.close
268
+ File.delete(staging)
269
+ end
270
+
271
+ def output_base
272
+ if @vizlevel >= 3
273
+ @pics_dir
274
+ else
275
+ "bud_doc"
276
+ end
277
+ end
278
+
279
+ def dump(shredded_rules)
280
+ return if shredded_rules.nil?
281
+
282
+ fout = File.new("#{output_base}/style.css", "w")
283
+ fout.puts css
284
+ fout.close
285
+
286
+ code = {}
287
+ rules = {}
288
+ convertor = Syntax::Convertors::HTML.for_syntax "ruby"
289
+ shredded_rules.each do |s|
290
+ #fout = File.new("#{output_base}/#{s[0]}.html", "w+")
291
+ fout = File.new("#{output_base}/#{s[0]}.html", "w+")
292
+ fout.puts header
293
+ fout.puts "<h1>Rule #{s[0]}</h1><br>"
294
+
295
+ c = convertor.convert(s[3])
296
+ c.sub!(/^<pre>/, "<pre class=\"code\" style='font-size:20px'>\n")
297
+ fout.puts c
298
+ rules[s[0]] = [s[1], s[3]]
299
+ fout.close
300
+ end
301
+
302
+ rules.each_pair do |k, v|
303
+ if !code[v[0]]
304
+ code[v[0]] = ""
305
+ end
306
+ #code[v[0]] = "<br># RULE #{k}<br> " + code[v[0]] + "<br>" + v[1]
307
+ code[v[0]] = "\n# RULE #{k}\n " + code[v[0]] + "\n" + v[1]
308
+ end
309
+ @nodes.each_pair do |k, v|
310
+ fout = File.new("#{output_base}/#{k}.html", "w+")
311
+ fout.puts header
312
+ k.split(", ").each do |i|
313
+ unless code[i].nil?
314
+ c = convertor.convert(code[i])
315
+ c.sub!(/^<pre>/, "<pre class=\"code\">\n")
316
+ fout.puts c
317
+ end
318
+ end
319
+ fout.puts("</body></html>")
320
+ fout.close
321
+ end
322
+ end
323
+
324
+
325
+ def header
326
+ return "<html><meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>\n<head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" /></head><body>"
327
+ end
328
+
329
+ def css
330
+ return "pre.code {
331
+ padding: 1ex 1ex 1ex 1ex;
332
+ border: 4px groove #CC0000;
333
+ overflow-x: auto;
334
+ }
335
+
336
+ pre.code span.attribute { color: #009900; }
337
+ pre.code span.char { color: #F00; }
338
+ pre.code span.class { color: #A020F0; font-weight: bold; }
339
+ pre.code span.comment { color: #0000FF; }
340
+ pre.code span.constant { color: #008B8B; }
341
+ pre.code span.escape { color: #6A5ACD; }
342
+ pre.code span.expr { color: #2222CC; }
343
+ pre.code span.global { color: #11AA44; }
344
+ pre.code span.ident { color: #000000; }
345
+ pre.code span.keyword { color: #A52A2A; font-weight: bold; }
346
+ pre.code span.method { color: #008B8B; }
347
+ pre.code span.module { color: #A020F0; font-weight: bold; }
348
+ pre.code span.number { color: #DD00DD; }
349
+ pre.code span.punct { color: #6A5ACD; }
350
+ pre.code span.regex { color: #DD00DD; }
351
+ pre.code span.string { color: #DD00DD; }
352
+ pre.code span.symbol { color: #008B8B; }
353
+ "
354
+ end
355
+
356
+
357
+ end
358
+
359
+ def svg_javascript
360
+ return "
361
+ <script type='text/javascript'>
362
+ <![CDATA[
363
+
364
+ var windows = new Array()
365
+ var info = new Array()
366
+
367
+ function openWin(target, time) {
368
+ win = window.open(target + \"_\" + time + \".html\", target, \"location=no,width=400,height=180,left=0,status=no\");
369
+ // hm, an associative array, how strange.
370
+ info[target] = 1
371
+ }
372
+
373
+ function advanceTo(time) {
374
+ arr = gup(\"wins\").split(\",\");
375
+ for (i=0; i < arr.length; i++) {
376
+ if (arr[i] != \"\") {
377
+ openWin(arr[i], time);
378
+ }
379
+ }
380
+ str = '';
381
+ // getting 'key_cols'
382
+ for (var i in info) {
383
+ str = str + ',' + i;
384
+ }
385
+ self.window.location.href = 'tm_' + time + '_expanded.svg?wins=' + str;
386
+ }
387
+
388
+ // off the netz
389
+ function gup( name )
390
+ {
391
+ name = name.replace(/[\[]/,\"\\\[\").replace(/[\]]/,\"\\\]\");
392
+ var regexS = \"[\\?&]\"+name+\"=([^&#]*)\";
393
+ var regex = new RegExp( regexS );
394
+ var results = regex.exec( window.location.href );
395
+ if( results == null )
396
+ return \"\";
397
+ else
398
+ return results[1];
399
+ }
400
+
401
+ ]]>
402
+ </script>
403
+ "
404
+ end
405
+
data/lib/bud/joins.rb ADDED
@@ -0,0 +1,300 @@
1
+ module Bud
2
+ class BudJoin < BudCollection
3
+ attr_accessor :rels, :origrels, :origpreds # :nodoc: all
4
+ attr_reader :hash_tables # :nodoc: all
5
+
6
+ def initialize(rellist, bud_instance, preds=nil) # :nodoc: all
7
+ @schema = []
8
+ otherpreds = nil
9
+ @origpreds = preds
10
+ @bud_instance = bud_instance
11
+ @localpreds = nil
12
+
13
+ # if any elements on rellist are BudJoins, suck up their contents
14
+ tmprels = []
15
+ rellist.each do |r|
16
+ if r.class <= BudJoin
17
+ tmprels += r.origrels
18
+ preds += r.origpreds
19
+ else
20
+ tmprels << r
21
+ end
22
+ end
23
+ rellist = tmprels
24
+ @origrels = rellist
25
+
26
+ # recurse to form a tree of binary BudJoins
27
+ @rels = [rellist[0]]
28
+ @rels << (rellist.length == 2 ? rellist[1] : BudJoin.new(rellist[1..rellist.length-1], @bud_instance, nil))
29
+ # derive schema: one column for each table.
30
+ # duplicated inputs get distinguishing numeral
31
+ @schema = []
32
+ index = 0
33
+ retval = rellist.reduce({}) do |memo, r|
34
+ index += 1
35
+ memo[r.tabname.to_s] ||= 0
36
+ newstr = r.tabname.to_s + ((memo[r.tabname.to_s] > 0) ? ("_" + memo[r.tabname.to_s].to_s) : "")
37
+ @schema << newstr.to_sym
38
+ memo[r.tabname.to_s] += 1
39
+ memo
40
+ end
41
+
42
+ setup_preds(preds) unless preds.nil? or preds.empty?
43
+ setup_state
44
+ end
45
+
46
+ public
47
+ def state_id # :nodoc: all
48
+ Marshal.dump([@rels.map{|r| r.tabname}, @localpreds]).hash
49
+ end
50
+
51
+ # initialize the state for this join to be carried across iterations within a fixpoint
52
+ private
53
+ def setup_state
54
+ sid = state_id
55
+ @tabname = ("temp_join"+state_id.to_s).to_sym
56
+ @bud_instance.joinstate[sid] ||= [{:storage => {}, :delta => {}}, {:storage => {}, :delta => {}}]
57
+ @hash_tables = @bud_instance.joinstate[sid]
58
+ end
59
+
60
+ private_class_method
61
+ def self.natural_preds(bud_instance, rels)
62
+ preds = []
63
+ rels.each do |r|
64
+ rels.each do |s|
65
+ matches = r.schema & s.schema
66
+ matches.each do |c|
67
+ preds << [bud_instance.send(r.tabname).send(c), bud_instance.send(s.tabname).send(c)] unless r.tabname.to_s >= s.tabname.to_s
68
+ end
69
+ end
70
+ end
71
+ preds.uniq
72
+ end
73
+
74
+ # flatten joined items into arrays, with attribute accessors inherited
75
+ # from the input collections, disambiguated via suffix indexes as needed.
76
+ # similar to <tt>SELECT * FROM ... WHERE...</tt> block in SQL.
77
+ public
78
+ def flatten(*preds)
79
+ setup_preds(preds) unless preds.nil? or preds.size == 0
80
+ flat_schema = @rels.map{|r| r.schema}.flatten(1)
81
+ dupfree_schema = []
82
+ # while loop here (inefficiently) ensures no collisions
83
+ while dupfree_schema == [] or dupfree_schema.uniq.length < dupfree_schema.length
84
+ dupfree_schema = []
85
+ flat_schema.reduce({}) do |memo, r|
86
+ if r.to_s.include?("_") and ((r.to_s.rpartition("_")[2] =~ /^\d+$/) == 0)
87
+ r = r.to_s.rpartition("_")[0].to_sym
88
+ end
89
+ memo[r] ||= 0
90
+ if memo[r] == 0
91
+ dupfree_schema << r.to_s.to_sym
92
+ else
93
+ dupfree_schema << (r.to_s + "_" + (memo[r]).to_s).to_sym
94
+ end
95
+ memo[r] += 1
96
+ memo
97
+ end
98
+ flat_schema = dupfree_schema
99
+ end
100
+ retval = BudScratch.new('temp_flatten', bud_instance, dupfree_schema)
101
+ retval.uniquify_tabname
102
+ retval.merge(self.map{|r,s| r + s}, retval.storage)
103
+ end
104
+
105
+ undef do_insert
106
+
107
+ public
108
+ # map each (nested) item in the collection into a string, suitable for placement in stdio
109
+ def inspected
110
+ raise BudError, "join left unconverted to binary" if @rels.length > 2
111
+ self.map{|r1, r2| ["\[ #{r1.inspect} #{r2.inspect} \]"]}
112
+ end
113
+
114
+ public
115
+ def pro(&blk) # :nodoc: all
116
+ pairs(&blk)
117
+ end
118
+
119
+ public
120
+ def each(mode=:both, &block) # :nodoc: all
121
+ mode = :storage if @bud_instance.stratum_first_iter
122
+ if mode == :storage
123
+ methods = [:storage]
124
+ else
125
+ methods = [:delta, :storage]
126
+ end
127
+
128
+ methods.each do |left_rel|
129
+ methods.each do |right_rel|
130
+ next if (mode == :delta and left_rel == :storage and right_rel == :storage)
131
+ if @localpreds.nil? or @localpreds.empty?
132
+ nestloop_join(left_rel, right_rel, &block)
133
+ else
134
+ hash_join(left_rel, right_rel, &block)
135
+ end
136
+ end
137
+ end
138
+ tick_hash_deltas
139
+ end
140
+
141
+ public
142
+ def each_from_sym(buf_syms, &block) # :nodoc: all
143
+ buf_syms.each do |s|
144
+ each(s, &block)
145
+ end
146
+ end
147
+
148
+ private
149
+ # r is a tuple
150
+ # s is an array (combo) of joined tuples
151
+ def test_locals(r, s, *skips)
152
+ retval = true
153
+ if (@localpreds and skips and @localpreds.length > skips.length)
154
+ # check remainder of the predicates
155
+ @localpreds.each do |pred|
156
+ next if skips.include? pred
157
+ r_offset, s_index, s_offset = join_offsets(pred)
158
+ if r[r_offset] != s[s_index][s_offset]
159
+ retval = false
160
+ break
161
+ end
162
+ end
163
+ end
164
+ return retval
165
+ end
166
+
167
+ private
168
+ def nestloop_join(left_rel, right_rel, &block)
169
+ @rels[0].each_from_sym([left_rel]) do |r|
170
+ @rels[1].each_from_sym([right_rel]) do |s|
171
+ s = [s] if origrels.length == 2
172
+ yield([r] + s) if test_locals(r, s)
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+ # calculate the attribute position for the left table in the join ("left_offset")
179
+ # the right table may itself be a nested tuple from a join, so calculate
180
+ # the tuple offset ("right_subtuple") and the attribute position within it
181
+ # ("right_offset")
182
+ def join_offsets(pred)
183
+ right_entry = pred[1]
184
+ right_name, right_offset = right_entry[0], right_entry[1]
185
+ left_entry = pred[0]
186
+ left_name, left_offset = left_entry[0], left_entry[1]
187
+
188
+ # determine which subtuple of right collection contains the table
189
+ # referenced in RHS of pred. note that right collection doesn't contain the
190
+ # first entry in rels, which is the left collection
191
+ right_subtuple = 0
192
+ origrels[1..origrels.length].each_with_index do |t,i|
193
+ if t.tabname == pred[1][0]
194
+ right_subtuple = i
195
+ break
196
+ end
197
+ end
198
+
199
+ return left_offset, right_subtuple, right_offset
200
+ end
201
+
202
+ def tick_hash_deltas
203
+ # for hash_join, move old delta hashtables into storage hashtables
204
+ return if @hash_tables.nil?
205
+ (0..1).each do |i|
206
+ @hash_tables[i][:storage].merge!(@hash_tables[i][:delta]) do |k,l,r|
207
+ l+r
208
+ end
209
+ @hash_tables[i][:delta] = {}
210
+ end
211
+ end
212
+
213
+ # semi-naive symmetric hash join on first predicate
214
+ private
215
+ def hash_join(left_sym, right_sym, &block)
216
+ left_offset, right_subtuple, right_offset = join_offsets(@localpreds.first)
217
+
218
+ syms = [left_sym, right_sym]
219
+
220
+ syms.each_with_index do |probe_sym, probe_ix|
221
+ other_ix = 1 - probe_ix # bit-flip
222
+ other_sym = syms[other_ix]
223
+ probe_offset = (probe_ix == 0) ? left_offset : right_offset
224
+
225
+ # in a delta/storage join we do traditional one-sided hash join
226
+ # so don't probe from the storage side.
227
+ # the other side should have been built already!
228
+ if probe_sym == :storage and probe_sym != other_sym
229
+ next
230
+ end
231
+
232
+ # ready to do the symmetric hash join
233
+ rels[probe_ix].each_from_sym([probe_sym]) do |r|
234
+ r = [r] unless probe_ix == 1 and origrels.length > 2
235
+ attrval = (probe_ix == 0) ? r[0][left_offset] : r[right_subtuple][right_offset]
236
+
237
+ # insert into the prober's hashtable only if symmetric ...
238
+ if probe_sym == other_sym
239
+ @hash_tables[probe_ix][probe_sym][attrval] ||= []
240
+ @hash_tables[probe_ix][probe_sym][attrval] << r
241
+ end
242
+
243
+ # ...and probe the other hashtable
244
+ next if @hash_tables[other_ix][other_sym][attrval].nil?
245
+ @hash_tables[other_ix][other_sym][attrval].each do |s_tup|
246
+ if probe_ix == 0
247
+ left = r; right = s_tup
248
+ else
249
+ left = s_tup; right = r
250
+ end
251
+ retval = left + right
252
+ yield retval if test_locals(left[0], right, @localpreds.first)
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ class BudLeftJoin < BudJoin # :nodoc: all
260
+ def initialize(rellist, bud_instance, preds=nil)
261
+ raise(BudError, "Left Join only defined for two relations") unless rellist.length == 2
262
+ super(rellist, bud_instance, preds)
263
+ @origpreds = preds
264
+ preds.each do |k,v|
265
+ if k.class <= Array
266
+ raise Bud::CompileError, "in leftjoin, attribute refs must have style :col1 => :col2"
267
+ end
268
+ end
269
+ end
270
+
271
+ public
272
+ def each(&block) # :nodoc:all
273
+ super(&block)
274
+ # previous line finds all the matches.
275
+ # now its time to ``preserve'' the outer tuples with no matches.
276
+ # this is totally inefficient: we should fold the identification of non-matches
277
+ # into the join algorithms. Another day.
278
+ # our trick: for each tuple of the outer, generate a singleton relation
279
+ # and join with inner. If result is empty, preserve tuple.
280
+ @rels[0].each do |r|
281
+ t = @origrels[0].clone_empty
282
+ # need to uniquify the tablename here to avoid sharing join state with original
283
+ t.uniquify_tabname
284
+ t << r
285
+ j = BudJoin.new([t, @origrels[1]], @bud_instance, @origpreds)
286
+
287
+ # the following is "next if j.any?" on storage tuples *only*
288
+ any = false
289
+ j.each(:storage) do |j|
290
+ any = true
291
+ break
292
+ end
293
+ next if any
294
+
295
+ nulltup = @origrels[1].null_tuple
296
+ yield [r, nulltup]
297
+ end
298
+ end
299
+ end
300
+ end