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 217     217   676794 use warnings;
  217         475  
  217         6267  
4 217     217   842 use strict;
  217         517  
  217         4463  
5              
6 217     217   874 use List::Util 'first';
  217         407  
  217         15009  
7 217     217   996 use namespace::clean;
  217         476  
  217         1807  
8              
9             # constants are used not only here, but also in comparison tests
10             sub __rows_bindtype () {
11 1597     1597   12370 +{ sqlt_datatype => 'integer' }
12             }
13             sub __offset_bindtype () {
14 110     110   622 +{ sqlt_datatype => 'integer' }
15             }
16             sub __total_bindtype () {
17 43     43   224 +{ 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. 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 classes. After reimplementation of close to
31             80% of the L 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 and B
49              
50             =cut
51             sub _LimitOffset {
52 1549     1549   3474 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
53 1549         4855 $sql .= $self->_parse_rs_attrs( $rs_attrs ) . " LIMIT ?";
54 1549         2441 push @{$self->{limit_bind}}, [ $self->__rows_bindtype => $rows ];
  1549         5603  
55 1549 100       4035 if ($offset) {
56 43         79 $sql .= " OFFSET ?";
57 43         78 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset ];
  43         172  
58             }
59 1549         3848 return $sql;
60             }
61              
62             =head2 LimitXY
63              
64             SELECT ... LIMIT $offset, $limit
65              
66             Supported by B and any L based DBD
67              
68             =cut
69             sub _LimitXY {
70 3     3   11 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
71 3         18 $sql .= $self->_parse_rs_attrs( $rs_attrs ) . " LIMIT ";
72 3 100       14 if ($offset) {
73 2         5 $sql .= '?, ';
74 2         3 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset ];
  2         11  
75             }
76 3         9 $sql .= '?';
77 3         6 push @{$self->{limit_bind}}, [ $self->__rows_bindtype => $rows ];
  3         18  
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 and
92             B<< MSSQL >= 2005 >>.
93              
94             =cut
95             sub _RowNumberOver {
96 14     14   32 my ($self, $sql, $rs_attrs, $rows, $offset ) = @_;
97              
98             # get selectors, and scan the order_by (if any)
99 14         68 my $sq_attrs = $self->_subqueried_limit_attrs ( $sql, $rs_attrs );
100              
101             # make up an order if none exists
102 14   66     97 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         36 local $self->{order_bind};
106 14         62 my $rno_ord = $self->_order_by ($requested_order);
107 14         22 push @{$self->{select_bind}}, @{$self->{order_bind}};
  14         27  
  14         27  
108              
109             # this is the order supplement magic
110 14         30 my $mid_sel = $sq_attrs->{selection_outer};
111 14 100       43 if (my $extra_order_sel = $sq_attrs->{order_supplement}) {
112 4         23 for my $extra_col (sort
113 2         10 { $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         26 $extra_order_sel->{$extra_col},
119             );
120             }
121             }
122              
123             # and this is order re-alias magic
124 14         37 for my $map ($sq_attrs->{order_supplement}, $sq_attrs->{outer_renames}) {
125 28 100       31 for my $col (sort { (length $b) <=> (length $a) } keys %{$map||{}} ) {
  21         31  
  28         156  
126 28         45 my $re_col = quotemeta ($col);
127 28         336 $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         56 my $group_having = $self->_parse_rs_attrs($rs_attrs);
133              
134 14         45 my $qalias = $self->_quote ($rs_attrs->{alias});
135 14         330 my $idx_name = $self->_quote ('rno__row__index');
136              
137 14         250 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset + 1], [ $self->__total_bindtype => $offset + $rows ];
  14         81  
138              
139 14         169 return <
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   24 return undef;
154             }
155              
156             =head2 SkipFirst
157              
158             SELECT SKIP $offset FIRST $limit * FROM ...
159              
160             Supported by B, almost like LimitOffset. According to
161             L C<... SKIP $offset LIMIT $limit ...> is also supported.
162              
163             =cut
164             sub _SkipFirst {
165 10     10   21 my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
166              
167 10 50       72 $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         11 push @{$self->{pre_select_bind}}, [ $self->__offset_bindtype => $offset];
  8         37  
174 8         14 'SKIP ? '
175             }
176             : ''
177             ,
178 10 100       20 do {
179 10         10 push @{$self->{pre_select_bind}}, [ $self->__rows_bindtype => $rows ];
  10         33  
180 10         38 '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, reverse of SkipFirst. According to
192             L C<... ROWS $limit TO $offset ...> is also supported.
193              
194             =cut
195             sub _FirstSkip {
196 10     10   24 my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
197              
198 10 50       70 $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         15 push @{$self->{pre_select_bind}}, [ $self->__rows_bindtype => $rows ];
  10         56  
204 10         37 'FIRST ? '
205             },
206             $offset
207 10 100       17 ? do {
208 8         10 push @{$self->{pre_select_bind}}, [ $self->__offset_bindtype => $offset];
  8         36  
209 8         29 '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.
244              
245             =cut
246             sub _RowNum {
247 14     14   34 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
248              
249 14         59 my $sq_attrs = $self->_subqueried_limit_attrs ($sql, $rs_attrs);
250              
251 14         59 my $qalias = $self->_quote ($rs_attrs->{alias});
252 14         252 my $idx_name = $self->_quote ('rownum__index');
253 14         233 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       46 if (! $offset) {
258 4         6 push @{$self->{limit_bind}}, [ $self->__rows_bindtype => $rows ];
  4         26  
259              
260 4         34 return <
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     63 if (
279             $rs_attrs->{order_by}
280             and
281             $rs_attrs->{result_source}->storage->_order_by_is_stable(
282 5         114 @{$rs_attrs}{qw/from order_by where/}
283             )
284             ) {
285 3         10 push @{$self->{limit_bind}}, [ $self->__total_bindtype => $offset + $rows ], [ $self->__offset_bindtype => $offset + 1 ];
  3         22  
286              
287 3         51 return <
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         10 push @{$self->{limit_bind}}, [ $self->__offset_bindtype => $offset + 1 ], [ $self->__total_bindtype => $offset + $rows ];
  7         48  
297              
298 7         95 return <
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   93 my ( $self, $sql, $rs_attrs ) = @_;
311              
312             # get selectors
313 49         176 my $sq_attrs = $self->_subqueried_limit_attrs ($sql, $rs_attrs);
314              
315 49         145 my $requested_order = delete $rs_attrs->{order_by};
316 49         212 $sq_attrs->{order_by_requested} = $self->_order_by ($requested_order);
317 49         162 $sq_attrs->{grpby_having} = $self->_parse_rs_attrs ($rs_attrs);
318              
319             # without an offset things are easy
320 49 100       150 if (! $rs_attrs->{offset}) {
321 9         23 $sq_attrs->{order_by_inner} = $sq_attrs->{order_by_requested};
322             }
323             else {
324 40         126 $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         704 local $self->{order_bind};
328              
329             # make up an order unless supplied or sanity check what we are given
330 40         54 my $inner_order;
331 40 100       95 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       162 ));
339              
340 25         60 $inner_order = $requested_order;
341             }
342             else {
343             $inner_order = [ map
344 15         79 { "$rs_attrs->{alias}.$_" }
345             ( @{
346 15         25 $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       97 . "dialect based on source '%s'", $rs_attrs->{result_source}->name) );
351             } )
352             ];
353             }
354              
355 40         146 $sq_attrs->{order_by_inner} = $self->_order_by ($inner_order);
356              
357 40         65 my @out_chunks;
358 40         129 for my $ch ($self->_order_by_chunks ($inner_order)) {
359 56 100       1635 $ch = $ch->[0] if ref $ch eq 'ARRAY';
360              
361 56         174 ($ch, my $is_desc) = $self->_split_order_chunk($ch);
362              
363             # !NOTE! outside chunks come in reverse order ( !$is_desc )
364 56 100       236 push @out_chunks, { ($is_desc ? '-asc' : '-desc') => \$ch };
365             }
366              
367 40         368 $sq_attrs->{order_by_middle} = $self->_order_by (\@out_chunks);
368              
369             # this is the order supplement magic
370 40         106 $sq_attrs->{selection_middle} = $sq_attrs->{selection_outer};
371 40 100       160 if (my $extra_order_sel = $sq_attrs->{order_supplement}) {
372 19         87 for my $extra_col (sort
373 11         34 { $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         104 $extra_order_sel->{$extra_col},
379             );
380              
381 29         66 $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         43 push @{$self->{select_bind}}, @{$self->{order_bind}};
  19         43  
  19         34  
387             }
388              
389             # and this is order re-alias magic
390 40         127 for my $map ($sq_attrs->{order_supplement}, $sq_attrs->{outer_renames}) {
391 80 100       91 for my $col (sort { (length $b) <=> (length $a) } keys %{$map||{}}) {
  57         125  
  80         437  
392 101         136 my $re_col = quotemeta ($col);
393             $_ =~ s/$re_col/$map->{$col}/
394 101         1240 for ($sq_attrs->{order_by_middle}, $sq_attrs->{order_by_requested});
395             }
396             }
397             }
398              
399 49         102 $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
417             when $limit+$offset > total amount of rows in the resultset.
418              
419             =cut
420              
421             sub _Top {
422 26     26   73 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
423              
424 26         110 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     260 );
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       153 ) 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     202 );
      66        
450              
451 26         157 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
472             when $limit+$offset > total amount of rows in the resultset.
473              
474             =cut
475              
476             sub _FetchFirst {
477 23     23   56 my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
478              
479 23         84 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     193 $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       122 $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     172 );
      66        
506              
507 23         115 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 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-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. You
526             may have much better luck using L
527             instead.
528              
529             Currently used by B, due to lack of any other option.
530              
531             =cut
532             sub _GenericSubQ {
533 24     24   61 my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
534              
535 24         44 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       72 ) unless $rs_attrs->{order_by};
545              
546 24         116 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       165 ! keys %{$usable_order_colinfo||{}}
554             or
555             grep
556 24 50 33     47 { $_->{-source_alias} ne $rs_attrs->{alias} }
  70         222  
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         52 my $supplied_order = delete $rs_attrs->{order_by};
565              
566 24         31 my @order_bits = do {
567 24         61 local $self->{quote_char};
568 24         61 local $self->{order_bind};
569 24 100       90 map { ref $_ ? $_->[0] : $_ } $self->_order_by_chunks ($supplied_order)
  84         3182  
570             };
571              
572             # truncate to what we'll use
573 24         115 $#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       159 if ($self->quote_char) {
579             $usable_order_colinfo->{$self->_quote($_)} = $usable_order_colinfo->{$_}
580 15         92 for keys %$usable_order_colinfo;
581             }
582              
583             # calculate the condition
584 24         761 my $count_tbl_alias = 'rownum__emulation';
585 24         55 my $main_alias = $rs_attrs->{alias};
586 24         85 my $main_tbl_name = $main_rsrc->name;
587              
588 24         31 my (@unqualified_names, @qualified_names, @is_desc, @new_order_by);
589              
590 24         61 for my $bit (@order_bits) {
591              
592 70         168 ($bit, my $is_desc) = $self->_split_order_chunk($bit);
593              
594 70         208 push @is_desc, $is_desc;
595 70         145 push @unqualified_names, $usable_order_colinfo->{$bit}{-colname};
596 70         114 push @qualified_names, $usable_order_colinfo->{$bit}{-fq_colname};
597              
598 70 100       246 push @new_order_by, { ($is_desc ? '-desc' : '-asc') => $usable_order_colinfo->{$bit}{-fq_colname} };
599             };
600              
601 24         45 my (@where_cond, @skip_colpair_stack);
602 24         72 for my $i (0 .. $#order_bits) {
603 70         105 my $ci = $usable_order_colinfo->{$order_bits[$i]};
604              
605 70         92 my ($subq_col, $main_col) = map { "$_.$ci->{-colname}" } ($count_tbl_alias, $main_alias);
  140         319  
606 70 100       326 my $cur_cond = { $subq_col => { ($is_desc[$i] ? '>' : '<') => { -ident => $main_col } } };
607              
608 70         195 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       157 if ($ci->{is_nullable}) {
616 24         29 push @{$skip_colpair_stack[-1]}, { $main_col => undef, $subq_col=> undef };
  24         75  
617              
618 24 100       195 $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         278 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         31 my $counted_where = do {
636 24         82 local $self->{where_bind};
637 24         96 $self->where(\@where_cond);
638             };
639              
640             # construct the rownum condition by hand
641 24         3219 my $rownum_cond;
642 24 100       82 if ($offset) {
643 15         97 $rownum_cond = 'BETWEEN ? AND ?';
644 15         29 push @{$self->{limit_bind}},
  15         103  
645             [ $self->__offset_bindtype => $offset ],
646             [ $self->__total_bindtype => $offset + $rows - 1]
647             ;
648             }
649             else {
650 9         19 $rownum_cond = '< ?';
651 9         16 push @{$self->{limit_bind}},
  9         57  
652             [ $self->__rows_bindtype => $rows ]
653             ;
654             }
655              
656             # and what we will order by inside
657 24         40 my $inner_order_sql = do {
658 24         52 local $self->{order_bind};
659              
660 24         111 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       43 if @{$self->{order_bind}};
  24         117  
664              
665 24         60 $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         392 my $sq_attrs = $self->_subqueried_limit_attrs (
674             $sql, { %$rs_attrs, order_by => \@new_order_by }
675             );
676              
677 24         126 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         34 $in_sel .= ", $_" for sort keys %{$sq_attrs->{order_supplement}};
  24         89  
681              
682 24         104 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         792 ", map { $self->_quote ($_) } (
693             $rs_attrs->{alias},
694 24         174 $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   162 my ($self, $proto_sql, $rs_attrs) = @_;
720              
721 101 50       346 $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     2633 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         329 my ($re_sep, $re_alias) = map { quotemeta $_ } ( $self->{name_sep}, $rs_attrs->{alias} );
  202         518  
735              
736             # correlate select and as, build selection index
737 101         145 my (@sel, $in_sel_index);
738 101         152 for my $i (0 .. $#{$rs_attrs->{select}}) {
  101         499  
739              
740 425         580 my $s = $rs_attrs->{select}[$i];
741 425 50       793 my $sql_alias = (ref $s) eq 'HASH' ? $s->{-as} : undef;
742              
743             # we throw away the @bind here deliberately
744 425         1026 my ($sql_sel) = $self->_recurse_fields ($s);
745              
746             push @sel, {
747             arg => $s,
748             sql => $sql_sel,
749             unquoted_sql => do {
750 425         643 local $self->{quote_char};
751 425         853 ($self->_recurse_fields ($s))[0]; # ignore binds again
752             },
753             as =>
754             $sql_alias
755             ||
756 425   33     7430 $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       8245 $in_sel_index->{$sql_sel}++ unless $sql_sel =~ / (?: ^ | \W ) \? (?: \W | $ ) /x;
764              
765 425 50       737 $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     2927 if (! ref $s && $sql_sel =~ / $re_sep (.+) $/x) {
771 378         1158 $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         161 my ($sel, $renamed);
782 101         222 for my $node (@sel) {
783 425         3869 push @{$sel->{original}}, $node->{sql};
  425         931  
784              
785 425 100 100     4019 if (
      100        
786             ! $in_sel_index->{$node->{sql}}
787             or
788             $node->{as} =~ / (?
789             or
790             $node->{unquoted_sql} =~ / (?
791             ) {
792 163         467 $node->{as} = $self->_unqualify_colname($node->{as});
793 163         398 my $quoted_as = $self->_quote($node->{as});
794 163         2591 push @{$sel->{inner}}, sprintf '%s AS %s', $node->{sql}, $quoted_as;
  163         677  
795 163         164 push @{$sel->{outer}}, $quoted_as;
  163         286  
796 163         471 $renamed->{$node->{sql}} = $quoted_as;
797             }
798             else {
799 262         243 push @{$sel->{inner}}, $node->{sql};
  262         509  
800 262 50       260 push @{$sel->{outer}}, $self->_quote (ref $node->{arg} ? $node->{as} : $node->{arg});
  262         951  
801             }
802             }
803              
804             # see if the order gives us anything
805 101         797 my $extra_order_sel;
806 101         367 for my $chunk ($self->_order_by_chunks ($rs_attrs->{order_by})) {
807             # order with bind
808 136 100       4779 $chunk = $chunk->[0] if (ref $chunk) eq 'ARRAY';
809 136         425 ($chunk) = $self->_split_order_chunk($chunk);
810              
811 136 100       420 next if $in_sel_index->{$chunk};
812              
813             $extra_order_sel->{$chunk} ||= $self->_quote (
814 55 50 33     257 'ORDER__BY__' . sprintf '%03d', scalar keys %{$extra_order_sel||{}}
  55         467  
815             );
816             }
817              
818             return {
819             query_leftover => $proto_sql,
820 101         3077 (map {( "selection_$_" => join (', ', @{$sel->{$_}} ) )} keys %$sel ),
  303         441  
  303         1898  
821             outer_renames => $renamed,
822             order_supplement => $extra_order_sel,
823             };
824             }
825              
826             sub _unqualify_colname {
827 163     163   281 my ($self, $fqcn) = @_;
828 163         376 $fqcn =~ s/ \. /__/xg;
829 163         322 return $fqcn;
830             }
831              
832             =head1 FURTHER QUESTIONS?
833              
834             Check the list of L.
835              
836             =head1 COPYRIGHT AND LICENSE
837              
838             This module is free software L
839             by the L. You can
840             redistribute it and/or modify it under the same terms as the
841             L.
842              
843             =cut
844              
845             1;