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