bud 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README +33 -16
  2. data/bin/budplot +42 -65
  3. data/bin/budtimelines +235 -0
  4. data/bin/budvis +24 -122
  5. data/bin/rebl +1 -0
  6. data/docs/README.md +21 -10
  7. data/docs/bfs.md +4 -6
  8. data/docs/c.html +251 -0
  9. data/docs/cheat.md +45 -30
  10. data/docs/deploy.md +26 -26
  11. data/docs/getstarted.md +6 -4
  12. data/docs/visualizations.md +43 -31
  13. data/examples/chat/chat.rb +4 -9
  14. data/examples/chat/chat_server.rb +1 -8
  15. data/examples/deploy/deploy_ip_port +1 -0
  16. data/examples/deploy/keys.rb +5 -0
  17. data/examples/deploy/tokenring-ec2.rb +9 -9
  18. data/examples/deploy/{tokenring-local.rb → tokenring-fork.rb} +3 -5
  19. data/examples/deploy/tokenring-thread.rb +15 -0
  20. data/examples/deploy/tokenring.rb +25 -17
  21. data/lib/bud/aggs.rb +87 -25
  22. data/lib/bud/bud_meta.rb +48 -31
  23. data/lib/bud/bust/bust.rb +16 -15
  24. data/lib/bud/collections.rb +207 -232
  25. data/lib/bud/depanalysis.rb +1 -0
  26. data/lib/bud/deploy/countatomicdelivery.rb +8 -20
  27. data/lib/bud/deploy/deployer.rb +16 -16
  28. data/lib/bud/deploy/ec2deploy.rb +34 -35
  29. data/lib/bud/deploy/forkdeploy.rb +90 -0
  30. data/lib/bud/deploy/threaddeploy.rb +38 -0
  31. data/lib/bud/graphs.rb +103 -199
  32. data/lib/bud/joins.rb +190 -41
  33. data/lib/bud/monkeypatch.rb +84 -0
  34. data/lib/bud/rebl.rb +8 -1
  35. data/lib/bud/rewrite.rb +152 -49
  36. data/lib/bud/server.rb +1 -0
  37. data/lib/bud/state.rb +24 -10
  38. data/lib/bud/storage/dbm.rb +170 -0
  39. data/lib/bud/storage/tokyocabinet.rb +5 -1
  40. data/lib/bud/stratify.rb +6 -7
  41. data/lib/bud/viz.rb +31 -17
  42. data/lib/bud/viz_util.rb +204 -0
  43. data/lib/bud.rb +271 -244
  44. data/lib/bud.rb.orig +806 -0
  45. metadata +43 -22
  46. data/docs/bfs.raw +0 -251
  47. data/docs/diffs +0 -181
  48. data/examples/basics/out +0 -1103
  49. data/examples/basics/out.new +0 -856
  50. data/lib/bud/deploy/localdeploy.rb +0 -53
@@ -1,7 +1,7 @@
1
1
  require 'msgpack'
2
2
 
3
3
  module Bud
4
- ########
4
+ ########
5
5
  #--
6
6
  # the collection types
7
7
  # each collection is partitioned into 4:
@@ -16,7 +16,7 @@ module Bud
16
16
 
17
17
  attr_accessor :bud_instance, :locspec_idx # :nodoc: all
18
18
  attr_reader :schema, :tabname # :nodoc: all
19
- attr_reader :storage, :delta, :new_delta # :nodoc: all
19
+ attr_reader :storage, :delta, :new_delta, :pending # :nodoc: all
20
20
 
21
21
  def initialize(name, bud_instance, given_schema=nil, defer_schema=false) # :nodoc: all
22
22
  @tabname = name
@@ -27,7 +27,6 @@ module Bud
27
27
 
28
28
  private
29
29
  def init_buffers
30
- @sealed = false
31
30
  init_storage
32
31
  init_pending
33
32
  init_deltas
@@ -70,7 +69,7 @@ module Bud
70
69
  return [schema, key_cols]
71
70
  end
72
71
 
73
- public
72
+ public
74
73
  def clone_empty #:nodoc: all
75
74
  self.class.new(tabname, bud_instance, @given_schema)
76
75
  end
@@ -80,7 +79,7 @@ module Bud
80
79
  def key_cols
81
80
  @key_cols
82
81
  end
83
-
82
+
84
83
  # subset of the schema (i.e. an array of attribute names) that is not in the key
85
84
  public
86
85
  def val_cols # :nodoc: all
@@ -155,25 +154,21 @@ module Bud
155
154
  public
156
155
  def pro(&blk)
157
156
  if @bud_instance.stratum_first_iter
158
- return map(&blk)
157
+ return map(&blk)
159
158
  else
160
- if @delta.empty?
161
- return []
162
- else
163
- retval = []
164
- each_from([@delta]) do |t|
165
- newitem = blk.call(t)
166
- retval << newitem unless newitem.nil?
167
- end
168
- return retval
159
+ retval = []
160
+ each_from([@delta]) do |t|
161
+ newitem = blk.call(t)
162
+ retval << newitem unless newitem.nil?
169
163
  end
170
- end
164
+ return retval
165
+ end
171
166
  end
172
167
 
173
168
  # By default, all tuples in any rhs are in storage or delta. Tuples in
174
169
  # new_delta will get transitioned to delta in the next iteration of the
175
170
  # evaluator (but within the current time tick).
176
- public
171
+ public
177
172
  def each(&block) # :nodoc: all
178
173
  each_from([@storage, @delta], &block)
179
174
  end
@@ -223,7 +218,8 @@ module Bud
223
218
  # checks for key +k+ in the key columns
224
219
  public
225
220
  def has_key?(k)
226
- return false if k.nil? or k.empty? or self[k].nil?
221
+ check_enumerable(k)
222
+ return false if k.nil? or self[k].nil?
227
223
  return true
228
224
  end
229
225
 
@@ -232,7 +228,8 @@ module Bud
232
228
  def [](k)
233
229
  # assumes that key is in storage or delta, but not both
234
230
  # is this enforced in do_insert?
235
- return @storage[k].nil? ? @delta[k] : @storage[k]
231
+ t = @storage[k]
232
+ return t.nil? ? @delta[k] : t
236
233
  end
237
234
 
238
235
  # checks for +item+ in the collection
@@ -257,15 +254,18 @@ module Bud
257
254
  end
258
255
 
259
256
  private
260
- def raise_pk_error(new, old)
257
+ def raise_pk_error(new_guy, old)
261
258
  keycols = key_cols.map{|k| old[schema.index(k)]}
262
- raise KeyConstraintError, "Key conflict inserting #{old.inspect} into \"#{tabname}\": existing tuple #{new.inspect}, key_cols = #{keycols.inspect}"
259
+ raise KeyConstraintError, "Key conflict inserting #{new_guy.inspect} into \"#{tabname}\": existing tuple #{old.inspect}, key_cols = #{keycols.inspect}"
263
260
  end
264
261
 
265
262
  private
266
263
  def prep_tuple(o)
267
264
  unless o.respond_to?(:length) and o.respond_to?(:[])
268
- raise BudTypeError, "non-indexable type inserted into BudCollection #{self.tabname}: #{o.inspect}"
265
+ raise BudTypeError, "non-indexable type inserted into \"#{tabname}\": #{o.inspect}"
266
+ end
267
+ if o.class <= String
268
+ raise BudTypeError, "String value used as a fact inserted into \"#{tabname}\": #{o.inspect}"
269
269
  end
270
270
 
271
271
  if o.length < schema.length then
@@ -308,8 +308,8 @@ module Bud
308
308
 
309
309
  private
310
310
  def check_enumerable(o)
311
- unless (o.nil? or o.class < Enumerable) and o.respond_to? 'each'
312
- raise BudTypeError, "Attempt to merge non-enumerable type into BudCollection"
311
+ unless o.nil? or o.class < Enumerable
312
+ raise BudTypeError, "Collection #{tabname} expected Enumerable value, not #{o.inspect} (class = #{o.class})"
313
313
  end
314
314
  end
315
315
 
@@ -318,10 +318,15 @@ module Bud
318
318
  private
319
319
  def establish_schema(o)
320
320
  # use o's schema if available
321
- deduce_schema(o) if @schema.nil?
322
- # else use arity of first tuple of o
323
- fit_schema(o.first.size) if @schema.nil? and not o.first.nil?
324
- return @schema
321
+ deduce_schema(o)
322
+ # else use arity of first non-nil tuple of o
323
+ if @schema.nil?
324
+ o.each do |t|
325
+ next if t.nil?
326
+ fit_schema(t.size)
327
+ break
328
+ end
329
+ end
325
330
  end
326
331
 
327
332
  # Copy over the schema from +o+ if available
@@ -331,8 +336,7 @@ module Bud
331
336
  # must have been initialized with defer_schema==true. take schema from rhs
332
337
  init_schema(o.schema)
333
338
  end
334
- # returns old state of @schema (nil) if nothing available
335
- return @schema
339
+ # if nothing available, leave @schema unchanged
336
340
  end
337
341
 
338
342
  # manufacture schema of the form [:c0, :c1, ...] with width = +arity+
@@ -340,24 +344,36 @@ module Bud
340
344
  def fit_schema(arity)
341
345
  # rhs is schemaless. create schema from first tuple merged
342
346
  init_schema((0..arity-1).map{|indx| ("c"+indx.to_s).to_sym})
343
- return @schema
347
+ end
348
+
349
+ private
350
+ def include_any_buf?(t, key_vals)
351
+ bufs = [self, @delta, @new_delta]
352
+ bufs.each do |b|
353
+ old = b[key_vals]
354
+ next if old.nil?
355
+ if old != t
356
+ raise_pk_error(t, old)
357
+ else
358
+ return true
359
+ end
360
+ end
361
+ return false
344
362
  end
345
363
 
346
364
  public
347
365
  def merge(o, buf=@new_delta) # :nodoc: all
348
- check_enumerable(o)
349
- establish_schema(o) if @schema.nil?
350
-
351
- delta = o.map do |i|
352
- next if i.nil? or i == []
353
- i = prep_tuple(i)
354
- key_vals = @key_colnums.map{|k| i[k]}
355
- if (old = self[key_vals])
356
- raise_pk_error(i, old) if old != i
357
- elsif (oldnew = self.new_delta[key_vals])
358
- raise_pk_error(i, oldnew) if oldnew != i
359
- else
360
- buf[key_vals] = tuple_accessors(i)
366
+ unless o.nil?
367
+ o = o.uniq.compact if o.respond_to?(:uniq)
368
+ check_enumerable(o)
369
+ establish_schema(o) if @schema.nil?
370
+
371
+ # it's a pity that we are massaging the tuples that already exist in the head
372
+ o.each do |t|
373
+ next if t.nil? or t == []
374
+ t = prep_tuple(t)
375
+ key_vals = @key_colnums.map{|k| t[k]}
376
+ buf[key_vals] = tuple_accessors(t) unless include_any_buf?(t, key_vals)
361
377
  end
362
378
  end
363
379
  return self
@@ -365,7 +381,7 @@ module Bud
365
381
 
366
382
  public
367
383
  # instantaneously merge items from collection +o+ into +buf+
368
- def <=(collection)
384
+ def <=(collection)
369
385
  merge(collection)
370
386
  end
371
387
 
@@ -373,7 +389,7 @@ module Bud
373
389
  public
374
390
  def pending_merge(o) # :nodoc: all
375
391
  check_enumerable(o)
376
- deduce_schema(o)
392
+ establish_schema(o) if @schema.nil?
377
393
 
378
394
  o.each {|i| do_insert(i, @pending)}
379
395
  return self
@@ -395,7 +411,7 @@ module Bud
395
411
  end
396
412
 
397
413
  # move deltas to storage, and new_deltas to deltas.
398
- public
414
+ public
399
415
  def tick_deltas # :nodoc: all
400
416
  # assertion: intersect(@storage, @delta) == nil
401
417
  @storage.merge!(@delta)
@@ -421,7 +437,7 @@ module Bud
421
437
  return []
422
438
  end
423
439
  end
424
-
440
+
425
441
 
426
442
  # a generalization of argmin/argmax to arbitrary exemplary aggregates.
427
443
  # for each distinct value in the grouping key columns, return the item in that group
@@ -447,30 +463,42 @@ module Bud
447
463
  if memo[pkey_cols].nil?
448
464
  memo[pkey_cols] = {:agg=>agg.send(:init, p[colnum]), :tups => [p]}
449
465
  else
450
- newval = agg.send(:trans, memo[pkey_cols][:agg], p[colnum])
451
- if memo[pkey_cols][:agg] == newval
452
- if agg.send(:tie, memo[pkey_cols][:agg], p[colnum])
453
- memo[pkey_cols][:tups] << p
454
- end
455
- else
456
- memo[pkey_cols] = {:agg=>newval, :tups=>[p]}
466
+ memo[pkey_cols][:agg], argflag = \
467
+ agg.send(:trans, memo[pkey_cols][:agg], p[colnum])
468
+ if argflag == :keep or agg.send(:tie, memo[pkey_cols][:agg], p[colnum])
469
+ memo[pkey_cols][:tups] << p
470
+ elsif argflag == :replace
471
+ memo[pkey_cols][:tups] = [p]
472
+ elsif argflag.class <= Array and argflag[0] == :delete
473
+ memo[pkey_cols][:tups] -= argflag[1..-1]
457
474
  end
458
475
  end
459
476
  memo
460
477
  end
461
478
 
479
+ # now we need to finalize the agg per group
480
+ finalaggs = {}
462
481
  finals = []
463
- outs = tups.each_value do |t|
464
- ties = t[:tups].map do |tie|
465
- finals << tie
482
+ tups.each do |k,v|
483
+ finalaggs[k] = agg.send(:final, v[:agg])
484
+ end
485
+
486
+ # and winnow the tups to match
487
+ finalaggs.each do |k,v|
488
+ tups[k][:tups].each do |t|
489
+ finals << t if (t[colnum] == v)
466
490
  end
467
491
  end
468
492
 
469
- # merge directly into retval.storage, so that the temp tuples get picked up
470
- # by the lhs of the rule
471
- retval = BudScratch.new('argagg_temp', bud_instance, @given_schema)
472
- retval.uniquify_tabname
473
- retval.merge(finals, retval.storage)
493
+ if block_given?
494
+ finals.map{|r| yield r}
495
+ else
496
+ # merge directly into retval.storage, so that the temp tuples get picked up
497
+ # by the lhs of the rule
498
+ retval = BudScratch.new('argagg_temp', bud_instance, @given_schema)
499
+ retval.uniquify_tabname
500
+ retval.merge(finals, retval.storage)
501
+ end
474
502
  end
475
503
 
476
504
  # for each distinct value in the grouping key columns, return the item in that group
@@ -487,14 +515,29 @@ module Bud
487
515
  argagg(:max, gbkey_cols, col)
488
516
  end
489
517
 
518
+ private
519
+ def wrap_map(j, &blk)
520
+ if blk.nil?
521
+ return j
522
+ else
523
+ return j.map(&blk)
524
+ end
525
+ end
526
+
527
+ def join(collections, *preds, &blk)
528
+ # since joins are stateful, we want to allocate them once and store in this Bud instance
529
+ # we ID them on their tablenames, preds, and block
530
+ return wrap_map(BudJoin.new(collections, @bud_instance, preds), &blk)
531
+ end
532
+
490
533
  # form a collection containing all pairs of items in +self+ and items in
491
534
  # +collection+
492
535
  public
493
536
  def *(collection)
494
- bud_instance.join([self, collection])
537
+ join([self, collection])
495
538
  end
496
539
 
497
- # SQL-style grouping. first argument is an array of attributes to group by.
540
+ # SQL-style grouping. first argument is an array of attributes to group by.
498
541
  # Followed by a variable-length list of aggregates over attributes (e.g. +min(:x)+)
499
542
  # Attributes can be referenced as symbols, or as +collection_name.attribute_name+
500
543
  public
@@ -514,21 +557,24 @@ module Bud
514
557
  aggcolsdups.each_with_index do |n, i|
515
558
  aggcols << "#{n.downcase}_#{i}".to_sym
516
559
  end
560
+ aggpairs = aggpairs.map do |ap|
561
+ if ap[1].class == Symbol
562
+ colnum = ap[1].nil? ? nil : self.send(ap[1].to_s)[1]
563
+ else
564
+ colnum = ap[1].nil? ? nil : ap[1][1]
565
+ end
566
+ [ap[0], colnum]
567
+ end
517
568
  tups = agg_in.inject({}) do |memo, p|
518
569
  pkey_cols = keynames.map{|n| p.send(n)}
519
570
  memo[pkey_cols] = [] if memo[pkey_cols].nil?
520
571
  aggpairs.each_with_index do |ap, i|
521
572
  agg = ap[0]
522
- if ap[1].class == Symbol
523
- colnum = ap[1].nil? ? nil : self.send(ap[1].to_s)[1]
524
- else
525
- colnum = ap[1].nil? ? nil : ap[1][1]
526
- end
527
- colval = colnum.nil? ? nil : p[colnum]
573
+ colval = ap[1].nil? ? nil : p[ap[1]]
528
574
  if memo[pkey_cols][i].nil?
529
575
  memo[pkey_cols][i] = agg.send(:init, colval)
530
576
  else
531
- memo[pkey_cols][i] = agg.send(:trans, memo[pkey_cols][i], colval)
577
+ memo[pkey_cols][i], ignore = agg.send(:trans, memo[pkey_cols][i], colval)
532
578
  end
533
579
  end
534
580
  memo
@@ -559,132 +605,6 @@ module Bud
559
605
 
560
606
  alias reduce inject
561
607
 
562
- # methods that work on nested collections (resulting from joins)
563
-
564
-
565
- # given a * expression over n collections, form all combinations of items
566
- # subject to an array of predicates, pred
567
- # currently supports two options for equijoin predicates:
568
- # general form: an array of arrays capturing a conjunction of equiv. classes
569
- # [[table1.col1, table2.col2, table3.col3], [table1.col2, table2.col3]]
570
- # common form: a hash capturing equality of a column on left with one on right.
571
- # :col1 => :col2 (same as lefttable.col1 => righttable.col2)
572
- public
573
- def pairs(*preds, &blk)
574
- setup_preds(preds) unless (preds.nil? or preds.empty?)
575
- # given new preds, the state for the join will be different. set it up again.
576
- setup_state if self.class <= Bud::BudJoin
577
- blk.nil? ? self : map(&blk)
578
- end
579
-
580
- alias combos pairs
581
-
582
- # the natural join: given a * expression over 2 collections, form all
583
- # combinations of items that have the same values in matching fiels
584
- public
585
- def matches(&blk)
586
- preds = BudJoin::natural_preds(@bud_instance, @rels)
587
- pairs(*preds, &blk)
588
- end
589
-
590
- # given a * expression over 2 collections, form all
591
- # combinations of items that satisfy the predicates +preds+,
592
- # and project only onto the attributes of the first collection
593
- public
594
- def lefts(*preds)
595
- @localpreds = disambiguate_preds(preds)
596
- map{ |l,r| l }
597
- end
598
-
599
- # given a * expression over 2 collections, form all
600
- # combinations of items that satisfy the predicates +preds+,
601
- # and project only onto the attributes of the second item
602
- public
603
- def rights(*preds)
604
- @localpreds = disambiguate_preds(preds)
605
- map{ |l,r| r }
606
- end
607
-
608
- # extract predicates on rellist[0] and recurse to right side with remainder
609
- protected
610
- def setup_preds(preds) # :nodoc: all
611
- allpreds = disambiguate_preds(preds)
612
- allpreds = canonicalize_localpreds(@rels, allpreds)
613
- @localpreds = allpreds.reject { |p| p[0][0] != @rels[0].tabname }
614
- otherpreds = allpreds - @localpreds
615
- otherpreds = nil if otherpreds.empty?
616
- unless otherpreds.nil?
617
- unless @rels[1].class <= Bud::BudJoin
618
- raise BudError, "join predicates don't match tables being joined: #{otherpreds.inspect}"
619
- end
620
- @rels[1].setup_preds(otherpreds)
621
- end
622
- end
623
-
624
- protected
625
- def disambiguate_preds(preds) # :nodoc: all
626
- if preds.size == 1 and preds[0].class <= Hash
627
- predarray = preds[0].map do |k,v|
628
- if k.class != v.class
629
- raise Bud::CompileError, "inconsistent attribute ref style #{k.inspect} => #{v.inspect}"
630
- elsif k.class <= Array
631
- [k,v]
632
- elsif k.class <= Symbol
633
- if @origrels and @origrels.length == 2
634
- [find_attr_match(k,@origrels[0]), find_attr_match(v,@origrels[1])]
635
- else
636
- [find_attr_match(k), find_attr_match(v)]
637
- end
638
- else
639
- raise Bud::CompileError, "invalid attribute ref in #{k.inspect} => #{v.inspect}"
640
- end
641
- end
642
- return decomp_preds(*predarray)
643
- else
644
- return decomp_preds(*preds)
645
- end
646
- end
647
-
648
- # find element in @origrels that contains this aname method
649
- # if 2nd arg is non-nil, only check that collection.
650
- # after found, return the result of invoking aname from chosen collection
651
- protected
652
- def find_attr_match(aname, rel=nil) # :nodoc: all
653
- dorels = (rel.nil? ? @origrels : [rel])
654
- match = nil
655
- dorels.each do |r|
656
- match ||= r if r.respond_to?(aname)
657
- if r.respond_to?(aname) and match != r
658
- raise Bud::CompileError, "ambiguous attribute :#{aname} in both #{match.tabname} and #{r.tabname}"
659
- end
660
- end
661
- if match.nil?
662
- raise Bud::CompileError, "attribute :#{aname} not found in any of #{dorels.map{|t| t.tabname}.inspect}"
663
- end
664
- match.send(aname)
665
- end
666
-
667
- protected
668
- def decomp_preds(*preds) # :nodoc:all
669
- # decompose each pred into a binary pred
670
- return nil if preds.nil? or preds.empty? or preds == [nil]
671
- newpreds = []
672
- preds.each do |p|
673
- p.each_with_index do |c, i|
674
- newpreds << [p[i], p[i+1]] unless p[i+1].nil?
675
- end
676
- end
677
- newpreds
678
- end
679
-
680
- protected
681
- def canonicalize_localpreds(rel_list, preds) # :nodoc:all
682
- return if preds.nil?
683
- retval = preds.map do |p|
684
- p[1][0] == rel_list[0].tabname ? p.reverse : p
685
- end
686
- end
687
-
688
608
  public
689
609
  def uniquify_tabname # :nodoc: all
690
610
  # just append current number of microseconds
@@ -701,18 +621,25 @@ module Bud
701
621
  class BudChannel < BudCollection
702
622
  attr_reader :locspec_idx # :nodoc: all
703
623
 
704
- def initialize(name, bud_instance, given_schema=nil) # :nodoc: all
624
+ def initialize(name, bud_instance, given_schema=nil, loopback=false) # :nodoc: all
705
625
  given_schema ||= [:@address, :val]
706
- the_schema, the_key_cols = parse_schema(given_schema)
707
- the_val_cols = the_schema - the_key_cols
708
- @locspec_idx = remove_at_sign!(the_key_cols)
709
- @locspec_idx = remove_at_sign!(the_schema) if @locspec_idx.nil?
710
- # If @locspec_idx is still nil, this is a loopback channel
711
-
712
- # We mutate the hash key above, so we need to recreate the hash
713
- # XXX: ugh, hacky
714
- if given_schema.respond_to? :keys
715
- given_schema = {the_key_cols => the_val_cols}
626
+ @is_loopback = loopback
627
+ @locspec_idx = nil
628
+
629
+ unless @is_loopback
630
+ the_schema, the_key_cols = parse_schema(given_schema)
631
+ the_val_cols = the_schema - the_key_cols
632
+ @locspec_idx = remove_at_sign!(the_key_cols)
633
+ @locspec_idx = remove_at_sign!(the_schema) if @locspec_idx.nil?
634
+ if @locspec_idx.nil?
635
+ raise BudError, "Missing location specifier for channel '#{name}'"
636
+ end
637
+
638
+ # We mutate the hash key above, so we need to recreate the hash
639
+ # XXX: ugh, hacky
640
+ if given_schema.respond_to? :keys
641
+ given_schema = {the_key_cols => the_val_cols}
642
+ end
716
643
  end
717
644
 
718
645
  super(name, bud_instance, given_schema)
@@ -728,37 +655,38 @@ module Bud
728
655
  end
729
656
 
730
657
  private
731
- def split_locspec(l)
732
- lsplit = l.split(':')
733
- lsplit[1] = lsplit[1].to_i
734
- return lsplit
658
+ def split_locspec(t, idx)
659
+ begin
660
+ lsplit = t[idx].split(':')
661
+ lsplit[1] = lsplit[1].to_i
662
+ return lsplit
663
+ rescue Exception => e
664
+ raise BudError, "Illegal location specifier in tuple #{t.inspect} for channel \"#{tabname}\": #{e.to_s}"
665
+ end
735
666
  end
736
667
 
737
668
  public
738
669
  def clone_empty
739
- retval = super
740
- retval.locspec_idx = @locspec_idx
741
- retval
670
+ self.class.new(tabname, bud_instance, @given_schema, @is_loopback)
742
671
  end
743
672
 
744
- public
673
+ public
745
674
  def tick # :nodoc: all
746
- @sealed = false
747
675
  @storage = {}
748
676
  # Note that we do not clear @pending here: if the user inserted into the
749
677
  # channel manually (e.g., via <~ from inside a sync_do block), we send the
750
678
  # message at the end of the current tick.
751
679
  end
752
680
 
753
- public
681
+ public
754
682
  def flush # :nodoc: all
755
683
  ip = @bud_instance.ip
756
684
  port = @bud_instance.port
757
685
  each_from([@pending]) do |t|
758
- if @locspec_idx.nil?
686
+ if @is_loopback
759
687
  the_locspec = [ip, port]
760
688
  else
761
- the_locspec = split_locspec(t[@locspec_idx])
689
+ the_locspec = split_locspec(t, @locspec_idx)
762
690
  raise BudError, "'#{t[@locspec_idx]}', channel '#{@tabname}'" if the_locspec[0].nil? or the_locspec[1].nil? or the_locspec[0] == '' or the_locspec[1] == ''
763
691
  end
764
692
  @bud_instance.dsock.send_datagram([@tabname, t].to_msgpack, the_locspec[0], the_locspec[1])
@@ -769,6 +697,8 @@ module Bud
769
697
  public
770
698
  # project to the non-address fields
771
699
  def payloads
700
+ return self.pro if @is_loopback
701
+
772
702
  if schema.size > 2
773
703
  # bundle up each tuple's non-locspec fields into an array
774
704
  retval = case @locspec_idx
@@ -792,7 +722,7 @@ module Bud
792
722
  end
793
723
 
794
724
  undef merge
795
-
725
+
796
726
  def <=(o)
797
727
  raise BudError, "Illegal use of <= with channel '#{@tabname}' on left"
798
728
  end
@@ -804,15 +734,18 @@ module Bud
804
734
  @prompt = prompt
805
735
  end
806
736
 
807
- public
737
+ public
808
738
  def start_stdin_reader # :nodoc: all
809
739
  # XXX: Ugly hack. Rather than sending terminal data to EM via UDP,
810
740
  # we should add the terminal file descriptor to the EM event loop.
811
741
  @reader = Thread.new do
812
742
  begin
813
743
  while true
814
- $stdout.print("#{tabname} > ") if @prompt
815
- s = $stdin.gets
744
+ out_io = get_out_io
745
+ out_io.print("#{tabname} > ") if @prompt
746
+
747
+ in_io = @bud_instance.options[:stdin]
748
+ s = in_io.gets
816
749
  break if s.nil? # Hit EOF
817
750
  s = s.chomp if s
818
751
  tup = [s]
@@ -834,9 +767,10 @@ module Bud
834
767
 
835
768
  public
836
769
  def flush #:nodoc: all
770
+ out_io = get_out_io
837
771
  @pending.each do |p|
838
- $stdout.puts p[0]
839
- $stdout.flush
772
+ out_io.puts p[0]
773
+ out_io.flush
840
774
  end
841
775
  @pending = {}
842
776
  end
@@ -857,9 +791,35 @@ module Bud
857
791
  superator "<~" do |o|
858
792
  pending_merge(o)
859
793
  end
794
+
795
+ private
796
+ def get_out_io
797
+ rv = @bud_instance.options[:stdout]
798
+ rv ||= $stdout
799
+ rv
800
+ end
860
801
  end
861
802
 
862
803
  class BudPeriodic < BudCollection # :nodoc: all
804
+ def <=(o)
805
+ raise BudError, "Illegal use of <= with periodic '#{tabname}' on left"
806
+ end
807
+
808
+ superator "<~" do |o|
809
+ raise BudError, "Illegal use of <~ with periodic '#{tabname}' on left"
810
+ end
811
+
812
+ superator "<-" do |o|
813
+ raise BudError, "Illegal use of <- with periodic '#{tabname}' on left"
814
+ end
815
+
816
+ superator "<+" do |o|
817
+ raise BudError, "Illegal use of <+ with periodic '#{tabname}' on left"
818
+ end
819
+
820
+ def add_periodic_tuple(id)
821
+ pending_merge([[id, Time.now]])
822
+ end
863
823
  end
864
824
 
865
825
  class BudTable < BudCollection # :nodoc: all
@@ -876,16 +836,22 @@ module Bud
876
836
  @storage.delete keycols
877
837
  end
878
838
  end
879
- @storage.merge! @pending
839
+ @pending.each do |keycols, tuple|
840
+ old = @storage[keycols]
841
+ if old.nil?
842
+ @storage[keycols] = tuple
843
+ else
844
+ raise_pk_error(tuple, old) unless tuple == old
845
+ end
846
+ end
880
847
  @to_delete = []
881
848
  @pending = {}
882
849
  end
883
850
 
884
851
  superator "<-" do |o|
885
- o.each do |tuple|
886
- next if tuple.nil?
887
- tuple = prep_tuple(tuple)
888
- @to_delete << tuple
852
+ o.each do |t|
853
+ next if t.nil?
854
+ @to_delete << prep_tuple(t)
889
855
  end
890
856
  end
891
857
  end
@@ -910,6 +876,15 @@ module Bud
910
876
  @linenum = 0
911
877
  end
912
878
 
879
+ public
880
+ def pro(&blk)
881
+ if @bud_instance.stratum_first_iter
882
+ return map(&blk)
883
+ else
884
+ return []
885
+ end
886
+ end
887
+
913
888
  public
914
889
  def each(&block) # :nodoc: all
915
890
  while (l = @fd.gets)