bud 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +33 -16
- data/bin/budplot +42 -65
- data/bin/budtimelines +235 -0
- data/bin/budvis +24 -122
- data/bin/rebl +1 -0
- data/docs/README.md +21 -10
- data/docs/bfs.md +4 -6
- data/docs/c.html +251 -0
- data/docs/cheat.md +45 -30
- data/docs/deploy.md +26 -26
- data/docs/getstarted.md +6 -4
- data/docs/visualizations.md +43 -31
- data/examples/chat/chat.rb +4 -9
- data/examples/chat/chat_server.rb +1 -8
- data/examples/deploy/deploy_ip_port +1 -0
- data/examples/deploy/keys.rb +5 -0
- data/examples/deploy/tokenring-ec2.rb +9 -9
- data/examples/deploy/{tokenring-local.rb → tokenring-fork.rb} +3 -5
- data/examples/deploy/tokenring-thread.rb +15 -0
- data/examples/deploy/tokenring.rb +25 -17
- data/lib/bud/aggs.rb +87 -25
- data/lib/bud/bud_meta.rb +48 -31
- data/lib/bud/bust/bust.rb +16 -15
- data/lib/bud/collections.rb +207 -232
- data/lib/bud/depanalysis.rb +1 -0
- data/lib/bud/deploy/countatomicdelivery.rb +8 -20
- data/lib/bud/deploy/deployer.rb +16 -16
- data/lib/bud/deploy/ec2deploy.rb +34 -35
- data/lib/bud/deploy/forkdeploy.rb +90 -0
- data/lib/bud/deploy/threaddeploy.rb +38 -0
- data/lib/bud/graphs.rb +103 -199
- data/lib/bud/joins.rb +190 -41
- data/lib/bud/monkeypatch.rb +84 -0
- data/lib/bud/rebl.rb +8 -1
- data/lib/bud/rewrite.rb +152 -49
- data/lib/bud/server.rb +1 -0
- data/lib/bud/state.rb +24 -10
- data/lib/bud/storage/dbm.rb +170 -0
- data/lib/bud/storage/tokyocabinet.rb +5 -1
- data/lib/bud/stratify.rb +6 -7
- data/lib/bud/viz.rb +31 -17
- data/lib/bud/viz_util.rb +204 -0
- data/lib/bud.rb +271 -244
- data/lib/bud.rb.orig +806 -0
- metadata +43 -22
- data/docs/bfs.raw +0 -251
- data/docs/diffs +0 -181
- data/examples/basics/out +0 -1103
- data/examples/basics/out.new +0 -856
- 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, :
|
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
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
56
|
-
|
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
|
-
|
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
|
-
|
87
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
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.,
|
238
|
-
#
|
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(
|
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
|
-
|
414
|
+
new_name = name_s.sub(/^(__bootstrap__)(.+)$/, "\\1#{@local_name}__\\2")
|
317
415
|
elsif name_s =~ /^__state\d+__/
|
318
|
-
|
416
|
+
new_name = name_s.sub(/^(__state\d+__)(.*)$/, "\\1#{@local_name}__\\2")
|
319
417
|
elsif name_s =~ /^__bloom__.+$/
|
320
|
-
|
418
|
+
new_name = name_s.sub(/^(__bloom__)(.+)$/, "\\1#{@local_name}__\\2")
|
321
419
|
else
|
322
|
-
|
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,
|
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
|
337
|
-
#
|
338
|
-
# (b)
|
339
|
-
#
|
340
|
-
# (
|
341
|
-
# (
|
342
|
-
#
|
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 =
|
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
|
381
|
-
# rewrite system to work with ParseTree > 3.0.7 and get rid of this
|
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
|
448
|
-
#
|
449
|
-
#
|
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 [
|
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
|
614
|
+
return ast
|
512
615
|
end
|
513
616
|
|
514
617
|
def self.ast_update_refs(ast, rename_tbl)
|
data/lib/bud/server.rb
CHANGED
data/lib/bud/state.rb
CHANGED
@@ -32,7 +32,7 @@ module Bud
|
|
32
32
|
false
|
33
33
|
end
|
34
34
|
|
35
|
-
# declare a
|
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
|
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]
|
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
|
-
|
81
|
-
@
|
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
|
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
|
-
#
|
56
|
-
# so we need only consider
|
57
|
-
|
58
|
-
#
|
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[
|
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
|
}
|