# $args and $attrs to _mk_row_parser are separated to delineate what is
# core collapser stuff and what is dbic $rs specific
- @{$self->{_row_parser}{$parser_type}}{qw(cref nullcheck)} = $rsrc->_mk_row_parser({
+ $self->{_row_parser}{$parser_type}{cref} = $rsrc->_mk_row_parser({
eval => 1,
inflate_map => $infmap,
collapse => $attrs->{collapse},
prune_null_branches => $self->{_result_inflator}{is_hri} || $self->{_result_inflator}{is_core_row},
}, $attrs) unless $self->{_row_parser}{$parser_type}{cref};
- # column_info metadata historically hasn't been too reliable.
- # We need to start fixing this somehow (the collapse resolver
- # can't work without it). Add an explicit check for the *main*
- # result, hopefully this will gradually weed out such errors
- #
- # FIXME - this is a temporary kludge that reduces performance
- # It is however necessary for the time being
- my ($unrolled_non_null_cols_to_check, $err);
-
- if (my $check_non_null_cols = $self->{_row_parser}{$parser_type}{nullcheck} ) {
-
- $err =
- 'Collapse aborted due to invalid ResultSource metadata - the following '
- . 'selections are declared non-nullable but NULLs were retrieved: '
- ;
-
- my @violating_idx;
- COL: for my $i (@$check_non_null_cols) {
- ! defined $_->[$i] and push @violating_idx, $i and next COL for @$rows;
- }
-
- $self->throw_exception( $err . join (', ', map { "'$infmap->[$_]'" } @violating_idx ) )
- if @violating_idx;
-
- $unrolled_non_null_cols_to_check = join (',', @$check_non_null_cols);
-
- utf8::upgrade($unrolled_non_null_cols_to_check)
- if DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE;
- }
-
- my $next_cref =
- ($did_fetch_all or ! $attrs->{collapse}) ? undef
- : defined $unrolled_non_null_cols_to_check ? eval sprintf <<'EOS', $unrolled_non_null_cols_to_check
-sub {
- # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
- my @r = $cursor->next or return;
- if (my @violating_idx = grep { ! defined $r[$_] } (%s) ) {
- $self->throw_exception( $err . join (', ', map { "'$infmap->[$_]'" } @violating_idx ) )
- }
- \@r
-}
-EOS
- : sub {
+ my $next_cref = ($did_fetch_all or ! $attrs->{collapse})
+ ? undef
+ : sub {
# FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
my @r = $cursor->next or return;
\@r
$rows,
$next_cref,
( $self->{_stashed_rows} = [] ),
+ ( my $null_violations = {} ),
);
+ $self->throw_exception(
+ 'Collapse aborted - the following columns are declared (or defaulted to) '
+ . 'non-nullable within DBIC but NULLs were retrieved from storage: '
+ . join( ', ', map { "'$infmap->[$_]'" } sort { $a <=> $b } keys %$null_violations )
+ . ' within data row ' . dump_value({
+ map {
+ $infmap->[$_] =>
+ ( ! defined $self->{_stashed_rows}[0][$_] or length $self->{_stashed_rows}[0][$_] < 50 )
+ ? $self->{_stashed_rows}[0][$_]
+ : substr( $self->{_stashed_rows}[0][$_], 0, 50 ) . '...'
+ } 0 .. $#{$self->{_stashed_rows}[0]}
+ })
+ ) if keys %$null_violations;
+
# simple in-place substitution, does not regrow $rows
if ($self->{_result_inflator}{is_core_row}) {
$_ = $inflator_cref->($res_class, $rsrc, @$_) for @$rows
)
;
- my $parser_src = sprintf (<<'EOS', $row_id_defs, $top_node_key_assembler, $top_node_key, join( "\n", @$data_assemblers ) );
+ my $null_checks = '';
+
+ for my $c ( sort { $a <=> $b } keys %{$stats->{nullchecks}{mandatory}} ) {
+ $null_checks .= sprintf <<'EOS', $c
+( defined( $cur_row_data->[%1$s] ) or $_[3]->{%1$s} = 1 ),
+
+EOS
+ }
+
+ for my $set ( @{ $stats->{nullchecks}{from_first_encounter} || [] } ) {
+ my @sub_checks;
+
+ for my $i (0 .. $#$set - 1) {
+
+ push @sub_checks, sprintf
+ '( not defined $cur_row_data->[%1$s] ) ? ( %2$s or ( $_[3]->{%1$s} = 1 ) )',
+ $set->[$i],
+ join( ' and ', map
+ { "( not defined \$cur_row_data->[$set->[$_]] )" }
+ ( $i+1 .. $#$set )
+ ),
+ ;
+ }
+
+ $null_checks .= "(\n @{[ join qq(\n: ), @sub_checks, '()' ]} \n),\n";
+ }
+
+ for my $set ( @{ $stats->{nullchecks}{all_or_nothing} || [] } ) {
+
+ $null_checks .= sprintf "(\n( %s )\n or\n(\n%s\n)\n),\n",
+ join ( ' and ', map
+ { "( not defined \$cur_row_data->[$_] )" }
+ sort { $a <=> $b } keys %$set
+ ),
+ join ( ",\n", map
+ { "( defined(\$cur_row_data->[$_]) or \$_[3]->{$_} = 1 )" }
+ sort { $a <=> $b } keys %$set
+ ),
+ ;
+ }
+
+ # If any of the above generators produced something, we need to add the
+ # final "if seen any violations - croak" part
+ # Do not throw from within the string eval itself as it does not have
+ # the necessary metadata to construct a nice exception text. As a bonus
+ # we get to entirely avoid https://github.com/Test-More/Test2/issues/16
+ # and https://rt.perl.org/Public/Bug/Display.html?id=127774
+
+ $null_checks .= <<'EOS' if $null_checks;
+
+( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+) ),
+EOS
+
+
+ my $parser_src = sprintf (<<'EOS', $null_checks, $row_id_defs, $top_node_key_assembler, $top_node_key, join( "\n", @$data_assemblers ) );
### BEGIN LITERAL STRING EVAL
my $rows_pos = 0;
my ($result_pos, @collapse_idx, $cur_row_data, %%cur_row_ids );
( $_[1] and $_[1]->() )
) ) {
- # the undef checks may or may not be there
- # depending on whether we prune or not
+ # column_info metadata historically hasn't been too reliable.
+ # We need to start fixing this somehow (the collapse resolver
+ # can't work without it). Add explicit checks for several cases
+ # of "unexpected NULL", based on the metadata returned by
+ # __visit_infmap_collapse
#
+ # FIXME - this is a temporary kludge that reduces performance
+ # It is however necessary for the time being, until way into the
+ # future when the extra errors clear out all invalid metadata
+%s
+
# due to left joins some of the ids may be NULL/undef, and
# won't play well when used as hash lookups
# we also need to differentiate NULLs on per-row/per-col basis
# (otherwise folding of optional 1:1s will be greatly confused
+ #
+ # the undef checks may or may not be there depending on whether
+ # we prune or not
%s
# in the case of an underdefined root - calculate the virtual id (otherwise no code at all)
( $_[1] and $_[1]->() )
) ) {
+
+ # NULL checks
+ # mandatory => { 4 => 1, 5 => 1 }
+ # from_first_encounter => [ [ 1, 3, 0 ] ]
+ #
+ ( defined( $cur_row_data->[4] ) or $_[3]->{4} = 1 ),
+
+ ( defined( $cur_row_data->[5] ) or $_[3]->{5} = 1 ),
+
+ (
+ ( not defined $cur_row_data->[1] )
+ ? (
+ ( not defined $cur_row_data->[3] )
+ and
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{1} = 1 )
+ )
+ : ( not defined $cur_row_data->[3] )
+ ? (
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{3} = 1 )
+ )
+ : ()
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
( @cur_row_ids{0,1,3,4,5} = (
( $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0" ),
( $cur_row_data->[1] // "\0NULL\xFF$rows_pos\xFF1\0" ),
( $_[1] and $_[1]->() )
) ) {
+
+ # NULL checks
+ # mandatory => { 4 => 1, 5 => 1 }
+ # from_first_encounter => [ [ 1, 3, 0 ] ]
+ #
+ ( defined( $cur_row_data->[4] ) or $_[3]->{4} = 1 ),
+
+ ( defined( $cur_row_data->[5] ) or $_[3]->{5} = 1 ),
+
+ (
+ ( not defined $cur_row_data->[1] )
+ ? (
+ ( not defined $cur_row_data->[3] )
+ and
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{1} = 1 )
+ )
+ : ( not defined $cur_row_data->[3] )
+ ? (
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{3} = 1 )
+ )
+ : ()
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
( @cur_row_ids{0, 1, 3, 4, 5} = @{$cur_row_data}[0, 1, 3, 4, 5] ),
# a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
( $_[1] and $_[1]->() )
) ) {
+
+ # NULL checks
+ # mandatory => { 1 => 1 }
+ # from_first_encounter => [ [6, 8], [5, 10, 0] ],
+ #
+ ( defined( $cur_row_data->[1] ) or $_[3]->{1} = 1 ),
+
+ (
+ ( not defined $cur_row_data->[6] )
+ ? (
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{6} = 1 )
+ )
+ : ()
+ ),
+
+ (
+ ( not defined $cur_row_data->[5] )
+ ? (
+ ( not defined $cur_row_data->[10] )
+ and
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{5} = 1 )
+ )
+ : ( not defined $cur_row_data->[10] )
+ ? (
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{10} = 1 )
+ )
+ : ()
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
( @cur_row_ids{0, 1, 5, 6, 8, 10} = (
$cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0",
$cur_row_data->[1],
( $_[1] and $_[1]->() )
) ) {
+
+ # NULL checks
+ # mandatory => { 1 => 1 }
+ # from_first_encounter => [ [6, 8], [5, 10, 0] ],
+ #
+ ( defined( $cur_row_data->[1] ) or $_[3]->{1} = 1 ),
+
+ (
+ ( not defined $cur_row_data->[6] )
+ ? (
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{6} = 1 )
+ )
+ : ()
+ ),
+
+ (
+ ( not defined $cur_row_data->[5] )
+ ? (
+ ( not defined $cur_row_data->[10] )
+ and
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{5} = 1 )
+ )
+ : ( not defined $cur_row_data->[10] )
+ ? (
+ ( not defined $cur_row_data->[0] )
+ or
+ ( $_[3]->{10} = 1 )
+ )
+ : ()
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
( @cur_row_ids{( 0, 1, 5, 6, 8, 10 )} = @{$cur_row_data}[( 0, 1, 5, 6, 8, 10 )] ),
# a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
( $_[1] and $_[1]->() )
) ) {
+
+ # NULL checks
+ #
+ # from_first_encounter => [ [0, 4, 8] ]
+ # all_or_nothing => [ { 2 => 1, 3 => 1 } ]
+ (
+ ( not defined $cur_row_data->[0] )
+ ? (
+ ( not defined $cur_row_data->[4] )
+ and
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{0} = 1 )
+ )
+ : ( not defined $cur_row_data->[4] )
+ ? (
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{4} = 1 )
+ )
+ : ()
+ ),
+
+ (
+ (
+ ( not defined $cur_row_data->[2] )
+ and
+ ( not defined $cur_row_data->[3] )
+ )
+ or
+ (
+ ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
+ ( defined($cur_row_data->[3]) or $_[3]->{3} = 1 ),
+ )
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
( @cur_row_ids{( 0, 2, 3, 4, 8 )} = (
$cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0",
$cur_row_data->[2] // "\0NULL\xFF$rows_pos\xFF2\0",
( $_[1] and $_[1]->() )
) ) {
+
+ # NULL checks
+ #
+ # from_first_encounter => [ [0, 4, 8] ]
+ # all_or_nothing => [ { 2 => 1, 3 => 1 } ]
+ (
+ ( not defined $cur_row_data->[0] )
+ ? (
+ ( not defined $cur_row_data->[4] )
+ and
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{0} = 1 )
+ )
+ : ( not defined $cur_row_data->[4] )
+ ? (
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{4} = 1 )
+ )
+ : ()
+ ),
+
+ (
+ (
+ ( not defined $cur_row_data->[2] )
+ and
+ ( not defined $cur_row_data->[3] )
+ )
+ or
+ (
+ ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
+ ( defined($cur_row_data->[3]) or $_[3]->{3} = 1 ),
+ )
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
# do not care about nullability here
( @cur_row_ids{( 0, 2, 3, 4, 8 )} = @{$cur_row_data}[( 0, 2, 3, 4, 8 )] ),
( $_[1] and $_[1]->() )
) ) {
+ # NULL checks
+ #
+ # from_first_encounter => [ [6, 4, 8], [6, 0, 9] ]
+ # all_or_nothing => [ { 2 => 1, 3 => 1 } ]
+ (
+ ( not defined $cur_row_data->[6] )
+ ? (
+ ( not defined $cur_row_data->[4] )
+ and
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{6} = 1 )
+ )
+ : ( not defined $cur_row_data->[4] )
+ ? (
+ ( not defined $cur_row_data->[8] )
+ or
+ ( $_[3]->{4} = 1 )
+ )
+ : ()
+ ),
+
+ (
+ ( not defined $cur_row_data->[6] )
+ ? (
+ ( not defined $cur_row_data->[0] )
+ and
+ ( not defined $cur_row_data->[9] )
+ or
+ ( $_[3]->{6} = 1 )
+ )
+ : ( not defined $cur_row_data->[0] )
+ ? (
+ ( not defined $cur_row_data->[9] )
+ or
+ ( $_[3]->{0} = 1 )
+ )
+ : ()
+ ),
+
+ (
+ (
+ ( not defined $cur_row_data->[2] )
+ and
+ ( not defined $cur_row_data->[3] )
+ )
+ or
+ (
+ ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
+ ( defined($cur_row_data->[3]) or $_[3]->{3} = 1 ),
+ )
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
# do not care about nullability here
( @cur_row_ids{( 0, 2, 3, 4, 6, 8, 9 )} = @{$cur_row_data}[( 0, 2, 3, 4, 6, 8, 9 )] ),
'Non-premultiplied implicit collapse with missing join columns',
);
+is_same_src (
+ ($schema->source('Artist')->_mk_row_parser({
+ inflate_map => [qw( artistid cds.artist cds.title cds.tracks.title )],
+ collapse => 1,
+ prune_null_branches => 1,
+ }))[0],
+ ' my $rows_pos = 0;
+ my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids );
+
+ while ($cur_row_data = (
+ (
+ $rows_pos >= 0
+ and
+ (
+ $_[0][$rows_pos++]
+ or
+ ( ($rows_pos = -1), undef )
+ )
+ )
+ or
+ ( $_[1] and $_[1]->() )
+ ) ) {
+
+ # NULL checks
+ #
+ # mandatory => { 0 => 1 }
+ # from_first_encounter => [ [1, 2, 3] ]
+ # all_or_nothing => [ { 1 => 1, 2 => 1 } ]
+
+ ( defined( $cur_row_data->[0] ) or $_[3]->{0} = 1 ),
+
+ (
+ ( not defined $cur_row_data->[1] )
+ ? (
+ ( not defined $cur_row_data->[2] )
+ and
+ ( not defined $cur_row_data->[3] )
+ or
+ $_[3]->{1} = 1
+ )
+ : ( not defined $cur_row_data->[2] )
+ ? (
+ ( not defined $cur_row_data->[3] )
+ or
+ $_[3]->{2} = 1
+ )
+ : ()
+ ),
+
+ (
+ (
+ ( not defined $cur_row_data->[1] )
+ and
+ ( not defined $cur_row_data->[2] )
+ )
+ or
+ (
+ ( defined($cur_row_data->[1]) or $_[3]->{1} = 1 ),
+ ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
+ )
+ ),
+
+ ( keys %{$_[3]} and (
+ ( @{$_[2]} = $cur_row_data ),
+ ( $result_pos = 0 ),
+ last
+ ) ),
+
+
+ ( @cur_row_ids{( 0, 1, 2, 3 )} = @{$cur_row_data}[ 0, 1, 2, 3 ] ),
+
+ ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{0}} and (unshift @{$_[2]}, $cur_row_data) and last ),
+
+ ( $collapse_idx[0]{ $cur_row_ids{0} }
+ //= $_[0][$result_pos++] = [ { "artistid" => $cur_row_data->[0] } ]
+ ),
+
+ ( ( ! defined $cur_row_data->[1] ) ? $collapse_idx[0]{ $cur_row_ids{0} }[1]{"cds"} = [] : do {
+
+ (
+ ! $collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }
+ and
+ push @{$collapse_idx[0]{ $cur_row_ids{0} }[1]{"cds"}},
+ $collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }
+ = [ { "artist" => $cur_row_data->[1], "title" => $cur_row_data->[2] } ]
+ ),
+
+ ( ( ! defined $cur_row_data->[3] ) ? $collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }[1]{"tracks"} = [] : do {
+ (
+ ! $collapse_idx[2]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }{ $cur_row_ids{3} }
+ and
+ push @{$collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }[1]{"tracks"}},
+ $collapse_idx[2]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }{ $cur_row_ids{3} }
+ = [ { "title" => $cur_row_data->[3] } ]
+ ),
+ } ),
+ } ),
+ }
+
+ $#{$_[0]} = $result_pos - 1
+ ',
+ 'A rolled out version of inflate map of misled_rowparser.t'
+);
+
done_testing;
my $deparser;