File Coverage

blib/lib/DBIx/Class/SQLMaker/LimitDialects.pm
Criterion Covered Total %
statement 255 256 99.6
branch 80 94 85.1
condition 29 43 67.4
subroutine 20 20 100.0
pod n/a
total 384 413 92.9


line stmt bran cond sub pod time code
1             package DBIx::Class::SQLMaker::LimitDialects;
2              
3 220     220   809118 use warnings;
  220         928  
  220         7236  
4 220     220   1308 use strict;
  220         784  
  220         5360  
5              
6 220     220   1369 use List::Util 'first';
  220         780  
  220         17076  
7 220     220   1598 use namespace::clean;
  220         775  
  220         2122  
8              
9             # constants are used not only here, but also in comparison tests
10             sub __rows_bindtype () {
11 1599     1599   7350 +{ sqlt_datatype => 'integer' }
12             }
13             sub __offset_bindtype () {
14 110     110   670 +{ sqlt_datatype => 'integer' }
15             }
16             sub __total_bindtype () {
17 43     43   437 +{ sqlt_datatype => 'integer' }
18             }
19              
20             =head1 NAME
21              
22             DBIx::Class::SQLMaker::LimitDialects - SQL::Abstract::Limit-like functionality for DBIx::Class::SQLMaker
23              
24             =head1 DESCRIPTION
25              
26             This module replicates a lot of the functionality originally found in
27             L<SQL::Abstract::Limit>. While simple limits would work as-is, the more
28             complex dialects that require e.g. subqueries could not be reliably
29             implemented without taking full advantage of the metadata locked within
30             L<DBIx::Class::ResultSource> classes. After reimplementation of close to
31             80% of the L<SQL::Abstract::Limit> functionality it was deemed more
32             practical to simply make an independent DBIx::Class-specific limit-dialect
33             provider.
34              
35             =head1 SQL LIMIT DIALECTS
36              
37             Note that the actual implementations listed below never use C<*> literally.
38             Instead proper re-aliasing of selectors and order criteria is done, so that
39             the limit dialect are safe to use on joined resultsets with clashing column
40             names.
41              
42             Currently the provided dialects are:
43              
44             =head2 LimitOffset
45              
46             SELECT ... LIMIT $limit OFFSET $offset
47              
48             Supported by B<PostgreSQL> and B<SQLite>
49              
50             =cut
51             sub _LimitOffset {
52 1551     1551   4911 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
53 1551         5004 $sql .= $self->_parse_rs_attrs( $rs_attrs ) . " LIMIT ?";
54 1551         3257 push @{$self->{limit_bind}}, [ $self->__rows_bindtype => $rows ];
  1551         5711  
55 1551 100       4593 if ($offset) {
56 43         117 $sql .= " OFFSET ?";
57 43         96 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset ];
  43         217  
58             }
59 1551         4383 return $sql;
60             }
61              
62             =head2 LimitXY
63              
64             SELECT ... LIMIT $offset, $limit
65              
66             Supported by B<MySQL> and any L<SQL::Statement> based DBD
67              
68             =cut
69             sub _LimitXY {
70 3     3   15 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
71 3         13 $sql .= $self->_parse_rs_attrs( $rs_attrs ) . " LIMIT ";
72 3 100       12 if ($offset) {
73 2         7 $sql .= '?, ';
74 2         4 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset ];
  2         12  
75             }
76 3         10 $sql .= '?';
77 3         6 push @{$self->{limit_bind}}, [ $self->__rows_bindtype => $rows ];
  3         13  
78              
79 3         11 return $sql;
80             }
81              
82             =head2 RowNumberOver
83              
84             SELECT * FROM (
85             SELECT *, ROW_NUMBER() OVER( ORDER BY ... ) AS RNO__ROW__INDEX FROM (
86             SELECT ...
87             )
88             ) WHERE RNO__ROW__INDEX BETWEEN ($offset+1) AND ($limit+$offset)
89              
90              
91             ANSI standard Limit/Offset implementation. Supported by B<DB2> and
92             B<< MSSQL >= 2005 >>.
93              
94             =cut
95             sub _RowNumberOver {
96 14     14   57 my ($self, $sql, $rs_attrs, $rows, $offset ) = @_;
97              
98             # get selectors, and scan the order_by (if any)
99 14         80 my $sq_attrs = $self->_subqueried_limit_attrs ( $sql, $rs_attrs );
100              
101             # make up an order if none exists
102 14   66     98 my $requested_order = (delete $rs_attrs->{order_by}) || $self->_rno_default_order;
103              
104             # the order binds (if any) will need to go at the end of the entire inner select
105 14         40 local $self->{order_bind};
106 14         60 my $rno_ord = $self->_order_by ($requested_order);
107 14         34 push @{$self->{select_bind}}, @{$self->{order_bind}};
  14         36  
  14         34  
108              
109             # this is the order supplement magic
110 14         37 my $mid_sel = $sq_attrs->{selection_outer};
111 14 100       50 if (my $extra_order_sel = $sq_attrs->{order_supplement}) {
112 4         25 for my $extra_col (sort
113 2         11 { $extra_order_sel->{$a} cmp $extra_order_sel->{$b} }
114             keys %$extra_order_sel
115             ) {
116             $sq_attrs->{selection_inner} .= sprintf (', %s AS %s',
117             $extra_col,
118 6         33 $extra_order_sel->{$extra_col},
119             );
120             }
121             }
122              
123             # and this is order re-alias magic
124 14         78 for my $map ($sq_attrs->{order_supplement}, $sq_attrs->{outer_renames}) {
125 28 100       54 for my $col (sort { (length $b) <=> (length $a) } keys %{$map||{}} ) {
  24         53  
  28         155  
126 28         69 my $re_col = quotemeta ($col);
127 28         275 $rno_ord =~ s/$re_col/$map->{$col}/;
128             }
129             }
130              
131             # whatever is left of the order_by (only where is processed at this point)
132 14         67 my $group_having = $self->_parse_rs_attrs($rs_attrs);
133              
134 14         67 my $qalias = $self->_quote ($rs_attrs->{alias});
135 14         387 my $idx_name = $self->_quote ('rno__row__index');
136              
137 14         329 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset + 1], [ $self->__total_bindtype => $offset + $rows ];
  14         94  
138              
139 14         184 return <<EOS;
140              
141             SELECT $sq_attrs->{selection_outer} FROM (
142             SELECT $mid_sel, ROW_NUMBER() OVER( $rno_ord ) AS $idx_name FROM (
143             SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${group_having}
144             ) $qalias
145             ) $qalias WHERE $idx_name >= ? AND $idx_name <= ?
146              
147             EOS
148              
149             }
150              
151             # some databases are happy with OVER (), some need OVER (ORDER BY (SELECT (1)) )
152             sub _rno_default_order {
153 8     8   29 return undef;
154             }
155              
156             =head2 SkipFirst
157              
158             SELECT SKIP $offset FIRST $limit * FROM ...
159              
160             Supported by B<Informix>, almost like LimitOffset. According to
161             L<SQL::Abstract::Limit> C<... SKIP $offset LIMIT $limit ...> is also supported.
162              
163             =cut
164             sub _SkipFirst {
165 10     10   39 my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
166              
167 10 50       71 $sql =~ s/^ \s* SELECT \s+ //ix
168             or $self->throw_exception("Unrecognizable SELECT: $sql");
169              
170             return sprintf ('SELECT %s%s%s%s',
171             $offset
172             ? do {
173 8         31 push @{$self->{pre_select_bind}}, [ $self->__offset_bindtype => $offset];
  8         48  
174 8         20 'SKIP ? '
175             }
176             : ''
177             ,
178 10 100       34 do {
179 10         18 push @{$self->{pre_select_bind}}, [ $self->__rows_bindtype => $rows ];
  10         42  
180 10         42 'FIRST ? '
181             },
182             $sql,
183             $self->_parse_rs_attrs ($rs_attrs),
184             );
185             }
186              
187             =head2 FirstSkip
188              
189             SELECT FIRST $limit SKIP $offset * FROM ...
190              
191             Supported by B<Firebird/Interbase>, reverse of SkipFirst. According to
192             L<SQL::Abstract::Limit> C<... ROWS $limit TO $offset ...> is also supported.
193              
194             =cut
195             sub _FirstSkip {
196 10     10   33 my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
197              
198 10 50       62 $sql =~ s/^ \s* SELECT \s+ //ix
199             or $self->throw_exception("Unrecognizable SELECT: $sql");
200              
201             return sprintf ('SELECT %s%s%s%s',
202             do {
203 10         17 push @{$self->{pre_select_bind}}, [ $self->__rows_bindtype => $rows ];
  10         46  
204 10         30 'FIRST ? '
205             },
206             $offset
207 10 100       21 ? do {
208 8         16 push @{$self->{pre_select_bind}}, [ $self->__offset_bindtype => $offset];
  8         36  
209 8         26 'SKIP ? '
210             }
211             : ''
212             ,
213             $sql,
214             $self->_parse_rs_attrs ($rs_attrs),
215             );
216             }
217              
218              
219             =head2 RowNum
220              
221             Depending on the resultset attributes one of:
222              
223             SELECT * FROM (
224             SELECT *, ROWNUM AS rownum__index FROM (
225             SELECT ...
226             ) WHERE ROWNUM <= ($limit+$offset)
227             ) WHERE rownum__index >= ($offset+1)
228              
229             or
230              
231             SELECT * FROM (
232             SELECT *, ROWNUM AS rownum__index FROM (
233             SELECT ...
234             )
235             ) WHERE rownum__index BETWEEN ($offset+1) AND ($limit+$offset)
236              
237             or
238              
239             SELECT * FROM (
240             SELECT ...
241             ) WHERE ROWNUM <= ($limit+1)
242              
243             Supported by B<Oracle>.
244              
245             =cut
246             sub _RowNum {
247 14     14   48 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
248              
249 14         65 my $sq_attrs = $self->_subqueried_limit_attrs ($sql, $rs_attrs);
250              
251 14         64 my $qalias = $self->_quote ($rs_attrs->{alias});
252 14         498 my $idx_name = $self->_quote ('rownum__index');
253 14         315 my $order_group_having = $self->_parse_rs_attrs($rs_attrs);
254              
255              
256             # if no offset (e.g. first page) - we can skip one of the subqueries
257 14 100       53 if (! $offset) {
258 4         11 push @{$self->{limit_bind}}, [ $self->__rows_bindtype => $rows ];
  4         24  
259              
260 4         45 return <<EOS;
261             SELECT $sq_attrs->{selection_outer} FROM (
262             SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${order_group_having}
263             ) $qalias WHERE ROWNUM <= ?
264             EOS
265             }
266              
267             #
268             # There are two ways to limit in Oracle, one vastly faster than the other
269             # on large resultsets: https://decipherinfosys.wordpress.com/2007/08/09/paging-and-countstopkey-optimization/
270             # However Oracle is retarded and does not preserve stable ROWNUM() values
271             # when called twice in the same scope. Therefore unless the resultset is
272             # ordered by a unique set of columns, it is not safe to use the faster
273             # method, and the slower BETWEEN query is used instead
274             #
275             # FIXME - this is quite expensive, and does not perform caching of any sort
276             # as soon as some of the DQ work becomes viable consider switching this
277             # over
278 10 100 100     57 if (
279             $rs_attrs->{order_by}
280             and
281             $rs_attrs->{result_source}->storage->_order_by_is_stable(
282 5         110 @{$rs_attrs}{qw/from order_by where/}
283             )
284             ) {
285 3         8 push @{$self->{limit_bind}}, [ $self->__total_bindtype => $offset + $rows ], [ $self->__offset_bindtype => $offset + 1 ];
  3         23  
286              
287 3         45 return <<EOS;
288             SELECT $sq_attrs->{selection_outer} FROM (
289             SELECT $sq_attrs->{selection_outer}, ROWNUM AS $idx_name FROM (
290             SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${order_group_having}
291             ) $qalias WHERE ROWNUM <= ?
292             ) $qalias WHERE $idx_name >= ?
293             EOS
294             }
295             else {
296 7         20 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset + 1 ], [ $self->__total_bindtype => $offset + $rows ];
  7         42  
297              
298 7         85 return <<EOS;
299             SELECT $sq_attrs->{selection_outer} FROM (
300             SELECT $sq_attrs->{selection_outer}, ROWNUM AS $idx_name FROM (
301             SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${order_group_having}
302             ) $qalias
303             ) $qalias WHERE $idx_name BETWEEN ? AND ?
304             EOS
305             }
306             }
307              
308             # used by _Top and _FetchFirst below
309             sub _prep_for_skimming_limit {
310 49     49   114 my ( $self, $sql, $rs_attrs ) = @_;
311              
312             # get selectors
313 49         162 my $sq_attrs = $self->_subqueried_limit_attrs ($sql, $rs_attrs);
314              
315 49         154 my $requested_order = delete $rs_attrs->{order_by};
316 49         197 $sq_attrs->{order_by_requested} = $self->_order_by ($requested_order);
317 49         149 $sq_attrs->{grpby_having} = $self->_parse_rs_attrs ($rs_attrs);
318              
319             # without an offset things are easy
320 49 100       145 if (! $rs_attrs->{offset}) {
321 9         29 $sq_attrs->{order_by_inner} = $sq_attrs->{order_by_requested};
322             }
323             else {
324 40         143 $sq_attrs->{quoted_rs_alias} = $self->_quote ($rs_attrs->{alias});
325              
326             # localise as we already have all the bind values we need
327 40         859 local $self->{order_bind};
328              
329             # make up an order unless supplied or sanity check what we are given
330 40         92 my $inner_order;
331 40 100       116 if ($sq_attrs->{order_by_requested}) {
332             $self->throw_exception (
333             'Unable to safely perform "skimming type" limit with supplied unstable order criteria'
334             ) unless ($rs_attrs->{result_source}->schema->storage->_order_by_is_stable(
335             $rs_attrs->{from},
336             $requested_order,
337             $rs_attrs->{where},
338 25 50       119 ));
339              
340 25         82 $inner_order = $requested_order;
341             }
342             else {
343             $inner_order = [ map
344 15         73 { "$rs_attrs->{alias}.$_" }
345             ( @{
346 15         32 $rs_attrs->{result_source}->_identifying_column_set
347             ||
348             $self->throw_exception(sprintf(
349             'Unable to auto-construct stable order criteria for "skimming type" limit '
350 15 50       94 . "dialect based on source '%s'", $rs_attrs->{result_source}->name) );
351             } )
352             ];
353             }
354              
355 40         149 $sq_attrs->{order_by_inner} = $self->_order_by ($inner_order);
356              
357 40         88 my @out_chunks;
358 40         135 for my $ch ($self->_order_by_chunks ($inner_order)) {
359 56 100       2163 $ch = $ch->[0] if ref $ch eq 'ARRAY';
360              
361 56         198 ($ch, my $is_desc) = $self->_split_order_chunk($ch);
362              
363             # !NOTE! outside chunks come in reverse order ( !$is_desc )
364 56 100       286 push @out_chunks, { ($is_desc ? '-asc' : '-desc') => \$ch };
365             }
366              
367 40         375 $sq_attrs->{order_by_middle} = $self->_order_by (\@out_chunks);
368              
369             # this is the order supplement magic
370 40         133 $sq_attrs->{selection_middle} = $sq_attrs->{selection_outer};
371 40 100       140 if (my $extra_order_sel = $sq_attrs->{order_supplement}) {
372 19         96 for my $extra_col (sort
373 10         47 { $extra_order_sel->{$a} cmp $extra_order_sel->{$b} }
374             keys %$extra_order_sel
375             ) {
376             $sq_attrs->{selection_inner} .= sprintf (', %s AS %s',
377             $extra_col,
378 29         110 $extra_order_sel->{$extra_col},
379             );
380              
381 29         89 $sq_attrs->{selection_middle} .= ', ' . $extra_order_sel->{$extra_col};
382             }
383              
384             # Whatever order bindvals there are, they will be realiased and
385             # reselected, and need to show up at end of the initial inner select
386 19         40 push @{$self->{select_bind}}, @{$self->{order_bind}};
  19         50  
  19         49  
387             }
388              
389             # and this is order re-alias magic
390 40         115 for my $map ($sq_attrs->{order_supplement}, $sq_attrs->{outer_renames}) {
391 80 100       144 for my $col (sort { (length $b) <=> (length $a) } keys %{$map||{}}) {
  60         175  
  80         428  
392 101         222 my $re_col = quotemeta ($col);
393             $_ =~ s/$re_col/$map->{$col}/
394 101         1268 for ($sq_attrs->{order_by_middle}, $sq_attrs->{order_by_requested});
395             }
396             }
397             }
398              
399 49         155 $sq_attrs;
400             }
401              
402             =head2 Top
403              
404             SELECT * FROM
405              
406             SELECT TOP $limit FROM (
407             SELECT TOP $limit FROM (
408             SELECT TOP ($limit+$offset) ...
409             ) ORDER BY $reversed_original_order
410             ) ORDER BY $original_order
411              
412             Unreliable Top-based implementation, supported by B<< MSSQL < 2005 >>.
413              
414             =head3 CAVEAT
415              
416             Due to its implementation, this limit dialect returns B<incorrect results>
417             when $limit+$offset > total amount of rows in the resultset.
418              
419             =cut
420              
421             sub _Top {
422 26     26   83 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
423              
424 26         96 my $lim = $self->_prep_for_skimming_limit($sql, $rs_attrs);
425              
426             $sql = sprintf ('SELECT TOP %u %s %s %s %s',
427             $rows + ($offset||0),
428             $offset ? $lim->{selection_inner} : $lim->{selection_original},
429             $lim->{query_leftover},
430             $lim->{grpby_having},
431             $lim->{order_by_inner},
432 26 100 100     276 );
433              
434             $sql = sprintf ('SELECT TOP %u %s FROM ( %s ) %s %s',
435             $rows,
436             $lim->{selection_middle},
437             $sql,
438             $lim->{quoted_rs_alias},
439             $lim->{order_by_middle},
440 26 100       138 ) if $offset;
441              
442             $sql = sprintf ('SELECT %s FROM ( %s ) %s %s',
443             $lim->{selection_outer},
444             $sql,
445             $lim->{quoted_rs_alias},
446             $lim->{order_by_requested},
447             ) if $offset and (
448             $lim->{order_by_requested} or $lim->{selection_middle} ne $lim->{selection_outer}
449 26 100 66     165 );
      66        
450              
451 26         153 return $sql;
452             }
453              
454             =head2 FetchFirst
455              
456             SELECT * FROM
457             (
458             SELECT * FROM (
459             SELECT * FROM (
460             SELECT * FROM ...
461             ) ORDER BY $reversed_original_order
462             FETCH FIRST $limit ROWS ONLY
463             ) ORDER BY $original_order
464             FETCH FIRST $limit ROWS ONLY
465             )
466              
467             Unreliable FetchFirst-based implementation, supported by B<< IBM DB2 <= V5R3 >>.
468              
469             =head3 CAVEAT
470              
471             Due to its implementation, this limit dialect returns B<incorrect results>
472             when $limit+$offset > total amount of rows in the resultset.
473              
474             =cut
475              
476             sub _FetchFirst {
477 23     23   78 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
478              
479 23         85 my $lim = $self->_prep_for_skimming_limit($sql, $rs_attrs);
480              
481             $sql = sprintf ('SELECT %s %s %s %s FETCH FIRST %u ROWS ONLY',
482             $offset ? $lim->{selection_inner} : $lim->{selection_original},
483             $lim->{query_leftover},
484             $lim->{grpby_having},
485             $lim->{order_by_inner},
486 23 100 100     184 $rows + ($offset||0),
487             );
488              
489             $sql = sprintf ('SELECT %s FROM ( %s ) %s %s FETCH FIRST %u ROWS ONLY',
490             $lim->{selection_middle},
491             $sql,
492             $lim->{quoted_rs_alias},
493             $lim->{order_by_middle},
494 23 100       111 $rows,
495             ) if $offset;
496              
497              
498             $sql = sprintf ('SELECT %s FROM ( %s ) %s %s',
499             $lim->{selection_outer},
500             $sql,
501             $lim->{quoted_rs_alias},
502             $lim->{order_by_requested},
503             ) if $offset and (
504             $lim->{order_by_requested} or $lim->{selection_middle} ne $lim->{selection_outer}
505 23 100 66     187 );
      66        
506              
507 23         140 return $sql;
508             }
509              
510             =head2 GenericSubQ
511              
512             SELECT * FROM (
513             SELECT ...
514             )
515             WHERE (
516             SELECT COUNT(*) FROM $original_table cnt WHERE cnt.id < $original_table.id
517             ) BETWEEN $offset AND ($offset+$rows-1)
518              
519             This is the most evil limit "dialect" (more of a hack) for I<really> stupid
520             databases. It works by ordering the set by some unique column, and calculating
521             the amount of rows that have a less-er value (thus emulating a L</RowNum>-like
522             index). Of course this implies the set can only be ordered by a single unique
523             column.
524              
525             Also note that this technique can be and often is B<excruciatingly slow>. You
526             may have much better luck using L<DBIx::Class::ResultSet/software_limit>
527             instead.
528              
529             Currently used by B<Sybase ASE>, due to lack of any other option.
530              
531             =cut
532             sub _GenericSubQ {
533 24     24   90 my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
534              
535 24         50 my $main_rsrc = $rs_attrs->{result_source};
536              
537             # Explicitly require an order_by
538             # GenSubQ is slow enough as it is, just emulating things
539             # like in other cases is not wise - make the user work
540             # to shoot their DBA in the foot
541             $self->throw_exception (
542             'Generic Subquery Limit does not work on resultsets without an order. Provide a stable, '
543             . 'main-table-based order criteria.'
544 24 50       65 ) unless $rs_attrs->{order_by};
545              
546 24         99 my $usable_order_colinfo = $main_rsrc->storage->_extract_colinfo_of_stable_main_source_order_by_portion(
547             $rs_attrs
548             );
549              
550             $self->throw_exception(
551             'Generic Subquery Limit can not work with order criteria based on sources other than the main one'
552             ) if (
553 24 50       128 ! keys %{$usable_order_colinfo||{}}
554             or
555             grep
556 24 50 33     63 { $_->{-source_alias} ne $rs_attrs->{alias} }
  70         199  
557             (values %$usable_order_colinfo)
558             );
559              
560             ###
561             ###
562             ### we need to know the directions after we figured out the above - reextract *again*
563             ### this is eyebleed - trying to get it to work at first
564 24         59 my $supplied_order = delete $rs_attrs->{order_by};
565              
566 24         44 my @order_bits = do {
567 24         62 local $self->{quote_char};
568 24         55 local $self->{order_bind};
569 24 100       78 map { ref $_ ? $_->[0] : $_ } $self->_order_by_chunks ($supplied_order)
  84         3870  
570             };
571              
572             # truncate to what we'll use
573 24         119 $#order_bits = ( (keys %$usable_order_colinfo) - 1 );
574              
575             # @order_bits likely will come back quoted (due to how the prefetch
576             # rewriter operates
577             # Hence supplement the column_info lookup table with quoted versions
578 24 100       99 if ($self->quote_char) {
579             $usable_order_colinfo->{$self->_quote($_)} = $usable_order_colinfo->{$_}
580 15         78 for keys %$usable_order_colinfo;
581             }
582              
583             # calculate the condition
584 24         824 my $count_tbl_alias = 'rownum__emulation';
585 24         46 my $main_alias = $rs_attrs->{alias};
586 24         89 my $main_tbl_name = $main_rsrc->name;
587              
588 24         49 my (@unqualified_names, @qualified_names, @is_desc, @new_order_by);
589              
590 24         73 for my $bit (@order_bits) {
591              
592 70         159 ($bit, my $is_desc) = $self->_split_order_chunk($bit);
593              
594 70         134 push @is_desc, $is_desc;
595 70         185 push @unqualified_names, $usable_order_colinfo->{$bit}{-colname};
596 70         120 push @qualified_names, $usable_order_colinfo->{$bit}{-fq_colname};
597              
598 70 100       213 push @new_order_by, { ($is_desc ? '-desc' : '-asc') => $usable_order_colinfo->{$bit}{-fq_colname} };
599             };
600              
601 24         52 my (@where_cond, @skip_colpair_stack);
602 24         79 for my $i (0 .. $#order_bits) {
603 70         139 my $ci = $usable_order_colinfo->{$order_bits[$i]};
604              
605 70         113 my ($subq_col, $main_col) = map { "$_.$ci->{-colname}" } ($count_tbl_alias, $main_alias);
  140         334  
606 70 100       304 my $cur_cond = { $subq_col => { ($is_desc[$i] ? '>' : '<') => { -ident => $main_col } } };
607              
608 70         206 push @skip_colpair_stack, [
609             { $main_col => { -ident => $subq_col } },
610             ];
611              
612             # we can trust the nullability flag because
613             # we already used it during _id_col_set resolution
614             #
615 70 100       177 if ($ci->{is_nullable}) {
616 24         33 push @{$skip_colpair_stack[-1]}, { $main_col => undef, $subq_col=> undef };
  24         74  
617              
618 24 100       154 $cur_cond = [
    100          
619             {
620             ($is_desc[$i] ? $subq_col : $main_col) => { '!=', undef },
621             ($is_desc[$i] ? $main_col : $subq_col) => undef,
622             },
623             {
624             $subq_col => { '!=', undef },
625             $main_col => { '!=', undef },
626             -and => $cur_cond,
627             },
628             ];
629             }
630              
631 70         239 push @where_cond, { '-and', => [ @skip_colpair_stack[0..$i-1], $cur_cond ] };
632             }
633              
634             # reuse the sqlmaker WHERE, this will not be returning binds
635 24         42 my $counted_where = do {
636 24         72 local $self->{where_bind};
637 24         85 $self->where(\@where_cond);
638             };
639              
640             # construct the rownum condition by hand
641 24         3422 my $rownum_cond;
642 24 100       69 if ($offset) {
643 15         36 $rownum_cond = 'BETWEEN ? AND ?';
644 15         34 push @{$self->{limit_bind}},
  15         85  
645             [ $self->__offset_bindtype => $offset ],
646             [ $self->__total_bindtype => $offset + $rows - 1]
647             ;
648             }
649             else {
650 9         17 $rownum_cond = '< ?';
651 9         18 push @{$self->{limit_bind}},
  9         52  
652             [ $self->__rows_bindtype => $rows ]
653             ;
654             }
655              
656             # and what we will order by inside
657 24         51 my $inner_order_sql = do {
658 24         58 local $self->{order_bind};
659              
660 24         100 my $s = $self->_order_by (\@new_order_by);
661              
662             $self->throw_exception('Inner gensubq order may not contain binds... something went wrong')
663 24 50       40 if @{$self->{order_bind}};
  24         82  
664              
665 24         66 $s;
666             };
667              
668             ### resume originally scheduled programming
669             ###
670             ###
671              
672             # we need to supply the order for the supplements to be properly calculated
673 24         354 my $sq_attrs = $self->_subqueried_limit_attrs (
674             $sql, { %$rs_attrs, order_by => \@new_order_by }
675             );
676              
677 24         110 my $in_sel = $sq_attrs->{selection_inner};
678              
679             # add the order supplement (if any) as this is what will be used for the outer WHERE
680 24         60 $in_sel .= ", $_" for sort keys %{$sq_attrs->{order_supplement}};
  24         114  
681              
682 24         110 my $group_having_sql = $self->_parse_rs_attrs($rs_attrs);
683              
684              
685             return sprintf ("
686             SELECT $sq_attrs->{selection_outer}
687             FROM (
688             SELECT $in_sel $sq_attrs->{query_leftover}${group_having_sql}
689             ) %s
690             WHERE ( SELECT COUNT(*) FROM %s %s $counted_where ) $rownum_cond
691             $inner_order_sql
692 72         1181 ", map { $self->_quote ($_) } (
693             $rs_attrs->{alias},
694 24         145 $main_tbl_name,
695             $count_tbl_alias,
696             ));
697             }
698              
699              
700             # !!! THIS IS ALSO HORRIFIC !!! /me ashamed
701             #
702             # Generates inner/outer select lists for various limit dialects
703             # which result in one or more subqueries (e.g. RNO, Top, RowNum)
704             # Any non-main-table columns need to have their table qualifier
705             # turned into a column alias (otherwise names in subqueries clash
706             # and/or lose their source table)
707             #
708             # Returns mangled proto-sql, inner/outer strings of SQL QUOTED selectors
709             # with aliases (to be used in whatever select statement), and an alias
710             # index hashref of QUOTED SEL => QUOTED ALIAS pairs (to maybe be used
711             # for string-subst higher up).
712             # If an order_by is supplied, the inner select needs to bring out columns
713             # used in implicit (non-selected) orders, and the order condition itself
714             # needs to be realiased to the proper names in the outer query. Thus we
715             # also return a hashref (order doesn't matter) of QUOTED EXTRA-SEL =>
716             # QUOTED ALIAS pairs, which is a list of extra selectors that do *not*
717             # exist in the original select list
718             sub _subqueried_limit_attrs {
719 101     101   253 my ($self, $proto_sql, $rs_attrs) = @_;
720              
721 101 50       362 $self->throw_exception(
722             'Limit dialect implementation usable only in the context of DBIC (missing $rs_attrs)'
723             ) unless ref ($rs_attrs) eq 'HASH';
724              
725             # mangle the input sql as we will be replacing the selector entirely
726 101 50 33     2415 unless (
727             $rs_attrs->{_selector_sql}
728             and
729             $proto_sql =~ s/^ \s* SELECT \s* \Q$rs_attrs->{_selector_sql}//ix
730             ) {
731 0         0 $self->throw_exception("Unrecognizable SELECT: $proto_sql");
732             }
733              
734 101         432 my ($re_sep, $re_alias) = map { quotemeta $_ } ( $self->{name_sep}, $rs_attrs->{alias} );
  202         600  
735              
736             # correlate select and as, build selection index
737 101         251 my (@sel, $in_sel_index);
738 101         210 for my $i (0 .. $#{$rs_attrs->{select}}) {
  101         424  
739              
740 425         846 my $s = $rs_attrs->{select}[$i];
741 425 50       971 my $sql_alias = (ref $s) eq 'HASH' ? $s->{-as} : undef;
742              
743             # we throw away the @bind here deliberately
744 425         1092 my ($sql_sel) = $self->_recurse_fields ($s);
745              
746             push @sel, {
747             arg => $s,
748             sql => $sql_sel,
749             unquoted_sql => do {
750 425         886 local $self->{quote_char};
751 425         979 ($self->_recurse_fields ($s))[0]; # ignore binds again
752             },
753             as =>
754             $sql_alias
755             ||
756 425   33     8181 $rs_attrs->{as}[$i]
757             ||
758             $self->throw_exception("Select argument $i ($s) without corresponding 'as'")
759             ,
760             };
761              
762             # anything with a placeholder in it needs re-selection
763 425 100       8989 $in_sel_index->{$sql_sel}++ unless $sql_sel =~ / (?: ^ | \W ) \? (?: \W | $ ) /x;
764              
765 425 50       894 $in_sel_index->{$self->_quote ($sql_alias)}++ if $sql_alias;
766              
767             # record unqualified versions too, so we do not have
768             # to reselect the same column twice (in qualified and
769             # unqualified form)
770 425 100 66     2707 if (! ref $s && $sql_sel =~ / $re_sep (.+) $/x) {
771 378         1407 $in_sel_index->{$1}++;
772             }
773             }
774              
775              
776             # re-alias and remove any name separators from aliases,
777             # unless we are dealing with the current source alias
778             # (which will transcend the subqueries as it is necessary
779             # for possible further chaining)
780             # same for anything we do not recognize
781 101         252 my ($sel, $renamed);
782 101         273 for my $node (@sel) {
783 425         4796 push @{$sel->{original}}, $node->{sql};
  425         1002  
784              
785 425 100 100     3595 if (
      100        
786             ! $in_sel_index->{$node->{sql}}
787             or
788             $node->{as} =~ / (?<! ^ $re_alias ) \. /x
789             or
790             $node->{unquoted_sql} =~ / (?<! ^ $re_alias ) $re_sep /x
791             ) {
792 163         502 $node->{as} = $self->_unqualify_colname($node->{as});
793 163         418 my $quoted_as = $self->_quote($node->{as});
794 163         2738 push @{$sel->{inner}}, sprintf '%s AS %s', $node->{sql}, $quoted_as;
  163         738  
795 163         291 push @{$sel->{outer}}, $quoted_as;
  163         344  
796 163         561 $renamed->{$node->{sql}} = $quoted_as;
797             }
798             else {
799 262         451 push @{$sel->{inner}}, $node->{sql};
  262         605  
800 262 50       414 push @{$sel->{outer}}, $self->_quote (ref $node->{arg} ? $node->{as} : $node->{arg});
  262         933  
801             }
802             }
803              
804             # see if the order gives us anything
805 101         1001 my $extra_order_sel;
806 101         385 for my $chunk ($self->_order_by_chunks ($rs_attrs->{order_by})) {
807             # order with bind
808 136 100       5426 $chunk = $chunk->[0] if (ref $chunk) eq 'ARRAY';
809 136         421 ($chunk) = $self->_split_order_chunk($chunk);
810              
811 136 100       434 next if $in_sel_index->{$chunk};
812              
813             $extra_order_sel->{$chunk} ||= $self->_quote (
814 55 50 33     248 'ORDER__BY__' . sprintf '%03d', scalar keys %{$extra_order_sel||{}}
  55         425  
815             );
816             }
817              
818             return {
819             query_leftover => $proto_sql,
820 101         3581 (map {( "selection_$_" => join (', ', @{$sel->{$_}} ) )} keys %$sel ),
  303         674  
  303         2019  
821             outer_renames => $renamed,
822             order_supplement => $extra_order_sel,
823             };
824             }
825              
826             sub _unqualify_colname {
827 163     163   348 my ($self, $fqcn) = @_;
828 163         447 $fqcn =~ s/ \. /__/xg;
829 163         399 return $fqcn;
830             }
831              
832             =head1 FURTHER QUESTIONS?
833              
834             Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
835              
836             =head1 COPYRIGHT AND LICENSE
837              
838             This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
839             by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
840             redistribute it and/or modify it under the same terms as the
841             L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
842              
843             =cut
844              
845             1;