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