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