bud 0.0.3 → 0.0.4

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 (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
  }