bud 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/budplot +97 -8
- data/bin/budvis +1 -2
- data/docs/cheat.md +1 -1
- data/lib/bud/aggs.rb +2 -1
- data/lib/bud/bud_meta.rb +8 -7
- data/lib/bud/collections.rb +36 -48
- data/lib/bud/graphs.rb +48 -21
- data/lib/bud/joins.rb +1 -1
- data/lib/bud/meta_algebra.rb +168 -0
- data/lib/bud/monkeypatch.rb +32 -5
- data/lib/bud/rewrite.rb +53 -14
- data/lib/bud/server.rb +25 -4
- data/lib/bud/state.rb +13 -14
- data/lib/bud/storage/dbm.rb +19 -8
- data/lib/bud/storage/tokyocabinet.rb +18 -8
- data/lib/bud/viz.rb +7 -5
- data/lib/bud/viz_util.rb +93 -11
- data/lib/bud.rb +30 -16
- metadata +4 -3
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bud'
|
3
|
+
require 'bud/depanalysis'
|
4
|
+
|
5
|
+
module MetaAlgebra
|
6
|
+
state do
|
7
|
+
table :alg_path, [:from, :to, :path, :last_rule, :tag, :lastop]
|
8
|
+
scratch :clean_dep, [:body, :head, :rule_id] => [:tag, :lastop]
|
9
|
+
|
10
|
+
scratch :rule_nm, [:rule_id] => [:tag]
|
11
|
+
|
12
|
+
table :apj1, [:from, :head, :rule_id, :path, :tag, :tag2, :lastop]
|
13
|
+
table :apj2, [:from, :head, :rule_id] => [:path, :tag, :lastop]
|
14
|
+
table :seq_lattice, [:left, :right, :directional]
|
15
|
+
table :seq_lattice_closure, [:left, :right, :directional, :dist]
|
16
|
+
table :lub, [:left, :right] => [:result]
|
17
|
+
table :seq_lattice_result, [:left, :right, :result]
|
18
|
+
table :upper_bound, [:left, :right, :bound, :height]
|
19
|
+
table :lps, alg_path.schema
|
20
|
+
end
|
21
|
+
|
22
|
+
bootstrap do
|
23
|
+
seq_lattice <= [
|
24
|
+
[:M, :A, false],
|
25
|
+
[:M, :N, false],
|
26
|
+
[:A, :D, false],
|
27
|
+
[:N, :D, false],
|
28
|
+
[:N, :A, true] #,
|
29
|
+
|
30
|
+
# disabled, for now
|
31
|
+
#[:A, :D, true],
|
32
|
+
#[:D, :G, false],
|
33
|
+
#[:A, :G, false]
|
34
|
+
]
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def max_of(a, b)
|
39
|
+
if b > a
|
40
|
+
b
|
41
|
+
else
|
42
|
+
a
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
bloom :debug do
|
47
|
+
#stdio <~ upper_bound{|b| ["UPPERB: #{b.inspect}"]}
|
48
|
+
#stdio <~ seq_lattice_closure{|c| ["SLC: #{c.inspect}"]}
|
49
|
+
#stdio <~ jlr {|j| ["JLR: #{j.inspect}"]}
|
50
|
+
#stdio <~ lub {|l| ["LUB #{l.inspect}, left class #{l.left.class}"]}
|
51
|
+
end
|
52
|
+
|
53
|
+
bloom :lattice_rules do
|
54
|
+
seq_lattice_closure <= seq_lattice {|l| [l.left, l.right, l.directional, 1]}
|
55
|
+
seq_lattice_closure <= seq_lattice {|l| [l.left, l.left, false, 0]}
|
56
|
+
seq_lattice_closure <= seq_lattice {|l| [l.right, l.right, false, 0]}
|
57
|
+
seq_lattice_closure <= (seq_lattice_closure * seq_lattice).pairs(:right => :left) do |c, l|
|
58
|
+
[c.left, l.right, (c.directional or l.directional), c.dist + 1]
|
59
|
+
end
|
60
|
+
|
61
|
+
# the join lattice is symmetric
|
62
|
+
lub <= seq_lattice_closure {|l| [l.left, l.right, l.right]}
|
63
|
+
lub <= seq_lattice_closure {|l| [l.right, l.left, l.right] unless l.directional}
|
64
|
+
|
65
|
+
# still need a LUB for incomparable types.
|
66
|
+
upper_bound <= (seq_lattice_closure * seq_lattice_closure).map do |c1, c2|
|
67
|
+
if c1.right == c2.right and seq_lattice_closure.find_all{|c| c.left == c1.left and c.right == c2.left}.empty?
|
68
|
+
unless c1.left == c1.right or c2.left == c2.right
|
69
|
+
[c1.left, c2.left, c1.right, max_of(c1.dist, c2.dist) + 1]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
temp :jlr <= upper_bound.argagg(:min, [upper_bound.left, upper_bound.right], upper_bound.height)
|
75
|
+
lub <+ jlr {|j| [j.left, j.right, j.bound] unless lub.map{|l| [l.left, l.right]}.include? [j.left, j.right] }
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_tag(nm, op)
|
79
|
+
if nm and op == '<~'
|
80
|
+
:D
|
81
|
+
elsif nm
|
82
|
+
:N
|
83
|
+
elsif op == '<~'
|
84
|
+
:A
|
85
|
+
else
|
86
|
+
:M
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def in_prefix(node, path)
|
91
|
+
path.split("|").include? node
|
92
|
+
end
|
93
|
+
|
94
|
+
bloom :make_paths do
|
95
|
+
rule_nm <= t_depends.reduce({}) do |memo, i|
|
96
|
+
tag = get_tag(i.nm, i.op)
|
97
|
+
memo[i.rule_id] = tag if memo[i.rule_id].nil?
|
98
|
+
memo[i.rule_id] = tag if tag == :N or tag == :A
|
99
|
+
memo
|
100
|
+
end
|
101
|
+
|
102
|
+
clean_dep <= (t_depends * rule_nm).pairs(:rule_id => :rule_id) do |dep, rn|
|
103
|
+
unless dep.lhs == 'alg_path'
|
104
|
+
[dep.body, dep.lhs, dep.rule_id, rn.tag, dep.op]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
alg_path <= clean_dep.map do |dep|
|
109
|
+
[dep.body, dep.head, "#{dep.body}|#{dep.head}", dep.rule_id, dep.tag, dep.lastop]
|
110
|
+
end
|
111
|
+
|
112
|
+
lps <= (alg_path * t_provides).pairs(:from => :interface) do |a, p|
|
113
|
+
if p.input
|
114
|
+
a
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
apj1 <= (alg_path * clean_dep).pairs(:to => :body) do |a, c|
|
119
|
+
[a.from, c.head, c.rule_id, a.path, a.tag, c.tag, a.lastop]
|
120
|
+
end
|
121
|
+
apj2 <= (apj1 * lub).pairs(:tag => :left, :tag2 => :right)
|
122
|
+
alg_path <= apj2.map do |p, l|
|
123
|
+
unless in_prefix(p.head, p.path)
|
124
|
+
[p.from, p.head, "#{p.path}|#{p.head}", p.rule_id, l.result, p.lastop]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module MetaReports
|
131
|
+
state do
|
132
|
+
table :global_property, [:from, :to, :tag, :c1, :c2]
|
133
|
+
scratch :paths, [:from, :to] => [:cnt]
|
134
|
+
table :tags, [:from, :to, :tag, :cnt]
|
135
|
+
table :d_begins, [:from, :tag, :path, :len, :lastop]
|
136
|
+
table :ap, d_begins.schema
|
137
|
+
scratch :a_preds, d_begins.schema + [:fullpath]
|
138
|
+
end
|
139
|
+
|
140
|
+
bloom :loci do
|
141
|
+
# one approach: for every paths that 'turns D', identify the last async edge before
|
142
|
+
# the critical transition. ordering this edge prevents diffluence.
|
143
|
+
# find the first point of diffluence in each paths: d_begins already does this.
|
144
|
+
# for each "D"-entry in d_begins, find the longest subpath ending in an async rule.
|
145
|
+
a_preds <= (d_begins * ap).pairs(:from => :from) do |b, a|
|
146
|
+
if a.len < b.len and a.tag == :A and b.path.index(a.path) == 0 and a.lastop == "<~" and b.tag == :D
|
147
|
+
[a.from, a.tag, a.path, a.len, a.lastop, b.path]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
bloom do
|
153
|
+
paths <= alg_path.group([:from, :to], count(:tag))
|
154
|
+
tags <= alg_path.group([:from, :to, :tag], count())
|
155
|
+
global_property <= (paths * tags).pairs(:from => :from, :to => :to, :cnt => :cnt) do |p, t|
|
156
|
+
[t.from, t.to, t.tag, p.cnt, t.cnt]
|
157
|
+
end
|
158
|
+
|
159
|
+
ap <= (alg_path * t_provides).pairs(:from => :interface) do |p, pr|
|
160
|
+
if pr.input
|
161
|
+
[p.from, p.tag, p.path, p.path.split("|").length, p.lastop]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
d_begins <= ap.argagg(:min, [:from, :tag], :len)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
data/lib/bud/monkeypatch.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# We monkeypatch Module to add support for Bloom
|
1
|
+
# We monkeypatch Module to add support for Bloom's syntax additions: "state",
|
2
|
+
# "bloom", and "bootstrap" blocks, plus the "import" statement.
|
2
3
|
class Module
|
3
4
|
# import another module and assign to a qualifier symbol: <tt>import MyModule => :m</tt>
|
4
5
|
def import(spec)
|
@@ -15,11 +16,13 @@ class Module
|
|
15
16
|
# To correctly expand qualified references to an imported module, we keep a
|
16
17
|
# table with the local bind names of all the modules imported by this
|
17
18
|
# module. To handle nested references (a.b.c.d etc.), the import table for
|
18
|
-
# module X points to X's own nested import table.
|
19
|
+
# module X points to X's own nested import table. If a single module
|
20
|
+
# attempts to import multiple sub-modules with the same local name, we merge
|
21
|
+
# the import tables of all the modules.
|
19
22
|
@bud_import_tbl ||= {}
|
20
|
-
|
21
|
-
|
22
|
-
@bud_import_tbl[local_name] =
|
23
|
+
prev_tbl = @bud_import_tbl[local_name]
|
24
|
+
child_tbl = NestedRefRewriter.build_import_table(mod)
|
25
|
+
@bud_import_tbl[local_name] = NestedRefRewriter.merge_import_table(prev_tbl, child_tbl)
|
23
26
|
|
24
27
|
rewritten_mod_name = ModuleRewriter.do_import(self, mod, local_name)
|
25
28
|
self.module_eval "include #{rewritten_mod_name}"
|
@@ -88,3 +91,27 @@ class Module
|
|
88
91
|
return r
|
89
92
|
end
|
90
93
|
end
|
94
|
+
|
95
|
+
|
96
|
+
module Enumerable
|
97
|
+
public
|
98
|
+
# Support for renaming collections and their schemas
|
99
|
+
def rename(new_tabname, new_schema=nil)
|
100
|
+
budi = (respond_to?(:bud_instance)) ? bud_instance : nil
|
101
|
+
if new_schema.nil? and respond_to?(:schema)
|
102
|
+
new_schema = schema
|
103
|
+
end
|
104
|
+
scr = Bud::BudScratch.new(new_tabname.to_s, budi, new_schema)
|
105
|
+
scr.uniquify_tabname
|
106
|
+
scr.merge(self, scr.storage)
|
107
|
+
scr
|
108
|
+
end
|
109
|
+
|
110
|
+
public
|
111
|
+
# We rewrite "map" calls in Bloom blocks to invoke the "pro" method
|
112
|
+
# instead. This is fine when applied to a BudCollection; when applied to a
|
113
|
+
# normal Enumerable, just treat pro as an alias for map.
|
114
|
+
def pro(&blk)
|
115
|
+
map(&blk)
|
116
|
+
end
|
117
|
+
end
|
data/lib/bud/rewrite.rb
CHANGED
@@ -7,11 +7,11 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
7
7
|
@bud_instance = bud_instance
|
8
8
|
@ops = {:<< => 1, :< => 1, :<= => 1}
|
9
9
|
@monotonic_whitelist = {
|
10
|
-
:== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1,
|
10
|
+
:== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1, :~ => 1,
|
11
11
|
:* => 1, :pairs => 1, :matches => 1, :combos => 1, :flatten => 1,
|
12
12
|
:lefts => 1, :rights => 1, :map => 1, :flat_map => 1, :pro => 1,
|
13
|
-
:schema => 1, :
|
14
|
-
:
|
13
|
+
:schema => 1, :cols => 1, :key_cols => 1, :val_cols => 1,
|
14
|
+
:payloads => 1, :tabname => 1, :+@ => 1
|
15
15
|
}
|
16
16
|
@temp_ops = {:-@ => 1, :~ => 1, :+@ => 1}
|
17
17
|
@tables = {}
|
@@ -40,7 +40,12 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
40
40
|
# :defn block -- this is where we expect Bloom statements to appear
|
41
41
|
do_rule(exp)
|
42
42
|
else
|
43
|
-
if
|
43
|
+
if op == :notin
|
44
|
+
# a <= b.m1.m2.notin(c, ... ) ..
|
45
|
+
# b contributes positively to a, but c contributes negatively.
|
46
|
+
notintab = args[1][2].to_s # args == (:arglist (:call, nil, :c, ...))
|
47
|
+
@tables[notintab] = true
|
48
|
+
elsif recv and recv.class == Sexp
|
44
49
|
# for CALM analysis, mark deletion rules as non-monotonic
|
45
50
|
@nm = true if op == :-@
|
46
51
|
# don't worry about monotone ops, table names, table.attr calls, or accessors of iterator variables
|
@@ -90,7 +95,7 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
90
95
|
t = exp[1].to_s
|
91
96
|
# If we're called on a "table-like" part of the AST that doesn't correspond
|
92
97
|
# to an extant table, ignore it.
|
93
|
-
@tables[t] = @nm if @bud_instance.tables.has_key? t.to_sym
|
98
|
+
@tables[t] = @nm if @bud_instance.tables.has_key? t.to_sym and not @tables[t]
|
94
99
|
drain(exp)
|
95
100
|
return t
|
96
101
|
end
|
@@ -103,7 +108,8 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
103
108
|
rhs = collect_rhs(pro_rules)
|
104
109
|
rhs_pos = rhs
|
105
110
|
else
|
106
|
-
# need a deep copy of the rules so we can keep a version without AttrName
|
111
|
+
# need a deep copy of the rules so we can keep a version without AttrName
|
112
|
+
# Rewrite
|
107
113
|
pro_rules2 = Marshal.load(Marshal.dump(pro_rules))
|
108
114
|
rhs = collect_rhs(pro_rules)
|
109
115
|
reset_instance_vars
|
@@ -114,7 +120,7 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
114
120
|
end
|
115
121
|
|
116
122
|
# We want to rewrite "map" calls on BudCollections to "pro" calls. It is hard
|
117
|
-
# to do this
|
123
|
+
# to do this precisely (issue #225), so we just replace map calls liberally
|
118
124
|
# and define Enumerable#pro as an alias for "map".
|
119
125
|
def map2pro(exp)
|
120
126
|
if exp[1] and exp[1][0] and exp[1][0] == :iter \
|
@@ -143,7 +149,8 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
143
149
|
@bud_instance = bud_instance
|
144
150
|
end
|
145
151
|
|
146
|
-
# some icky special-case parsing to find mapping between collection names and
|
152
|
+
# some icky special-case parsing to find mapping between collection names and
|
153
|
+
# iter vars
|
147
154
|
def process_iter(exp)
|
148
155
|
if exp[1] and exp[1][0] == :call
|
149
156
|
gather_collection_names(exp[1])
|
@@ -207,7 +214,7 @@ end
|
|
207
214
|
# Given a table of renames from x => y, replace all calls to "x" with calls to
|
208
215
|
# "y" instead. We don't try to handle shadowing due to block variables: if a
|
209
216
|
# block references a block variable that shadows an identifier in the rename
|
210
|
-
#
|
217
|
+
# table, it should appear as an :lvar node rather than a :call, so we should be
|
211
218
|
# okay.
|
212
219
|
class CallRewriter < SexpProcessor # :nodoc: all
|
213
220
|
def initialize(rename_tbl)
|
@@ -243,14 +250,39 @@ end
|
|
243
250
|
class NestedRefRewriter < SexpProcessor # :nodoc: all
|
244
251
|
attr_accessor :did_work
|
245
252
|
|
246
|
-
def initialize(
|
253
|
+
def initialize(mod)
|
247
254
|
super()
|
248
255
|
self.require_empty = false
|
249
256
|
self.expected = Sexp
|
250
|
-
|
257
|
+
|
258
|
+
@import_tbl = NestedRefRewriter.build_import_table(mod)
|
251
259
|
@did_work = false
|
252
260
|
end
|
253
261
|
|
262
|
+
# If module Y imports Z as "z" and X includes Y, X can contain a reference
|
263
|
+
# to "z.foo". Hence, when expanding nested references in X, we want to merge
|
264
|
+
# the import tables of X and any modules that X includes; however, we can
|
265
|
+
# skip the Bud module, as well as any modules generated via the import
|
266
|
+
# system.
|
267
|
+
def self.build_import_table(mod)
|
268
|
+
child_tbl = mod.bud_import_table.clone
|
269
|
+
mod.modules.each do |m|
|
270
|
+
next if m == Bud
|
271
|
+
next if m.instance_variable_get('@bud_imported_module')
|
272
|
+
|
273
|
+
child_tbl = NestedRefRewriter.merge_import_table(child_tbl,
|
274
|
+
m.bud_import_table)
|
275
|
+
end
|
276
|
+
child_tbl
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.merge_import_table(old, new)
|
280
|
+
old ||= {}
|
281
|
+
old.merge(new) do |key, old_val, new_val|
|
282
|
+
NestedRefRewriter.merge_import_table(old_val, new_val)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
254
286
|
def process_call(exp)
|
255
287
|
return exp if @import_tbl.empty?
|
256
288
|
tag, recv, meth_name, args = exp
|
@@ -572,7 +604,7 @@ module ModuleRewriter # :nodoc: all
|
|
572
604
|
# and returns a matching ast.
|
573
605
|
# hence we run it before the other rewrites.
|
574
606
|
ast = ast_process_withs(mod)
|
575
|
-
ast = ast_flatten_nested_refs(ast, mod
|
607
|
+
ast = ast_flatten_nested_refs(ast, mod)
|
576
608
|
ast = ast_process_temps(ast, mod)
|
577
609
|
|
578
610
|
ast, new_mod_name = ast_rename_module(ast, import_site, mod, local_name)
|
@@ -584,6 +616,13 @@ module ModuleRewriter # :nodoc: all
|
|
584
616
|
str = Ruby2Ruby.new.process(ast)
|
585
617
|
rv = import_site.module_eval str
|
586
618
|
raise Bud::CompileError unless rv.nil?
|
619
|
+
|
620
|
+
# Set an instance variable to allow modules produced by the import/rewrite
|
621
|
+
# process to be distinguished from "normal" Ruby modules.
|
622
|
+
mod = import_site.module_eval new_mod_name
|
623
|
+
raise Bud::CompileError unless mod.class == Module
|
624
|
+
mod.instance_variable_set("@bud_imported_module", true)
|
625
|
+
|
587
626
|
return new_mod_name
|
588
627
|
end
|
589
628
|
|
@@ -651,8 +690,8 @@ module ModuleRewriter # :nodoc: all
|
|
651
690
|
|
652
691
|
# If this module imports a submodule and binds it to :x, references to x.t1
|
653
692
|
# need to be flattened to the mangled name of x.t1.
|
654
|
-
def self.ast_flatten_nested_refs(ast,
|
655
|
-
NestedRefRewriter.new(
|
693
|
+
def self.ast_flatten_nested_refs(ast, mod)
|
694
|
+
NestedRefRewriter.new(mod).process(ast)
|
656
695
|
end
|
657
696
|
|
658
697
|
# Handle temp collections defined in the module's Bloom blocks.
|
data/lib/bud/server.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
class Bud::BudServer < EM::Connection #:nodoc: all
|
4
|
-
def initialize(bud)
|
4
|
+
def initialize(bud, channel_filter)
|
5
5
|
@bud = bud
|
6
|
+
@channel_filter = channel_filter
|
7
|
+
@filter_buf = {}
|
6
8
|
@pac = MessagePack::Unpacker.new
|
7
9
|
super
|
8
10
|
end
|
@@ -16,6 +18,24 @@ class Bud::BudServer < EM::Connection #:nodoc: all
|
|
16
18
|
message_received(obj)
|
17
19
|
end
|
18
20
|
|
21
|
+
# apply the channel filter to each channel's pending tuples
|
22
|
+
buf_leftover = {}
|
23
|
+
@filter_buf.each do |tbl_name, buf|
|
24
|
+
if @channel_filter
|
25
|
+
accepted, saved = @channel_filter.call(tbl_name, buf)
|
26
|
+
else
|
27
|
+
accepted = buf
|
28
|
+
saved = []
|
29
|
+
end
|
30
|
+
|
31
|
+
unless accepted.empty?
|
32
|
+
@bud.inbound[tbl_name] ||= []
|
33
|
+
@bud.inbound[tbl_name] += accepted
|
34
|
+
end
|
35
|
+
buf_leftover[tbl_name] = saved unless saved.empty?
|
36
|
+
end
|
37
|
+
@filter_buf = buf_leftover
|
38
|
+
|
19
39
|
begin
|
20
40
|
@bud.tick_internal if @bud.running_async
|
21
41
|
rescue Exception
|
@@ -24,8 +44,8 @@ class Bud::BudServer < EM::Connection #:nodoc: all
|
|
24
44
|
# error isn't best though -- we should do better (#74).
|
25
45
|
puts "Exception handling network messages: #{$!}"
|
26
46
|
puts "Inbound messages:"
|
27
|
-
@bud.inbound.each do |
|
28
|
-
puts " #{
|
47
|
+
@bud.inbound.each do |chn_name, t|
|
48
|
+
puts " #{t.inspect} (channel: #{chn_name})"
|
29
49
|
end
|
30
50
|
@bud.inbound.clear
|
31
51
|
end
|
@@ -40,6 +60,7 @@ class Bud::BudServer < EM::Connection #:nodoc: all
|
|
40
60
|
end
|
41
61
|
|
42
62
|
@bud.rtracer.recv(obj) if @bud.options[:rtrace]
|
43
|
-
@
|
63
|
+
@filter_buf[obj[0].to_sym] ||= []
|
64
|
+
@filter_buf[obj[0].to_sym] << obj[1]
|
44
65
|
end
|
45
66
|
end
|
data/lib/bud/state.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
module Bud
|
2
2
|
######## methods for registering collection types
|
3
3
|
private
|
4
|
-
def define_collection(name
|
5
|
-
# Don't allow duplicate collection definitions
|
4
|
+
def define_collection(name)
|
6
5
|
if @tables.has_key? name
|
7
6
|
raise Bud::CompileError, "collection already exists: #{name}"
|
8
7
|
end
|
@@ -11,19 +10,19 @@ module Bud
|
|
11
10
|
# previously-defined method names.
|
12
11
|
reserved = eval "defined?(#{name})"
|
13
12
|
unless reserved.nil?
|
14
|
-
raise Bud::CompileError, "symbol :#{name} reserved, cannot be used as
|
13
|
+
raise Bud::CompileError, "symbol :#{name} reserved, cannot be used as collection name"
|
15
14
|
end
|
16
15
|
self.singleton_class.send(:define_method, name) do |*args, &blk|
|
17
|
-
|
18
|
-
return @tables[name].pro(&blk)
|
19
|
-
else
|
16
|
+
if blk.nil?
|
20
17
|
return @tables[name]
|
18
|
+
else
|
19
|
+
return @tables[name].pro(&blk)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
public
|
26
|
-
|
25
|
+
|
27
26
|
def input # :nodoc: all
|
28
27
|
true
|
29
28
|
end
|
@@ -38,12 +37,12 @@ module Bud
|
|
38
37
|
scratch(name, schema)
|
39
38
|
end
|
40
39
|
|
41
|
-
# declare an in-memory, non-transient collection. default schema <tt>[:key] => [:val]</tt>.
|
40
|
+
# declare an in-memory, non-transient collection. default schema <tt>[:key] => [:val]</tt>.
|
42
41
|
def table(name, schema=nil)
|
43
42
|
define_collection(name)
|
44
43
|
@tables[name] = Bud::BudTable.new(name, self, schema)
|
45
44
|
end
|
46
|
-
|
45
|
+
|
47
46
|
# declare a syncronously-flushed persistent collection. default schema <tt>[:key] => [:val]</tt>.
|
48
47
|
def sync(name, storage, schema=nil)
|
49
48
|
define_collection(name)
|
@@ -58,7 +57,7 @@ module Bud
|
|
58
57
|
raise Bud::Error, "unknown synchronous storage engine #{storage.to_s}"
|
59
58
|
end
|
60
59
|
end
|
61
|
-
|
60
|
+
|
62
61
|
def store(name, storage, schema=nil)
|
63
62
|
define_collection(name)
|
64
63
|
case storage
|
@@ -79,7 +78,7 @@ module Bud
|
|
79
78
|
define_collection(name)
|
80
79
|
@tables[name] = Bud::BudScratch.new(name, self, schema)
|
81
80
|
end
|
82
|
-
|
81
|
+
|
83
82
|
def readonly(name, schema=nil)
|
84
83
|
define_collection(name)
|
85
84
|
@tables[name] = Bud::BudReadOnly.new(name, self, schema)
|
@@ -91,7 +90,7 @@ module Bud
|
|
91
90
|
# defer schema definition until merge
|
92
91
|
@tables[name] = Bud::BudTemp.new(name, self, nil, true)
|
93
92
|
end
|
94
|
-
|
93
|
+
|
95
94
|
# declare a transient network collection. default schema <tt>[:address, :val] => []</tt>
|
96
95
|
def channel(name, schema=nil, loopback=false)
|
97
96
|
define_collection(name)
|
@@ -114,7 +113,7 @@ module Bud
|
|
114
113
|
@tables[name] = Bud::BudFileReader.new(name, filename, delimiter, self)
|
115
114
|
end
|
116
115
|
|
117
|
-
# declare a collection to be auto-populated every +period+ seconds. schema <tt>[:key] => [:val]</tt>.
|
116
|
+
# declare a collection to be auto-populated every +period+ seconds. schema <tt>[:key] => [:val]</tt>.
|
118
117
|
# rhs of statements only.
|
119
118
|
def periodic(name, period=1)
|
120
119
|
define_collection(name)
|
data/lib/bud/storage/dbm.rb
CHANGED
@@ -41,6 +41,7 @@ module Bud
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def [](key)
|
44
|
+
check_enumerable(key)
|
44
45
|
key_s = MessagePack.pack(key)
|
45
46
|
val_s = @dbm[key_s]
|
46
47
|
if val_s
|
@@ -51,13 +52,14 @@ module Bud
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def has_key?(k)
|
55
|
+
check_enumerable(k)
|
54
56
|
key_s = MessagePack.pack(k)
|
55
57
|
return true if @dbm.has_key? key_s
|
56
58
|
return @delta.has_key? k
|
57
59
|
end
|
58
60
|
|
59
61
|
def include?(tuple)
|
60
|
-
key =
|
62
|
+
key = get_key_vals(tuple)
|
61
63
|
value = self[key]
|
62
64
|
return (value == tuple)
|
63
65
|
end
|
@@ -110,11 +112,11 @@ module Bud
|
|
110
112
|
|
111
113
|
def merge_to_db(buf)
|
112
114
|
buf.each do |key,tuple|
|
113
|
-
|
115
|
+
merge_tuple_to_db(key, tuple)
|
114
116
|
end
|
115
117
|
end
|
116
118
|
|
117
|
-
def
|
119
|
+
def merge_tuple_to_db(key, tuple)
|
118
120
|
val = val_cols.map{|c| tuple[cols.index(c)]}
|
119
121
|
key_s = MessagePack.pack(key)
|
120
122
|
val_s = MessagePack.pack(val)
|
@@ -140,16 +142,19 @@ module Bud
|
|
140
142
|
end
|
141
143
|
|
142
144
|
def insert(tuple)
|
143
|
-
key =
|
144
|
-
|
145
|
+
key = get_key_vals(tuple)
|
146
|
+
merge_tuple_to_db(key, tuple)
|
145
147
|
end
|
146
148
|
|
147
149
|
alias << insert
|
148
150
|
|
149
151
|
# Remove to_delete and then add pending to db
|
150
152
|
def tick
|
153
|
+
raise Bud::Error, "orphaned tuples in @delta for #{@tabname}" unless @delta.empty?
|
154
|
+
raise Bud::Error, "orphaned tuples in @new_delta for #{@tabname}" unless @new_delta.empty?
|
155
|
+
|
151
156
|
@to_delete.each do |tuple|
|
152
|
-
k =
|
157
|
+
k = get_key_vals(tuple)
|
153
158
|
k_str = MessagePack.pack(k)
|
154
159
|
cols_str = @dbm[k_str]
|
155
160
|
unless cols_str.nil?
|
@@ -168,8 +173,14 @@ module Bud
|
|
168
173
|
flush
|
169
174
|
end
|
170
175
|
|
171
|
-
|
172
|
-
|
176
|
+
public
|
177
|
+
def length
|
178
|
+
@dbm.length
|
179
|
+
end
|
180
|
+
|
181
|
+
public
|
182
|
+
def empty?
|
183
|
+
@dbm.empty?
|
173
184
|
end
|
174
185
|
end
|
175
186
|
end
|
@@ -47,6 +47,7 @@ module Bud
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def [](key)
|
50
|
+
check_enumerable(key)
|
50
51
|
key_s = MessagePack.pack(key)
|
51
52
|
val_s = @hdb[key_s]
|
52
53
|
if val_s
|
@@ -57,13 +58,14 @@ module Bud
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def has_key?(k)
|
61
|
+
check_enumerable(k)
|
60
62
|
key_s = MessagePack.pack(k)
|
61
63
|
return true if @hdb.has_key? key_s
|
62
64
|
return @delta.has_key? k
|
63
65
|
end
|
64
66
|
|
65
67
|
def include?(tuple)
|
66
|
-
key =
|
68
|
+
key = get_key_vals(tuple)
|
67
69
|
value = self[key]
|
68
70
|
return (value == tuple)
|
69
71
|
end
|
@@ -116,11 +118,11 @@ module Bud
|
|
116
118
|
|
117
119
|
def merge_to_hdb(buf)
|
118
120
|
buf.each do |key,tuple|
|
119
|
-
|
121
|
+
merge_tuple_to_hdb(key, tuple)
|
120
122
|
end
|
121
123
|
end
|
122
124
|
|
123
|
-
def
|
125
|
+
def merge_tuple_to_hdb(key, tuple)
|
124
126
|
val = val_cols.map{|c| tuple[cols.index(c)]}
|
125
127
|
key_s = MessagePack.pack(key)
|
126
128
|
val_s = MessagePack.pack(val)
|
@@ -144,16 +146,20 @@ module Bud
|
|
144
146
|
end
|
145
147
|
|
146
148
|
def insert(tuple)
|
147
|
-
|
148
|
-
|
149
|
+
tuple = prep_tuple(tuple)
|
150
|
+
key = get_key_vals(tuple)
|
151
|
+
merge_tuple_to_hdb(key, tuple)
|
149
152
|
end
|
150
153
|
|
151
154
|
alias << insert
|
152
155
|
|
153
156
|
# Remove to_delete and then add pending to HDB
|
154
157
|
def tick
|
158
|
+
raise Bud::Error, "orphaned tuples in @delta for #{@tabname}" unless @delta.empty?
|
159
|
+
raise Bud::Error, "orphaned tuples in @new_delta for #{@tabname}" unless @new_delta.empty?
|
160
|
+
|
155
161
|
@to_delete.each do |tuple|
|
156
|
-
k =
|
162
|
+
k = get_key_vals(tuple)
|
157
163
|
k_str = MessagePack.pack(k)
|
158
164
|
cols_str = @hdb[k_str]
|
159
165
|
unless cols_str.nil?
|
@@ -173,8 +179,12 @@ module Bud
|
|
173
179
|
@hdb.tranbegin
|
174
180
|
end
|
175
181
|
|
176
|
-
def
|
177
|
-
@hdb.
|
182
|
+
def length
|
183
|
+
@hdb.length
|
184
|
+
end
|
185
|
+
|
186
|
+
def empty?
|
187
|
+
@hdb.empty?
|
178
188
|
end
|
179
189
|
end
|
180
190
|
end
|