bud 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -1,4 +1,5 @@
1
- # We monkeypatch Module to add support for Bloom state and code declarations.
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
- child_tbl = mod.bud_import_table
21
- raise Bud::CompileError, "import symbol #{local_name} already in use" if @bud_import_tbl.has_key? local_name
22
- @bud_import_tbl[local_name] = child_tbl.clone # XXX: clone needed?
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, :keys => 1, :values => 1, :cols => 1, :key_cols => 1,
14
- :val_cols => 1, :payloads => 1, :~ => 1
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 recv and recv.class == Sexp
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 Rewrite
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 accurately (issue #225), so we just replace map calls liberally
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 iter vars
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
- # tbl, it should appear as an :lvar node rather than a :call, so we should be
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(import_tbl)
253
+ def initialize(mod)
247
254
  super()
248
255
  self.require_empty = false
249
256
  self.expected = Sexp
250
- @import_tbl = import_tbl
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.bud_import_table)
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, import_tbl)
655
- NestedRefRewriter.new(import_tbl).process(ast)
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 |m|
28
- puts " #{m[1].inspect} (channel: #{m[0]})"
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
- @bud.inbound << obj
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, &block)
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 table name"
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
- unless blk.nil? then
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)
@@ -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 = @key_colnums.map{|k| tuple[k]}
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
- merge_tuple(key, tuple)
115
+ merge_tuple_to_db(key, tuple)
114
116
  end
115
117
  end
116
118
 
117
- def merge_tuple(key, tuple)
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 = @key_colnums.map{|k| tuple[k]}
144
- merge_tuple(key, tuple)
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 = @key_colnums.map{|c| tuple[c]}
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
- def method_missing(sym, *args, &block)
172
- @dbm.send sym, *args, &block
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 = @key_colnums.map{|k| tuple[k]}
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
- merge_tuple(key, tuple)
121
+ merge_tuple_to_hdb(key, tuple)
120
122
  end
121
123
  end
122
124
 
123
- def merge_tuple(key, tuple)
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
- key = @key_colnums.map{|k| tuple[k]}
148
- merge_tuple(key, tuple)
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 = @key_colnums.map{|c| tuple[c]}
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 method_missing(sym, *args, &block)
177
- @hdb.send sym, *args, &block
182
+ def length
183
+ @hdb.length
184
+ end
185
+
186
+ def empty?
187
+ @hdb.empty?
178
188
  end
179
189
  end
180
190
  end