bud 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README +33 -16
  2. data/bin/budplot +42 -65
  3. data/bin/budtimelines +235 -0
  4. data/bin/budvis +24 -122
  5. data/bin/rebl +1 -0
  6. data/docs/README.md +21 -10
  7. data/docs/bfs.md +4 -6
  8. data/docs/c.html +251 -0
  9. data/docs/cheat.md +45 -30
  10. data/docs/deploy.md +26 -26
  11. data/docs/getstarted.md +6 -4
  12. data/docs/visualizations.md +43 -31
  13. data/examples/chat/chat.rb +4 -9
  14. data/examples/chat/chat_server.rb +1 -8
  15. data/examples/deploy/deploy_ip_port +1 -0
  16. data/examples/deploy/keys.rb +5 -0
  17. data/examples/deploy/tokenring-ec2.rb +9 -9
  18. data/examples/deploy/{tokenring-local.rb → tokenring-fork.rb} +3 -5
  19. data/examples/deploy/tokenring-thread.rb +15 -0
  20. data/examples/deploy/tokenring.rb +25 -17
  21. data/lib/bud/aggs.rb +87 -25
  22. data/lib/bud/bud_meta.rb +48 -31
  23. data/lib/bud/bust/bust.rb +16 -15
  24. data/lib/bud/collections.rb +207 -232
  25. data/lib/bud/depanalysis.rb +1 -0
  26. data/lib/bud/deploy/countatomicdelivery.rb +8 -20
  27. data/lib/bud/deploy/deployer.rb +16 -16
  28. data/lib/bud/deploy/ec2deploy.rb +34 -35
  29. data/lib/bud/deploy/forkdeploy.rb +90 -0
  30. data/lib/bud/deploy/threaddeploy.rb +38 -0
  31. data/lib/bud/graphs.rb +103 -199
  32. data/lib/bud/joins.rb +190 -41
  33. data/lib/bud/monkeypatch.rb +84 -0
  34. data/lib/bud/rebl.rb +8 -1
  35. data/lib/bud/rewrite.rb +152 -49
  36. data/lib/bud/server.rb +1 -0
  37. data/lib/bud/state.rb +24 -10
  38. data/lib/bud/storage/dbm.rb +170 -0
  39. data/lib/bud/storage/tokyocabinet.rb +5 -1
  40. data/lib/bud/stratify.rb +6 -7
  41. data/lib/bud/viz.rb +31 -17
  42. data/lib/bud/viz_util.rb +204 -0
  43. data/lib/bud.rb +271 -244
  44. data/lib/bud.rb.orig +806 -0
  45. metadata +43 -22
  46. data/docs/bfs.raw +0 -251
  47. data/docs/diffs +0 -181
  48. data/examples/basics/out +0 -1103
  49. data/examples/basics/out.new +0 -856
  50. data/lib/bud/deploy/localdeploy.rb +0 -53
data/lib/bud/rewrite.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'ruby2ruby'
3
3
 
4
- class RuleRewriter < Ruby2Ruby # :nodoc: all
4
+ class RuleRewriter < Ruby2Ruby # :nodoc: all
5
5
  attr_accessor :rule_indx, :rules, :depends
6
6
 
7
7
  def initialize(seed, bud_instance)
@@ -9,8 +9,9 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
9
9
  @ops = {:<< => 1, :< => 1, :<= => 1}
10
10
  @monotonic_whitelist = {
11
11
  :== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1,
12
- :* => 1, :pairs => 1, :matches => 1, :flatten => 1,
13
- :lefts => 1, :rights => 1, :map => 1, :pro => 1, :schema => 1
12
+ :* => 1, :pairs => 1, :matches => 1, :combos => 1, :flatten => 1,
13
+ :lefts => 1, :rights => 1, :map => 1, :flat_map => 1, :pro => 1,
14
+ :schema => 1, :keys => 1, :values => 1, :payloads => 1, :~ => 1
14
15
  }
15
16
  @temp_ops = {:-@ => 1, :~ => 1, :+@ => 1}
16
17
  @tables = {}
@@ -22,6 +23,16 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
22
23
  super()
23
24
  end
24
25
 
26
+ def call_is_attr_deref?(recv, op)
27
+ if recv.first == :call and @bud_instance.tables.has_key? recv[2]
28
+ schema = @bud_instance.send(recv[2]).schema
29
+ if schema and schema.include? op
30
+ return true
31
+ end
32
+ end
33
+ return false
34
+ end
35
+
25
36
  def process_call(exp)
26
37
  recv, op, args = exp
27
38
  if recv.nil? and args == s(:arglist) and @collect
@@ -32,10 +43,11 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
32
43
  do_rule(exp)
33
44
  else
34
45
  if recv and recv.class == Sexp
35
- # ignore accessors of iterator variables
36
- unless recv.first == :lvar
37
- @nm = true if op == :-@
38
- @nm = true unless (@monotonic_whitelist[op] or @bud_instance.tables.has_key? op)
46
+ # for CALM analysis, mark deletion rules as non-monotonic
47
+ @nm = true if op == :-@
48
+ # don't worry about monotone ops, table names, table.attr calls, or accessors of iterator variables
49
+ unless @monotonic_whitelist[op] or @bud_instance.tables.has_key? op or call_is_attr_deref?(recv, op) or recv.first == :lvar
50
+ @nm = true
39
51
  end
40
52
  end
41
53
  if @temp_ops[op]
@@ -52,22 +64,27 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
52
64
  return rhs
53
65
  end
54
66
 
55
- def record_rule(lhs, op, rhs)
56
- rule_txt = "#{lhs} #{op} (#{rhs})"
67
+ def reset_instance_vars
68
+ @tables = {}
69
+ @nm = false
70
+ @temp_op = nil
71
+ end
72
+
73
+ def record_rule(lhs, op, rhs_pos, rhs)
74
+ rule_txt_orig = "#{lhs} #{op} (#{rhs})"
75
+ rule_txt = "#{lhs} #{op} (#{rhs_pos})"
57
76
  if op == :<
58
77
  op = "<#{@temp_op}"
59
78
  else
60
79
  op = op.to_s
61
80
  end
62
81
 
63
- @rules << [@rule_indx, lhs, op, rule_txt]
82
+ @rules << [@rule_indx, lhs, op, rule_txt, rule_txt_orig]
64
83
  @tables.each_pair do |t, non_monotonic|
65
84
  @depends << [@rule_indx, lhs, op, t, non_monotonic]
66
85
  end
67
86
 
68
- @tables = {}
69
- @nm = false
70
- @temp_op = nil
87
+ reset_instance_vars
71
88
  @rule_indx += 1
72
89
  end
73
90
 
@@ -83,17 +100,28 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
83
100
  def do_rule(exp)
84
101
  lhs = process exp[0]
85
102
  op = exp[1]
86
- rhs = collect_rhs(map2pro(exp[2]))
87
- record_rule(lhs, op, rhs)
103
+ pro_rules = map2pro(exp[2])
104
+ if @bud_instance.options[:no_attr_rewrite]
105
+ rhs = collect_rhs(pro_rules)
106
+ rhs_pos = rhs
107
+ else
108
+ # need a deep copy of the rules so we can keep a version without AttrName Rewrite
109
+ pro_rules2 = Marshal.load(Marshal.dump(pro_rules))
110
+ rhs = collect_rhs(pro_rules)
111
+ reset_instance_vars
112
+ rhs_pos = collect_rhs(AttrNameRewriter.new(@bud_instance).process(pro_rules2))
113
+ end
114
+ record_rule(lhs, op, rhs_pos, rhs)
88
115
  drain(exp)
89
116
  end
90
117
 
91
118
  # Look for top-level map on a base-table on rhs, and rewrite to pro
92
119
  def map2pro(exp)
93
120
  if exp[1] and exp[1][0] and exp[1][0] == :iter \
94
- and exp[1][1] and exp[1][1][1] == :call \
95
- and exp[1][1][2] == :map
96
- exp[1][1][2] = :pro
121
+ and exp[1][1] and exp[1][1][1] and exp[1][1][1][0] == :call
122
+ if exp[1][1][2] == :map
123
+ exp[1][1][2] = :pro
124
+ end
97
125
  end
98
126
  exp
99
127
  end
@@ -104,6 +132,77 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
104
132
  end
105
133
  end
106
134
 
135
+ # Rewrite named-column refs to positional refs
136
+ class AttrNameRewriter < SexpProcessor # :nodoc: all
137
+ def initialize(bud_instance)
138
+ super()
139
+ self.require_empty = false
140
+ self.expected = Sexp
141
+ @iterhash ||= {}
142
+ @collnames = []
143
+ @bud_instance = bud_instance
144
+ end
145
+
146
+ # some icky special-case parsing to find mapping between collection names and iter vars
147
+ def process_iter(exp)
148
+ if exp[1] and exp[1][0] == :call
149
+ gather_collection_names(exp[1])
150
+
151
+ # now find iter vars and match up
152
+ if exp[2] and exp[2][0] == :lasgn and @collnames.size == 1 #single-table iter
153
+ raise Bud::CompileError, "nested redefinition of block variable \"#{exp[2][1]}\" not allowed" if @iterhash[exp[2][1]]
154
+ @iterhash[exp[2][1]] = @collnames[0]
155
+ elsif exp[2] and exp[2][0] == :lasgn and @collnames.size > 1 # join iter with lefts/rights
156
+ if exp[1] and exp[1][2] == :lefts
157
+ @iterhash[exp[2][1]] = @collnames[0]
158
+ elsif exp[1] and exp[1][2] == :rights
159
+ @iterhash[exp[2][1]] = @collnames[1]
160
+ else
161
+ raise Bud::CompileError, "nested redefinition of block variable \"#{exp[2][1]}\" not allowed" if @iterhash[exp[2][1]]
162
+ end
163
+ elsif exp[2] and exp[2][0] == :masgn and not @collnames.empty? # join iter
164
+ next unless exp[2][1] and exp[2][1][0] == :array
165
+ @collnames.each_with_index do |c, i|
166
+ next unless exp[2][1][i+1] and exp[2][1][i+1][0] == :lasgn
167
+ @iterhash[exp[2][1][i+1][1]] = c
168
+ end
169
+ end
170
+ end
171
+ (1..(exp.length-1)).each {|i| exp[i] = process(exp[i])}
172
+ exp
173
+ end
174
+
175
+ def gather_collection_names(exp)
176
+ if exp[0] == :call and exp[1].nil?
177
+ @collnames << exp[2]
178
+ else
179
+ exp.each { |e| gather_collection_names(e) if e and e.class <= Sexp }
180
+ end
181
+ end
182
+
183
+ def process_call(exp)
184
+ call, recv, op, args = exp
185
+
186
+ if recv and recv.class == Sexp and recv.first == :lvar and recv[1] and @iterhash[recv[1]]
187
+ if @bud_instance.respond_to?(@iterhash[recv[1]])
188
+ if @bud_instance.send(@iterhash[recv[1]]).class <= Bud::BudCollection
189
+ schema = @bud_instance.send(@iterhash[recv[1]]).schema
190
+ if op != :[] and @bud_instance.send(@iterhash[recv[1]]).respond_to?(op)
191
+ # if the op is an attribute name in the schema, col is its index
192
+ col = schema.index(op) unless schema.nil?
193
+ unless col.nil?
194
+ op = :[]
195
+ args = s(:arglist, s(:lit, col))
196
+ end
197
+ end
198
+ end
199
+ return s(call, recv, op, args)
200
+ end
201
+ end
202
+ return s(call, process(recv), op, process(args))
203
+ end
204
+ end
205
+
107
206
  # Given a table of renames from x => y, replace all calls to "x" with calls to
108
207
  # "y" instead. We don't try to handle shadowing due to block variables: if a
109
208
  # block references a block variable that shadows an identifier in the rename
@@ -234,8 +333,8 @@ class TempExpander < SexpProcessor # :nodoc: all
234
333
  end
235
334
 
236
335
  # temp declarations are misparsed if the RHS contains certain constructs
237
- # (e.g., old-style join syntax, group, "do |f| ... end" rather than
238
- # "{|f| ... }"). Rewrite to correct the misparsing.
336
+ # (e.g., group, "do |f| ... end" rather than "{|f| ... }"). Rewrite to
337
+ # correct the misparsing.
239
338
  if n.sexp_type == :iter
240
339
  iter_body = n.sexp_body
241
340
 
@@ -299,13 +398,12 @@ class TempExpander < SexpProcessor # :nodoc: all
299
398
  end
300
399
 
301
400
  class DefnRenamer < SexpProcessor # :nodoc: all
302
- def initialize(old_mod_name, new_mod_name, local_name)
401
+ def initialize(local_name, rename_tbl)
303
402
  super()
304
403
  self.require_empty = false
305
404
  self.expected = Sexp
306
- @old_mod_name = old_mod_name
307
- @new_mod_name = new_mod_name
308
405
  @local_name = local_name
406
+ @rename_tbl = rename_tbl
309
407
  end
310
408
 
311
409
  def process_defn(exp)
@@ -313,18 +411,21 @@ class DefnRenamer < SexpProcessor # :nodoc: all
313
411
  name_s = name.to_s
314
412
 
315
413
  if name_s =~ /^__bootstrap__.+$/
316
- name = name_s.sub(/^(__bootstrap__)(.+)$/, "\\1#{@local_name}__\\2").to_sym
414
+ new_name = name_s.sub(/^(__bootstrap__)(.+)$/, "\\1#{@local_name}__\\2")
317
415
  elsif name_s =~ /^__state\d+__/
318
- name = name_s.sub(/^(__state\d+__)(.*)$/, "\\1#{@local_name}__\\2").to_sym
416
+ new_name = name_s.sub(/^(__state\d+__)(.*)$/, "\\1#{@local_name}__\\2")
319
417
  elsif name_s =~ /^__bloom__.+$/
320
- name = name_s.sub(/^(__bloom__)(.+)$/, "\\1#{@local_name}__\\2").to_sym
418
+ new_name = name_s.sub(/^(__bloom__)(.+)$/, "\\1#{@local_name}__\\2")
321
419
  else
322
- name = "#{@local_name}__#{name_s}".to_sym
420
+ new_name = "#{@local_name}__#{name_s}"
323
421
  end
324
422
 
423
+ new_name = new_name.to_sym
424
+ @rename_tbl[name] = new_name
425
+
325
426
  # Note that we don't bother to recurse further into the AST: we're only
326
427
  # interested in top-level :defn nodes.
327
- s(tag, name, args, scope)
428
+ s(tag, new_name, args, scope)
328
429
  end
329
430
  end
330
431
 
@@ -333,13 +434,13 @@ module ModuleRewriter # :nodoc: all
333
434
  # "import_site", bound to "local_name" at the import site. We implement this
334
435
  # by converting the imported module into an AST and rewriting the AST like so:
335
436
  #
336
- # (a) the module name is mangled to include the local bind name and the
337
- # importer
338
- # (b) instance method names are mangled to include the local bind name
339
- # (c) state defined by the module is mangled to include the local bind name
340
- # (d) statements in the module are rewritten to reference the mangled names
341
- # (e) statements in the module that reference sub-modules are rewritten to
342
- # reference the mangled name of the submodule.
437
+ # (a) statements in the module that reference sub-modules are rewritten to
438
+ # reference the mangled name of the submodule
439
+ # (b) the module name is mangled to include the local bind name and the
440
+ # import site
441
+ # (c) instance method names are mangled to include the local bind name
442
+ # (d) collection names are mangled to include the local bind name
443
+ # (e) statements in the module are rewritten to reference the mangled names
343
444
  #
344
445
  # We then convert the rewritten AST back into Ruby source code using Ruby2Ruby
345
446
  # and eval() it to define a new module. We return the name of that newly
@@ -352,7 +453,9 @@ module ModuleRewriter # :nodoc: all
352
453
  ast = ast_flatten_nested_refs(ast, mod.bud_import_table)
353
454
  ast = ast_process_temps(ast, mod)
354
455
  ast, new_mod_name = ast_rename_module(ast, import_site, mod, local_name)
355
- rename_tbl = ast_rename_state(ast, local_name)
456
+ rename_tbl = {}
457
+ ast = ast_rename_methods(ast, local_name, rename_tbl)
458
+ ast = ast_rename_state(ast, local_name, rename_tbl)
356
459
  ast = ast_update_refs(ast, rename_tbl)
357
460
 
358
461
  str = Ruby2Ruby.new.process(ast)
@@ -377,9 +480,9 @@ module ModuleRewriter # :nodoc: all
377
480
  # the AST is returned in a different format than we expect. In particular, we
378
481
  # expect that the methods from any modules included in the target module will
379
482
  # be "inlined" into the dumped AST; ParseTree > 3.0.7 adds an "include"
380
- # statement to the AST instead. In the long run we should adapt the module
381
- # rewrite system to work with ParseTree > 3.0.7 and get rid of this code, but
382
- # that will require further changes.
483
+ # statement to the AST instead. In the long run we should probably adapt the
484
+ # module rewrite system to work with ParseTree > 3.0.7 and get rid of this
485
+ # code, but that will require further changes.
383
486
  def self.get_raw_parse_tree(klass)
384
487
  pt = RawParseTree.new(false)
385
488
  klassname = klass.name
@@ -444,9 +547,9 @@ module ModuleRewriter # :nodoc: all
444
547
  end
445
548
 
446
549
  # Rename the given module's name to be a mangle of import site, imported
447
- # module, and local bind name. We also need to rename special "state" and
448
- # "bootstrap" methods. We also rename "bloom" methods, but we can just mangle
449
- # with the local bind name for those.
550
+ # module, and local bind name. We also rename all the instance methods defined
551
+ # in the module to include the local bind name (including the special "state",
552
+ # "bootstrap", and "bloom" methods).
450
553
  def self.ast_rename_module(ast, importer, importee, local_name)
451
554
  mod_name = ast.sexp_body.first
452
555
  raise Bud::BudError if mod_name.to_s != importee.to_s
@@ -458,21 +561,21 @@ module ModuleRewriter # :nodoc: all
458
561
  new_name = "#{importer_name}__#{importee_name}__#{local_name}"
459
562
  ast[1] = new_name.to_sym
460
563
 
461
- dr = DefnRenamer.new(mod_name, new_name, local_name)
462
- new_ast = dr.process(ast)
463
-
464
564
  # XXX: it would be nice to return a Module, rather than a string containing
465
565
  # the Module's name. Unfortunately, I can't see how to do that.
466
- return [new_ast, new_name]
566
+ return [ast, new_name]
567
+ end
568
+
569
+ def self.ast_rename_methods(ast, local_name, rename_tbl)
570
+ DefnRenamer.new(local_name, rename_tbl).process(ast)
467
571
  end
468
572
 
469
573
  # Mangle the names of all the collections defined in state blocks found in the
470
574
  # given module's AST. Returns a table mapping old => new names.
471
- def self.ast_rename_state(ast, local_name)
575
+ def self.ast_rename_state(ast, local_name, rename_tbl)
472
576
  # Find all the state blocks in the AST
473
577
  raise Bud::BudError unless ast.sexp_type == :module
474
578
 
475
- rename_tbl = {}
476
579
  ast.sexp_body.each do |b|
477
580
  next unless b.class <= Sexp
478
581
  next if b.sexp_type != :defn
@@ -508,7 +611,7 @@ module ModuleRewriter # :nodoc: all
508
611
  end
509
612
  end
510
613
 
511
- return rename_tbl
614
+ return ast
512
615
  end
513
616
 
514
617
  def self.ast_update_refs(ast, rename_tbl)
data/lib/bud/server.rb CHANGED
@@ -37,6 +37,7 @@ module Bud
37
37
  # other Bud instances in the same process will crash). Ignoring the
38
38
  # error isn't best though -- we should do better (#74).
39
39
  puts "Exception handling network message (channel '#{obj[0]}'): #{$!}"
40
+ puts caller.join("\n")
40
41
  end
41
42
  end
42
43
  end
data/lib/bud/state.rb CHANGED
@@ -32,7 +32,7 @@ module Bud
32
32
  false
33
33
  end
34
34
 
35
- # declare a scratch collection to be an input or output interface
35
+ # declare a transient collection to be an input or output interface
36
36
  def interface(mode, name, schema=nil)
37
37
  t_provides << [name.to_s, mode]
38
38
  scratch(name, schema)
@@ -44,7 +44,7 @@ module Bud
44
44
  @tables[name] = Bud::BudTable.new(name, self, schema)
45
45
  end
46
46
 
47
- # declare a transient (1-timestep) collection. default schema <tt>[:key] => [:val]</tt>
47
+ # declare a transient collection. default schema <tt>[:key] => [:val]</tt>
48
48
  def scratch(name, schema=nil)
49
49
  define_collection(name)
50
50
  @tables[name] = Bud::BudScratch.new(name, self, schema)
@@ -58,10 +58,19 @@ module Bud
58
58
  end
59
59
 
60
60
  # declare a transient network collection. default schema <tt>[:address, :val] => []</tt>
61
- def channel(name, schema=nil)
61
+ def channel(name, schema=nil, loopback=false)
62
62
  define_collection(name)
63
- @tables[name] = Bud::BudChannel.new(name, self, schema)
64
- @channels[name] = @tables[name].locspec_idx
63
+ @tables[name] = Bud::BudChannel.new(name, self, schema, loopback)
64
+ @channels[name] = @tables[name]
65
+ end
66
+
67
+ # declare a transient network collection that delivers facts back to the
68
+ # current Bud instance. This is syntax sugar for a channel that always
69
+ # delivers to the IP/port of the current Bud instance. Default schema
70
+ # <tt>[:key] => [:val]</tt>
71
+ def loopback(name, schema=nil)
72
+ schema ||= {[:key] => [:val]}
73
+ channel(name, schema, true)
65
74
  end
66
75
 
67
76
  # declare a collection to be read from +filename+. rhs of statements only
@@ -74,11 +83,9 @@ module Bud
74
83
  # rhs of statements only.
75
84
  def periodic(name, period=1)
76
85
  define_collection(name)
77
- # stick with default schema -- [:key] => [:val]
78
- @tables[name] = Bud::BudPeriodic.new(name, self)
79
86
  raise BudError if @periodics.has_key? [name]
80
- t = [name, gen_id, period]
81
- @periodics << t
87
+ @periodics << [name, gen_id, period]
88
+ @tables[name] = Bud::BudPeriodic.new(name, self)
82
89
  end
83
90
 
84
91
  def terminal(name) # :nodoc: all
@@ -88,8 +95,8 @@ module Bud
88
95
  @terminal = name
89
96
  end
90
97
  define_collection(name)
91
- @channels[name] = nil
92
98
  @tables[name] = Bud::BudTerminal.new(name, [:line], self)
99
+ @channels[name] = @tables[name]
93
100
  end
94
101
 
95
102
  # declare a TokyoCabinet table
@@ -98,6 +105,13 @@ module Bud
98
105
  @tables[name] = Bud::BudTcTable.new(name, self, schema)
99
106
  @tc_tables[name] = @tables[name]
100
107
  end
108
+
109
+ # declare a dbm table
110
+ def dbm_table(name, schema=nil)
111
+ define_collection(name)
112
+ @tables[name] = Bud::BudDbmTable.new(name, self, schema)
113
+ @dbm_tables[name] = @tables[name]
114
+ end
101
115
 
102
116
  # declare an Apache ZooKeeper table
103
117
  def zktable(name, path, addr="localhost:2181")
@@ -0,0 +1,170 @@
1
+ require 'dbm'
2
+
3
+ module Bud
4
+ # Persistent table implementation based on ndbm.
5
+ class BudDbmTable < BudCollection # :nodoc: all
6
+ def initialize(name, bud_instance, given_schema)
7
+ dbm_dir = bud_instance.options[:dbm_dir]
8
+ raise BudError, "dbm support must be enabled via 'dbm_dir'" unless dbm_dir
9
+ unless File.exists?(dbm_dir)
10
+ Dir.mkdir(dbm_dir)
11
+ puts "Created directory: #{dbm_dir}" unless bud_instance.options[:quiet]
12
+ end
13
+
14
+ dirname = "#{dbm_dir}/bud_#{bud_instance.port}"
15
+ unless File.exists?(dirname)
16
+ Dir.mkdir(dirname)
17
+ puts "Created directory: #{dirname}" unless bud_instance.options[:quiet]
18
+ end
19
+
20
+ super(name, bud_instance, given_schema)
21
+ @to_delete = []
22
+
23
+ db_fname = "#{dirname}/#{name}.dbm"
24
+ flags = DBM::WRCREAT
25
+ if bud_instance.options[:dbm_truncate] == true
26
+ flags |= DBM::NEWDB
27
+ end
28
+ @dbm = DBM.open(db_fname, 0666, flags)
29
+ if @dbm.nil?
30
+ raise BudError, "Failed to open dbm database '#{db_fname}': #{@dbm.errmsg}"
31
+ end
32
+ end
33
+
34
+ def init_storage
35
+ # XXX: we can't easily use the @storage infrastructure provided by
36
+ # BudCollection; issue #33
37
+ @storage = nil
38
+ end
39
+
40
+ def [](key)
41
+ key_s = MessagePack.pack(key)
42
+ val_s = @dbm[key_s]
43
+ if val_s
44
+ return make_tuple(key, MessagePack.unpack(val_s))
45
+ else
46
+ return @delta[key]
47
+ end
48
+ end
49
+
50
+ def has_key?(k)
51
+ key_s = MessagePack.pack(k)
52
+ return true if @dbm.has_key? key_s
53
+ return @delta.has_key? k
54
+ end
55
+
56
+ def include?(tuple)
57
+ key = @key_colnums.map{|k| tuple[k]}
58
+ value = self[key]
59
+ return (value == tuple)
60
+ end
61
+
62
+ def make_tuple(k_ary, v_ary)
63
+ t = Array.new(k_ary.length + v_ary.length)
64
+ @key_colnums.each_with_index do |k,i|
65
+ t[k] = k_ary[i]
66
+ end
67
+ val_cols.each_with_index do |c,i|
68
+ t[schema.index(c)] = v_ary[i]
69
+ end
70
+ tuple_accessors(t)
71
+ end
72
+
73
+ def each(&block)
74
+ each_from([@delta], &block)
75
+ each_storage(&block)
76
+ end
77
+
78
+ def each_from(bufs, &block)
79
+ bufs.each do |b|
80
+ if b == @storage then
81
+ each_storage(&block)
82
+ else
83
+ b.each_value do |v|
84
+ yield v
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def each_storage(&block)
91
+ @dbm.each do |k,v|
92
+ k_ary = MessagePack.unpack(k)
93
+ v_ary = MessagePack.unpack(v)
94
+ yield make_tuple(k_ary, v_ary)
95
+ end
96
+ end
97
+
98
+ def flush
99
+ end
100
+
101
+ def close
102
+ @dbm.close unless @dbm.nil?
103
+ @dbm = nil
104
+ end
105
+
106
+ def merge_to_db(buf)
107
+ buf.each do |key,tuple|
108
+ merge_tuple(key, tuple)
109
+ end
110
+ end
111
+
112
+ def merge_tuple(key, tuple)
113
+ val = val_cols.map{|c| tuple[schema.index(c)]}
114
+ key_s = MessagePack.pack(key)
115
+ val_s = MessagePack.pack(val)
116
+ if @dbm.has_key?(key_s)
117
+ old_tuple = self[key]
118
+ raise_pk_error(tuple, old_tuple) if tuple != old_tuple
119
+ else
120
+ @dbm[key_s] = val_s
121
+ end
122
+ end
123
+
124
+ # move deltas to on-disk storage, and new_deltas to deltas
125
+ def tick_deltas
126
+ merge_to_db(@delta)
127
+ @delta = @new_delta
128
+ @new_delta = {}
129
+ end
130
+
131
+ superator "<-" do |o|
132
+ o.each do |tuple|
133
+ @to_delete << tuple unless tuple.nil?
134
+ end
135
+ end
136
+
137
+ def insert(tuple)
138
+ key = @key_colnums.map{|k| tuple[k]}
139
+ merge_tuple(key, tuple)
140
+ end
141
+
142
+ alias << insert
143
+
144
+ # Remove to_delete and then add pending to db
145
+ def tick
146
+ @to_delete.each do |tuple|
147
+ k = @key_colnums.map{|c| tuple[c]}
148
+ k_str = MessagePack.pack(k)
149
+ cols_str = @dbm[k_str]
150
+ unless cols_str.nil?
151
+ db_cols = MessagePack.unpack(cols_str)
152
+ delete_cols = val_cols.map{|c| tuple[schema.index(c)]}
153
+ if db_cols == delete_cols
154
+ @dbm.delete k_str
155
+ end
156
+ end
157
+ end
158
+ @to_delete = []
159
+
160
+ merge_to_db(@pending)
161
+ @pending = {}
162
+
163
+ flush
164
+ end
165
+
166
+ def method_missing(sym, *args, &block)
167
+ @dbm.send sym, *args, &block
168
+ end
169
+ end
170
+ end
@@ -1,4 +1,8 @@
1
- require 'tokyocabinet'
1
+ begin
2
+ require 'tokyocabinet'
3
+ Bud::HAVE_TOKYOCABINET = true
4
+ rescue LoadError
5
+ end
2
6
 
3
7
  module Bud
4
8
  # Persistent table implementation based on TokyoCabinet.
data/lib/bud/stratify.rb CHANGED
@@ -50,13 +50,12 @@ class Stratification # :nodoc: all
50
50
  # classic stratification:
51
51
  # if A depends on B, A is >= B.
52
52
  # if A depends nonmonotonically on B, A > B.
53
- # if A are B are co-dependent, give up.
53
+ # if A are B are nonmonotonically co-dependent, give up.
54
54
  # (don't need to do this, b/c we've ruled out deductive cycles)
55
- # stratum choice will represent local evaluation order,
56
- # so we need only consider 'synchronous' dependencies (<=)
57
-
58
- # pass 1: assume no deductive cycles
59
- # do "vanilla stratification" on deductive rules
55
+ #
56
+ # Stratum choice controls local evaluation order, so we need only consider
57
+ # deductive rules (<=). Temporal rules are placed in an extra "top"
58
+ # stratum afterward.
60
59
  stratum_base <= (depends * stratum_base).pairs(:body => :predicate) do |d, s|
61
60
  if d.op.to_s == '<='
62
61
  if d.neg
@@ -75,7 +74,7 @@ class Stratification # :nodoc: all
75
74
  }
76
75
 
77
76
  strata[3] = lambda {
78
- # there is no good reason that top_strat can't be computed in strata[3] over stratum_base.
77
+ # there is no good reason that top_strat can't be computed in strata[2] over stratum_base.
79
78
  # however, when it is deduced that way, it is empty after a tick
80
79
  top_strat <= stratum.group([], max(stratum.stratum))
81
80
  }