723001d4aa6e594f471e1d6c30294d24d50f4957
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / SQLMaker / LimitDialects.pm
1 package DBIx::Class::SQLMaker::LimitDialects;
2
3 use warnings;
4 use strict;
5
6 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract|^Try::Tiny/;
7 use List::Util 'first';
8 use namespace::clean;
9
10 # FIXME
11 # This dialect has not been ported to the subquery-realiasing code
12 # that all other subquerying dialects are using. It is very possible
13 # that this dialect is entirely unnecessary - it is currently only
14 # used by ::Storage::DBI::ODBC::DB2_400_SQL which *should* be able to
15 # just subclass ::Storage::DBI::DB2 and use the already rewritten
16 # RowNumberOver. However nobody has access to this specific database
17 # engine, thus keeping legacy code as-is
18 # IF someone ever manages to test DB2-AS/400 with RNO, all the code
19 # in this block should go on to meet its maker
20 {
21   sub _FetchFirst {
22     my ( $self, $sql, $order, $rows, $offset ) = @_;
23
24     my $last = $rows + $offset;
25
26     my ( $order_by_up, $order_by_down ) = $self->_order_directions( $order );
27
28     $sql = "
29       SELECT * FROM (
30         SELECT * FROM (
31           $sql
32           $order_by_up
33           FETCH FIRST $last ROWS ONLY
34         ) foo
35         $order_by_down
36         FETCH FIRST $rows ROWS ONLY
37       ) bar
38       $order_by_up
39     ";
40
41     return $sql;
42   }
43
44   sub _order_directions {
45     my ( $self, $order ) = @_;
46
47     return unless $order;
48
49     my $ref = ref $order;
50
51     my @order;
52
53     CASE: {
54       @order = @$order,     last CASE if $ref eq 'ARRAY';
55       @order = ( $order ),  last CASE unless $ref;
56       @order = ( $$order ), last CASE if $ref eq 'SCALAR';
57       croak __PACKAGE__ . ": Unsupported data struct $ref for ORDER BY";
58     }
59
60     my ( $order_by_up, $order_by_down );
61
62     foreach my $spec ( @order )
63     {
64         my @spec = split ' ', $spec;
65         croak( "bad column order spec: $spec" ) if @spec > 2;
66         push( @spec, 'ASC' ) unless @spec == 2;
67         my ( $col, $up ) = @spec; # or maybe down
68         $up = uc( $up );
69         croak( "bad direction: $up" ) unless $up =~ /^(?:ASC|DESC)$/;
70         $order_by_up .= ", $col $up";
71         my $down = $up eq 'ASC' ? 'DESC' : 'ASC';
72         $order_by_down .= ", $col $down";
73     }
74
75     s/^,/ORDER BY/ for ( $order_by_up, $order_by_down );
76
77     return $order_by_up, $order_by_down;
78   }
79 }
80 ### end-of-FIXME
81
82 =head1 NAME
83
84 DBIx::Class::SQLMaker::LimitDialects - SQL::Abstract::Limit-like functionality for DBIx::Class::SQLMaker
85
86 =head1 DESCRIPTION
87
88 This module replicates a lot of the functionality originally found in
89 L<SQL::Abstract::Limit>. While simple limits would work as-is, the more
90 complex dialects that require e.g. subqueries could not be reliably
91 implemented without taking full advantage of the metadata locked within
92 L<DBIx::Class::ResultSource> classes. After reimplementation of close to
93 80% of the L<SQL::Abstract::Limit> functionality it was deemed more
94 practical to simply make an independent DBIx::Class-specific limit-dialect
95 provider.
96
97 =head1 SQL LIMIT DIALECTS
98
99 Note that the actual implementations listed below never use C<*> literally.
100 Instead proper re-aliasing of selectors and order criteria is done, so that
101 the limit dialect are safe to use on joined resultsets with clashing column
102 names.
103
104 Currently the provided dialects are:
105
106 =cut
107
108 =head2 LimitOffset
109
110  SELECT ... LIMIT $limit OFFSET $offset
111
112 Supported by B<PostgreSQL> and B<SQLite>
113
114 =cut
115 sub _LimitOffset {
116     my ( $self, $sql, $order, $rows, $offset ) = @_;
117     $sql .= $self->_order_by( $order ) . " LIMIT $rows";
118     $sql .= " OFFSET $offset" if +$offset;
119     return $sql;
120 }
121
122 =head2 LimitXY
123
124  SELECT ... LIMIT $offset $limit
125
126 Supported by B<MySQL> and any L<SQL::Statement> based DBD
127
128 =cut
129 sub _LimitXY {
130     my ( $self, $sql, $order, $rows, $offset ) = @_;
131     $sql .= $self->_order_by( $order ) . " LIMIT ";
132     $sql .= "$offset, " if +$offset;
133     $sql .= $rows;
134     return $sql;
135 }
136
137 =head2 RowNumberOver
138
139  SELECT * FROM (
140   SELECT *, ROW_NUMBER() OVER( ORDER BY ... ) AS RNO__ROW__INDEX FROM (
141    SELECT ...
142   )
143  ) WHERE RNO__ROW__INDEX BETWEEN ($offset+1) AND ($limit+$offset)
144
145
146 ANSI standard Limit/Offset implementation. Supported by B<DB2> and
147 B<< MSSQL >= 2005 >>.
148
149 =cut
150 sub _RowNumberOver {
151   my ($self, $sql, $rs_attrs, $rows, $offset ) = @_;
152
153   # mangle the input sql as we will be replacing the selector
154   $sql =~ s/^ \s* SELECT \s+ .+? \s+ (?= \b FROM \b )//ix
155     or croak "Unrecognizable SELECT: $sql";
156
157   # get selectors, and scan the order_by (if any)
158   my ($in_sel, $out_sel, $alias_map, $extra_order_sel)
159     = $self->_subqueried_limit_attrs ( $rs_attrs );
160
161   # make up an order if none exists
162   my $requested_order = (delete $rs_attrs->{order_by}) || $self->_rno_default_order;
163   my $rno_ord = $self->_order_by ($requested_order);
164
165   # this is the order supplement magic
166   my $mid_sel = $out_sel;
167   if ($extra_order_sel) {
168     for my $extra_col (sort
169       { $extra_order_sel->{$a} cmp $extra_order_sel->{$b} }
170       keys %$extra_order_sel
171     ) {
172       $in_sel .= sprintf (', %s AS %s',
173         $extra_col,
174         $extra_order_sel->{$extra_col},
175       );
176
177       $mid_sel .= ', ' . $extra_order_sel->{$extra_col};
178     }
179   }
180
181   # and this is order re-alias magic
182   for ($extra_order_sel, $alias_map) {
183     for my $col (keys %$_) {
184       my $re_col = quotemeta ($col);
185       $rno_ord =~ s/$re_col/$_->{$col}/;
186     }
187   }
188
189   # whatever is left of the order_by (only where is processed at this point)
190   my $group_having = $self->_parse_rs_attrs($rs_attrs);
191
192   my $qalias = $self->_quote ($rs_attrs->{alias});
193   my $idx_name = $self->_quote ('rno__row__index');
194
195   $sql = sprintf (<<EOS, $offset + 1, $offset + $rows, );
196
197 SELECT $out_sel FROM (
198   SELECT $mid_sel, ROW_NUMBER() OVER( $rno_ord ) AS $idx_name FROM (
199     SELECT $in_sel ${sql}${group_having}
200   ) $qalias
201 ) $qalias WHERE $idx_name BETWEEN %u AND %u
202
203 EOS
204
205   $sql =~ s/\s*\n\s*/ /g;   # easier to read in the debugger
206   return $sql;
207 }
208
209 # some databases are happy with OVER (), some need OVER (ORDER BY (SELECT (1)) )
210 sub _rno_default_order {
211   return undef;
212 }
213
214 =head2 SkipFirst
215
216  SELECT SKIP $offset FIRST $limit * FROM ...
217
218 Suported by B<Informix>, almost like LimitOffset. According to
219 L<SQL::Abstract::Limit> C<... SKIP $offset LIMIT $limit ...> is also supported.
220
221 =cut
222 sub _SkipFirst {
223   my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
224
225   $sql =~ s/^ \s* SELECT \s+ //ix
226     or croak "Unrecognizable SELECT: $sql";
227
228   return sprintf ('SELECT %s%s%s%s',
229     $offset
230       ? sprintf ('SKIP %u ', $offset)
231       : ''
232     ,
233     sprintf ('FIRST %u ', $rows),
234     $sql,
235     $self->_parse_rs_attrs ($rs_attrs),
236   );
237 }
238
239 =head2 FirstSkip
240
241  SELECT FIRST $limit SKIP $offset * FROM ...
242
243 Supported by B<Firebird/Interbase>, reverse of SkipFirst. According to
244 L<SQL::Abstract::Limit> C<... ROWS $limit TO $offset ...> is also supported.
245
246 =cut
247 sub _FirstSkip {
248   my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
249
250   $sql =~ s/^ \s* SELECT \s+ //ix
251     or croak "Unrecognizable SELECT: $sql";
252
253   return sprintf ('SELECT %s%s%s%s',
254     sprintf ('FIRST %u ', $rows),
255     $offset
256       ? sprintf ('SKIP %u ', $offset)
257       : ''
258     ,
259     $sql,
260     $self->_parse_rs_attrs ($rs_attrs),
261   );
262 }
263
264 =head2 RowNum
265
266  SELECT * FROM (
267   SELECT *, ROWNUM rownum__index FROM (
268    SELECT ...
269   ) WHERE ROWNUM <= ($limit+$offset)
270  ) WHERE rownum__index >= ($offset+1)
271
272 Supported by B<Oracle>.
273
274 =cut
275 sub _RowNum {
276   my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
277
278   # mangle the input sql as we will be replacing the selector
279   $sql =~ s/^ \s* SELECT \s+ .+? \s+ (?= \b FROM \b )//ix
280     or croak "Unrecognizable SELECT: $sql";
281
282   my ($insel, $outsel) = $self->_subqueried_limit_attrs ($rs_attrs);
283
284   my $qalias = $self->_quote ($rs_attrs->{alias});
285   my $idx_name = $self->_quote ('rownum__index');
286   my $order_group_having = $self->_parse_rs_attrs($rs_attrs);
287
288   if ($offset) {
289
290     $sql = sprintf (<<EOS, $offset + $rows, $offset + 1 );
291
292 SELECT $outsel FROM (
293   SELECT $outsel, ROWNUM $idx_name FROM (
294     SELECT $insel ${sql}${order_group_having}
295   ) $qalias WHERE ROWNUM <= %u
296 ) $qalias WHERE $idx_name >= %u
297
298 EOS
299   }
300   else {
301     $sql = sprintf (<<EOS, $rows );
302
303   SELECT $outsel FROM (
304     SELECT $insel ${sql}${order_group_having}
305   ) $qalias WHERE ROWNUM <= %u
306
307 EOS
308   }
309
310   $sql =~ s/\s*\n\s*/ /g;   # easier to read in the debugger
311   return $sql;
312 }
313
314 =head2 Top
315
316  SELECT * FROM
317
318  SELECT TOP $limit FROM (
319   SELECT TOP $limit FROM (
320    SELECT TOP ($limit+$offset) ...
321   ) ORDER BY $reversed_original_order
322  ) ORDER BY $original_order
323
324 Unreliable Top-based implementation, supported by B<< MSSQL < 2005 >>.
325
326 =head3 CAVEAT
327
328 Due to its implementation, this limit dialect returns B<incorrect results>
329 when $limit+$offset > total amount of rows in the resultset.
330
331 =cut
332 sub _Top {
333   my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
334
335   # mangle the input sql as we will be replacing the selector
336   $sql =~ s/^ \s* SELECT \s+ .+? \s+ (?= \b FROM \b )//ix
337     or croak "Unrecognizable SELECT: $sql";
338
339   # get selectors
340   my ($in_sel, $out_sel, $alias_map, $extra_order_sel)
341     = $self->_subqueried_limit_attrs ($rs_attrs);
342
343   my $requested_order = delete $rs_attrs->{order_by};
344
345   my $order_by_requested = $self->_order_by ($requested_order);
346
347   # make up an order unless supplied
348   my $inner_order = ($order_by_requested
349     ? $requested_order
350     : [ map
351       { join ('', $rs_attrs->{alias}, $self->{name_sep}||'.', $_ ) }
352       ( $rs_attrs->{_rsroot_source_handle}->resolve->_pri_cols )
353     ]
354   );
355
356   my ($order_by_inner, $order_by_reversed);
357
358   # localise as we already have all the bind values we need
359   {
360     local $self->{order_bind};
361     $order_by_inner = $self->_order_by ($inner_order);
362
363     my @out_chunks;
364     for my $ch ($self->_order_by_chunks ($inner_order)) {
365       $ch = $ch->[0] if ref $ch eq 'ARRAY';
366
367       $ch =~ s/\s+ ( ASC|DESC ) \s* $//ix;
368       my $dir = uc ($1||'ASC');
369
370       push @out_chunks, \join (' ', $ch, $dir eq 'ASC' ? 'DESC' : 'ASC' );
371     }
372
373     $order_by_reversed = $self->_order_by (\@out_chunks);
374   }
375
376   # this is the order supplement magic
377   my $mid_sel = $out_sel;
378   if ($extra_order_sel) {
379     for my $extra_col (sort
380       { $extra_order_sel->{$a} cmp $extra_order_sel->{$b} }
381       keys %$extra_order_sel
382     ) {
383       $in_sel .= sprintf (', %s AS %s',
384         $extra_col,
385         $extra_order_sel->{$extra_col},
386       );
387
388       $mid_sel .= ', ' . $extra_order_sel->{$extra_col};
389     }
390
391     # since whatever order bindvals there are, they will be realiased
392     # and need to show up in front of the entire initial inner subquery
393     # Unshift *from_bind* to make this happen (horrible, horrible, but
394     # we don't have another mechanism yet)
395     unshift @{$self->{from_bind}}, @{$self->{order_bind}};
396   }
397
398   # and this is order re-alias magic
399   for my $map ($extra_order_sel, $alias_map) {
400     for my $col (keys %$map) {
401       my $re_col = quotemeta ($col);
402       $_ =~ s/$re_col/$map->{$col}/
403         for ($order_by_reversed, $order_by_requested);
404     }
405   }
406
407   # generate the rest of the sql
408   my $grpby_having = $self->_parse_rs_attrs ($rs_attrs);
409
410   my $quoted_rs_alias = $self->_quote ($rs_attrs->{alias});
411
412   $sql = sprintf ('SELECT TOP %u %s %s %s %s',
413     $rows + ($offset||0),
414     $in_sel,
415     $sql,
416     $grpby_having,
417     $order_by_inner,
418   );
419
420   $sql = sprintf ('SELECT TOP %u %s FROM ( %s ) %s %s',
421     $rows,
422     $mid_sel,
423     $sql,
424     $quoted_rs_alias,
425     $order_by_reversed,
426   ) if $offset;
427
428   $sql = sprintf ('SELECT TOP %u %s FROM ( %s ) %s %s',
429     $rows,
430     $out_sel,
431     $sql,
432     $quoted_rs_alias,
433     $order_by_requested,
434   ) if ( ($offset && $order_by_requested) || ($mid_sel ne $out_sel) );
435
436   $sql =~ s/\s*\n\s*/ /g;   # easier to read in the debugger
437   return $sql;
438 }
439
440 =head2 RowCountOrGenericSubQ
441
442 This is not exactly a limit dialect, but more of a proxy for B<Sybase ASE>.
443 If no $offset is supplied the limit is simply performed as:
444
445  SET ROWCOUNT $limit
446  SELECT ...
447  SET ROWCOUNT 0
448
449 Otherwise we fall back to L</GenericSubQ>
450
451 =cut
452 sub _RowCountOrGenericSubQ {
453   my $self = shift;
454   my ($sql, $rs_attrs, $rows, $offset) = @_;
455
456   return $self->_GenericSubQ(@_) if $offset;
457
458   return sprintf <<"EOF", $rows, $sql;
459 SET ROWCOUNT %d
460 %s
461 SET ROWCOUNT 0
462 EOF
463 }
464
465 =head2 GenericSubQ
466
467  SELECT * FROM (
468   SELECT ...
469  )
470  WHERE (
471   SELECT COUNT(*) FROM $original_table cnt WHERE cnt.id < $original_table.id
472  ) BETWEEN $offset AND ($offset+$rows-1)
473
474 This is the most evil limit "dialect" (more of a hack) for I<really> stupid
475 databases. It works by ordering the set by some unique column, and calculating
476 the amount of rows that have a less-er value (thus emulating a L</RowNum>-like
477 index). Of course this implies the set can only be ordered by a single unique
478 column. Also note that this technique can be and often is B<excruciatingly
479 slow>.
480
481 Currently used by B<Sybase ASE>, due to lack of any other option.
482
483 =cut
484 sub _GenericSubQ {
485   my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
486
487   my $root_rsrc = $rs_attrs->{_rsroot_source_handle}->resolve;
488   my $root_tbl_name = $root_rsrc->name;
489
490   # mangle the input sql as we will be replacing the selector
491   $sql =~ s/^ \s* SELECT \s+ .+? \s+ (?= \b FROM \b )//ix
492     or croak "Unrecognizable SELECT: $sql";
493
494   my ($order_by, @rest) = do {
495     local $self->{quote_char};
496     $self->_order_by_chunks ($rs_attrs->{order_by})
497   };
498
499   unless (
500     $order_by
501       &&
502     ! @rest
503       &&
504     ( ! ref $order_by
505         ||
506       ( ref $order_by eq 'ARRAY' and @$order_by == 1 )
507     )
508   ) {
509     croak (
510       'Generic Subquery Limit does not work on resultsets without an order, or resultsets '
511     . 'with complex order criteria (multicolumn and/or functions). Provide a single, '
512     . 'unique-column order criteria.'
513     );
514   }
515
516   ($order_by) = @$order_by if ref $order_by;
517
518   $order_by =~ s/\s+ ( ASC|DESC ) \s* $//ix;
519   my $direction = lc ($1 || 'asc');
520
521   my ($unq_sort_col) = $order_by =~ /(?:^|\.)([^\.]+)$/;
522
523   my $inf = $root_rsrc->storage->_resolve_column_info (
524     $rs_attrs->{from}, [$order_by, $unq_sort_col]
525   );
526
527   my $ord_colinfo = $inf->{$order_by} || croak "Unable to determine source of order-criteria '$order_by'";
528
529   if ($ord_colinfo->{-result_source}->name ne $root_tbl_name) {
530     croak "Generic Subquery Limit order criteria can be only based on the root-source '"
531         . $root_rsrc->source_name . "' (aliased as '$rs_attrs->{alias}')";
532   }
533
534   # make sure order column is qualified
535   $order_by = "$rs_attrs->{alias}.$order_by"
536     unless $order_by =~ /^$rs_attrs->{alias}\./;
537
538   my $is_u;
539   my $ucs = { $root_rsrc->unique_constraints };
540   for (values %$ucs ) {
541     if (@$_ == 1 && "$rs_attrs->{alias}.$_->[0]" eq $order_by) {
542       $is_u++;
543       last;
544     }
545   }
546   croak "Generic Subquery Limit order criteria column '$order_by' must be unique (no unique constraint found)"
547     unless $is_u;
548
549   my ($in_sel, $out_sel, $alias_map, $extra_order_sel)
550     = $self->_subqueried_limit_attrs ($rs_attrs);
551
552   my $cmp_op = $direction eq 'desc' ? '>' : '<';
553   my $count_tbl_alias = 'rownum__emulation';
554
555   my $order_sql = $self->_order_by (delete $rs_attrs->{order_by});
556   my $group_having_sql = $self->_parse_rs_attrs($rs_attrs);
557
558   # add the order supplement (if any) as this is what will be used for the outer WHERE
559   $in_sel .= ", $_" for keys %{$extra_order_sel||{}};
560
561   $sql = sprintf (<<EOS,
562 SELECT $out_sel
563   FROM (
564     SELECT $in_sel ${sql}${group_having_sql}
565   ) %s
566 WHERE ( SELECT COUNT(*) FROM %s %s WHERE %s $cmp_op %s ) %s
567 $order_sql
568 EOS
569     ( map { $self->_quote ($_) } (
570       $rs_attrs->{alias},
571       $root_tbl_name,
572       $count_tbl_alias,
573       "$count_tbl_alias.$unq_sort_col",
574       $order_by,
575     )),
576     $offset
577       ? sprintf ('BETWEEN %u AND %u', $offset, $offset + $rows - 1)
578       : sprintf ('< %u', $rows )
579     ,
580   );
581
582   $sql =~ s/\s*\n\s*/ /g;   # easier to read in the debugger
583   return $sql;
584 }
585
586
587 # !!! THIS IS ALSO HORRIFIC !!! /me ashamed
588 #
589 # Generates inner/outer select lists for various limit dialects
590 # which result in one or more subqueries (e.g. RNO, Top, RowNum)
591 # Any non-root-table columns need to have their table qualifier
592 # turned into a column alias (otherwise names in subqueries clash
593 # and/or lose their source table)
594 #
595 # Returns inner/outer strings of SQL QUOTED selectors with aliases
596 # (to be used in whatever select statement), and an alias index hashref
597 # of QUOTED SEL => QUOTED ALIAS pairs (to maybe be used for string-subst
598 # higher up).
599 # If an order_by is supplied, the inner select needs to bring out columns
600 # used in implicit (non-selected) orders, and the order condition itself
601 # needs to be realiased to the proper names in the outer query. Thus we
602 # also return a hashref (order doesn't matter) of QUOTED EXTRA-SEL =>
603 # QUOTED ALIAS pairs, which is a list of extra selectors that do *not*
604 # exist in the original select list
605 sub _subqueried_limit_attrs {
606   my ($self, $rs_attrs) = @_;
607
608   croak 'Limit dialect implementation usable only in the context of DBIC (missing $rs_attrs)'
609     unless ref ($rs_attrs) eq 'HASH';
610
611   my ($re_sep, $re_alias) = map { quotemeta $_ } (
612     $self->name_sep || '.',
613     $rs_attrs->{alias},
614   );
615
616   # correlate select and as, build selection index
617   my (@sel, $in_sel_index);
618   for my $i (0 .. $#{$rs_attrs->{select}}) {
619
620     my $s = $rs_attrs->{select}[$i];
621     my $sql_sel = $self->_recurse_fields ($s);
622     my $sql_alias = (ref $s) eq 'HASH' ? $s->{-as} : undef;
623
624     push @sel, {
625       sql => $sql_sel,
626       unquoted_sql => do { local $self->{quote_char}; $self->_recurse_fields ($s) },
627       as =>
628         $sql_alias
629           ||
630         $rs_attrs->{as}[$i]
631           ||
632         croak "Select argument $i ($s) without corresponding 'as'"
633       ,
634     };
635
636     $in_sel_index->{$sql_sel}++;
637     $in_sel_index->{$self->_quote ($sql_alias)}++ if $sql_alias;
638
639     # record unqualified versions too, so we do not have
640     # to reselect the same column twice (in qualified and
641     # unqualified form)
642     if (! ref $s && $sql_sel =~ / $re_sep (.+) $/x) {
643       $in_sel_index->{$1}++;
644     }
645   }
646
647
648   # re-alias and remove any name separators from aliases,
649   # unless we are dealing with the current source alias
650   # (which will transcend the subqueries as it is necessary
651   # for possible further chaining)
652   my (@in_sel, @out_sel, %renamed);
653   for my $node (@sel) {
654     if (first { $_ =~ / (?<! ^ $re_alias ) $re_sep /x } ($node->{as}, $node->{unquoted_sql}) )  {
655       $node->{as} = $self->_unqualify_colname($node->{as});
656       my $quoted_as = $self->_quote($node->{as});
657       push @in_sel, sprintf '%s AS %s', $node->{sql}, $quoted_as;
658       push @out_sel, $quoted_as;
659       $renamed{$node->{sql}} = $quoted_as;
660     }
661     else {
662       push @in_sel, $node->{sql};
663       push @out_sel, $self->_quote ($node->{as});
664     }
665   }
666
667   # see if the order gives us anything
668   my %extra_order_sel;
669   for my $chunk ($self->_order_by_chunks ($rs_attrs->{order_by})) {
670     # order with bind
671     $chunk = $chunk->[0] if (ref $chunk) eq 'ARRAY';
672     $chunk =~ s/\s+ (?: ASC|DESC ) \s* $//ix;
673
674     next if $in_sel_index->{$chunk};
675
676     $extra_order_sel{$chunk} ||= $self->_quote (
677       'ORDER__BY__' . scalar keys %extra_order_sel
678     );
679   }
680
681   return (
682     (map { join (', ', @$_ ) } (
683       \@in_sel,
684       \@out_sel)
685     ),
686     \%renamed,
687     keys %extra_order_sel ? \%extra_order_sel : (),
688   );
689 }
690
691 sub _unqualify_colname {
692   my ($self, $fqcn) = @_;
693   my $re_sep = quotemeta($self->name_sep || '.');
694   $fqcn =~ s/ $re_sep /__/xg;
695   return $fqcn;
696 }
697
698 1;
699
700 =head1 AUTHORS
701
702 See L<DBIx::Class/CONTRIBUTORS>.
703
704 =head1 LICENSE
705
706 You may distribute this code under the same terms as Perl itself.
707
708 =cut