bud 0.0.3 → 0.0.4

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