File Coverage

blib/lib/DBIx/Class/ResultSource/RowParser.pm
Criterion Covered Total %
statement 156 173 90.1
branch 66 84 78.5
condition 52 63 82.5
subroutine 13 15 86.6
pod n/a
total 287 335 85.6


line stmt bran cond sub pod time code
1             package # hide from the pauses
2             DBIx::Class::ResultSource::RowParser;
3              
4 312     312   2319 use strict;
  312         761  
  312         8561  
5 312     312   1641 use warnings;
  312         655  
  312         8018  
6              
7 312     312   1650 use base 'DBIx::Class';
  312         728  
  312         35513  
8              
9 312         23466 use DBIx::Class::ResultSource::RowParser::Util qw(
10             assemble_simple_parser
11             assemble_collapsing_parser
12 312     312   105397 );
  312         1023  
13 312     312   2576 use DBIx::Class::_Util qw( DUMMY_ALIASPAIR dbic_internal_try dbic_internal_catch );
  312         833  
  312         15136  
14              
15 312     312   2012 use DBIx::Class::Carp;
  312         783  
  312         2436  
16              
17             # FIXME - this should go away
18             # instead Carp::Skip should export usable keywords or something like that
19             my $unique_carper;
20 312     312   6575 BEGIN { $unique_carper = \&carp_unique }
21              
22 312     312   1973 use namespace::clean;
  312         858  
  312         2392  
23              
24             # Accepts a prefetch map (one or more relationships for the current source),
25             # returns a set of select/as pairs for each of those relationships. Columns
26             # are fully qualified inflation_slot names
27             sub _resolve_selection_from_prefetch {
28 964     964   2905 my ($self, $pre, $alias_map, $pref_path) = @_;
29              
30             # internal recursion marker
31 964   100     3490 $pref_path ||= [];
32              
33 964 100 100     8196 if (not defined $pre or not length $pre) {
    100          
    100          
    50          
34 97         590 return ();
35             }
36             elsif( ref $pre eq 'ARRAY' ) {
37 360         1095 map { $self->_resolve_selection_from_prefetch( $_, $alias_map, [ @$pref_path ] ) }
  420         1883  
38             @$pre;
39             }
40             elsif( ref $pre eq 'HASH' ) {
41             map {
42 97         421 $self->_resolve_selection_from_prefetch($_, $alias_map, [ @$pref_path ] ),
43             $self->related_source($_)->_resolve_selection_from_prefetch(
44 96         1643 $pre->{$_}, $alias_map, [ @$pref_path, $_] )
45             } keys %$pre;
46             }
47             elsif( ref $pre ) {
48 0         0 $self->throw_exception(
49             "don't know how to resolve prefetch reftype ".ref($pre));
50             }
51             else {
52 410         882 my $p = $alias_map;
53 410         1506 $p = $p->{$_} for @$pref_path, $pre;
54              
55             $self->throw_exception (
56             "Unable to resolve prefetch '$pre' - join alias map does not contain an entry for path: "
57             . join (' -> ', @$pref_path, $pre)
58 410 50 33     1836 ) if (ref $p->{-join_aliases} ne 'ARRAY' or not @{$p->{-join_aliases}} );
  410         1550  
59              
60             # this shift() is critical - it is what allows prefetch => [ (foo) x 2 ] to work
61 410         821 my $src_alias = shift @{$p->{-join_aliases}};
  410         1088  
62              
63             # ordered [select => as] pairs
64 410         1723 map { [
65 1848         10305 "${src_alias}.$_" => join ( '.',
66             @$pref_path,
67             $pre,
68             $_,
69             )
70             ] } $self->related_source($pre)->columns;
71             }
72             }
73              
74             sub _resolve_prefetch {
75 0     0   0 carp_unique(
76             'There is no good reason to call this internal deprecated method - '
77             . 'please open a ticket detailing your usage, so that a better plan can '
78             . 'be devised for your case. In either case _resolve_prefetch() is '
79             . 'deprecated in favor of _resolve_selection_from_prefetch(), which has '
80             . 'a greatly simplified arglist.'
81             );
82              
83 0         0 $_[0]->_resolve_selection_from_prefetch( $_[1], $_[3] );
84             }
85              
86              
87             # Takes an arrayref of {as} dbic column aliases and the collapse and select
88             # attributes from the same $rs (the selector requirement is a temporary
89             # workaround... I hope), and returns a coderef capable of:
90             # my $me_pref_clps = $coderef->([$rs->cursor->next/all])
91             # Where the $me_pref_clps arrayref is the future argument to inflate_result()
92             #
93             # For an example of this coderef in action (and to see its guts) look at
94             # t/resultset/rowparser_internals.t
95             #
96             # This is a huge performance win, as we call the same code for every row
97             # returned from the db, thus avoiding repeated method lookups when traversing
98             # relationships
99             #
100             # Also since the coderef is completely stateless (the returned structure is
101             # always fresh on every new invocation) this is a very good opportunity for
102             # memoization if further speed improvements are needed
103             #
104             # The way we construct this coderef is somewhat fugly, although the result is
105             # really worth it. The final coderef does not perform any kind of recursion -
106             # the entire nested structure constructor is rolled out into a single scope.
107             #
108             # In any case - the output of this thing is meticulously micro-tested, so
109             # any sort of adjustment/rewrite should be relatively easy (fsvo relatively)
110             #
111             sub _mk_row_parser {
112             # $args and $attrs are separated to delineate what is core collapser stuff and
113             # what is dbic $rs specific
114 242     242   979 my ($self, $args, $attrs) = @_;
115              
116             die "HRI without pruning makes zero sense"
117 242 50 66     1345 if ( $args->{hri_style} && ! $args->{prune_null_branches} );
118              
119             my %common = (
120             hri_style => $args->{hri_style},
121             prune_null_branches => $args->{prune_null_branches},
122             val_index => { map
123 2249         6177 { $args->{inflate_map}[$_] => $_ }
124 242         1012 ( 0 .. $#{$args->{inflate_map}} )
  242         1015  
125             },
126             );
127              
128 242 100       1458 my $src = (! $args->{collapse} ) ? assemble_simple_parser(\%common) : do {
129             my $collapse_map = $self->_resolve_collapse ({
130             # FIXME
131             # only consider real columns (not functions) during collapse resolution
132             # this check shouldn't really be here, as fucktards are not supposed to
133             # alias random crap to existing column names anyway, but still - just in
134             # case
135             # FIXME !!!! - this does not yet deal with unbalanced selectors correctly
136             # (it is now trivial as the attrs specify where things go out of sync
137             # needs MOAR tests)
138             as => { map
139 1687 100       5402 { ref $attrs->{select}[$common{val_index}{$_}] ? () : ( $_ => $common{val_index}{$_} ) }
140 174         854 keys %{$common{val_index}}
141             },
142             premultiplied => $args->{premultiplied},
143 174         512 });
144              
145 174         2802 assemble_collapsing_parser({
146             %common,
147             collapse_map => $collapse_map,
148             });
149             };
150              
151 242         1006 utf8::upgrade($src)
152             if DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE;
153              
154 242         2214 $src;
155             }
156              
157              
158             # Takes an arrayref selection list and generates a collapse-map representing
159             # row-object fold-points. Every relationship is assigned a set of unique,
160             # non-nullable columns (which may *not even be* from the same resultset)
161             # and the collapser will use this information to correctly distinguish
162             # data of individual to-be-row-objects. See t/resultset/rowparser_internals.t
163             # for extensive RV examples
164             sub _resolve_collapse {
165 639     639   2627 my ($self, $args, $common_args) = @_;
166              
167             # for comprehensible error messages put ourselves at the head of the relationship chain
168 639   100     7917 $args->{_rel_chain} ||= [ $self->source_name ];
169              
170             # record top-level fully-qualified column index, signify toplevelness
171 639 100       2902 unless ($common_args->{_as_fq_idx}) {
172 178         427 $common_args->{_as_fq_idx} = { %{$args->{as}} };
  178         1355  
173 178         683 $args->{_is_top_level} = 1;
174             };
175              
176 639         1875 my ($my_cols, $rel_cols, $native_cols);
177 639         1425 for (keys %{$args->{as}}) {
  639         3359  
178 3878 100       11931 if ($_ =~ /^ ([^\.]+) \. (.+) /x) {
179 2101         7120 $rel_cols->{$1}{$2} = 1;
180             }
181             else {
182 1777         4711 $native_cols->{$_} = $my_cols->{$_} = {}; # important for ||='s below
183             }
184             }
185              
186 639         1671 my $relinfo;
187             # run through relationships, collect metadata
188 639         2334 for my $rel (keys %$rel_cols) {
189 446         10420 my $inf = $self->relationship_info ($rel);
190              
191             $relinfo->{$rel} = {
192             is_single => ( $inf->{attrs}{accessor} && $inf->{attrs}{accessor} ne 'multi' ),
193             is_inner => ( ( $inf->{attrs}{join_type} || '' ) !~ /^left/i),
194             rsrc => $self->related_source($rel),
195             fk_map => (
196             dbic_internal_try {
197             $self->resolve_relationship_condition(
198             rel_name => $rel,
199              
200             # an API where these are optional would be too cumbersome,
201             # instead always pass in some dummy values
202             DUMMY_ALIASPAIR,
203             )->{identity_map},
204 446     446   2677 }
205             dbic_internal_catch {
206              
207 0     0   0 $unique_carper->(
208             "Resolution of relationship '$rel' failed unexpectedly, "
209             . 'please relay the following error and seek assistance via '
210             . DBIx::Class::_ENV_::HELP_URL . ". Encountered error: $_"
211             );
212              
213             # RV
214             +{}
215 0         0 }
216 446   66     5729 ),
      100        
217             };
218             }
219              
220             # inject non-left fk-bridges from *INNER-JOINED* children (if any)
221 639         2635 for my $rel (grep { $relinfo->{$_}{is_inner} } keys %$relinfo) {
  446         1794  
222 158         479 my $ri = $relinfo->{$rel};
223 158         390 for (keys %{$ri->{fk_map}} ) {
  158         667  
224             # need to know source from *our* pov, hence $rel.col
225             $my_cols->{$_} ||= { via_fk => "$rel.$ri->{fk_map}{$_}" }
226 158 100 100     1412 if defined $rel_cols->{$rel}{$ri->{fk_map}{$_}} # in fact selected
227             }
228             }
229              
230             # if the parent is already defined *AND* we have an inner reverse relationship
231             # (i.e. do not exist without it) , assume all of its related FKs are selected
232             # (even if they in fact are NOT in the select list). Keep a record of what we
233             # assumed, and if any such phantom-column becomes part of our own collapser,
234             # throw everything assumed-from-parent away and replace with the collapser of
235             # the parent (whatever it may be)
236 639         1606 my $assumed_from_parent;
237 639 100 100     4140 if ( ! $args->{_parent_info}{underdefined} and ! $args->{_parent_info}{rev_rel_is_optional} ) {
238 434 100       938 for my $col ( values %{$args->{_parent_info}{rel_condition} || {}} ) {
  434         2466  
239 256 100       1103 next if exists $my_cols->{$col};
240 66         210 $my_cols->{$col} = {};
241 66         283 $assumed_from_parent->{columns}{$col}++;
242             }
243             }
244              
245             # get colinfo for everything
246 639 100       2196 if ($my_cols) {
247 604         16360 my $ci = $self->columns_info;
248 604         4934 $my_cols->{$_}{colinfo} = $ci->{$_} for keys %$my_cols;
249             }
250              
251 639         1633 my $collapse_map;
252              
253             # first try to reuse the parent's collapser (i.e. reuse collapser over 1:1)
254             # (makes for a leaner coderef later)
255 639 100 66     4057 if(
256             ! $collapse_map->{-identifying_columns}
257             and
258             $args->{_parent_info}{collapser_reusable}
259             ) {
260             $collapse_map->{-identifying_columns} = $args->{_parent_info}{collapse_on_idcols}
261 121         420 }
262              
263             # Still don't know how to collapse - in case we are a *single* relationship
264             # AND our parent is defined AND we have any *native* non-nullable pieces: then
265             # we are still good to go
266             # NOTE: it doesn't matter if the nonnullable set is unique or not - it will be
267             # made unique by the parents identifying cols
268 639 100 100     3818 if(
      100        
      100        
269             ! $collapse_map->{-identifying_columns}
270             and
271             $args->{_parent_info}{is_single}
272             and
273 44         335 @{ $args->{_parent_info}{collapse_on_idcols} }
274             and
275             ( my @native_nonnull_cols = grep {
276             $native_cols->{$_}{colinfo}
277             and
278             ! $native_cols->{$_}{colinfo}{is_nullable}
279 72 50       335 } keys %$native_cols )
280             ) {
281              
282             $collapse_map->{-identifying_columns} = [ __unique_numlist(
283 17 50       75 @{ $args->{_parent_info}{collapse_on_idcols}||[] },
284              
285             # FIXME - we don't really need *all* of the columns, $our_nonnull_cols[0]
286             # is sufficient. However map the entire thing to engage the extra nonnull
287             # explicit checks, just to be on the safe side
288             # Remove some day in the future
289             (map
290             {
291 17         42 $common_args->{_as_fq_idx}{join ('.',
292 52         96 @{$args->{_rel_chain}}[1 .. $#{$args->{_rel_chain}}],
  52         203  
  52         98  
293             $_,
294             )}
295             }
296             @native_nonnull_cols
297             ),
298             )];
299             }
300              
301             # Still don't know how to collapse - try to resolve based on our columns (plus already inserted FK bridges)
302 639 100 100     4312 if (
      100        
303             ! $collapse_map->{-identifying_columns}
304             and
305             $my_cols
306             and
307 1538         5175 my $idset = $self->_identifying_column_set ({map { $_ => $my_cols->{$_}{colinfo} } keys %$my_cols})
308             ) {
309             # see if the resulting collapser relies on any implied columns,
310             # and fix stuff up if this is the case
311 444         1363 my @reduced_set = grep { ! $assumed_from_parent->{columns}{$_} } @$idset;
  559         2792  
312              
313             $collapse_map->{-identifying_columns} = [ __unique_numlist(
314 444 100       2418 @{ $args->{_parent_info}{collapse_on_idcols}||[] },
315              
316             (map
317             {
318 444         1112 my $fqc = join ('.',
319 523         3482 @{$args->{_rel_chain}}[1 .. $#{$args->{_rel_chain}}],
  523         1545  
320 523   66     1371 ( $my_cols->{$_}{via_fk} || $_ ),
321             );
322              
323 523         2697 $common_args->{_as_fq_idx}->{$fqc};
324             }
325             @reduced_set
326             ),
327             )];
328             }
329              
330             # Stil don't know how to collapse - keep descending down 1:1 chains - if
331             # a related non-LEFT 1:1 is resolvable - its condition will collapse us
332             # too
333 639 100       3132 unless ($collapse_map->{-identifying_columns}) {
334 57         140 my @candidates;
335              
336 57         199 for my $rel (keys %$relinfo) {
337 74 100 100     403 next unless ($relinfo->{$rel}{is_single} && $relinfo->{$rel}{is_inner});
338              
339 40 50       118 if ( my $rel_collapse = $relinfo->{$rel}{rsrc}->_resolve_collapse ({
340             as => $rel_cols->{$rel},
341 40         368 _rel_chain => [ @{$args->{_rel_chain}}, $rel ],
342             _parent_info => { underdefined => 1 },
343             }, $common_args)) {
344 40         171 push @candidates, $rel_collapse->{-identifying_columns};
345             }
346             }
347              
348             # get the set with least amount of columns
349             # FIXME - maybe need to implement a data type order as well (i.e. prefer several ints
350             # to a single varchar)
351 57 100       218 if (@candidates) {
352 40         117 ($collapse_map->{-identifying_columns}) = sort { scalar @$a <=> scalar @$b } (@candidates);
  0         0  
353             }
354             }
355              
356             # Stil don't know how to collapse, and we are the root node. Last ditch
357             # effort in case we are *NOT* premultiplied.
358             # Run through *each multi* all the way down, left or not, and all
359             # *left* singles (a single may become a multi underneath) . When everything
360             # gets back see if all the rels link to us definitively. If this is the
361             # case we are good - either one of them will define us, or if all are NULLs
362             # we know we are "unique" due to the "non-premultiplied" check
363 639 50 66     2952 if (
      66        
364             ! $collapse_map->{-identifying_columns}
365             and
366             ! $args->{premultiplied}
367             and
368             $args->{_is_top_level}
369             ) {
370 17         58 my (@collapse_sets, $uncollapsible_chain);
371              
372 17         65 for my $rel (keys %$relinfo) {
373              
374             # we already looked at these higher up
375 27 50 66     144 next if ($relinfo->{$rel}{is_single} && $relinfo->{$rel}{is_inner});
376              
377 27 50       94 if (my $clps = $relinfo->{$rel}{rsrc}->_resolve_collapse ({
378             as => $rel_cols->{$rel},
379 27         237 _rel_chain => [ @{$args->{_rel_chain}}, $rel ],
380             _parent_info => { underdefined => 1 },
381             }, $common_args) ) {
382              
383             # for singles use the idcols wholesale (either there or not)
384 27 100       138 if ($relinfo->{$rel}{is_single}) {
    50          
385 10         63 push @collapse_sets, $clps->{-identifying_columns};
386             }
387             elsif (! $relinfo->{$rel}{fk_map}) {
388 0         0 $uncollapsible_chain = 1;
389 0         0 last;
390             }
391             else {
392 17         51 my $defined_cols_parent_side;
393              
394 17         44 for my $fq_col ( grep { /^$rel\.[^\.]+$/ } keys %{$args->{as}} ) {
  117         689  
  17         92  
395 44         261 my ($col) = $fq_col =~ /([^\.]+)$/;
396              
397 44         105 $defined_cols_parent_side->{$_} = $args->{as}{$fq_col} for grep
398 44         224 { $relinfo->{$rel}{fk_map}{$_} eq $col }
399 44         153 keys %{$relinfo->{$rel}{fk_map}}
400             ;
401             }
402              
403 17 50       122 if (my $set = $self->_identifying_column_set([ keys %$defined_cols_parent_side ]) ) {
404 17         69 push @collapse_sets, [ sort map { $defined_cols_parent_side->{$_} } @$set ];
  17         168  
405             }
406             else {
407 0         0 $uncollapsible_chain = 1;
408 0         0 last;
409             }
410             }
411             }
412             else {
413 0         0 $uncollapsible_chain = 1;
414 0         0 last;
415             }
416             }
417              
418 17 50       74 unless ($uncollapsible_chain) {
419             # if we got here - we are good to go, but the construction is tricky
420             # since our children will want to include our collapse criteria - we
421             # don't give them anything (safe, since they are all collapsible on their own)
422             # in addition we record the individual collapse possibilities
423             # of all left children node collapsers, and merge them in the rowparser
424             # coderef later
425 17         52 $collapse_map->{-identifying_columns} = [];
426             $collapse_map->{-identifying_columns_variants} = [ sort {
427 17         80 (scalar @$a) <=> (scalar @$b)
428             or
429             (
430             # Poor man's max()
431 0         0 ( sort { $b <=> $a } @$a )[0]
432             <=>
433 10 0       106 ( sort { $b <=> $a } @$b )[0]
  0         0  
434             )
435             } @collapse_sets ];
436             }
437             }
438              
439             # stop descending into children if we were called by a parent for first-pass
440             # and don't despair if nothing was found (there may be other parallel branches
441             # to dive into)
442 639 100       3016 if ($args->{_parent_info}{underdefined}) {
    50          
443 67 50       584 return $collapse_map->{-identifying_columns} ? $collapse_map : undef
444             }
445             # nothing down the chain resolved - can't calculate a collapse-map
446             elsif (! $collapse_map->{-identifying_columns}) {
447             $self->throw_exception ( sprintf
448             "Unable to calculate a definitive collapse column set for %s%s: fetch more unique non-nullable columns",
449             $self->source_name,
450 0         0 @{$args->{_rel_chain}} > 1
451 0 0       0 ? sprintf (' (last member of the %s chain)', join ' -> ', @{$args->{_rel_chain}} )
  0         0  
452             : ''
453             ,
454             );
455             }
456              
457             # If we got that far - we are collapsable - GREAT! Now go down all children
458             # a second time, and fill in the rest
459              
460             $collapse_map->{-identifying_columns} = [ __unique_numlist(
461 572 100       2548 @{ $args->{_parent_info}{collapse_on_idcols}||[] },
462 572         1239 @{ $collapse_map->{-identifying_columns} },
  572         2075  
463             )];
464              
465 572         2671 for my $rel (sort keys %$relinfo) {
466              
467             $collapse_map->{$rel} = $relinfo->{$rel}{rsrc}->_resolve_collapse ({
468 1913         4206 as => { map { $_ => 1 } ( keys %{$rel_cols->{$rel}} ) },
  394         1631  
469 394         1595 _rel_chain => [ @{$args->{_rel_chain}}, $rel],
470             _parent_info => {
471             # shallow copy
472 394         1857 collapse_on_idcols => [ @{$collapse_map->{-identifying_columns}} ],
473              
474             rel_condition => $relinfo->{$rel}{fk_map},
475              
476             is_optional => ! $relinfo->{$rel}{is_inner},
477              
478             is_single => $relinfo->{$rel}{is_single},
479              
480             # if there is at least one *inner* reverse relationship ( meaning identity-only )
481             # we can safely assume that the child can not exist without us
482             rev_rel_is_optional => (
483             ( grep {
484 804   100     10998 ($_->{attrs}{join_type}||'') !~ /^left/i
485 394         2051 } values %{ $self->reverse_relationship_info($rel) } )
486             ? 0
487             : 1
488             ),
489              
490             # if this is a 1:1 our own collapser can be used as a collapse-map
491             # (regardless of left or not)
492             collapser_reusable => (
493             $relinfo->{$rel}{is_single}
494             &&
495             $relinfo->{$rel}{is_inner}
496             &&
497 394 100 66     1289 @{$collapse_map->{-identifying_columns}}
    100          
498             ) ? 1 : 0,
499             },
500             }, $common_args );
501              
502 394 100       3865 $collapse_map->{$rel}{-is_single} = 1 if $relinfo->{$rel}{is_single};
503 394 100 50     2193 $collapse_map->{$rel}{-is_optional} ||= 1 unless $relinfo->{$rel}{is_inner};
504             }
505              
506 572         5114 return $collapse_map;
507             }
508              
509             # adding a dep on MoreUtils *just* for this is retarded
510             sub __unique_numlist {
511 1033     1033   1905 sort { $a <=> $b } keys %{ {map { $_ => 1 } @_ }}
  1522         5413  
  1033         2343  
  2680         10498  
512             }
513              
514             1;