_extract_order_criteria can now handle ident only or full criteria
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBIHacks.pm
1 package   #hide from PAUSE
2   DBIx::Class::Storage::DBIHacks;
3
4 #
5 # This module contains code that should never have seen the light of day,
6 # does not belong in the Storage, or is otherwise unfit for public
7 # display. The arrival of SQLA2 should immediately obsolete 90% of this
8 #
9
10 use strict;
11 use warnings;
12
13 use base 'DBIx::Class::Storage';
14 use mro 'c3';
15
16 use List::Util 'first';
17 use Scalar::Util 'blessed';
18 use Sub::Name 'subname';
19 use Data::Query::Constants;
20 use Data::Query::ExprHelpers;
21 use namespace::clean;
22
23 #
24 # This code will remove non-selecting/non-restricting joins from
25 # {from} specs, aiding the RDBMS query optimizer
26 #
27 sub _prune_unused_joins {
28   my $self = shift;
29   my ($from, $select, $where, $attrs) = @_;
30
31   return $from unless $self->_use_join_optimizer;
32
33   if (ref $from ne 'ARRAY' || ref $from->[0] ne 'HASH' || ref $from->[1] ne 'ARRAY') {
34     return $from;   # only standard {from} specs are supported
35   }
36
37   my $aliastypes = $self->_resolve_aliastypes_from_select_args(@_);
38
39   my $orig_joins = delete $aliastypes->{joining};
40   my $orig_multiplying = $aliastypes->{multiplying};
41
42   # a grouped set will not be affected by amount of rows. Thus any
43   # {multiplying} joins can go
44   delete $aliastypes->{multiplying}
45     if $attrs->{_force_prune_multiplying_joins} or $attrs->{group_by};
46
47   my @newfrom = $from->[0]; # FROM head is always present
48
49   my %need_joins;
50
51   for (values %$aliastypes) {
52     # add all requested aliases
53     $need_joins{$_} = 1 for keys %$_;
54
55     # add all their parents (as per joinpath which is an AoH { table => alias })
56     $need_joins{$_} = 1 for map { values %$_ } map { @{$_->{-parents}} } values %$_;
57   }
58
59   for my $j (@{$from}[1..$#$from]) {
60     push @newfrom, $j if (
61       (! defined $j->[0]{-alias}) # legacy crap
62         ||
63       $need_joins{$j->[0]{-alias}}
64     );
65   }
66
67   return ( \@newfrom, {
68     multiplying => { map { $need_joins{$_} ? ($_  => $orig_multiplying->{$_}) : () } keys %$orig_multiplying },
69     %$aliastypes,
70     joining => { map { $_ => $orig_joins->{$_} } keys %need_joins },
71   } );
72 }
73
74 #
75 # This is the code producing joined subqueries like:
76 # SELECT me.*, other.* FROM ( SELECT me.* FROM ... ) JOIN other ON ...
77 #
78 sub _adjust_select_args_for_complex_prefetch {
79   my ($self, $from, $select, $where, $attrs) = @_;
80
81   $self->throw_exception ('Complex prefetches are not supported on resultsets with a custom from attribute')
82     if (ref $from ne 'ARRAY' || ref $from->[0] ne 'HASH' || ref $from->[1] ne 'ARRAY');
83
84   my $root_alias = $attrs->{alias};
85
86   # generate inner/outer attribute lists, remove stuff that doesn't apply
87   my $outer_attrs = { %$attrs };
88   delete @{$outer_attrs}{qw(where bind rows offset group_by _grouped_by_distinct having)};
89
90   my $inner_attrs = { %$attrs };
91   delete @{$inner_attrs}{qw(from for collapse select as _related_results_construction)};
92
93   # there is no point of ordering the insides if there is no limit
94   delete $inner_attrs->{order_by} if (
95     delete $inner_attrs->{_order_is_artificial}
96       or
97     ! $inner_attrs->{rows}
98   );
99
100   # generate the inner/outer select lists
101   # for inside we consider only stuff *not* brought in by the prefetch
102   # on the outside we substitute any function for its alias
103   my $outer_select = [ @$select ];
104   my $inner_select;
105
106   my ($root_node, $root_node_offset);
107
108   for my $i (0 .. $#$from) {
109     my $node = $from->[$i];
110     my $h = (ref $node eq 'HASH')                                ? $node
111           : (ref $node  eq 'ARRAY' and ref $node->[0] eq 'HASH') ? $node->[0]
112           : next
113     ;
114
115     if ( ($h->{-alias}||'') eq $root_alias and $h->{-rsrc} ) {
116       $root_node = $h;
117       $root_node_offset = $i;
118       last;
119     }
120   }
121
122   $self->throw_exception ('Complex prefetches are not supported on resultsets with a custom from attribute')
123     unless $root_node;
124
125   # use the heavy duty resolver to take care of aliased/nonaliased naming
126   my $colinfo = $self->_resolve_column_info($from);
127   my $selected_root_columns;
128
129   for my $i (0 .. $#$outer_select) {
130     my $sel = $outer_select->[$i];
131
132     next if (
133       $colinfo->{$sel} and $colinfo->{$sel}{-source_alias} ne $root_alias
134     );
135
136     if (ref $sel eq 'HASH' ) {
137       $sel->{-as} ||= $attrs->{as}[$i];
138       $outer_select->[$i] = join ('.', $root_alias, ($sel->{-as} || "inner_column_$i") );
139     }
140     elsif (! ref $sel and my $ci = $colinfo->{$sel}) {
141       $selected_root_columns->{$ci->{-colname}} = 1;
142     }
143
144     push @$inner_select, $sel;
145
146     push @{$inner_attrs->{as}}, $attrs->{as}[$i];
147   }
148
149   # We will need to fetch all native columns in the inner subquery, which may
150   # be a part of an *outer* join condition, or an order_by (which needs to be
151   # preserved outside)
152   # We can not just fetch everything because a potential has_many restricting
153   # join collapse *will not work* on heavy data types.
154   my $connecting_aliastypes = $self->_resolve_aliastypes_from_select_args(
155     $from,
156     undef,
157     $where,
158     $inner_attrs
159   );
160
161   for (sort map { keys %{$_->{-seen_columns}||{}} } map { values %$_ } values %$connecting_aliastypes) {
162     my $ci = $colinfo->{$_} or next;
163     if (
164       $ci->{-source_alias} eq $root_alias
165         and
166       ! $selected_root_columns->{$ci->{-colname}}++
167     ) {
168       # adding it to both to keep limits not supporting dark selectors happy
169       push @$inner_select, $ci->{-fq_colname};
170       push @{$inner_attrs->{as}}, $ci->{-fq_colname};
171     }
172   }
173
174   # construct the inner $from and lock it in a subquery
175   # we need to prune first, because this will determine if we need a group_by below
176   # throw away all non-selecting, non-restricting multijoins
177   # (since we def. do not care about multiplication those inside the subquery)
178   my $inner_subq = do {
179
180     # must use it here regardless of user requests
181     local $self->{_use_join_optimizer} = 1;
182
183     # throw away multijoins since we def. do not care about those inside the subquery
184     my ($inner_from, $inner_aliastypes) = $self->_prune_unused_joins ($from, $inner_select, $where, {
185       %$inner_attrs, _force_prune_multiplying_joins => 1
186     });
187
188     # uh-oh a multiplier (which is not us) left in, this is a problem
189     if (
190       $inner_aliastypes->{multiplying}
191         and
192       # if there are user-supplied groups - assume user knows wtf they are up to
193       ( ! $inner_aliastypes->{grouping} or $inner_attrs->{_grouped_by_distinct} )
194         and
195       my @multipliers = grep { $_ ne $root_alias } keys %{$inner_aliastypes->{multiplying}}
196     ) {
197
198       # if none of the multipliers came from an order_by (guaranteed to have been combined
199       # with a limit) - easy - just slap a group_by to simulate a collapse and be on our way
200       if (
201         ! $inner_aliastypes->{ordering}
202           or
203         ! first { $inner_aliastypes->{ordering}{$_} } @multipliers
204       ) {
205
206         my $unprocessed_order_chunks;
207         ($inner_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection ({
208           %$inner_attrs,
209           from => $inner_from,
210           select => $inner_select,
211         });
212
213         $self->throw_exception (
214           'A required group_by clause could not be constructed automatically due to a complex '
215         . 'order_by criteria. Either order_by columns only (no functions) or construct a suitable '
216         . 'group_by by hand'
217         )  if $unprocessed_order_chunks;
218       }
219       else {
220         # We need to order by external columns and group at the same time
221         # so we can calculate the proper limit
222         # This doesn't really make sense in SQL, however from DBICs point
223         # of view is rather valid (order the leftmost objects by whatever
224         # criteria and get the offset/rows many). There is a way around
225         # this however in SQL - we simply tae the direction of each piece
226         # of the foreign order and convert them to MIN(X) for ASC or MAX(X)
227         # for DESC, and group_by the root columns. The end result should be
228         # exactly what we expect
229
230         # supplement the main selection with pks if not already there,
231         # as they will have to be a part of the group_by to collapse
232         # things properly
233         my $cur_sel = { map { $_ => 1 } @$inner_select };
234
235         my @pks = map { "$root_alias.$_" } $root_node->{-rsrc}->primary_columns
236           or $self->throw_exception( sprintf
237             'Unable to perform complex limited prefetch off %s without declared primary key',
238             $root_node->{-rsrc}->source_name,
239           );
240         for my $col (@pks) {
241           push @$inner_select, $col
242             unless $cur_sel->{$col}++;
243         }
244
245         # wrap any part of the order_by that "responds" to an ordering alias
246         # into a MIN/MAX
247         # FIXME - this code is a joke, will need to be completely rewritten in
248         # the DQ branch. But I need to push a POC here, otherwise the
249         # pesky tests won't pass
250         my $sql_maker = $self->sql_maker;
251         my ($lquote, $rquote, $sep) = map { quotemeta $_ } ($sql_maker->_quote_chars, $sql_maker->name_sep);
252         my $own_re = qr/ $lquote \Q$root_alias\E $rquote $sep | \b \Q$root_alias\E $sep /x;
253         my $inner_columns_info = $self->_resolve_column_info($inner_from);
254
255         my $order_dq = $sql_maker->converter
256                                  ->_order_by_to_dq($attrs->{order_by});
257
258         my @new_order;
259
260         # loop through and replace stuff that is not "ours" with a min/max func
261         # everything is a literal at this point, since we are likely properly
262         # quoted and stuff
263         while (is_Order($order_dq)) {
264           my ($chunk, @args) = $sql_maker->_render_dq(my $by = $order_dq->{by});
265
266           my $is_desc = $order_dq->{reverse};
267
268           push @new_order, $is_desc ? { -desc => \$by } : \$by;
269
270           $order_dq = $order_dq->{from};
271
272           # skip ourselves
273           next if $chunk =~ $own_re;
274
275           # maybe our own unqualified column
276           my $ord_bit = (
277             $lquote and $sep and $chunk =~ /^ $lquote ([^$sep]+) $rquote $/x
278           ) ? $1 : $chunk;
279
280           next if (
281             $ord_bit
282               and
283             $inner_columns_info->{$ord_bit}
284               and
285             $inner_columns_info->{$ord_bit}{-source_alias} eq $root_alias
286           );
287
288           ($new_order[-1]) = map {
289             ($is_desc ? { -desc => $_ } : $_)
290           } \[
291             sprintf(
292               '%s(%s)',
293               ($is_desc ? 'MAX' : 'MIN'),
294               $chunk,
295             ),
296             @args
297           ];
298         }
299
300         $inner_attrs->{order_by} = \@new_order;
301
302         # do not care about leftovers here - it will be all the functions
303         # we just created
304         ($inner_attrs->{group_by}) = $self->_group_over_selection ({
305           %$inner_attrs,
306           from => $inner_from,
307           select => $inner_select,
308         });
309       }
310     }
311
312     # we already optimized $inner_from above
313     # and already local()ized
314     $self->{_use_join_optimizer} = 0;
315
316     # generate the subquery
317     $self->_select_args_to_query (
318       $inner_from,
319       $inner_select,
320       $where,
321       $inner_attrs,
322     );
323   };
324
325   # Generate the outer from - this is relatively easy (really just replace
326   # the join slot with the subquery), with a major caveat - we can not
327   # join anything that is non-selecting (not part of the prefetch), but at
328   # the same time is a multi-type relationship, as it will explode the result.
329   #
330   # There are two possibilities here
331   # - either the join is non-restricting, in which case we simply throw it away
332   # - it is part of the restrictions, in which case we need to collapse the outer
333   #   result by tackling yet another group_by to the outside of the query
334
335   # work on a shallow copy
336   $from = [ @$from ];
337
338   my @outer_from;
339
340   # we may not be the head
341   if ($root_node_offset) {
342     # first generate the outer_from, up and including the substitution point
343     @outer_from = splice @$from, 0, $root_node_offset;
344
345     push @outer_from, [
346       {
347         -alias => $root_alias,
348         -rsrc => $root_node->{-rsrc},
349         $root_alias => $inner_subq,
350       },
351       @{$from->[0]}[1 .. $#{$from->[0]}],
352     ];
353   }
354   else {
355     @outer_from = {
356       -alias => $root_alias,
357       -rsrc => $root_node->{-rsrc},
358       $root_alias => $inner_subq,
359     };
360   }
361
362   shift @$from; # what we just replaced above
363
364   # scan the *remaining* from spec against different attributes, and see which joins are needed
365   # in what role
366   my $outer_aliastypes = $outer_attrs->{_aliastypes} =
367     $self->_resolve_aliastypes_from_select_args( $from, $outer_select, $where, $outer_attrs );
368
369   # unroll parents
370   my ($outer_select_chain, @outer_nonselecting_chains) = map { +{
371     map { $_ => 1 } map { values %$_} map { @{$_->{-parents}} } values %{ $outer_aliastypes->{$_} || {} }
372   } } qw/selecting restricting grouping ordering/;
373
374   # see what's left - throw away if not selecting/restricting
375   # also throw in a group_by if a non-selecting multiplier,
376   # to guard against cross-join explosions
377   my $need_outer_group_by;
378   while (my $j = shift @$from) {
379     my $alias = $j->[0]{-alias};
380
381     if (
382       $outer_select_chain->{$alias}
383     ) {
384       push @outer_from, $j
385     }
386     elsif (first { $_->{$alias} } @outer_nonselecting_chains ) {
387       push @outer_from, $j;
388       $need_outer_group_by ||= $outer_aliastypes->{multiplying}{$alias} ? 1 : 0;
389     }
390   }
391
392   if ( $need_outer_group_by and $attrs->{_grouped_by_distinct} ) {
393     my $unprocessed_order_chunks;
394     ($outer_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection ({
395       %$outer_attrs,
396       from => \@outer_from,
397       select => $outer_select,
398     });
399
400     $self->throw_exception (
401       'A required group_by clause could not be constructed automatically due to a complex '
402     . 'order_by criteria. Either order_by columns only (no functions) or construct a suitable '
403     . 'group_by by hand'
404     ) if $unprocessed_order_chunks;
405
406   }
407
408   # This is totally horrific - the $where ends up in both the inner and outer query
409   # Unfortunately not much can be done until SQLA2 introspection arrives, and even
410   # then if where conditions apply to the *right* side of the prefetch, you may have
411   # to both filter the inner select (e.g. to apply a limit) and then have to re-filter
412   # the outer select to exclude joins you didn't want in the first place
413   #
414   # OTOH it can be seen as a plus: <ash> (notes that this query would make a DBA cry ;)
415   return (\@outer_from, $outer_select, $where, $outer_attrs);
416 }
417
418 #
419 # I KNOW THIS SUCKS! GET SQLA2 OUT THE DOOR SO THIS CAN DIE!
420 #
421 # Due to a lack of SQLA2 we fall back to crude scans of all the
422 # select/where/order/group attributes, in order to determine what
423 # aliases are needed to fulfill the query. This information is used
424 # throughout the code to prune unnecessary JOINs from the queries
425 # in an attempt to reduce the execution time.
426 # Although the method is pretty horrific, the worst thing that can
427 # happen is for it to fail due to some scalar SQL, which in turn will
428 # result in a vocal exception.
429 sub _resolve_aliastypes_from_select_args {
430   my ( $self, $from, $select, $where, $attrs ) = @_;
431
432   $self->throw_exception ('Unable to analyze custom {from}')
433     if ref $from ne 'ARRAY';
434
435   # what we will return
436   my $aliases_by_type;
437
438   # see what aliases are there to work with
439   my $alias_list;
440   for (@$from) {
441     my $j = $_;
442     $j = $j->[0] if ref $j eq 'ARRAY';
443     my $al = $j->{-alias}
444       or next;
445
446     $alias_list->{$al} = $j;
447     $aliases_by_type->{multiplying}{$al} ||= { -parents => $j->{-join_path}||[] } if (
448       # not array == {from} head == can't be multiplying
449       ( ref($_) eq 'ARRAY' and ! $j->{-is_single} )
450         or
451       # a parent of ours is already a multiplier
452       ( grep { $aliases_by_type->{multiplying}{$_} } @{ $j->{-join_path}||[] } )
453     );
454   }
455
456   # get a column to source/alias map (including unambiguous unqualified ones)
457   my $colinfo = $self->_resolve_column_info ($from);
458
459   # set up a botched SQLA
460   my $sql_maker = $self->sql_maker;
461
462   # these are throw away results, do not pollute the bind stack
463   local $sql_maker->{select_bind};
464   local $sql_maker->{where_bind};
465   local $sql_maker->{group_bind};
466   local $sql_maker->{having_bind};
467   local $sql_maker->{from_bind};
468
469   # we can't scan properly without any quoting (\b doesn't cut it
470   # everywhere), so unless there is proper quoting set - use our
471   # own weird impossible character.
472   # Also in the case of no quoting, we need to explicitly disable
473   # name_sep, otherwise sorry nasty legacy syntax like
474   # { 'count(foo.id)' => { '>' => 3 } } will stop working >:(
475   local $sql_maker->{quote_char} = $sql_maker->{quote_char};
476   local $sql_maker->{name_sep} = $sql_maker->{name_sep};
477
478   unless (defined $sql_maker->{quote_char} and length $sql_maker->{quote_char}) {
479     $sql_maker->{quote_char} = ["\x00", "\xFF"];
480     # if we don't unset it we screw up retarded but unfortunately working
481     # 'MAX(foo.bar)' => { '>', 3 }
482     $sql_maker->{name_sep} = '';
483   }
484
485   # local is not enough - need to ensure the inner objects get rebuilt
486   $sql_maker->clear_renderer;
487   $sql_maker->clear_converter;
488
489   my ($lquote, $rquote, $sep) = map { quotemeta $_ } ($sql_maker->_quote_chars, $sql_maker->name_sep);
490
491   # generate sql chunks
492   my $to_scan = {
493     restricting => [
494       ($where
495         ? ($sql_maker->_recurse_where($where))[0]
496         : ()
497       ),
498       ($attrs->{having}
499         ? ($sql_maker->_recurse_where($attrs->{having}))[0]
500         : ()
501       ),
502     ],
503     grouping => [
504       ($attrs->{group_by}
505         ? ($sql_maker->_render_sqla(group_by => $attrs->{group_by}))[0]
506         : (),
507       )
508     ],
509     joining => [
510       $sql_maker->_recurse_from (
511         ref $from->[0] eq 'ARRAY' ? $from->[0][0] : $from->[0],
512         @{$from}[1 .. $#$from],
513       ),
514     ],
515     selecting => [
516       ($select
517         ? ($sql_maker->_render_sqla(select_select => $select))[0]
518         : ()),
519     ],
520     ordering => [
521       map { $_->[0] } $self->_extract_order_criteria ($attrs->{order_by}, $sql_maker),
522     ],
523   };
524
525   # local is not enough - need to ensure the inner objects get rebuilt
526   $sql_maker->clear_renderer;
527   $sql_maker->clear_converter;
528
529   # throw away empty chunks
530   $_ = [ map { $_ || () } @$_ ] for values %$to_scan;
531
532   # first see if we have any exact matches (qualified or unqualified)
533   for my $type (keys %$to_scan) {
534     for my $piece (@{$to_scan->{$type}}) {
535       if ($colinfo->{$piece} and my $alias = $colinfo->{$piece}{-source_alias}) {
536         $aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] };
537         $aliases_by_type->{$type}{$alias}{-seen_columns}{$colinfo->{$piece}{-fq_colname}} = $piece;
538       }
539     }
540   }
541
542   # now loop through all fully qualified columns and get the corresponding
543   # alias (should work even if they are in scalarrefs)
544   for my $alias (keys %$alias_list) {
545     my $al_re = qr/
546       $lquote $alias $rquote $sep (?: $lquote ([^$rquote]+) $rquote )?
547         |
548       \b $alias \. ([^\s\)\($rquote]+)?
549     /x;
550
551     for my $type (keys %$to_scan) {
552       for my $piece (@{$to_scan->{$type}}) {
553         if (my @matches = $piece =~ /$al_re/g) {
554           $aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] };
555           $aliases_by_type->{$type}{$alias}{-seen_columns}{"$alias.$_"} = "$alias.$_"
556             for grep { defined $_ } @matches;
557         }
558       }
559     }
560   }
561
562   # now loop through unqualified column names, and try to locate them within
563   # the chunks
564   for my $col (keys %$colinfo) {
565     next if $col =~ / \. /x;   # if column is qualified it was caught by the above
566
567     my $col_re = qr/ $lquote ($col) $rquote /x;
568
569     for my $type (keys %$to_scan) {
570       for my $piece (@{$to_scan->{$type}}) {
571         if ( my @matches = $piece =~ /$col_re/g) {
572           my $alias = $colinfo->{$col}{-source_alias};
573           $aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] };
574           $aliases_by_type->{$type}{$alias}{-seen_columns}{"$alias.$_"} = $_
575             for grep { defined $_ } @matches;
576         }
577       }
578     }
579   }
580
581   # Add any non-left joins to the restriction list (such joins are indeed restrictions)
582   for my $j (values %$alias_list) {
583     my $alias = $j->{-alias} or next;
584     $aliases_by_type->{restricting}{$alias} ||= { -parents => $j->{-join_path}||[] } if (
585       (not $j->{-join_type})
586         or
587       ($j->{-join_type} !~ /^left (?: \s+ outer)? $/xi)
588     );
589   }
590
591   for (keys %$aliases_by_type) {
592     delete $aliases_by_type->{$_} unless keys %{$aliases_by_type->{$_}};
593   }
594
595   return $aliases_by_type;
596 }
597
598 # This is the engine behind { distinct => 1 }
599 sub _group_over_selection {
600   my ($self, $attrs) = @_;
601
602   my $colinfos = $self->_resolve_column_info ($attrs->{from});
603
604   my (@group_by, %group_index);
605
606   # the logic is: if it is a { func => val } we assume an aggregate,
607   # otherwise if \'...' or \[...] we assume the user knows what is
608   # going on thus group over it
609   for (@{$attrs->{select}}) {
610     if (! ref($_) or ref ($_) ne 'HASH' ) {
611       push @group_by, $_;
612       $group_index{$_}++;
613       if ($colinfos->{$_} and $_ !~ /\./ ) {
614         # add a fully qualified version as well
615         $group_index{"$colinfos->{$_}{-source_alias}.$_"}++;
616       }
617     }
618   }
619
620   # add any order_by parts *from the main source* that are not already
621   # present in the group_by
622   # we need to be careful not to add any named functions/aggregates
623   # i.e. order_by => [ ... { count => 'foo' } ... ]
624   my @leftovers;
625   for ($self->_extract_order_criteria($attrs->{order_by})) {
626     # only consider real columns (for functions the user got to do an explicit group_by)
627     if (@$_ != 1) {
628       push @leftovers, $_;
629       next;
630     }
631     my $chunk = $_->[0];
632
633     if (
634       !$colinfos->{$chunk}
635         or
636       $colinfos->{$chunk}{-source_alias} ne $attrs->{alias}
637     ) {
638       push @leftovers, $_;
639       next;
640     }
641
642     $chunk = $colinfos->{$chunk}{-fq_colname};
643     push @group_by, $chunk unless $group_index{$chunk}++;
644   }
645
646   return wantarray
647     ? (\@group_by, (@leftovers ? \@leftovers : undef) )
648     : \@group_by
649   ;
650 }
651
652 sub _resolve_ident_sources {
653   my ($self, $ident) = @_;
654
655   my $alias2source = {};
656
657   # the reason this is so contrived is that $ident may be a {from}
658   # structure, specifying multiple tables to join
659   if ( blessed $ident && $ident->isa("DBIx::Class::ResultSource") ) {
660     # this is compat mode for insert/update/delete which do not deal with aliases
661     $alias2source->{me} = $ident;
662   }
663   elsif (ref $ident eq 'ARRAY') {
664
665     for (@$ident) {
666       my $tabinfo;
667       if (ref $_ eq 'HASH') {
668         $tabinfo = $_;
669       }
670       if (ref $_ eq 'ARRAY' and ref $_->[0] eq 'HASH') {
671         $tabinfo = $_->[0];
672       }
673
674       $alias2source->{$tabinfo->{-alias}} = $tabinfo->{-rsrc}
675         if ($tabinfo->{-rsrc});
676     }
677   }
678
679   return $alias2source;
680 }
681
682 # Takes $ident, \@column_names
683 #
684 # returns { $column_name => \%column_info, ... }
685 # also note: this adds -result_source => $rsrc to the column info
686 #
687 # If no columns_names are supplied returns info about *all* columns
688 # for all sources
689 sub _resolve_column_info {
690   my ($self, $ident, $colnames) = @_;
691   my $alias2src = $self->_resolve_ident_sources($ident);
692
693   my (%seen_cols, @auto_colnames);
694
695   # compile a global list of column names, to be able to properly
696   # disambiguate unqualified column names (if at all possible)
697   for my $alias (keys %$alias2src) {
698     my $rsrc = $alias2src->{$alias};
699     for my $colname ($rsrc->columns) {
700       push @{$seen_cols{$colname}}, $alias;
701       push @auto_colnames, "$alias.$colname" unless $colnames;
702     }
703   }
704
705   $colnames ||= [
706     @auto_colnames,
707     grep { @{$seen_cols{$_}} == 1 } (keys %seen_cols),
708   ];
709
710   my (%return, $colinfos);
711   foreach my $col (@$colnames) {
712     my ($source_alias, $colname) = $col =~ m/^ (?: ([^\.]+) \. )? (.+) $/x;
713
714     # if the column was seen exactly once - we know which rsrc it came from
715     $source_alias ||= $seen_cols{$colname}[0]
716       if ($seen_cols{$colname} and @{$seen_cols{$colname}} == 1);
717
718     next unless $source_alias;
719
720     my $rsrc = $alias2src->{$source_alias}
721       or next;
722
723     $return{$col} = {
724       %{
725           ( $colinfos->{$source_alias} ||= $rsrc->columns_info )->{$colname}
726             ||
727           $self->throw_exception(
728             "No such column '$colname' on source " . $rsrc->source_name
729           );
730       },
731       -result_source => $rsrc,
732       -source_alias => $source_alias,
733       -fq_colname => $col eq $colname ? "$source_alias.$col" : $col,
734       -colname => $colname,
735     };
736
737     $return{"$source_alias.$colname"} = $return{$col} if $col eq $colname;
738   }
739
740   return \%return;
741 }
742
743 # The DBIC relationship chaining implementation is pretty simple - every
744 # new related_relationship is pushed onto the {from} stack, and the {select}
745 # window simply slides further in. This means that when we count somewhere
746 # in the middle, we got to make sure that everything in the join chain is an
747 # actual inner join, otherwise the count will come back with unpredictable
748 # results (a resultset may be generated with _some_ rows regardless of if
749 # the relation which the $rs currently selects has rows or not). E.g.
750 # $artist_rs->cds->count - normally generates:
751 # SELECT COUNT( * ) FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid
752 # which actually returns the number of artists * (number of cds || 1)
753 #
754 # So what we do here is crawl {from}, determine if the current alias is at
755 # the top of the stack, and if not - make sure the chain is inner-joined down
756 # to the root.
757 #
758 sub _inner_join_to_node {
759   my ($self, $from, $alias) = @_;
760
761   # subqueries and other oddness are naturally not supported
762   return $from if (
763     ref $from ne 'ARRAY'
764       ||
765     @$from <= 1
766       ||
767     ref $from->[0] ne 'HASH'
768       ||
769     ! $from->[0]{-alias}
770       ||
771     $from->[0]{-alias} eq $alias  # this last bit means $alias is the head of $from - nothing to do
772   );
773
774   # find the current $alias in the $from structure
775   my $switch_branch;
776   JOINSCAN:
777   for my $j (@{$from}[1 .. $#$from]) {
778     if ($j->[0]{-alias} eq $alias) {
779       $switch_branch = $j->[0]{-join_path};
780       last JOINSCAN;
781     }
782   }
783
784   # something else went quite wrong
785   return $from unless $switch_branch;
786
787   # So it looks like we will have to switch some stuff around.
788   # local() is useless here as we will be leaving the scope
789   # anyway, and deep cloning is just too fucking expensive
790   # So replace the first hashref in the node arrayref manually
791   my @new_from = ($from->[0]);
792   my $sw_idx = { map { (values %$_), 1 } @$switch_branch }; #there's one k/v per join-path
793
794   for my $j (@{$from}[1 .. $#$from]) {
795     my $jalias = $j->[0]{-alias};
796
797     if ($sw_idx->{$jalias}) {
798       my %attrs = %{$j->[0]};
799       delete $attrs{-join_type};
800       push @new_from, [
801         \%attrs,
802         @{$j}[ 1 .. $#$j ],
803       ];
804     }
805     else {
806       push @new_from, $j;
807     }
808   }
809
810   return \@new_from;
811 }
812
813 sub _extract_order_criteria {
814   my ($self, $order_by, $sql_maker, $ident_only) = @_;
815
816   $sql_maker ||= $self->sql_maker;
817
818   my $order_dq = $sql_maker->converter->_order_by_to_dq($order_by);
819
820   my @by;
821   while (is_Order($order_dq)) {
822     push @by, $order_dq->{by};
823     $order_dq = $order_dq->{from};
824   }
825
826   delete local @{$sql_maker}{qw(quote_char renderer converter)};
827
828   return map { [ $sql_maker->_render_dq($_) ] } do {
829     if ($ident_only) {
830       my @by_ident;
831       scan_dq_nodes({ DQ_IDENTIFIER ,=> sub { push @by_ident, $_[0] } }, @by);
832       @by_ident
833     } else {
834       @by
835     }
836   };
837
838   my $parser = sub {
839     my ($sql_maker, $order_by, $orig_quote_chars) = @_;
840
841     return scalar $sql_maker->_order_by_chunks ($order_by)
842       unless wantarray;
843
844     my ($lq, $rq, $sep) = map { quotemeta($_) } (
845       ($orig_quote_chars ? @$orig_quote_chars : $sql_maker->_quote_chars),
846       $sql_maker->name_sep
847     );
848
849     my @chunks;
850     for ($sql_maker->_order_by_chunks ($order_by) ) {
851       my $chunk = ref $_ ? [ @$_ ] : [ $_ ];
852       ($chunk->[0]) = $sql_maker->_split_order_chunk($chunk->[0]);
853
854       # order criteria may have come back pre-quoted (literals and whatnot)
855       # this is fragile, but the best we can currently do
856       $chunk->[0] =~ s/^ $lq (.+?) $rq $sep $lq (.+?) $rq $/"$1.$2"/xe
857         or $chunk->[0] =~ s/^ $lq (.+) $rq $/$1/x;
858
859       push @chunks, $chunk;
860     }
861
862     return @chunks;
863   };
864
865   if ($sql_maker) {
866     return $parser->($sql_maker, $order_by);
867   }
868   else {
869     $sql_maker = $self->sql_maker;
870
871     # pass these in to deal with literals coming from
872     # the user or the deep guts of prefetch
873     my $orig_quote_chars = [$sql_maker->_quote_chars];
874
875     local $sql_maker->{quote_char};
876     return $parser->($sql_maker, $order_by, $orig_quote_chars);
877   }
878 }
879
880 sub _order_by_is_stable {
881   my ($self, $ident, $order_by, $where) = @_;
882
883   my $colinfo = $self->_resolve_column_info($ident, [
884     (map { $_->[0] } $self->_extract_order_criteria($order_by, undef, 1)),
885     $where ? @{$self->_extract_fixed_condition_columns($where)} :(),
886   ]);
887
888   return undef unless keys %$colinfo;
889
890   my $cols_per_src;
891   $cols_per_src->{$_->{-source_alias}}{$_->{-colname}} = $_ for values %$colinfo;
892
893   for (values %$cols_per_src) {
894     my $src = (values %$_)[0]->{-result_source};
895     return 1 if $src->_identifying_column_set($_);
896   }
897
898   return undef;
899 }
900
901 # this is almost identical to the above, except it accepts only
902 # a single rsrc, and will succeed only if the first portion of the order
903 # by is stable.
904 # returns that portion as a colinfo hashref on success
905 sub _main_source_order_by_portion_is_stable {
906   my ($self, $main_rsrc, $order_by, $where) = @_;
907
908   die "Huh... I expect a blessed result_source..."
909     if ref($main_rsrc) eq 'ARRAY';
910
911   my @ord_cols = map
912     { $_->[0] }
913     ( $self->_extract_order_criteria($order_by) )
914   ;
915   return unless @ord_cols;
916
917   my $colinfos = $self->_resolve_column_info($main_rsrc);
918
919   for (0 .. $#ord_cols) {
920     if (
921       ! $colinfos->{$ord_cols[$_]}
922         or
923       $colinfos->{$ord_cols[$_]}{-result_source} != $main_rsrc
924     ) {
925       $#ord_cols =  $_ - 1;
926       last;
927     }
928   }
929
930   # we just truncated it above
931   return unless @ord_cols;
932
933   my $order_portion_ci = { map {
934     $colinfos->{$_}{-colname} => $colinfos->{$_},
935     $colinfos->{$_}{-fq_colname} => $colinfos->{$_},
936   } @ord_cols };
937
938   # since all we check here are the start of the order_by belonging to the
939   # top level $rsrc, a present identifying set will mean that the resultset
940   # is ordered by its leftmost table in a stable manner
941   #
942   # RV of _identifying_column_set contains unqualified names only
943   my $unqualified_idset = $main_rsrc->_identifying_column_set({
944     ( $where ? %{
945       $self->_resolve_column_info(
946         $main_rsrc, $self->_extract_fixed_condition_columns($where)
947       )
948     } : () ),
949     %$order_portion_ci
950   }) or return;
951
952   my $ret_info;
953   my %unqualified_idcols_from_order = map {
954     $order_portion_ci->{$_} ? ( $_ => $order_portion_ci->{$_} ) : ()
955   } @$unqualified_idset;
956
957   # extra optimization - cut the order_by at the end of the identifying set
958   # (just in case the user was stupid and overlooked the obvious)
959   for my $i (0 .. $#ord_cols) {
960     my $col = $ord_cols[$i];
961     my $unqualified_colname = $order_portion_ci->{$col}{-colname};
962     $ret_info->{$col} = { %{$order_portion_ci->{$col}}, -idx_in_order_subset => $i };
963     delete $unqualified_idcols_from_order{$ret_info->{$col}{-colname}};
964
965     # we didn't reach the end of the identifying portion yet
966     return $ret_info unless keys %unqualified_idcols_from_order;
967   }
968
969   die 'How did we get here...';
970 }
971
972 # returns an arrayref of column names which *definitely* have some
973 # sort of non-nullable equality requested in the given condition
974 # specification. This is used to figure out if a resultset is
975 # constrained to a column which is part of a unique constraint,
976 # which in turn allows us to better predict how ordering will behave
977 # etc.
978 #
979 # this is a rudimentary, incomplete, and error-prone extractor
980 # however this is OK - it is conservative, and if we can not find
981 # something that is in fact there - the stack will recover gracefully
982 # Also - DQ and the mst it rode in on will save us all RSN!!!
983 sub _extract_fixed_condition_columns {
984   my ($self, $where, $nested) = @_;
985
986   return unless ref $where eq 'HASH';
987
988   my @cols;
989   for my $lhs (keys %$where) {
990     if ($lhs =~ /^\-and$/i) {
991       push @cols, ref $where->{$lhs} eq 'ARRAY'
992         ? ( map { $self->_extract_fixed_condition_columns($_, 1) } @{$where->{$lhs}} )
993         : $self->_extract_fixed_condition_columns($where->{$lhs}, 1)
994       ;
995     }
996     elsif ($lhs !~ /^\-/) {
997       my $val = $where->{$lhs};
998
999       push @cols, $lhs if (defined $val and (
1000         ! ref $val
1001           or
1002         (ref $val eq 'HASH' and keys %$val == 1 and defined $val->{'='})
1003       ));
1004     }
1005   }
1006   return $nested ? @cols : \@cols;
1007 }
1008
1009 1;