bud 0.9.2 → 0.9.3

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.
@@ -1,4 +1,18 @@
1
- == 0.9.2 / ???
1
+ == 0.9.3 / 2012-08-20
2
+
3
+ * Change behavior of accum() aggregate to return a Set, rather than an Array in
4
+ an unspecified order
5
+ * Fix several serious bugs in caching/invalidation of materialized operator
6
+ state (#276, #278, #279)
7
+ * Avoid possible spurious infinite loop with dbm-backed collections
8
+ * Optimize aggregation/grouping performance
9
+ * Fix bugs and improve performance for materialization of sort operator
10
+ * Fix REBL regression with push-based runtime (#274)
11
+ * Minor performance optimizations for simple projection rules
12
+ * Remove dependency on gchart
13
+ * Built-in support for code coverage with MRI 1.9 and SimpleCov
14
+
15
+ == 0.9.2 / 2012-05-19
2
16
 
3
17
  * Add new aggregate functions: bool_and() and bool_or()
4
18
  * Fix bugs in notin() stratification and implementation (#271)
@@ -16,7 +30,7 @@
16
30
  * Previous behavior was to ignore additional fields, but this was found to be
17
31
  error-prone
18
32
  * Remove builtin support for BUST (web services API); this avoids the need to
19
- depend on the json, nestful and i18n gems.
33
+ depend on the json, nestful and i18n gems
20
34
 
21
35
  == 0.9.0 / 2012-03-21
22
36
 
@@ -39,5 +53,4 @@
39
53
  * Support for Bloom-based signal handling has been removed
40
54
  * Support for the "with" syntax has been removed
41
55
  * The Bloom-based "deployment" framework has been removed
42
- * Support for Tokyo Cabinet-based collections has been removed
43
-
56
+ * Support for Tokyo Cabinet-backed collections has been removed
data/README.md CHANGED
@@ -37,6 +37,11 @@ To run the unit tests:
37
37
  % gem install minitest # unless already installed
38
38
  % cd test; ruby ts_bud.rb
39
39
 
40
+ To run the unit tests and produce a code coverage report:
41
+
42
+ % gem install simplecov # unless already installed
43
+ % cd test; COVERAGE=1 ruby ts_bud.rb
44
+
40
45
  ## Optional Dependencies
41
46
 
42
47
  The bud gem has a handful of mandatory dependencies. It also has one optional
@@ -239,7 +239,7 @@ Finally, we output every tuple of `bc` that does *not* appear in `t`.
239
239
  * `bc.group([:col1, :col2], min(:col3))`. *akin to min(col3) GROUP BY col1,col2*
240
240
  * exemplary aggs: `min`, `max`, `bool_and`, `bool_or`, `choose`
241
241
  * summary aggs: `sum`, `avg`, `count`
242
- * structural aggs: `accum`
242
+ * structural aggs: `accum` *accumulates inputs into a Set*
243
243
  * `bc.argmax([:attr1], :attr2)`      *returns the bc items per attr1 that have highest attr2*
244
244
  * `bc.argmin([:attr1], :attr2)`
245
245
  * `bc.argagg(:exemplary_agg_name, [:attr1], :attr2))`. *generalizes argmin/max: returns the bc items per attr1 that are chosen by the exemplary
@@ -14,7 +14,7 @@ This installs four things:
14
14
 
15
15
  * The `Bud` module, to embed Bloom code in Ruby.
16
16
  * The `rebl` executable: an interactive shell for trying out Bloom.
17
- * The `budplot` and `budvis` executables: graphical tools for visualizing and debugging Bloom programs.
17
+ * The `budplot`, `budvis`, and `budtimelines` executables: graphical tools for visualizing and debugging Bloom programs.
18
18
 
19
19
  ## First Blooms ##
20
20
 
data/lib/bud.rb CHANGED
@@ -315,12 +315,12 @@ module Bud
315
315
 
316
316
  # Check scan and merge_targets to see if any builtin_tables need to be added as well.
317
317
  @scanners.each do |scs|
318
- @app_tables += scs.values.map {|s| s.collection}
318
+ @app_tables.merge(scs.values.map {|s| s.collection})
319
319
  end
320
320
  @merge_targets.each do |mts| #mts == merge_targets at stratum
321
- @app_tables += mts
321
+ @app_tables.merge(mts)
322
322
  end
323
- @app_tables = @app_tables.nil? ? [] : @app_tables.to_a
323
+ @app_tables = @app_tables.to_a
324
324
 
325
325
  # for each stratum create a sorted list of push elements in topological order
326
326
  @push_sorted_elems = []
@@ -352,18 +352,18 @@ module Bud
352
352
  end
353
353
  end
354
354
 
355
- # sanity check
355
+ # Sanity check
356
356
  @push_sorted_elems.each do |stratum_elems|
357
- stratum_elems.each do |se|
358
- se.check_wiring
359
- end
357
+ stratum_elems.each {|se| se.check_wiring}
360
358
  end
361
359
 
362
- # create sets of elements and collections to invalidate or rescan at the beginning of each tick.
360
+ # Create sets of elements and collections to invalidate or rescan at the
361
+ # beginning of each tick
363
362
  prepare_invalidation_scheme
364
363
 
365
- # For all tables that are accessed (scanned) in a stratum higher than the one they are updated in, set
366
- # a flag to track deltas accumulated in that tick (see: collection.tick_delta)
364
+ # For all tables that are accessed (scanned) in a stratum higher than the
365
+ # one they are updated in, set a flag to track deltas accumulated in that
366
+ # tick (see: collection.tick_delta)
367
367
  stratum_accessed = {}
368
368
  (@num_strata-1).downto(0) do |stratum|
369
369
  @scanners[stratum].each_value do |s|
@@ -384,24 +384,38 @@ module Bud
384
384
  end
385
385
  end
386
386
 
387
- # All collections (elements included) are semantically required to erase any cached information at the start of a tick
388
- # and start from a clean slate. prepare_invalidation_scheme prepares a just-in-time invalidation scheme that
389
- # permits us to preserve data from one tick to the next, and to keep things in incremental mode unless there's a
390
- # negation.
387
+ # All collections (elements included) are semantically required to erase any
388
+ # cached information at the start of a tick and start from a clean
389
+ # slate. prepare_invalidation_scheme prepares a just-in-time invalidation
390
+ # scheme that permits us to preserve data from one tick to the next, and to
391
+ # keep things in incremental mode unless there's a negation.
392
+ #
391
393
  # This scheme solves the following constraints.
392
- # 1. A full scan of an element's contents results in downstream elements getting full scans themselves (i.e no \
393
- # deltas). This effect is transitive.
394
- # 2. Invalidation of an element's cache results in rebuilding of the cache and a consequent fullscan. See next.
395
- # 3. Invalidation of an element requires upstream elements to rescan their contents, or to transitively pass the
396
- # request on further upstream. Any element that has a cache can rescan without passing on the request to higher
397
- # levels.
398
394
  #
399
- # This set of constraints is solved once during wiring, resulting in four data structures
400
- # @default_invalidate = set of elements and tables to always invalidate at every tick. Organized by stratum
401
- # @default_rescan = set of elements and tables to always scan fully in the first iteration of every tick.
402
- # scanner[stratum].invalidate = Set of elements to additionally invalidate if the scanner's table is invalidated at
403
- # run-time
404
- # scanner[stratum].rescan = Similar to above.
395
+ # 1. A full scan of an element's contents results in downstream elements
396
+ # getting full scans themselves (i.e., no deltas). This effect is
397
+ # transitive.
398
+ # 2. Invalidation of an element's cache results in rebuilding of the cache and
399
+ # a consequent fullscan. See next.
400
+ # 3. Invalidation of an element requires upstream elements to rescan their
401
+ # contents, or to transitively pass the request on further upstream. Any
402
+ # element that has a cache can rescan without passing on the request to
403
+ # higher levels.
404
+ #
405
+ # This set of constraints is solved once during wiring, resulting in four data
406
+ # structures:
407
+ #
408
+ # @default_invalidate = Set of elements and tables to always invalidate at
409
+ # every tick.
410
+ #
411
+ # @default_rescan = Set of elements and tables to always scan fully in the
412
+ # first iteration of every tick.
413
+ #
414
+ # scanner[stratum].invalidate_set = Set of elements to additionally invalidate
415
+ # if the scanner's table is invalidated at
416
+ # run-time.
417
+ #
418
+ # scanner[stratum].rescan_set = Similar to above.
405
419
  def prepare_invalidation_scheme
406
420
  num_strata = @push_sorted_elems.size
407
421
  if $BUD_SAFE
@@ -443,22 +457,26 @@ module Bud
443
457
 
444
458
  num_strata.times do |stratum|
445
459
  @push_sorted_elems[stratum].each do |elem|
446
- if elem.rescan_at_tick
447
- rescan << elem
448
- end
460
+ rescan << elem if elem.rescan_at_tick
449
461
 
450
462
  if elem.outputs.any?{|tab| not(tab.class <= PushElement) and nm_targets.member? tab.qualified_tabname.to_sym }
451
- rescan += elem.wired_by
463
+ rescan.merge(elem.wired_by)
452
464
  end
453
465
  end
454
466
  rescan_invalidate_tc(stratum, rescan, invalidate)
455
467
  end
456
468
 
469
+ puts "(PRE) Default rescan: #{rescan.inspect}" if $BUD_DEBUG
470
+ puts "(PRE) Default inval: #{invalidate.inspect}" if $BUD_DEBUG
471
+
457
472
  prune_rescan_invalidate(rescan, invalidate)
458
473
  # transitive closure
459
474
  @default_rescan = rescan.to_a
460
475
  @default_invalidate = invalidate.to_a
461
476
 
477
+ puts "(POST) Default rescan: #{rescan.inspect}" if $BUD_DEBUG
478
+ puts "(POST) Default inval: #{invalidate.inspect}" if $BUD_DEBUG
479
+
462
480
  # Now compute for each table that is to be scanned, the set of dependent
463
481
  # tables and elements that will be invalidated if that table were to be
464
482
  # invalidated at run time.
@@ -475,7 +493,8 @@ module Bud
475
493
  invalidate = dflt_invalidate.clone
476
494
  rescan_invalidate_tc(stratum, rescan, invalidate)
477
495
  prune_rescan_invalidate(rescan, invalidate)
478
- to_reset += rescan + invalidate
496
+ to_reset.merge(rescan)
497
+ to_reset.merge(invalidate)
479
498
  # Give the diffs (from default) to scanner; these are elements that are
480
499
  # dependent on this scanner
481
500
  diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
@@ -953,7 +972,7 @@ module Bud
953
972
  # One timestep of Bloom execution. This MUST be invoked from the EventMachine
954
973
  # thread; it is not intended to be called directly by client code.
955
974
  def tick_internal
956
- puts "#{object_id}/#{port} : =============================================" if $BUD_DEBUG
975
+ puts "#{object_id}/#{port} : ============================================= (#{@budtime})" if $BUD_DEBUG
957
976
  begin
958
977
  starttime = Time.now if options[:metrics]
959
978
  if options[:metrics] and not @endtime.nil?
@@ -979,7 +998,7 @@ module Bud
979
998
  num_strata = @push_sorted_elems.size
980
999
  # The following loop invalidates additional (non-default) elements and
981
1000
  # tables that depend on the run-time invalidation state of a table.
982
- # Loop once to set the flags
1001
+ # Loop once to set the flags.
983
1002
  num_strata.times do |stratum|
984
1003
  @scanners[stratum].each_value do |scanner|
985
1004
  if scanner.rescan
@@ -1034,7 +1053,6 @@ module Bud
1034
1053
  invoke_callbacks
1035
1054
  @budtime += 1
1036
1055
  @inbound.clear
1037
-
1038
1056
  @reset_list.each {|e| e.invalidated = false; e.rescan = false}
1039
1057
 
1040
1058
  ensure
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module Bud
2
4
  ######## Agg definitions
3
5
  class Agg #:nodoc: all
@@ -12,7 +14,7 @@ module Bud
12
14
  # b. :keep tells the caller to save this input
13
15
  # c. :replace tells the caller to keep this input alone
14
16
  # d. :delete, [t1, t2, ...] tells the caller to delete the remaining tuples
15
- # For things that do not descend from ArgExemplary, the 2nd part can simply be nil.
17
+ # For aggs that do not inherit from ArgExemplary, the 2nd part can simply be nil.
16
18
  def trans(the_state, val)
17
19
  return the_state, :ignore
18
20
  end
@@ -22,16 +24,12 @@ module Bud
22
24
  end
23
25
  end
24
26
 
25
- class Exemplary < Agg #:nodoc: all
26
- end
27
-
28
27
  # ArgExemplary aggs are used by argagg. Canonical examples are min/min (argmin/max)
29
28
  # They must have a trivial final method and be monotonic, i.e. once a value v
30
- # is discarded in favor of another, v can never be the final result
31
-
29
+ # is discarded in favor of another, v can never be the final result.
32
30
  class ArgExemplary < Agg #:nodoc: all
33
31
  def tie(the_state, val)
34
- (the_state == val)
32
+ the_state == val
35
33
  end
36
34
  def final(the_state)
37
35
  the_state
@@ -184,7 +182,7 @@ module Bud
184
182
  return retval, nil
185
183
  end
186
184
  def final(the_state)
187
- the_state[0]*1.0 / the_state[1]
185
+ the_state[0].to_f / the_state[1]
188
186
  end
189
187
  end
190
188
 
@@ -196,7 +194,7 @@ module Bud
196
194
 
197
195
  class Accum < Agg #:nodoc: all
198
196
  def init(x)
199
- [x]
197
+ [x].to_set
200
198
  end
201
199
  def trans(the_state, val)
202
200
  the_state << val
@@ -205,8 +203,7 @@ module Bud
205
203
  end
206
204
 
207
205
  # aggregate method to be used in Bud::BudCollection.group.
208
- # accumulates all x inputs into an array. note that the order of the elements
209
- # in the resulting array is undefined.
206
+ # accumulates all x inputs into a set.
210
207
  def accum(x)
211
208
  [Accum.new, x]
212
209
  end
@@ -23,10 +23,12 @@ class BudMeta #:nodoc: all
23
23
  # slot each rule into the stratum corresponding to its lhs pred (from stratum_map)
24
24
  stratified_rules = Array.new(top_stratum + 2) { [] } # stratum -> [ rules ]
25
25
  @bud_instance.t_rules.each do |rule|
26
- if rule.op.to_s == '<='
26
+ if rule.op == '<='
27
27
  # Deductive rules are assigned to strata based on the basic Datalog
28
28
  # stratification algorithm
29
29
  belongs_in = stratum_map[rule.lhs]
30
+ # If the rule body doesn't reference any collections, it won't be
31
+ # assigned a stratum, so just place it in stratum zero
30
32
  belongs_in ||= 0
31
33
  stratified_rules[belongs_in] << rule
32
34
  else
@@ -112,18 +114,16 @@ class BudMeta #:nodoc: all
112
114
  # a.b.c == s(:call, s1, :c, (:args))
113
115
  # where s1 == s(:call, s2, :b, (:args))
114
116
  # where s2 == s(:call, nil, :a, (:args))
115
-
116
117
  tag, recv, name, args = pt
117
- return nil unless tag == :call or args.length == 1
118
+ return nil unless tag == :call and args.length == 1
118
119
 
119
120
  if recv
120
121
  qn = get_qual_name(recv)
121
122
  return nil if qn.nil? or qn.size == 0
122
- qn = "#{qn}.#{name}"
123
+ return "#{qn}.#{name}"
123
124
  else
124
- qn = name.to_s
125
+ return name.to_s
125
126
  end
126
- qn
127
127
  end
128
128
 
129
129
  # Perform some basic sanity checks on the AST of a rule block. We expect a
@@ -156,9 +156,9 @@ class BudMeta #:nodoc: all
156
156
 
157
157
  # Check that LHS references a named collection
158
158
  lhs_name = get_qual_name(lhs)
159
- return [n, "Unexpected lhs format: #{lhs}"] if lhs.nil?
159
+ return [n, "unexpected lhs format: #{lhs}"] if lhs_name.nil?
160
160
  unless @bud_instance.tables.has_key? lhs_name.to_sym
161
- return [n, "Collection does not exist: '#{lhs_name}'"]
161
+ return [n, "collection does not exist: '#{lhs_name}'"]
162
162
  end
163
163
 
164
164
  return [n, "illegal operator: '#{op}'"] unless [:<, :<=].include? op
@@ -194,21 +194,21 @@ class BudMeta #:nodoc: all
194
194
  nodes = {}
195
195
  bud.t_depends.each do |d|
196
196
  #t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm]
197
- lhs = (nodes[d.lhs.to_s] ||= Node.new(d.lhs.to_s, :init, 0, [], true, false, false, false))
197
+ lhs = (nodes[d.lhs] ||= Node.new(d.lhs, :init, 0, [], true, false, false, false))
198
198
  lhs.in_lhs = true
199
- body = (nodes[d.body.to_s] ||= Node.new(d.body.to_s, :init, 0, [], false, true, false, false))
199
+ body = (nodes[d.body] ||= Node.new(d.body, :init, 0, [], false, true, false, false))
200
200
  temporal = d.op != "<="
201
201
  lhs.edges << Edge.new(body, d.op, d.nm, temporal)
202
202
  body.in_body = true
203
203
  end
204
204
 
205
- nodes.values.each {|n| calc_stratum(n, false, false, [n.name])}
205
+ nodes.each_value {|n| calc_stratum(n, false, false, [n.name])}
206
206
  # Normalize stratum numbers because they may not be 0-based or consecutive
207
207
  remap = {}
208
208
  # if the nodes stratum numbers are [2, 3, 2, 4], remap = {2 => 0, 3 => 1, 4 => 2}
209
- nodes.values.map {|n| n.stratum}.uniq.sort.each_with_index{|num, i|
209
+ nodes.values.map {|n| n.stratum}.uniq.sort.each_with_index do |num, i|
210
210
  remap[num] = i
211
- }
211
+ end
212
212
  stratum_map = {}
213
213
  top_stratum = -1
214
214
  nodes.each_pair do |name, n|
@@ -232,9 +232,9 @@ class BudMeta #:nodoc: all
232
232
  node.status = :in_process
233
233
  node.edges.each do |edge|
234
234
  node.is_neg_head = edge.neg
235
- next if edge.op != "<="
235
+ next unless edge.op == "<="
236
236
  body_stratum = calc_stratum(edge.to, (neg or edge.neg), (edge.temporal or temporal), path + [edge.to.name])
237
- node.is_neg_head = false #reset for next edge
237
+ node.is_neg_head = false # reset for next edge
238
238
  node.stratum = max(node.stratum, body_stratum + (edge.neg ? 1 : 0))
239
239
  end
240
240
  node.status = :done
@@ -253,15 +253,15 @@ class BudMeta #:nodoc: all
253
253
  bud.t_provides.each do |p|
254
254
  pred, input = p.interface, p.input
255
255
  if input
256
- unless preds_in_body.include? pred.to_s
256
+ unless preds_in_body.include? pred
257
257
  # input interface is underspecified if not used in any rule body
258
258
  bud.t_underspecified << [pred, true] # true indicates input mode
259
259
  out.puts "Warning: input interface #{pred} not used"
260
260
  end
261
261
  else
262
- unless preds_in_lhs.include? pred.to_s
262
+ unless preds_in_lhs.include? pred
263
263
  # output interface underspecified if not in any rule's lhs
264
- bud.t_underspecified << [pred, false] #false indicates output mode.
264
+ bud.t_underspecified << [pred, false] # false indicates output mode
265
265
  out.puts "Warning: output interface #{pred} not used"
266
266
  end
267
267
  end
@@ -1,7 +1,6 @@
1
1
  require 'msgpack'
2
2
 
3
3
  $struct_classes = {}
4
- $EMPTY_HASH = {}
5
4
  module Bud
6
5
  ########
7
6
  #--
@@ -74,6 +73,10 @@ module Bud
74
73
  @qualified_tabname ||= @bud_instance.toplevel? ? tabname : "#{@bud_instance.qualified_name}.#{tabname}".to_sym
75
74
  end
76
75
 
76
+ def inspect
77
+ "#{self.class}:#{self.object_id.to_s(16)} [#{qualified_tabname}]"
78
+ end
79
+
77
80
  # The user-specified schema might come in two forms: a hash of Array =>
78
81
  # Array (key_cols => remaining columns), or simply an Array of columns (if
79
82
  # no key_cols were specified). Return a pair: [list of (all) columns, list
@@ -102,10 +105,6 @@ module Bud
102
105
  return [cols, key_cols]
103
106
  end
104
107
 
105
- def inspect
106
- "#{self.class}:#{self.object_id.to_s(16)} [#{qualified_tabname}]"
107
- end
108
-
109
108
  # produces the schema in a format that is useful as the schema specification for another table
110
109
  public
111
110
  def schema
@@ -189,6 +188,8 @@ module Bud
189
188
  def pro(the_name=tabname, the_schema=schema, &blk)
190
189
  if @bud_instance.wiring?
191
190
  pusher = to_push_elem(the_name, the_schema)
191
+ # If there is no code block evaluate, use the scanner directly
192
+ return pusher if blk.nil?
192
193
  pusher_pro = pusher.pro(&blk)
193
194
  pusher_pro.elem_name = the_name
194
195
  pusher_pro.tabname = the_name
@@ -260,7 +261,7 @@ module Bud
260
261
 
261
262
  public
262
263
  def non_temporal_predecessors
263
- @wired_by.map {|elem| elem if elem.outputs.include? self}
264
+ @wired_by.select {|elem| elem.outputs.include? self}
264
265
  end
265
266
 
266
267
  public
@@ -393,7 +394,7 @@ module Bud
393
394
 
394
395
  private
395
396
  def get_key_vals(t)
396
- @key_colnums.map {|i| t[i]}
397
+ t.values_at(*@key_colnums)
397
398
  end
398
399
 
399
400
  public
@@ -567,7 +568,7 @@ module Bud
567
568
  unless @delta.empty?
568
569
  puts "#{qualified_tabname}.tick_delta delta --> storage (#{@delta.size} elems)" if $BUD_DEBUG
569
570
  @storage.merge!(@delta)
570
- @tick_delta += @delta.values if accumulate_tick_deltas
571
+ @tick_delta.concat(@delta.values) if accumulate_tick_deltas
571
572
  @delta.clear
572
573
  end
573
574
 
@@ -607,7 +608,7 @@ module Bud
607
608
  end
608
609
  unless @delta.empty?
609
610
  @storage.merge!(@delta)
610
- @tick_delta += @delta.values if accumulate_tick_deltas
611
+ @tick_delta.concat(@delta.values) if accumulate_tick_deltas
611
612
  @delta.clear
612
613
  end
613
614
  unless @new_delta.empty?
@@ -704,14 +705,6 @@ module Bud
704
705
  return to_push_elem.reduce(initial, &blk)
705
706
  end
706
707
 
707
- public
708
- def pretty_print_instance_variables
709
- # list of attributes (in order) to print when pretty_print is called.
710
- important = ["@tabname", "@storage", "@delta", "@new_delta", "@pending"]
711
- # everything except bud_instance
712
- important + (self.instance_variables - important - ["@bud_instance"])
713
- end
714
-
715
708
  public
716
709
  def uniquify_tabname # :nodoc: all
717
710
  # just append current number of microseconds
@@ -747,7 +740,7 @@ module Bud
747
740
  srcs = non_temporal_predecessors
748
741
  if srcs.any? {|e| rescan.member? e}
749
742
  invalidate << self
750
- rescan += srcs
743
+ rescan.merge(srcs)
751
744
  end
752
745
  end
753
746
 
@@ -1145,7 +1138,7 @@ module Bud
1145
1138
  # No cache to invalidate. Also, tables do not invalidate dependents,
1146
1139
  # because their own state is not considered invalidated; that happens only
1147
1140
  # if there were pending deletes at the beginning of a tick (see tick())
1148
- puts "******** invalidate_cache called on BudTable"
1141
+ puts "******** invalidate_cache called on BudTable" if $BUD_DEBUG
1149
1142
  end
1150
1143
 
1151
1144
  public
@@ -1190,7 +1183,7 @@ module Bud
1190
1183
 
1191
1184
  public
1192
1185
  def each(&block)
1193
- @expr.call.each {|i| yield i}
1186
+ @expr.call.each(&block)
1194
1187
  end
1195
1188
 
1196
1189
  public
@@ -1221,7 +1214,7 @@ module Bud
1221
1214
 
1222
1215
  public
1223
1216
  def each(&blk)
1224
- each_raw {|l| blk.call(l)}
1217
+ each_raw(&blk)
1225
1218
  end
1226
1219
  end
1227
1220
  end