bud 0.0.8 → 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -10,15 +10,11 @@ documentation.
10
10
 
11
11
  Main deficiencies at this point are:
12
12
 
13
- - Inefficient evaluation: Programs are run using semi-naive evaluation
14
- strategies, but no further query optimization has been implemented, and little
15
- effort has been spent in tuning.
16
-
17
13
  - No Ruby constraints: Within Bloom programs the full power of Ruby is also
18
14
  available, including mutable state. This allows programmers to get outside
19
15
  the Bloom framework and lose cleanliness.
20
16
 
21
- - Compatibility: Bud only works with Ruby (MRI) 1.8. MRI 1.9, JRuby and other
17
+ - Compatibility: Bud only works with Ruby (MRI) 1.8 and 1.9. JRuby and other
22
18
  Ruby implementations are currently not supported.
23
19
 
24
20
  ## Installation
@@ -39,8 +35,6 @@ To run the unit tests:
39
35
 
40
36
  ## Optional Dependencies
41
37
 
42
- The bud gem has a handful of mandatory dependencies. It also has two optional
43
- dependencies: if you wish to use Bud collections backed by Zookeeper or Tokyo
44
- Cabinet, the "zookeeper" and/or "tokyocabinet" gems must be installed. Note that
45
- before installing the "tokyocabinet" gem, the Tokyo Cabinet libraries should be
46
- installed first.
38
+ The bud gem has a handful of mandatory dependencies. It also has one optional
39
+ dependency: if you wish to use Bud collections backed by Zookeeper, the
40
+ "zookeeper" gem must be installed.
data/bin/budplot CHANGED
@@ -2,7 +2,6 @@
2
2
  require 'rubygems'
3
3
  require 'bud'
4
4
  require 'bud/bud_meta'
5
- require 'bud/depanalysis'
6
5
  require 'bud/graphs'
7
6
  require 'bud/meta_algebra'
8
7
  require 'bud/viz_util'
@@ -33,7 +32,7 @@ def make_instance(mods)
33
32
  if mods.length == 1
34
33
  return mod_klass.new
35
34
  else
36
- puts "Error: cannot intermix classes and modules"
35
+ puts "Error: cannot intermix class \"#{mod_klass}\" with modules"
37
36
  exit
38
37
  end
39
38
  elsif mod_klass.class != Module
data/docs/cheat.md CHANGED
@@ -114,13 +114,12 @@ Built-in scratch collection to be used on the lhs of a rule; permanently halts t
114
114
  If the item `[:kill]` is inserted, the Bud OS process (including all Bud instances) is also halted.
115
115
 
116
116
  ### sync ###
117
- Persistent collection mapped to an external storage engine, with synchronous write-flushing each timestep. Supported storage engines: `:dbm` and `:tokyo`.<br>
117
+ Persistent collection mapped to an external storage engine, with synchronous write-flushing each timestep.
118
118
  Default attributes: `[:key] => [:val]`.
119
119
 
120
120
  sync :s1, :dbm
121
- sync :s2, :tokyo, [:k1, :k2] => [:v1, :v2]
122
121
 
123
- Further info: [DBM](http://en.wikipedia.org/wiki/Dbm), [Tokyo Cabinet](http://fallabs.com/tokyocabinet/).
122
+ Currently only [dbm](http://en.wikipedia.org/wiki/Dbm) is supported. Support for tokyo cabinet present in an earlier release has been removed.<br>
124
123
 
125
124
  ### store ###
126
125
  Persistent collection mapped to an external storage engine, with asynchronous write-flushing. Supported storage engines: `:zookeeper`.<br>
@@ -173,8 +172,6 @@ implicit map:
173
172
 
174
173
  `flat_map`:
175
174
 
176
- require 'backports' # flat_map not included in Ruby 1.8 by default
177
-
178
175
  t3 <= bc.flat_map do |t| # unnest a collection-valued attribute
179
176
  bc.col4.map { |sub| [t.col1, t.col2, t.col3, sub] }
180
177
  end
@@ -323,16 +320,6 @@ The schema of a temp collection in inherited from the rhs; if the rhs has no
323
320
  schema, a simple one is manufactured to suit the data found in the rhs at
324
321
  runtime: `[c0, c1, ...]`.
325
322
 
326
- `with`<br>
327
- With statements define a temp collection that can be referenced only within the scope of the associated block. They are useful when you "fork" in a dataflow into two lhs destinations:
328
-
329
- with :biggies <= request {|r| r if r.quantity > 100}, begin
330
- to_process <= (biggies * known_good).lefts(:key=>:key)
331
- denied <= (biggies * known_good).nopairs(:key=>key)
332
- end
333
-
334
- The advantage of using `with` over `temp` is modularity: all the rules referencing `biggies` have to be bundled together, making it easier to see that the contents of `request` with quantity > 100 are handled properly.
335
-
336
323
  ## Bud Modules ##
337
324
  A Bud module combines state (collections) and logic (Bloom rules). Using modules allows your program to be decomposed into a collection of smaller units.
338
325
 
@@ -1,5 +1,5 @@
1
1
  # simple shortest paths
2
- # note use of program.tick at bottom to run a single timestemp
2
+ # note use of program.tick at bottom to run a single timestep
3
3
  # and inspect relations
4
4
  require 'rubygems'
5
5
  require 'bud'
@@ -9,19 +9,19 @@ class ShortestPaths
9
9
 
10
10
  state do
11
11
  table :link, [:from, :to, :cost]
12
- table :path, [:from, :to, :next, :cost]
13
- table :shortest, [:from, :to] => [:next, :cost]
12
+ table :path, [:from, :to, :nxt, :cost]
13
+ table :shortest, [:from, :to] => [:nxt, :cost]
14
14
  end
15
15
 
16
16
  # recursive rules to define all paths from links
17
17
  bloom :make_paths do
18
18
  # base case: every link is a path
19
- path <= link {|e| [e.from, e.to, e.to, e.cost]}
19
+ path <= link {|l| [l.from, l.to, l.to, l.cost]}
20
20
 
21
21
  # inductive case: make path of length n+1 by connecting a link to a path of
22
22
  # length n
23
23
  path <= (link*path).pairs(:to => :from) do |l,p|
24
- [l.from, p.to, p.from, l.cost+p.cost]
24
+ [l.from, p.to, l.to, l.cost+p.cost]
25
25
  end
26
26
  end
27
27
 
@@ -43,11 +43,11 @@ program.link <= [['a', 'b', 1],
43
43
  ['d', 'e', 1]]
44
44
 
45
45
  program.tick # one timestamp is enough for this simple program
46
- program.shortest.sort.each {|t| puts t.inspect}
46
+ program.shortest.to_a.sort.each {|t| puts t.inspect}
47
47
 
48
48
  puts "----"
49
49
 
50
50
  # now lets add an extra link and recompute
51
51
  program.link << ['e', 'f', 1]
52
52
  program.tick
53
- program.shortest.sort.each {|t| puts t.inspect}
53
+ program.shortest.to_a.sort.each {|t| puts t.inspect}
data/lib/bud/aggs.rb CHANGED
@@ -11,7 +11,7 @@ module Bud
11
11
  # a. :ignore tells the caller to ignore this input
12
12
  # b. :keep tells the caller to save this input
13
13
  # c. :replace tells the caller to keep this input alone
14
- # d. [:delete, t1, t2, ...] tells the caller to delete the remaining tuples
14
+ # d. :delete, [t1, t2, ...] tells the caller to delete the remaining tuples
15
15
  # For things that do not descend from ArgExemplary, the 2nd part can simply be nil.
16
16
  def trans(the_state, val)
17
17
  return the_state, :ignore
@@ -42,6 +42,8 @@ module Bud
42
42
  def trans(the_state, val)
43
43
  if the_state < val
44
44
  return the_state, :ignore
45
+ elsif the_state == val
46
+ return the_state, :keep
45
47
  else
46
48
  return val, :replace
47
49
  end
@@ -86,41 +88,35 @@ module Bud
86
88
  def choose(x)
87
89
  [Choose.new, x]
88
90
  end
89
-
90
- class ChooseRand < ArgExemplary #:nodoc: all
91
- @@reservoir_size = 1 # Vitter's reservoir sampling, k = 1
92
- def init(x=nil)
93
- the_state = {:cnt => 1, :vals => [x]}
91
+
92
+ class ChooseOneRand < ArgExemplary #:nodoc: all
93
+ def init(x=nil) # Vitter's reservoir sampling, sample size = 1
94
+ the_state = {:cnt => 1, :val => x}
94
95
  end
95
96
 
96
97
  def trans(the_state, val)
97
98
  the_state[:cnt] += 1
98
- if the_state[:cnt] < @@reservoir_size
99
- the_state[:vals] << val
100
- return the_state, :keep
99
+ j = rand(the_state[:cnt])
100
+ if j < 1 # replace
101
+ old_val = the_state[:val]
102
+ the_state[:val] = val
103
+ return the_state, :replace
101
104
  else
102
- j = rand(the_state[:cnt])
103
- if j < @@reservoir_size
104
- old_tup = the_state[:vals][j]
105
- the_state[:vals][j] = val
106
- return the_state, [:delete, old_tup]
107
- else
108
- return the_state, :keep
109
- end
105
+ return the_state, :ignore
110
106
  end
111
107
  end
112
108
  def tie(the_state, val)
113
109
  true
114
110
  end
115
111
  def final(the_state)
116
- the_state[:vals][rand(the_state[@@reservoir_size])]
112
+ the_state[:val] # XXX rand(@@reservoir_size will always be 0, since @@reservoir_size is 1
117
113
  end
118
114
  end
119
115
 
120
116
  # exemplary aggregate method to be used in Bud::BudCollection.group.
121
117
  # randomly chooses among x entries being aggregated.
122
118
  def choose_rand(x=nil)
123
- [ChooseRand.new, x]
119
+ [ChooseOneRand.new, x]
124
120
  end
125
121
 
126
122
  class Sum < Agg #:nodoc: all
data/lib/bud/bud_meta.rb CHANGED
@@ -1,66 +1,45 @@
1
1
  require 'bud/rewrite'
2
- require 'bud/state'
3
- require 'parse_tree'
4
2
  require 'pp'
5
3
 
6
- class BudMeta #:nodoc: all
7
- attr_reader :depanalysis
8
4
 
5
+ class BudMeta #:nodoc: all
9
6
  def initialize(bud_instance, declarations)
10
7
  @bud_instance = bud_instance
11
8
  @declarations = declarations
9
+ @dependency_analysis = nil # the results of bud_meta are analyzed further using a helper bloom instance. See depanalysis())
12
10
  end
13
11
 
14
12
  def meta_rewrite
15
- shred_rules
16
- top_stratum = stratify
17
- stratum_map = binaryrel2map(@bud_instance.t_stratum)
18
-
19
- rewritten_strata = Array.new(top_stratum + 2) { [] }
20
- no_attr_rewrite_strata = Array.new(top_stratum + 2) { [] }
21
- @bud_instance.t_rules.each do |d|
22
- if d.op.to_s == '<='
23
- # Deductive rules are assigned to strata based on the basic Datalog
24
- # stratification algorithm
25
- belongs_in = stratum_map[d.lhs]
26
- belongs_in ||= 0
27
- rewritten_strata[belongs_in] << d.src
28
- no_attr_rewrite_strata[belongs_in] << d.orig_src
29
- else
30
- # All temporal rules are placed in the last stratum
31
- rewritten_strata[top_stratum + 1] << d.src
32
- no_attr_rewrite_strata[top_stratum + 1] << d.orig_src
33
- end
34
- end
13
+ shred_rules # capture dependencies, rewrite rules
35
14
 
36
- @depanalysis = DepAnalysis.new
37
- @bud_instance.t_depends_tc.each {|d| @depanalysis.depends_tc << d}
38
- @bud_instance.t_provides.each {|p| @depanalysis.providing << p}
39
- 3.times { @depanalysis.tick_internal }
15
+ stratified_rules = []
16
+ if @bud_instance.toplevel == @bud_instance
17
+ nodes, stratum_map, top_stratum = stratify_preds
40
18
 
41
- @depanalysis.underspecified.each do |u|
42
- puts "Warning: underspecified dataflow: #{u.inspect}"
43
- @bud_instance.t_underspecified << u
44
- end
45
- @depanalysis.source.each do |s|
46
- @bud_instance.sources[s.first] = true
47
- end
48
- @depanalysis.sink.each do |s|
49
- @bud_instance.sinks[s.first] = true
50
- end
51
-
52
- dump_rewrite(no_attr_rewrite_strata) if @bud_instance.options[:dump_rewrite]
19
+ # stratum_map = {fully qualified pred => stratum}
53
20
 
54
- return rewritten_strata, no_attr_rewrite_strata
55
- end
21
+ #slot each rule into the stratum corresponding to its lhs pred (from stratum_map)
22
+ stratified_rules = Array.new(top_stratum + 2) { [] } # stratum -> [ rules ]
23
+ @bud_instance.t_rules.each do |rule|
24
+ if rule.op.to_s == '<='
25
+ # Deductive rules are assigned to strata based on the basic Datalog
26
+ # stratification algorithm
27
+ belongs_in = stratum_map[rule.lhs]
28
+ belongs_in ||= 0
29
+ stratified_rules[belongs_in] << rule
30
+ else
31
+ # All temporal rules are placed in the last stratum
32
+ stratified_rules[top_stratum + 1] << rule
33
+ end
34
+ end
35
+ # stratified_rules[0] may be empty if none of the nodes at stratum 0 are on the lhs
36
+ # stratified_rules[top_stratum+1] will be empty if there are no temporal rules.
37
+ # Cleanup
38
+ stratified_rules = stratified_rules.reject{|r| r.empty?}
39
+ dump_rewrite(stratified_rules) if @bud_instance.options[:dump_rewrite]
56
40
 
57
- def binaryrel2map(rel)
58
- map = {}
59
- rel.each do |s|
60
- raise Bud::Error unless s.length == 2
61
- map[s[0]] = s[1]
62
41
  end
63
- return map
42
+ return stratified_rules
64
43
  end
65
44
 
66
45
  def shred_rules
@@ -87,11 +66,18 @@ class BudMeta #:nodoc: all
87
66
  end
88
67
 
89
68
  def rewrite_rule_block(klass, block_name, seed)
90
- parse_tree = ParseTree.translate(klass, block_name)
91
- return unless parse_tree.first
69
+ return unless klass.respond_to? :__bloom_asts__
92
70
 
93
- pt = Unifier.new.process(parse_tree)
71
+ pt = klass.__bloom_asts__[block_name]
72
+ return if pt.nil?
73
+
74
+ pt = Marshal.load(Marshal.dump(pt)) #deep clone because RuleRewriter mucks up pt.
94
75
  pp pt if @bud_instance.options[:dump_ast]
76
+ tmp_expander = TempExpander.new
77
+ pt = tmp_expander.process(pt)
78
+ tmp_expander.tmp_tables.each do |t|
79
+ @bud_instance.temp(t.to_sym)
80
+ end
95
81
 
96
82
  rv = check_rule_ast(pt)
97
83
  unless rv.nil?
@@ -112,12 +98,30 @@ class BudMeta #:nodoc: all
112
98
  end
113
99
  raise Bud::CompileError, "#{error_msg} in rule block \"#{block_name}\"#{src_msg}"
114
100
  end
115
-
116
101
  rewriter = RuleRewriter.new(seed, @bud_instance)
117
102
  rewriter.process(pt)
118
103
  return rewriter
119
104
  end
120
105
 
106
+ def get_qual_name(pt)
107
+ # expect to see a parse tree corresponding to a dotted name
108
+ # a.b.c == s(:call, s1, :c, (:args))
109
+ # where s1 == s(:call, s2, :b, (:args))
110
+ # where s2 == s(:call, nil,:a, (:args))
111
+
112
+ tag, recv, name, args = pt
113
+ return nil unless tag == :call or args.length == 1
114
+
115
+ if recv
116
+ qn = get_qual_name(recv)
117
+ return nil if qn.nil? or qn.size == 0
118
+ qn = qn + "." + name.to_s
119
+ else
120
+ qn = name.to_s
121
+ end
122
+ qn
123
+ end
124
+
121
125
  # Perform some basic sanity checks on the AST of a rule block. We expect a
122
126
  # rule block to consist of a :defn, a nested :scope, and then a sequence of
123
127
  # statements. Each statement is a :call node. Returns nil (no error found), a
@@ -147,10 +151,9 @@ class BudMeta #:nodoc: all
147
151
  tag, lhs, op, rhs = n
148
152
 
149
153
  # Check that LHS references a named collection
150
- return n if lhs.nil? or lhs.sexp_type != :call
151
- lhs_name = lhs[2].to_sym
152
- unless @bud_instance.tables.has_key? lhs_name
153
- return [n, "collection does not exist: '#{lhs_name}'"]
154
+ lhs_name = get_qual_name(lhs)
155
+ unless lhs_name and @bud_instance.tables.has_key? lhs_name.to_sym
156
+ return [n, "Collection does not exist: '#{lhs_name}'"]
154
157
  end
155
158
 
156
159
  return [n, "illegal operator: '#{op}'"] unless [:<, :<=].include? op
@@ -176,36 +179,121 @@ class BudMeta #:nodoc: all
176
179
  return nil # No errors found
177
180
  end
178
181
 
179
- def stratify
180
- strat = Stratification.new
181
- @bud_instance.t_depends.each {|d| strat.depends << d}
182
- strat.tick_internal
183
-
184
- # Copy computed data back into Bud runtime
185
- @bud_instance.stratum_collection_map = []
186
- strat.stratum.each do |s|
187
- @bud_instance.t_stratum << s
188
- @bud_instance.stratum_collection_map[s[1]] ||= []
189
- @bud_instance.stratum_collection_map[s[1]] << s[0].to_sym
182
+
183
+ Node = Struct.new :name, :status, :stratum, :edges, :in_lhs, :in_body, :in_cycle, :is_neg_head
184
+ # Node.status is one of :init, :in_progress, :done
185
+ Edge = Struct.new :to, :op, :neg, :temporal
186
+
187
+ def stratify_preds
188
+ bud = @bud_instance.toplevel
189
+ nodes = {}
190
+ bud.t_depends.each do |d|
191
+ #t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm]
192
+ lhs = (nodes[d.lhs.to_s] ||= Node.new(d.lhs.to_s, :init, 0, [], true, false, false, false))
193
+ lhs.in_lhs = true
194
+ body = (nodes[d.body.to_s] ||= Node.new(d.body.to_s, :init, 0, [], false, true, false, false))
195
+ temporal = d.op != "<="
196
+ lhs.edges << Edge.new(body, d.op, d.nm, temporal)
197
+ body.in_body = true
190
198
  end
191
- strat.depends_tc.each {|d| @bud_instance.t_depends_tc << d}
192
- strat.cycle.each {|c| @bud_instance.t_cycle << c}
193
- if strat.top_strat.length > 0
194
- top = strat.top_strat.first.stratum
195
- else
196
- top = 1
199
+
200
+ nodes.values.each {|n| calc_stratum(n, false, false, [n.name])}
201
+ # Normalize stratum numbers because they may not be 0-based or consecutive
202
+ remap = {}
203
+ # if the nodes stratum numbers are [2, 3, 2, 4], remap = {2 => 0, 3 => 1, 4 => 2}
204
+ nodes.values.map {|n| n.stratum}.uniq.sort.each_with_index{|num, i|
205
+ remap[num] = i
206
+ }
207
+ stratum_map = {}
208
+ top_stratum = -1
209
+ nodes.each_pair do |name, n|
210
+ n.stratum = remap[n.stratum]
211
+ stratum_map[n.name] = n.stratum
212
+ top_stratum = max(top_stratum, n.stratum)
197
213
  end
214
+ analyze_dependencies(nodes)
215
+ return nodes, stratum_map, top_stratum
216
+ end
198
217
 
199
- return top
218
+ def max(a, b) ; a > b ? a : b ; end
219
+
220
+ def calc_stratum(node, neg, temporal, path)
221
+ if node.status == :in_process
222
+ node.in_cycle = true
223
+ if neg and !temporal and node.is_neg_head
224
+ raise Bud::CompileError, "unstratifiable program: #{path.uniq.join(',')}"
225
+ end
226
+ elsif node.status == :init
227
+ node.status = :in_process
228
+ node.edges.each do |edge|
229
+ node.is_neg_head = edge.neg
230
+ next if edge.op != "<="
231
+ body_stratum = calc_stratum(edge.to, (neg or edge.neg), (edge.temporal or temporal), path + [edge.to.name])
232
+ node.is_neg_head = false #reset for next edge
233
+ node.stratum = max(node.stratum, body_stratum + (edge.neg ? 1 : 0))
234
+ end
235
+ node.status = :done
236
+ end
237
+ node.stratum
238
+ end
239
+
240
+
241
+ def analyze_dependencies(nodes) # nodes = {node name => node}
242
+ bud = @bud_instance
243
+
244
+ preds_in_lhs = nodes.inject(Set.new) {|preds, name_n| preds.add(name_n[0]) if name_n[1].in_lhs; preds}
245
+ preds_in_body = nodes.inject(Set.new) {|preds, name_n| preds.add(name_n[0]) if name_n[1].in_body; preds}
246
+
247
+ bud.t_provides.each do |p|
248
+ pred, input = p.interface, p.input
249
+ if input
250
+ # an interface pred is a source if it is an input and it is not in any rule's lhs
251
+ #bud.sources << [pred] unless (preds_in_lhs.include? pred)
252
+ unless preds_in_body.include? pred.to_s
253
+ # input interface is underspecified if not used in any rule body
254
+ bud.t_underspecified << [pred, true] # true indicates input mode
255
+ puts "Warning: input interface #{pred} not used"
256
+ end
257
+ else
258
+ # an interface pred is a sink if it is not an input and it is not in any rule's body
259
+ #(if it is in the body, then it is an intermediate node feeding some lhs)
260
+ #bud.sinks << [pred] unless (preds_in_body.include? pred)
261
+ unless preds_in_lhs.include? pred.to_s
262
+ # output interface underspecified if not in any rule's lhs
263
+ bud.t_underspecified << [pred, false] #false indicates output mode.
264
+ puts "Warning: output interface #{pred} not used"
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ def depanalysis
271
+ if @dependency_analysis.nil?
272
+ require 'bud/depanalysis'
273
+ da = ::DepAnalysis.new
274
+ da.providing <+ @bud_instance.tables[:t_provides].to_a
275
+ da.depends <+ @bud_instance.t_depends.map{|d| [d.lhs, d.op, d.body, d.nm]}
276
+
277
+ #@bud_instance.tables[:t_provides].each {|t| da.providing <+ t}
278
+ #@bud_instance.tables[:t_depends].each {|t| da.depends_tc <+ t}
279
+ da.tick_internal
280
+ @dependency_analysis = da
281
+ end
282
+ @dependency_analysis
200
283
  end
201
284
 
202
285
  def dump_rewrite(strata)
203
286
  fout = File.new("#{@bud_instance.class}_rewritten.txt", "w")
204
287
  fout.puts "Declarations:"
205
288
 
206
- strata.each_with_index do |src_ary, i|
207
- text = src_ary.join("\n")
208
- fout.puts "R[#{i}]:\n#{text}"
289
+ strata.each_with_index do |rules, i|
290
+ fout.print "=================================\n"
291
+ fout.print "Stratum #{i}\n"
292
+ rules.each do |r|
293
+ fout.puts "#{r.bud_obj.class}##{r.bud_obj.object_id} #{r.rule_id}"
294
+ fout.puts "\tsrc: #{r.src}"
295
+ fout.puts "\torig src: #{r.orig_src}"
296
+ end
209
297
  end
210
298
  fout.close
211
299
  end
data/lib/bud/bust/bust.rb CHANGED
@@ -68,23 +68,30 @@ module Bust
68
68
  if @request =~ /GET .* HTTP*/
69
69
  puts "GET shouldn't have body" if @body
70
70
  # select the appropriate elements from the table
71
- desired_elements = (eval "@bud." + table_name).find_all do |t|
72
- uri_params.all? {|k, v| (eval "t." + k.to_s) == v[0]}
71
+ desired_elements = @bud.send(table_name.to_sym).find_all do |t|
72
+ uri_params.all? {|k, v|
73
+ (eval "t." + k.to_s) == v[0]
74
+ }
73
75
  end
74
76
  @session.print success
77
+ desired_elements = desired_elements .map{|elem|
78
+ elem.class <= Struct ? elem.to_a : elem
79
+ }
75
80
  @session.print desired_elements.to_json
76
81
  elsif @request =~ /POST .* HTTP*/
77
82
  # instantiate a new tuple
78
83
  tuple_to_insert = []
79
84
  @body.each do |k, v|
80
- index = (eval "@bud." + table_name).cols.find_index(k.to_sym)
85
+ index = @bud.send(table_name.to_sym).cols.find_index(k.to_sym)
81
86
  for i in (tuple_to_insert.size..index)
82
87
  tuple_to_insert << nil
83
88
  end
84
89
  tuple_to_insert[index] = v[0]
85
90
  end
86
91
  # actually insert the puppy
87
- @bud.async_do { (eval "@bud." + table_name) <+ [tuple_to_insert] }
92
+ @bud.async_do {
93
+ @bud.send(table_name.to_sym) <+ [tuple_to_insert]
94
+ }
88
95
  @session.print success
89
96
  end
90
97
  rescue Exception