8 use DBICTest ':DiffSQL';
10 my $schema = DBICTest->init_schema(
16 my $native_limit_dialect = $schema->storage->sql_maker->{limit_dialect};
18 my $where_string = '`me`.`title` = ? AND `source` != ? AND `source` = ?';
21 [ {} => 'kama sutra' ],
23 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'source' } => 'Library' ],
26 [ { sqlt_datatype => 'numeric' } => 11 ],
28 [ { sqlt_datatype => 'integer', dbic_colname => 'me.id' } => 13 ],
37 [ { sqlt_datatype => 'int' } => 1 ],
38 [ { sqlt_datatype => 'varchar', dbic_colname => 'name', sqlt_size => 100 } => 2 ],
47 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
50 ON `owner`.`id` = `me`.`owner`
52 GROUP BY (`me`.`id` / ?), `owner`.`id`
61 [ { sqlt_datatype => 'integer' } => 4 ],
66 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
69 ON `owner`.`id` = `me`.`owner`
71 GROUP BY (`me`.`id` / ?), `owner`.`id`
81 [ { sqlt_datatype => 'integer' } => 4 ],
82 [ { sqlt_datatype => 'integer' } => 3 ],
87 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
90 ON `owner`.`id` = `me`.`owner`
92 GROUP BY (`me`.`id` / ?), `owner`.`id`
103 [ { sqlt_datatype => 'integer' } => 4 ],
106 ordered_limit_offset => [
108 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
110 JOIN `owners` `owner`
111 ON `owner`.`id` = `me`.`owner`
113 GROUP BY (`me`.`id` / ?), `owner`.`id`
125 [ { sqlt_datatype => 'integer' } => 4 ],
126 [ { sqlt_datatype => 'integer' } => 3 ],
129 limit_offset_prefetch => [
131 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
133 SELECT `me`.`name`, `me`.`id`
137 LEFT JOIN `books` `books`
138 ON `books`.`owner` = `me`.`id`
141 [ { sqlt_datatype => 'integer' } => 3 ],
142 [ { sqlt_datatype => 'integer' } => 1 ],
148 ordered_limit_offset => [
150 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
152 JOIN `owners` `owner`
153 ON `owner`.`id` = `me`.`owner`
155 GROUP BY (`me`.`id` / ?), `owner`.`id`
166 [ { sqlt_datatype => 'integer' } => 3 ],
167 [ { sqlt_datatype => 'integer' } => 4 ],
170 limit_offset_prefetch => [
172 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
174 SELECT `me`.`name`, `me`.`id`
178 LEFT JOIN `books` `books`
179 ON `books`.`owner` = `me`.`id`
182 [ { sqlt_datatype => 'integer' } => 1 ],
183 [ { sqlt_datatype => 'integer' } => 3 ],
189 ordered_limit_offset => [
191 SELECT SKIP ? FIRST ? `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
193 JOIN `owners` `owner`
194 ON `owner`.`id` = `me`.`owner`
196 GROUP BY (`me`.`id` / ?), `owner`.`id`
201 [ { sqlt_datatype => 'integer' } => 3 ],
202 [ { sqlt_datatype => 'integer' } => 4 ],
210 limit_offset_prefetch => [
212 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
214 SELECT SKIP ? FIRST ? `me`.`name`, `me`.`id`
217 LEFT JOIN `books` `books`
218 ON `books`.`owner` = `me`.`id`
221 [ { sqlt_datatype => 'integer' } => 1 ],
222 [ { sqlt_datatype => 'integer' } => 3 ],
228 ordered_limit_offset => [
230 SELECT FIRST ? SKIP ? `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
232 JOIN `owners` `owner`
233 ON `owner`.`id` = `me`.`owner`
235 GROUP BY (`me`.`id` / ?), `owner`.`id`
240 [ { sqlt_datatype => 'integer' } => 4 ],
241 [ { sqlt_datatype => 'integer' } => 3 ],
249 limit_offset_prefetch => [
251 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
253 SELECT FIRST ? SKIP ? `me`.`name`, `me`.`id`
256 LEFT JOIN `books` `books`
257 ON `books`.`owner` = `me`.`id`
260 [ { sqlt_datatype => 'integer' } => 3 ],
261 [ { sqlt_datatype => 'integer' } => 1 ],
266 RowNumberOver => do {
267 my $unordered_sql = "(
268 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
270 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROW_NUMBER() OVER() AS `rno__row__index`
272 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
274 JOIN `owners` `owner`
275 ON `owner`.`id` = `me`.`owner`
277 GROUP BY (`me`.`id` / ?), `owner`.`id`
281 WHERE `rno__row__index` >= ? AND `rno__row__index` <= ?
285 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
287 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROW_NUMBER() OVER( ORDER BY `ORDER__BY__001`, `ORDER__BY__002` ) AS `rno__row__index`
289 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`,
290 ? / ? AS `ORDER__BY__001`, ? AS `ORDER__BY__002`
292 JOIN `owners` `owner`
293 ON `owner`.`id` = `me`.`owner`
295 GROUP BY (`me`.`id` / ?), `owner`.`id`
299 WHERE `rno__row__index` >= ? AND `rno__row__index` <= ?
303 limit => [$unordered_sql,
309 [ { sqlt_datatype => 'integer' } => 1 ],
310 [ { sqlt_datatype => 'integer' } => 4 ],
313 limit_offset => [$unordered_sql,
319 [ { sqlt_datatype => 'integer' } => 4 ],
320 [ { sqlt_datatype => 'integer' } => 7 ],
323 ordered_limit => [$ordered_sql,
330 [ { sqlt_datatype => 'integer' } => 1 ],
331 [ { sqlt_datatype => 'integer' } => 4 ],
334 ordered_limit_offset => [$ordered_sql,
341 [ { sqlt_datatype => 'integer' } => 4 ],
342 [ { sqlt_datatype => 'integer' } => 7 ],
345 limit_offset_prefetch => [
347 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
349 SELECT `me`.`name`, `me`.`id`
351 SELECT `me`.`name`, `me`.`id`, ROW_NUMBER() OVER() AS `rno__row__index`
353 SELECT `me`.`name`, `me`.`id` FROM `owners` `me`
356 WHERE `rno__row__index` >= ? AND `rno__row__index` <= ?
358 LEFT JOIN `books` `books`
359 ON `books`.`owner` = `me`.`id`
362 [ { sqlt_datatype => 'integer' } => 2 ],
363 [ { sqlt_datatype => 'integer' } => 4 ],
370 my $limit_sql = sub {
372 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
374 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
376 JOIN `owners` `owner`
377 ON `owner`.`id` = `me`.`owner`
379 GROUP BY (`me`.`id` / ?), `owner`.`id`
388 limit => [ $limit_sql->(),
394 [ { sqlt_datatype => 'integer' } => 4 ],
399 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
401 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROWNUM AS `rownum__index`
403 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
405 JOIN `owners` `owner`
406 ON `owner`.`id` = `me`.`owner`
408 GROUP BY (`me`.`id` / ?), `owner`.`id`
412 WHERE `rownum__index` BETWEEN ? AND ?
419 [ { sqlt_datatype => 'integer' } => 4 ],
420 [ { sqlt_datatype => 'integer' } => 7 ],
423 ordered_limit => [ $limit_sql->('ORDER BY ? / ?, ?'),
430 [ { sqlt_datatype => 'integer' } => 4 ],
433 ordered_limit_offset => [
435 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
437 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROWNUM AS `rownum__index`
439 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
441 JOIN `owners` `owner`
442 ON `owner`.`id` = `me`.`owner`
444 GROUP BY (`me`.`id` / ?), `owner`.`id`
450 WHERE `rownum__index` >= ?
458 [ { sqlt_datatype => 'integer' } => 7 ],
459 [ { sqlt_datatype => 'integer' } => 4 ],
462 limit_offset_prefetch => [
464 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
466 SELECT `me`.`name`, `me`.`id`
468 SELECT `me`.`name`, `me`.`id`, ROWNUM AS `rownum__index`
470 SELECT `me`.`name`, `me`.`id`
473 ) `me` WHERE `rownum__index` BETWEEN ? AND ?
475 LEFT JOIN `books` `books`
476 ON `books`.`owner` = `me`.`id`
479 [ { sqlt_datatype => 'integer' } => 2 ],
480 [ { sqlt_datatype => 'integer' } => 4 ],
489 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
491 JOIN `owners` `owner`
492 ON `owner`.`id` = `me`.`owner`
494 GROUP BY (`me`.`id` / ?), `owner`.`id`
496 FETCH FIRST 4 ROWS ONLY
507 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
509 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
511 JOIN `owners` `owner`
512 ON `owner`.`id` = `me`.`owner`
514 GROUP BY (`me`.`id` / ?), `owner`.`id`
517 FETCH FIRST 7 ROWS ONLY
519 ORDER BY `me`.`id` DESC
520 FETCH FIRST 4 ROWS ONLY
531 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
533 JOIN `owners` `owner`
534 ON `owner`.`id` = `me`.`owner`
536 GROUP BY (`me`.`id` / ?), `owner`.`id`
539 FETCH FIRST 4 ROWS ONLY
549 ordered_limit_offset => [
551 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
553 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, `ORDER__BY__001`, `ORDER__BY__002`
555 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`, ? / ? AS `ORDER__BY__001`, ? AS `ORDER__BY__002`
557 JOIN `owners` `owner`
558 ON `owner`.`id` = `me`.`owner`
560 GROUP BY (`me`.`id` / ?), `owner`.`id`
563 FETCH FIRST 7 ROWS ONLY
565 ORDER BY `ORDER__BY__001` DESC, `ORDER__BY__002` DESC
566 FETCH FIRST 4 ROWS ONLY
568 ORDER BY `ORDER__BY__001`, `ORDER__BY__002`
576 @{ dclone \@order_bind }, # without this is_deeply throws a fit
579 limit_offset_prefetch => [
581 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
583 SELECT `me`.`name`, `me`.`id`
585 SELECT `me`.`name`, `me`.`id`
588 FETCH FIRST 4 ROWS ONLY
590 ORDER BY `me`.`id` DESC
591 FETCH FIRST 3 ROWS ONLY
593 LEFT JOIN `books` `books`
594 ON `books`.`owner` = `me`.`id`
603 SELECT TOP 4 `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
605 JOIN `owners` `owner`
606 ON `owner`.`id` = `me`.`owner`
608 GROUP BY (`me`.`id` / ?), `owner`.`id`
620 SELECT TOP 4 `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
622 SELECT TOP 7 `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
624 JOIN `owners` `owner`
625 ON `owner`.`id` = `me`.`owner`
627 GROUP BY (`me`.`id` / ?), `owner`.`id`
631 ORDER BY `me`.`id` DESC
642 SELECT TOP 4 `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
644 JOIN `owners` `owner`
645 ON `owner`.`id` = `me`.`owner`
647 GROUP BY (`me`.`id` / ?), `owner`.`id`
659 ordered_limit_offset => [
661 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
663 SELECT TOP 4 `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, `ORDER__BY__001`, `ORDER__BY__002`
665 SELECT TOP 7 `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`, ? / ? AS `ORDER__BY__001`, ? AS `ORDER__BY__002`
667 JOIN `owners` `owner`
668 ON `owner`.`id` = `me`.`owner`
670 GROUP BY (`me`.`id` / ?), `owner`.`id`
674 ORDER BY `ORDER__BY__001` DESC, `ORDER__BY__002` DESC
676 ORDER BY `ORDER__BY__001`, `ORDER__BY__002`
684 @{ dclone \@order_bind }, # without this is_deeply throws a fit
687 limit_offset_prefetch => [
689 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
691 SELECT TOP 3 `me`.`name`, `me`.`id`
693 SELECT TOP 4 `me`.`name`, `me`.`id`
697 ORDER BY `me`.`id` DESC
699 LEFT JOIN `books` `books`
700 ON `books`.`owner` = `me`.`id`
709 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
711 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`, `me`.`price`
713 JOIN `owners` `owner`
714 ON `owner`.`id` = `me`.`owner`
716 GROUP BY (`me`.`id` / ?), `owner`.`id`
721 FROM `books` `rownum__emulation`
723 ( `me`.`price` IS NULL AND `rownum__emulation`.`price` IS NOT NULL )
726 `rownum__emulation`.`price` > `me`.`price`
728 `me`.`price` IS NOT NULL
730 `rownum__emulation`.`price` IS NOT NULL
735 `me`.`price` = `rownum__emulation`.`price`
737 ( `me`.`price` IS NULL AND `rownum__emulation`.`price` IS NULL )
740 `rownum__emulation`.`id` < `me`.`id`
743 ORDER BY `me`.`price` DESC, `me`.`id` ASC
750 [ { sqlt_datatype => 'integer' } => 4 ],
753 ordered_limit_offset => [
755 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
757 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`, `me`.`price`
759 JOIN `owners` `owner`
760 ON `owner`.`id` = `me`.`owner`
762 GROUP BY (`me`.`id` / ?), `owner`.`id`
767 FROM `books` `rownum__emulation`
769 ( `me`.`price` IS NULL AND `rownum__emulation`.`price` IS NOT NULL )
772 `rownum__emulation`.`price` > `me`.`price`
774 `me`.`price` IS NOT NULL
776 `rownum__emulation`.`price` IS NOT NULL
781 `me`.`price` = `rownum__emulation`.`price`
783 ( `me`.`price` IS NULL AND `rownum__emulation`.`price` IS NULL )
786 `rownum__emulation`.`id` < `me`.`id`
789 ORDER BY `me`.`price` DESC, `me`.`id` ASC
796 [ { sqlt_datatype => 'integer' } => 3 ],
797 [ { sqlt_datatype => 'integer' } => 6 ],
800 limit_offset_prefetch => [
802 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price`
804 SELECT `me`.`name`, `me`.`id`
806 SELECT `me`.`name`, `me`.`id`
812 FROM `owners` `rownum__emulation`
814 `rownum__emulation`.`name` < `me`.`name`
817 `me`.`name` = `rownum__emulation`.`name`
819 `rownum__emulation`.`id` > `me`.`id`
823 ORDER BY `me`.`name` ASC, `me`.`id` DESC
825 LEFT JOIN `books` `books`
826 ON `books`.`owner` = `me`.`id`
827 ORDER BY `me`.`name` ASC, `me`.`id` DESC
830 [ { sqlt_datatype => 'integer' } => 1 ],
831 [ { sqlt_datatype => 'integer' } => 3 ],
837 for my $limtype (sort keys %$tests) {
839 Test::Builder->new->is_passing or exit;
841 delete $schema->storage->_sql_maker->{_cached_syntax};
842 $schema->storage->_sql_maker->limit_dialect ($limtype);
844 # not deploying, so can't run
845 my $can_run = 0 && ($limtype eq $native_limit_dialect or $limtype eq 'GenericSubQ');
847 # chained search is necessary to exercise the recursive {where} parser
848 my $rs = $schema->resultset('BooksInLibrary')->search(
849 { 'me.title' => { '=' => \[ '?', 'kama sutra' ] } }
851 { source => { '!=', \[ '?', [ {} => 'Study' ] ] } },
853 columns => [ { identifier => 'me.id' }, 'owner.id', 'owner.name' ], # people actually do that. BLEH!!! :)
854 join => 'owner', # single-rel manual prefetch
856 '+columns' => { bar => \['? * ?', [ \ 'numeric' => 11 ], 12 ], baz => \[ '?', [ 'me.id' => 13 ] ] },
857 group_by => \[ '(`me`.`id` / ?), `owner`.`id`', 21 ],
858 having => \[ '?', 31 ],
863 # not all tests run on all dialects (somewhere impossible, somewhere makes no sense)
866 # only limit, no offset, no order
867 if ($tests->{$limtype}{limit}) {
871 @{$tests->{$limtype}{limit}},
872 "$limtype: Unordered limit with select/group/having",
875 $rs->all if $can_run;
876 } "Grouped limit under $limtype";
879 # limit + offset, no order
880 if ($tests->{$limtype}{limit_offset}) {
883 my $subrs = $rs->search({}, { offset => 3 });
887 @{$tests->{$limtype}{limit_offset}},
888 "$limtype: Unordered limit+offset with select/group/having",
891 $subrs->all if $can_run;
892 } "Grouped limit+offset runs under $limtype";
895 # order + limit, no offset
896 $rs = $rs->search(undef, {
897 order_by => ( $limtype =~ /GenericSubQ/
898 ? [ { -desc => 'price' }, 'me.id', \[ '`owner`.`name` + ?', 'bah' ] ] # needs a same-table stable order to be happy
899 : [ \['? / ?', [ \ 'int' => 1 ], [ name => 2 ]], \[ '?', 3 ] ]
903 if ($tests->{$limtype}{ordered_limit}) {
908 @{$tests->{$limtype}{ordered_limit}},
909 "$limtype: Ordered limit with select/group/having",
912 $rs->all if $can_run;
913 } "Grouped ordered limit runs under $limtype"
916 # order + limit + offset
917 if ($tests->{$limtype}{ordered_limit_offset}) {
919 my $subrs = $rs->search({}, { offset => 3 });
923 @{$tests->{$limtype}{ordered_limit_offset}},
924 "$limtype: Ordered limit+offset with select/group/having",
927 $subrs->all if $can_run;
928 } "Grouped ordered limit+offset runs under $limtype";
931 # complex prefetch on partial-fetch root with limit
932 my $pref_rs = $schema->resultset('Owners')->search({}, {
935 columns => 'name', # only the owner name, still prefetch all the books
937 ($limtype !~ /GenericSubQ/ ? () : (
938 # needs a same-table stable order to be happy
939 order_by => [ { -asc => 'me.name' }, \ '`me`.`id` DESC' ]
946 @{$tests->{$limtype}{limit_offset_prefetch}},
947 "$limtype: Prefetch with limit+offset",
948 ) if $tests->{$limtype}{limit_offset_prefetch};
950 is ($pref_rs->all, 1, 'Expected count of objects on limited prefetch')
952 } "Complex limited prefetch runs under $limtype";