Modify the null-branch pruning introduced in ce556881, restore compat
Peter Rabbitson [Tue, 19 Feb 2013 20:54:16 +0000 (21:54 +0100)]
In the case of "direct to HRI" we want to throw stuff away as before. In
other cases however we no longer take an educated guess when to prune.
Instead we mark each null-branch by simply blessing its containing
arrayref. This allows us to keep the arguments to inflate_result 100%
backwards compatible, while at the same time allowing "enlightened"
inflate_result implementations to skip over the dead branches with
minimal effort.

Both ::Row::inflate_result and ::HRI::inflate_result were adjusted to
react correctly to these marks. As a result the HRI folder gained
another 5% speedup (unless sidestepped by the direct-to-HRI code, which
is naturally much much more efficient).

The current name of the "null class" is stored in the global
$DBIx::Class::ResultSource::RowParser::Util::null_branch_class. Given
that the entire codebase begs for standalone CPAN extraction this should
do for the time being.

While implementing the test changes it became clear that
Test::More::is_deeply does not cut it (it ignores the blessed-ness of
structures by design: https://github.com/schwern/test-more/issues/347).
As a result a dep on Test::Deep was added, and several of the tests
looking at inflate_result RVs were converted.

15 files changed:
Changes
Makefile.PL
lib/DBIx/Class/ResultClass/HashRefInflator.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource/RowParser.pm
lib/DBIx/Class/ResultSource/RowParser/Util.pm
lib/DBIx/Class/Row.pm
t/inflate/hri_torture.t
t/prefetch/correlated.t
t/prefetch/false_colvalues.t
t/prefetch/incomplete.t
t/prefetch/manual.t
t/prefetch/multiple_hasmany_torture.t
t/resultset/inflate_result_api.t
t/resultset/rowparser_internals.t

diff --git a/Changes b/Changes
index 2812b1a..783f789 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,10 @@
 Revision history for DBIx::Class
 
+    * New Features / Changes
+        - Revert to passing the original (pre-0.08240) arguments to
+          inflate_result() and remove the warning about ResultClass
+          inheritance.
+
 0.08240-TRIAL (EXPERIMENTAL BETA RELEASE) 2013-02-14 05:56 (UTC)
     * New Features / Changes
         - Rewrite from scratch the result constructor codepath - many bugfixes
index 1b45288..89798e8 100644 (file)
@@ -94,6 +94,7 @@ my $build_requires = {
 
 my $test_requires = {
   'File::Temp'               => '0.22',
+  'Test::Deep'               => '0.101',
   'Test::Exception'          => '0.31',
   'Test::Warn'               => '0.21',
   'Test::More'               => '0.94',
index 4223930..4d002ab 100644 (file)
@@ -66,37 +66,26 @@ my $mk_hash;
 $mk_hash = sub {
 
   my $hash = {
+
     # the main hash could be an undef if we are processing a skipped-over join
     $_[0] ? %{$_[0]} : (),
 
     # the second arg is a hash of arrays for each prefetched relation
-    map {
-      ref $_[1]->{$_}[0] eq 'ARRAY' # multi rel or not?
-        ? ( $_ => [ map
-            { $mk_hash->(@$_) || () }
-            @{$_[1]->{$_}}
-        ] )
-        : ( $_ => $mk_hash->( @{$_[1]->{$_}} ) )
-
-    } ( $_[1] ? ( keys %{$_[1]} ) : () )
+    map { $_ => (
+
+      # null-branch or not
+      ref $_[1]->{$_} eq $DBIx::Class::ResultSource::RowParser::Util::null_branch_class
+
+        ? ref $_[1]->{$_}[0] eq 'ARRAY' ? [] : undef
+
+        : ref $_[1]->{$_}[0] eq 'ARRAY'
+          ? [ map { $mk_hash->( @$_ ) || () } @{$_[1]->{$_}} ]
+          : $mk_hash->( @{$_[1]->{$_}} )
+
+    ) } ($_[1] ? keys %{$_[1]} : ())
   };
 
-  # if there is at least one defined column *OR* we are at the root of
-  # the resultset - consider the result real (and not an emtpy has_many
-  # rel containing one empty hashref)
-  # an empty arrayref is an empty multi-sub-prefetch - don't consider
-  # those either
-  return $hash if $_[2];
-
-  for (values %$hash) {
-    return $hash if (
-      defined $_
-        and
-      (ref $_ ne 'ARRAY' or scalar @$_)
-    );
-  }
-
-  return undef;
+  ($_[2] || keys %$hash) ? $hash : undef;
 };
 
 =head1 METHODS
index 4943d5a..c944b45 100644 (file)
@@ -1406,39 +1406,17 @@ sub _construct_objects {
       collapse => $attrs->{collapse},
       premultiplied => $attrs->{_main_source_premultiplied},
       hri_style => 1,
-      prune_null_branches => 1,
     }) )->($rows, @extra_collapser_args);
   }
   # Regular multi-object
   else {
 
-    # The rationale is - if this is the ::Row inflator itself, or an around()
-    # we do prune, because we expect it.
-    # If not the case - let the user deal with the full output themselves
-    # Warn them while we are at it so we get a better idea what is out there
-    # on the DarkPan
-    $self->{_result_inflator}{prune_null_branches} = do {
-      $res_class->isa('DBIx::Class::Row')
-    } ? 1 : 0 unless defined $self->{_result_inflator}{prune_null_branches};
-
-    unless ($self->{_result_inflator}{prune_null_branches}) {
-      carp_once (
-        "ResultClass $res_class does not inherit from DBIx::Class::Row and "
-      . 'therefore its inflate_result() will receive the full prefetched data '
-      . 'tree, without any branch definedness checks. This is a compatibility '
-      . 'measure which will eventually disappear entirely. Please refer to '
-      . 't/resultset/inflate_result_api.t for an exhaustive description of the '
-      . 'upcoming changes'
-      );
-    }
-
-    ( $self->{_row_parser}{classic}{$self->{_result_inflator}{prune_null_branches}} ||= $rsrc->_mk_row_parser({
+    ( $self->{_row_parser}{classic} ||= $rsrc->_mk_row_parser({
       eval => 1,
       inflate_map => $infmap,
       selection => $attrs->{select},
       collapse => $attrs->{collapse},
       premultiplied => $attrs->{_main_source_premultiplied},
-      prune_null_branches => $self->{_result_inflator}{prune_null_branches},
     }) )->($rows, @extra_collapser_args);
 
     $_ = $inflator_cref->($res_class, $rsrc, @$_) for @$rows;
index 065c36c..5b970d0 100644 (file)
@@ -106,7 +106,6 @@ sub _mk_row_parser {
     $src = assemble_simple_parser({
       val_index => $val_index,
       hri_style => $args->{hri_style},
-      prune_null_branches => $args->{prune_null_branches},
     });
   }
   else {
@@ -130,13 +129,12 @@ sub _mk_row_parser {
       val_index => $val_index,
       collapse_map => $collapse_map,
       hri_style => $args->{hri_style},
-      prune_null_branches => $args->{prune_null_branches},
     });
   }
 
-  return (! $args->{eval})
-    ? $src
-    : eval "sub { $src }" || die $@
+  return $args->{eval}
+    ? ( eval "sub { $src }" || die $@ )
+    : $src
   ;
 }
 
index 7aa2b49..7e40b5c 100644 (file)
@@ -13,6 +13,9 @@ our @EXPORT_OK = qw(
   assemble_collapsing_parser
 );
 
+# working title - we are hoping to extract this eventually...
+our $null_branch_class = 'DBIx::ResultParser::RelatedNullBranch';
+
 sub assemble_simple_parser {
   #my ($args) = @_;
 
@@ -51,24 +54,38 @@ sub __visit_infmap_simple {
   my @relperl;
   for my $rel (sort keys %$rel_cols) {
 
-    push @relperl, join ' => ', perlstring($rel), __visit_infmap_simple({ %$args,
+    my $rel_struct = __visit_infmap_simple({ %$args,
       val_index => $rel_cols->{$rel},
     });
 
-    if ($args->{prune_null_branches} and keys %$my_cols) {
+    if (keys %$my_cols) {
 
-      my @branch_null_checks = map
+      my $branch_null_checks = join ' && ', map
         { "( ! defined '\xFF__VALPOS__${_}__\xFF' )" }
         sort { $a <=> $b } values %{$rel_cols->{$rel}}
       ;
 
-      $relperl[-1] = sprintf ( '(%s) ? ( %s => %s ) : ( %s )',
-        join (' && ', @branch_null_checks ),
-        perlstring($rel),
-        $args->{hri_style} ? 'undef' : '[]',
-        $relperl[-1],
-      );
+      if ($args->{hri_style}) {
+        $rel_struct = sprintf ( '( (%s) ? undef : %s )',
+          $branch_null_checks,
+          $rel_struct,
+        );
+      }
+      else {
+        $rel_struct = sprintf ( '( (%s) ? bless( (%s), %s ) : %s )',
+          $branch_null_checks,
+          $rel_struct,
+          perlstring($null_branch_class),
+          $rel_struct,
+        );
+      }
     }
+
+    push @relperl, sprintf '( %s => %s )',
+      perlstring($rel),
+      $rel_struct,
+    ;
+
   }
 
   my $me_struct;
@@ -159,7 +176,7 @@ sub assemble_collapsing_parser {
     );
 
     # the rel assemblers
-    %4$s
+%4$s
 
     $_[0][$result_pos++] = $collapse_idx[0]%2$s
       if $is_new_res;
@@ -195,24 +212,25 @@ sub __visit_infmap_collapse {
   }
 
 
-  my $node_key = $args->{collapse_map}->{-custom_node_key} || join ('', map
-    { "{'\xFF__IDVALPOS__${_}__\xFF'}" }
-    @{$args->{collapse_map}->{-identifying_columns}}
-  );
-
-  my $me_struct;
-
   if ($args->{hri_style}) {
     delete $my_cols->{$_} for grep { $rel_cols->{$_} } keys %$my_cols;
   }
 
-  if (keys %$my_cols) {
-    $me_struct = __visit_dump({ map { $_ => "\xFF__VALPOS__$my_cols->{$_}__\xFF" } (keys %$my_cols) });
-    $me_struct = "[ $me_struct ]" unless $args->{hri_style};
-  }
+  my $me_struct;
+  $me_struct = __visit_dump({ map { $_ => "\xFF__VALPOS__$my_cols->{$_}__\xFF" } (keys %$my_cols) })
+    if keys %$my_cols;
 
+  $me_struct = sprintf( '[ %s ]', $me_struct||'' )
+    unless $args->{hri_style};
+
+
+  my $node_key = $args->{collapse_map}->{-custom_node_key} || join ('', map
+    { "{'\xFF__IDVALPOS__${_}__\xFF'}" }
+    @{$args->{collapse_map}->{-identifying_columns}}
+  );
   my $node_idx_slot = sprintf '$collapse_idx[%d]%s', $cur_node_idx, $node_key;
 
+
   my @src;
 
   if ($cur_node_idx == 0) {
@@ -251,9 +269,6 @@ sub __visit_infmap_collapse {
   for my $rel (sort keys %$rel_cols) {
 
     my $relinfo = $args->{collapse_map}{$rel};
-    if ($args->{collapse_map}{-is_optional}) {
-      $relinfo = { %$relinfo, -is_optional => 1 };
-    }
 
     ($rel_src, $stats->{$rel}) = __visit_infmap_collapse({ %$args,
       val_index => $rel_cols->{$rel},
@@ -267,8 +282,6 @@ sub __visit_infmap_collapse {
     push @src, @$rel_src;
 
     if (
-      $args->{prune_null_branches}
-        and
       $relinfo->{-is_optional}
         and
       defined ( my $first_distinct_child_idcol = first
@@ -277,17 +290,28 @@ sub __visit_infmap_collapse {
       )
     ) {
 
-      $src[$rel_src_pos] = sprintf( '%s and %s',
-        "( defined '\xFF__VALPOS__${first_distinct_child_idcol}__\xFF' )",
-        $src[$rel_src_pos],
-      );
+      if ($args->{hri_style}) {
 
-      splice @src, $rel_src_pos + 1, 0, sprintf ( '%s%s{%s} ||= %s;',
-        $node_idx_slot,
-        $args->{hri_style} ? '' : '[1]',
-        perlstring($rel),
-        $args->{hri_style} && $relinfo->{-is_single} ? 'undef' : '[]',
-      );
+        $src[$rel_src_pos] = sprintf( '%s and %s',
+          "( defined '\xFF__VALPOS__${first_distinct_child_idcol}__\xFF' )",
+          $src[$rel_src_pos],
+        );
+
+        splice @src, $rel_src_pos + 1, 0, sprintf ( '%s{%s} ||= %s;',
+          $node_idx_slot,
+          perlstring($rel),
+          $relinfo->{-is_single} ? 'undef' : '[]',
+        );
+      }
+      else {
+
+        splice @src, $rel_src_pos + 1, 0, sprintf ( '(defined %s) or bless (%s[1]{%s}, %s);',
+          "'\xFF__VALPOS__${first_distinct_child_idcol}__\xFF'",
+          $node_idx_slot,
+          perlstring($rel),
+          perlstring($null_branch_class),
+        );
+      }
     }
   }
 
@@ -305,6 +329,7 @@ sub __visit_infmap_collapse {
 # keep our own DD object around so we don't have to fitz with quoting
 my $dumper_obj;
 sub __visit_dump {
+
   # we actually will be producing functional perl code here,
   # thus no second-guessing of what these globals might have
   # been set to. DO NOT CHANGE!
index d88edc2..39cd754 100644 (file)
@@ -1186,7 +1186,11 @@ sub inflate_result {
     for my $pre ( keys %$prefetch ) {
 
       my @pre_objects;
-      if (@{$prefetch->{$pre}||[]}) {
+      if (
+        @{$prefetch->{$pre}||[]}
+          and
+        ref($prefetch->{$pre}) ne $DBIx::Class::ResultSource::RowParser::Util::null_branch_class
+      ) {
         my $pre_source = $source->related_source($pre);
 
         @pre_objects = map {
index 04237f9..ceee197 100644 (file)
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 
 use Test::More;
+use Test::Deep;
 use lib qw(t/lib);
 use DBICTest;
 
@@ -48,17 +49,33 @@ for (1,2) {
   $schema->resultset('CD')->create({ artist => 1, year => 1977, title => "fuzzy_$_" });
 }
 
-my $rs = $schema->resultset('CD');
+{
+  package DBICTest::HRI::Subclass;
+  use base 'DBIx::Class::ResultClass::HashRefInflator';
+}
+
+{
+  package DBICTest::HRI::Around;
+  use base 'DBIx::Class::ResultClass::HashRefInflator';
+
+  sub inflate_result { shift->next::method(@_) }
+}
 
-is_deeply
-  $rs->search({}, {
+for my $rs (
+  $schema->resultset('CD')->search_rs({}, { result_class => 'DBIx::Class::ResultClass::HashRefInflator' }),
+  $schema->resultset('CD')->search_rs({}, { result_class => 'DBICTest::HRI::Subclass' }),
+  $schema->resultset('CD')->search_rs({}, { result_class => 'DBICTest::HRI::Around' }),
+) {
+
+cmp_deeply
+  [ $rs->search({}, {
     columns => {
       year                          => 'me.year',
       'single_track.cd.artist.name' => 'artist.name',
     },
     join => { single_track => { cd => 'artist' } },
     order_by => [qw/me.cdid artist.artistid/],
-  })->all_hri,
+  })->all ],
   [
     {
       single_track => undef,
@@ -87,11 +104,11 @@ is_deeply
       year => 1977
     },
   ],
-  'plain 1:1 descending chain'
+  'plain 1:1 descending chain ' . $rs->result_class
 ;
 
-is_deeply
-  $rs->search({}, {
+cmp_deeply
+  [ $rs->search({}, {
     columns => {
       'artist'                                  => 'me.artist',
       'title'                                   => 'me.title',
@@ -102,7 +119,7 @@ is_deeply
     },
     join => { single_track => { cd => { artist => { cds => 'tracks' } } } },
     order_by => [qw/me.cdid artist.artistid cds.cdid tracks.trackid/],
-  })->all_hri,
+  })->all ],
   [
     {
       artist => 1,
@@ -323,11 +340,11 @@ is_deeply
       year => 1977
     }
   ],
-  'non-collapsing 1:1:1:M:M chain',
+  'non-collapsing 1:1:1:M:M chain ' . $rs->result_class,
 ;
 
-is_deeply
-  $rs->search({}, {
+cmp_deeply
+  [ $rs->search({}, {
     columns => {
       'artist'                                  => 'me.artist',
       'title'                                   => 'me.title',
@@ -339,7 +356,7 @@ is_deeply
     join => { single_track => { cd => { artist => { cds => 'tracks' } } } },
     order_by => [qw/me.cdid artist.artistid cds.cdid tracks.trackid/],
     collapse => {}, #hashref to keep older DBIC versions happy (doesn't actually work)
-  })->all_hri,
+  })->all ],
   [
     {
       artist => 1,
@@ -430,7 +447,9 @@ is_deeply
       year => 1977
     }
   ],
-  'collapsing 1:1:1:M:M chain',
+  'collapsing 1:1:1:M:M chain ' . $rs->result_class,
 ;
 
+}
+
 done_testing;
index 694cf0b..8d99ff8 100644 (file)
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 
 use Test::More;
+use Test::Deep;
 use lib qw(t/lib);
 use DBICTest;
 use DBIC::SqlMakerTest;
@@ -66,7 +67,7 @@ my $queries = 0;
 $schema->storage->debugcb(sub { $queries++; });
 $schema->storage->debug(1);
 
-is_deeply (
+cmp_deeply (
   { map
     { $_->cdid => {
       track_titles => [ map { $_->title } ($_->tracks->all) ],
index b3b2ef6..4520bf4 100644 (file)
@@ -2,6 +2,7 @@ use warnings;
 use strict;
 
 use Test::More;
+use Test::Deep;
 
 use lib qw(t/lib);
 use DBICTest;
@@ -32,7 +33,7 @@ $schema->storage->debug(1);
 
 my $cd = $schema->resultset('CD')->search( {}, { prefetch => 'artist' })->next;
 
-is_deeply
+cmp_deeply
   { $cd->get_columns },
   {
     artist => 0,
@@ -45,7 +46,7 @@ is_deeply
   'Expected CD columns present',
 ;
 
-is_deeply
+cmp_deeply
   { $cd->artist->get_columns },
   {
     artistid => 0,
index e753962..cf6c514 100644 (file)
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 
 use Test::More;
+use Test::Deep;
 use Test::Exception;
 use lib qw(t/lib);
 use DBICTest;
@@ -41,7 +42,7 @@ lives_ok ( sub {
   );
   my $years = [qw/ 2001 2001 1999 1998 1997/];
 
-  is_deeply (
+  cmp_deeply (
     [ $rs->search->get_column('me.year')->all ],
     $years,
     'Expected years (at least one duplicate)',
@@ -67,13 +68,13 @@ lives_ok ( sub {
     push @pref_cds_and_tracks, $data;
   }
 
-  is_deeply (
+  cmp_deeply (
     \@pref_cds_and_tracks,
     \@cds_and_tracks,
     'Correct collapsing on non-unique primary object'
   );
 
-  is_deeply (
+  cmp_deeply (
     [ $pref_rs->search ({}, { result_class => 'DBIx::Class::ResultClass::HashRefInflator' })->all ],
     \@cds_and_tracks,
     'Correct HRI collapsing on non-unique primary object'
index 646b2d3..7ce13a7 100644 (file)
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 
 use Test::More;
+use Test::Deep;
 use Test::Exception;
 use lib qw(t/lib);
 use DBICTest;
@@ -64,7 +65,7 @@ my $rs = $schema->resultset ('CD')->search ({}, {
 
 my $hri_rs = $rs->search({}, { result_class => 'DBIx::Class::ResultClass::HashRefInflator' });
 
-is_deeply (
+cmp_deeply (
   [$hri_rs->all],
   [
     {
@@ -189,7 +190,7 @@ TODO: {
   local $TODO = 'Something is wrong with filter type rels, they throw on incomplete objects >.<';
 
   lives_ok {
-    is_deeply (
+    cmp_deeply (
       { $row->single_track->get_columns },
       {},
       'empty intermediate object ok',
@@ -310,13 +311,13 @@ $schema->storage->debugcb(undef);
 $schema->storage->debug($orig_debug);
 is ($queries, 2, "Only two queries for rwo prefetch calls total");
 
-# can't is_deeply a random set - need *some* order
+# can't cmp_deeply a random set - need *some* order
 my @hris = sort { $a->{year} cmp $b->{year} } @{$rs->search({}, {
   order_by => [ 'tracks_2.title', 'tracks.title', 'cds.cdid', \ 'RANDOM()' ],
 })->all_hri};
 is (@hris, 6, 'hri count matches' );
 
-is_deeply (\@hris, [
+cmp_deeply (\@hris, [
   {
     single_track => undef,
     tracks => [
index 98c3fa3..e0b73b5 100644 (file)
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 
 use Test::More;
+use Test::Deep;
 use Test::Exception;
 use lib qw(t/lib);
 use DBICTest;
@@ -91,7 +92,7 @@ my $mo = $mo_rs->next;
 
 is( @{$mo->{cds}}, 2, 'two CDs' );
 
-is_deeply(
+cmp_deeply(
     $mo,
     {
         'cds' => [
index 558a662..a6914d9 100644 (file)
@@ -2,12 +2,10 @@ use strict;
 use warnings;
 
 use Test::More;
-use Test::Warn;
+use Test::Deep;
 use lib qw(t/lib);
 use DBICTest;
 
-my $new_collapser_version = DBIx::Class::ResultSet->can('_construct_objects');
-
 my $schema = DBICTest->init_schema(no_populate => 1);
 
 $schema->resultset('Artist')->create({ name => 'JMJ', cds => [{
@@ -58,12 +56,6 @@ $schema->resultset('CD')->create({ artist => 1, year => 1977, title => "fuzzy_1"
   sub inflate_result { [@_[2,3]] };
 }
 
-warnings_exist
-  { $schema->resultset ('CD')->search ({}, { result_class => 'DBICTest::_IRCapture', prefetch => 'tracks' } )->all }
-  qr/\QResultClass DBICTest::_IRCapture does not inherit from DBIx::Class::Row and therefore its inflate_result() will receive the full prefetched data tree, without any branch definedness checks/,
-  'Legacy inflate_result() API warned',
-if $new_collapser_version;
-
 cmp_structures(
   ([$schema->resultset ('CD')->search ({}, {
     result_class => 'DBICTest::_IRCapture',
@@ -73,31 +65,31 @@ cmp_structures(
   [
     [
       { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-      { single_track => [
+      { single_track => bless( [
         { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-        {  cd => [
+        {  cd => bless ( [
           { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
           {
-            artist => [
+            artist => bless ( [
               { artistid => undef, name => undef, charfield => undef, rank => undef }
-            ]
+            ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class )
           }
-        ] }
-      ] }
+        ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
     ],
     [
       { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-      { single_track => [
+      { single_track => bless( [
         { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-        {  cd => [
+        {  cd => bless ( [
           { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
           {
-            artist => [
+            artist => bless ( [
               { artistid => undef, name => undef, charfield => undef, rank => undef }
-            ]
+            ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class )
           }
-        ] }
-      ] }
+        ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
     ],
     [
       { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
@@ -115,60 +107,22 @@ cmp_structures(
     ],
     [
       { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-      { single_track => [
+      { single_track => bless( [
         { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-        {  cd => [
+        {  cd => bless ( [
           { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
           {
-            artist => [
+            artist => bless ( [
               { artistid => undef, name => undef, charfield => undef, rank => undef }
-            ]
-          }
-        ] }
-      ] }
-    ],
-  ],
-  'Simple 1:1 descend with classic prefetch legacy'
-);
-
-cmp_structures(
-  ([$schema->resultset ('CD')->search ({}, {
-    result_class => 'DBICTest::_IRCaptureAround',
-    prefetch => { single_track => { cd => 'artist' } },
-    order_by => 'me.cdid',
-  })->all]),
-  [
-    [
-      { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-      { single_track => [] }
-    ],
-    [
-      { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-      { single_track => [] }
-    ],
-    [
-      { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-      { single_track => [
-        { trackid => 6, title => 'o1', position => 1, cd => 2, last_updated_at => undef, last_updated_on => undef },
-        {  cd => [
-          { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-          {
-            artist => [
-              { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 }
-            ]
+            ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class )
           }
-        ] }
-      ] }
-    ],
-    [
-      { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-      { single_track => [ ] }
+        ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
     ],
   ],
-  'Simple 1:1 descend with classic prefetch pruning'
+  'Simple 1:1 descend with classic prefetch'
 );
 
-
 cmp_structures(
   [$schema->resultset ('CD')->search ({}, {
     result_class => 'DBICTest::_IRCapture',
@@ -185,7 +139,7 @@ cmp_structures(
   [
     [
       { artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-      { single_track => [
+      { single_track => bless( [
         undef,
         {  cd => [
           undef,
@@ -195,11 +149,11 @@ cmp_structures(
             ]
           }
         ] }
-      ] }
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
     ],
     [
       { artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-      { single_track => [
+      { single_track => bless( [
         undef,
         {  cd => [
           undef,
@@ -209,7 +163,7 @@ cmp_structures(
             ]
           }
         ] }
-      ] }
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
     ],
     [
       { artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
@@ -227,7 +181,7 @@ cmp_structures(
     ],
     [
       { artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-      { single_track => [
+      { single_track => bless( [
         undef,
         {  cd => [
           undef,
@@ -237,54 +191,10 @@ cmp_structures(
             ]
           }
         ] }
-      ] }
-    ],
-  ],
-  'Simple 1:1 descend with missing selectors legacy'
-);
-
-cmp_structures(
-  [$schema->resultset ('CD')->search ({}, {
-    result_class => 'DBICTest::_IRCaptureAround',
-    join => { single_track => { cd => 'artist' } },
-    columns => [
-      { 'year'                                    => 'me.year' },
-      { 'genreid'                                 => 'me.genreid' },
-      { 'single_track.cd.artist.artistid'         => 'artist.artistid' },
-      { 'title'                                   => 'me.title' },
-      { 'artist'                                  => 'me.artist' },
-    ],
-    order_by => 'me.cdid',
-  })->all],
-  [
-    [
-      { artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-      { single_track => [] }
-    ],
-    [
-      { artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-      { single_track => [ ] }
-    ],
-    [
-      { artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-      { single_track => [
-        undef,
-        {  cd => [
-          undef,
-          {
-            artist => [
-              { artistid => 1 }
-            ]
-          }
-        ] }
-      ] }
-    ],
-    [
-      { artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-      { single_track => [] }
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) }
     ],
   ],
-  'Simple 1:1 descend with missing selectors pruning'
+  'Simple 1:1 descend with missing selectors'
 );
 
 cmp_structures(
@@ -296,43 +206,43 @@ cmp_structures(
   [
     [
       { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-      { single_track => [
+      { single_track => bless( [
         { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
         {  cd => [
           { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
           {
             artist => [
               { artistid => undef, name => undef, charfield => undef, rank => undef },
-              { cds => [ [
+              { cds => bless( [ [
                 { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
-                { tracks => [ [
+                { tracks => bless( [ [
                   { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-                ] ] },
-              ]]},
+                ] ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
+              ] ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
             ],
           },
         ] },
-      ] },
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
     ],
     [
       { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-      { single_track => [
+      { single_track => bless( [
         { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
         {  cd => [
           { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
           {
             artist => [
               { artistid => undef, name => undef, charfield => undef, rank => undef },
-              { cds => [ [
+              { cds => bless( [ [
                 { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
-                { tracks => [ [
+                { tracks => bless( [ [
                   { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-                ] ] },
-              ]]},
-            ]
-          }
-        ] }
-      ] }
+                ] ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
+              ] ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
+            ],
+          },
+        ] },
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
     ],
     [
       { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
@@ -346,9 +256,9 @@ cmp_structures(
               { cds => [
                 [
                   { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-                  { tracks => [
+                  { tracks => bless( [
                     [ { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef } ],
-                  ] },
+                  ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
                 ],
                 [
                   { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
@@ -382,93 +292,26 @@ cmp_structures(
     ],
     [
       { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-      { single_track => [
+      { single_track => bless( [
         { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
         {  cd => [
           { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
           {
             artist => [
               { artistid => undef, name => undef, charfield => undef, rank => undef },
-              { cds => [ [
+              { cds => bless( [ [
                 { cdid => undef, single_track => undef, artist => undef, genreid => undef, year => undef, title => undef },
-                { tracks => [ [
+                { tracks => bless( [ [
                   { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-                ] ] },
-              ]]},
-            ]
-          }
-        ] }
-      ] }
-    ],
-  ],
-  'Collapsing 1:1 ending in chained has_many with classic prefetch legacy'
-);
-
-cmp_structures(
-  ([$schema->resultset ('CD')->search ({}, {
-    result_class => 'DBICTest::_IRCaptureAround',
-    prefetch => [ { single_track => { cd => { artist => { cds => 'tracks' } } } } ],
-    order_by => [qw/me.cdid tracks.trackid/],
-  })->all]),
-  [
-    [
-      { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-      { single_track => [ ] },
-    ],
-    [
-      { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-      { single_track => [ ] },
-    ],
-    [
-      { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-      { single_track => [
-        { trackid => 6, title => 'o1', position => 1, cd => 2, last_updated_at => undef, last_updated_on => undef },
-        {  cd => [
-          { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-          {
-            artist => [
-              { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-              { cds => [
-                [
-                  { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-                  { tracks => [ ] },
-                ],
-                [
-                  { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-                  { tracks => [
-                    [ { trackid => 1, title => 'm1', position => 1, cd => 1, last_updated_at => undef, last_updated_on => undef } ],
-                    [ { trackid => 2, title => 'm2', position => 2, cd => 1, last_updated_at => undef, last_updated_on => undef } ],
-                    [ { trackid => 3, title => 'm3', position => 3, cd => 1, last_updated_at => undef, last_updated_on => undef } ],
-                    [ { trackid => 4, title => 'm4', position => 4, cd => 1, last_updated_at => undef, last_updated_on => undef } ],
-                  ]},
-                ],
-                [
-                  { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-                  { tracks => [
-                    [ { trackid => 5, title => 'o2', position => 2, cd => 2, last_updated_at => undef, last_updated_on => undef } ],
-                    [ { trackid => 6, title => 'o1', position => 1, cd => 2, last_updated_at => undef, last_updated_on => undef } ],
-                  ]},
-                ],
-                [
-                  { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-                  { tracks => [
-                    [ { trackid => 7, title => 'e1', position => 1, cd => 3, last_updated_at => undef, last_updated_on => undef } ],
-                    [ { trackid => 8, title => 'e2', position => 2, cd => 3, last_updated_at => undef, last_updated_on => undef } ],
-                    [ { trackid => 9, title => 'e3', position => 3, cd => 3, last_updated_at => undef, last_updated_on => undef } ],
-                  ]},
-                ],
-              ]},
-            ]
-          }
-        ] }
-      ] }
-    ],
-    [
-      { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-      { single_track => [ ] },
+                ] ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
+              ] ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
+            ],
+          },
+        ] },
+      ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
     ],
   ],
-  'Collapsing 1:1 ending in chained has_many with classic prefetch pruning'
+  'Collapsing 1:1 ending in chained has_many with classic prefetch'
 );
 
 cmp_structures (
@@ -567,126 +410,20 @@ cmp_structures (
       { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
       { cds => [
         { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-        { tracks => [
+        { tracks => bless( [
           { trackid => undef, title => undef, position => undef, cd => undef, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-  ],
-  'Non-Collapsing chained has_many legacy'
-);
-
-cmp_structures(
-  ([$schema->resultset ('Artist')->search ({}, {
-    result_class => 'DBICTest::_IRCaptureAround',
-    join => { cds => 'tracks' },
-    '+columns' => [
-      (map { "cds.$_" } $schema->source('CD')->columns),
-      (map { +{ "cds.tracks.$_" => "tracks.$_" } } $schema->source('Track')->columns),
-    ],
-    order_by => [qw/cds.cdid tracks.trackid/],
-  })->all]),
-  [
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-        { tracks => [
-          { trackid => 1, title => 'm1', position => 1, cd => 1, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-        { tracks => [
-          { trackid => 2, title => 'm2', position => 2, cd => 1, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-        { tracks => [
-          { trackid => 3, title => 'm3', position => 3, cd => 1, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 1, single_track => undef, artist => 1, genreid => 1, year => 1981, title => "Magnetic Fields" },
-        { tracks => [
-          { trackid => 4, title => 'm4', position => 4, cd => 1, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-        { tracks => [
-          { trackid => 5, title => 'o2', position => 2, cd => 2, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 2, single_track => undef, artist => 1, genreid => undef, year => 1976, title => "Oxygene" },
-        { tracks => [
-          { trackid => 6, title => 'o1', position => 1, cd => 2, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-        { tracks => [
-          { trackid => 7, title => 'e1', position => 1, cd => 3, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-        { tracks => [
-          { trackid => 8, title => 'e2', position => 2, cd => 3, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 3, single_track => 6, artist => 1, genreid => 1, year => 1978, title => "Equinoxe" },
-        { tracks => [
-          { trackid => 9, title => 'e3', position => 3, cd => 3, last_updated_at => undef, last_updated_on => undef },
-        ]},
-      ]},
-    ],
-    [
-      { artistid => 1, name => 'JMJ', charfield => undef, rank => 13 },
-      { cds => [
-        { cdid => 4, single_track => undef, artist => 1, genreid => undef, year => 1977, title => "fuzzy_1" },
-        { tracks => [ ] },
+        ], $DBIx::Class::ResultSource::RowParser::Util::null_branch_class ) },
       ]},
     ],
   ],
-  'Non-Collapsing chained has_many pruning'
+  'Non-Collapsing chained has_many'
 );
 
 sub cmp_structures {
   my ($left, $right, $msg) = @_;
 
-  local $TODO = "Pruning test won't work on pre-rewrite DBIC"
-    if ($msg||'') =~ /pruning$/ and ! $new_collapser_version;
-
   local $Test::Builder::Level = $Test::Builder::Level + 1;
-  is_deeply($left, $right, $msg||());
+  cmp_deeply($left, $right, $msg||());
 }
 
 done_testing;
index baa9da6..ddebcc4 100644 (file)
@@ -26,15 +26,26 @@ is_same_src (
   }),
   '$_ = [
     { year => $_->[1] },
-    { single_track => [
-      undef,
-      { cd => [
+    { single_track => ( ! defined( $_->[0]) )
+      ? bless( [
         undef,
-        { artist => [
-          { name  => $_->[0] },
+        { cd => [
+          undef,
+          { artist => [
+            { name  => $_->[0] },
+          ] },
         ] },
-      ]},
-    ]},
+      ], __NBC__ )
+      : [
+        undef,
+        { cd => [
+          undef,
+          { artist => [
+            { name  => $_->[0] },
+          ] },
+        ] },
+      ]
+    },
   ] for @{$_[0]}',
   'Simple 1:1 descending non-collapsing parser',
 );
@@ -47,42 +58,48 @@ $infmap = [qw/
   title
   artist
 /];
-is_same_src (
-  $schema->source ('CD')->_mk_row_parser({
-    inflate_map => $infmap,
-  }),
-  '$_ = [
-    { artist => $_->[5], title => $_->[4], year => $_->[2] },
-    { single_track => [
-      undef,
-      { cd => [
-        undef,
-        { artist => [
-          { artistid => $_->[1] },
-          { cds => [
-            { cdid => $_->[3] },
-            { tracks => [
-              { title => $_->[0] }
-            ] },
-          ] },
-        ] },
-      ] },
-    ] },
-  ] for @{$_[0]}',
-  '1:1 descending non-collapsing parser terminating with chained 1:M:M',
-);
 
 is_same_src (
   $schema->source ('CD')->_mk_row_parser({
-    prune_null_branches => 1,
     inflate_map => $infmap,
   }),
   '$_ = [
     { artist => $_->[5], title => $_->[4], year => $_->[2] },
     {
-      ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
-        ? ( single_track => [] )
-        : ( single_track => [
+      single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
+        ? bless( [
+          undef,
+          {
+            cd => [
+              undef,
+              {
+                artist => [
+                  { artistid => $_->[1] },
+                  {
+                    cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
+                      ? bless ([
+                        { cdid => $_->[3] },
+                        {
+                          tracks => ( ! defined $_->[0] )
+                            ? bless ( [{ title => $_->[0] }], __NBC__ )
+                            : [{ title => $_->[0] }]
+                        }
+                      ], __NBC__)
+                      : [
+                        { cdid => $_->[3] },
+                        {
+                          tracks => ( ! defined $_->[0] )
+                            ? bless ( [{ title => $_->[0] }], __NBC__ )
+                            : [{ title => $_->[0] }]
+                        }
+                      ]
+                  }
+                ]
+              }
+            ]
+          }
+        ], __NBC__)
+        : [
           undef,
           {
             cd => [
@@ -91,58 +108,65 @@ is_same_src (
                 artist => [
                   { artistid => $_->[1] },
                   {
-                    ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
-                      ? ( cds => [] )
-                      : ( cds => [
+                    cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
+                      ? bless ([
+                        { cdid => $_->[3] },
+                        {
+                          tracks => ( ! defined $_->[0] )
+                            ? bless ( [{ title => $_->[0] }], __NBC__ )
+                            : [{ title => $_->[0] }]
+                        }
+                      ], __NBC__)
+                      : [
                         { cdid => $_->[3] },
                         {
-                          ( ! defined $_->[0] )
-                            ? ( tracks => [] )
-                            : ( tracks => [{ title => $_->[0] }] )
+                          tracks => ( ! defined $_->[0] )
+                            ? bless ( [{ title => $_->[0] }], __NBC__ )
+                            : [{ title => $_->[0] }]
                         }
-                      ])
+                      ]
                   }
                 ]
               }
             ]
           }
-        ])
+        ]
     }
   ] for @{$_[0]}',
-  '1:1 descending non-collapsing null-pruning parser terminating with chained 1:M:M',
+  '1:1 descending non-collapsing parser terminating with chained 1:M:M',
 );
 
 is_same_src (
   $schema->source ('CD')->_mk_row_parser({
-    prune_null_branches => 1,
     hri_style => 1,
     inflate_map => $infmap,
   }),
   '$_ = {
       artist => $_->[5], title => $_->[4], year => $_->[2],
 
-      ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
-        ? ( single_track => undef )
-        : ( single_track => {
+      ( single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
+        ? undef
+        : {
             cd =>
               {
                 artist => {
                     artistid => $_->[1],
-                    ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
-                      ? ( cds => undef )
-                      : ( cds => {
+                    ( cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
+                      ? undef
+                      : {
                           cdid => $_->[3],
-                          ( ! defined $_->[0] )
-                            ? ( tracks => undef )
-                            : ( tracks => { title => $_->[0] } )
+                          ( tracks => ( ! defined $_->[0] )
+                            ? undef
+                            : { title => $_->[0] }
+                          )
                         }
-                       )
+                    )
                   }
               }
           }
-        )
+      )
     } for @{$_[0]}',
-  '1:1 descending non-collapsing null-pruning HRI-direct parser terminating with chained 1:M:M',
+  '1:1 descending non-collapsing HRI-direct parser terminating with chained 1:M:M',
 );
 
 
@@ -205,10 +229,11 @@ is_same_src (
       $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [{ artist => $cur_row_data->[5], title => $cur_row_data->[4], year => $cur_row_data->[2] }];
 
       # prefetch data of single_track (placed in root)
-      $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track} ||= $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}};
+      $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track} ||= $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [];
+      defined($cur_row_data->[1]) or bless( $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track}, __NBC__ );
 
       # prefetch data of cd (placed in single_track)
-      $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}};
+      $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [];
 
       # prefetch data of artist ( placed in single_track->cd)
       $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [{ artistid => $cur_row_data->[1] }];
@@ -219,6 +244,7 @@ is_same_src (
       push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}}, (
         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = [{ cdid => $cur_row_data->[3] }]
       );
+      defined($cur_row_data->[3]) or bless( $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}, __NBC__ );
 
       # prefetch data of tracks (if available)
       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
@@ -226,6 +252,7 @@ is_same_src (
       push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}}, (
         $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[0] }]
       );
+      defined($cur_row_data->[0]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}, __NBC__ );
 
       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}
         if $is_new_res;
@@ -239,7 +266,6 @@ is_same_src (
   $schema->source ('CD')->_mk_row_parser({
     inflate_map => $infmap,
     collapse => 1,
-    prune_null_branches => 1,
     hri_style => 1,
   }),
   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
@@ -296,7 +322,7 @@ is_same_src (
     }
     splice @{$_[0]}, $result_pos;
   ',
-  'Same 1:1 descending terminating with chained 1:M:M but with collapse, pruning, hri-style',
+  'Same 1:1 descending terminating with chained 1:M:M but with collapse, HRI-direct',
 );
 
 $infmap = [qw/
@@ -382,8 +408,8 @@ is_same_src (
 
       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
 
-      $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}};
-      $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}};
+      $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}} ||= [];
+      $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}} ||= [];
       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
 
       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
@@ -391,20 +417,24 @@ is_same_src (
       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} = [{ cdid => $cur_row_data->[6], genreid => $cur_row_data->[9], year => $cur_row_data->[2] }]
       );
+      defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
 
       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
         and
       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
       );
+      defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
 
       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
         and
       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
       );
+      defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
 
-      $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} ||= $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}};
+      $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} ||= $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} ||= [];
+      defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
 
       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
         and
@@ -425,7 +455,6 @@ is_same_src (
   $schema->source ('CD')->_mk_row_parser({
     inflate_map => $infmap,
     collapse => 1,
-    prune_null_branches => 1,
   }),
   ' my ($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
 
@@ -444,50 +473,39 @@ is_same_src (
 
       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
 
-      $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}};
-      $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}};
+      $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}} ||= [];
+      $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}} ||= [];
       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
 
-      (defined $cur_row_data->[6])
-        and
       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
         and
       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} = [{ cdid => $cur_row_data->[6], genreid => $cur_row_data->[9], year => $cur_row_data->[2] }]
       );
-      $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} ||= [];
+      defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
 
-      (defined $cur_row_data->[8])
-        and
       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
         and
       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
       );
-      $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} ||= [];
+      defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
 
-      (defined $cur_row_data->[5])
-        and
       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
         and
       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
       );
-      $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} ||= [];
+      defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
 
-      (defined $cur_row_data->[10])
-        and
-      $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} ||= $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}};
-      $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} ||= [];
+      $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} ||= $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} ||= [];
+      defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
 
-      (defined $cur_row_data->[0])
-        and
       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
         and
       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
         $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} = [{ lyric_id => $cur_row_data->[10], text => $cur_row_data->[0] }]
       );
-      $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} ||= [];
 
       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{1}}
         if $is_new_res;
@@ -495,7 +513,7 @@ is_same_src (
 
     splice @{$_[0]}, $result_pos;
   ',
-  'Multiple has_many on multiple branches with branch pruning torture test',
+  'Multiple has_many on multiple branches with branch torture test',
 );
 
 $infmap = [
@@ -577,29 +595,33 @@ is_same_src (
 
       $collapse_idx[0]{$cur_row_ids{10}} ||= [{ year => $$cur_row_data[1] }];
 
-      $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} ||= ($collapse_idx[1]{$cur_row_ids{0}} ||= [{ trackid => $$cur_row_data[0] }]);
+      $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} ||= ($collapse_idx[1]{$cur_row_ids{0}} ||= [{ trackid => $cur_row_data->[0] }]);
+      defined($cur_row_data->[0]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track}, __NBC__ );
 
-      $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{0}};
+      $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{0}} ||= [];
 
-      $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} ||= ($collapse_idx[3]{$cur_row_ids{0}} ||= [{ artistid => $$cur_row_data[6] }]);
+      $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} ||= ($collapse_idx[3]{$cur_row_ids{0}} ||= [{ artistid => $cur_row_data->[6] }]);
 
       (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
         and
       push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}}, (
-          $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} = [{ cdid => $$cur_row_data[4], genreid => $$cur_row_data[7], year => $$cur_row_data[5] }]
+          $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} = [{ cdid => $cur_row_data->[4], genreid => $cur_row_data->[7], year => $cur_row_data->[5] }]
       );
+      defined($cur_row_data->[4]) or bless ( $collapse_idx[3]{$cur_row_ids{0}}[1]{cds}, __NBC__ );
 
       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
         and
       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}}, (
-          $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $$cur_row_data[8] }]
+          $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
       );
+      defined($cur_row_data->[8]) or bless ( $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}, __NBC__ );
 
       (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
         and
       push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}}, (
-          $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $$cur_row_data[3] }]
+          $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $cur_row_data->[3] }]
       );
+      defined($cur_row_data->[2]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}, __NBC__ );
 
       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{10}}
         if $is_new_res;
@@ -614,7 +636,6 @@ is_same_src (
   $schema->source ('CD')->_mk_row_parser({
     inflate_map => $infmap,
     collapse => 1,
-    prune_null_branches => 1,
     hri_style => 1,
   }),
   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
@@ -685,7 +706,7 @@ is_same_src (
 
     splice @{$_[0]}, $result_pos;
   ',
-  'Multiple has_many on multiple branches with underdefined root, hri style with branch pruning torture test',
+  'Multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
 );
 
 done_testing;
@@ -695,18 +716,22 @@ sub is_same_src {
   $deparser ||= B::Deparse->new;
   local $Test::Builder::Level = $Test::Builder::Level + 1;
 
-  my ($got, $expect) = map {
+  my ($got, $expect) = @_;
+
+  $expect =~ s/__NBC__/B::perlstring($DBIx::Class::ResultSource::RowParser::Util::null_branch_class)/ge;
+
+  my @normalized = map {
     my $cref = eval "sub { $_ }" or do {
       fail "Coderef does not compile!\n\n$@\n\n$_";
       return undef;
     };
     $deparser->coderef2text($cref);
-  } @_[0,1];
-
-#use Test::Differences;
-#eq_or_diff($got, $expect);
-
-  is ($got, $expect, $_[2]||() )
-    or note ("Originals source:\n\n$_[0]\n\n$_[1]\n");
+  } ($got, $expect);
+
+  &is (@normalized, $_[2]||() ) or do {
+    eval { require Test::Differences }
+      ? &Test::Differences::eq_or_diff( @normalized, $_[2]||() )
+      : note ("Original sources:\n\n$got\n\n$expect\n");
+    BAIL_OUT('');
+  };
 }
-