bud 0.0.7 → 0.0.8

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