8 use DBICTest ':DiffSQL';
10 my $schema = DBICTest->init_schema(
16 my $bs = $schema->source('BooksInLibrary');
17 $bs->remove_column('price');
20 data_type => 'integer',
29 my $native_limit_dialect = $schema->storage->sql_maker->{limit_dialect};
31 my $where_string = '`me`.`title` = ? AND `source` != ? AND `source` = ?';
34 [ {} => 'kama sutra' ],
36 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'source' } => 'Library' ],
39 [ { sqlt_datatype => 'numeric' } => 11 ],
41 [ { sqlt_datatype => 'integer', dbic_colname => 'me.id' } => 13 ],
50 [ { sqlt_datatype => 'int' } => 1 ],
51 [ { sqlt_datatype => 'varchar', dbic_colname => 'name', sqlt_size => 100 } => 2 ],
60 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
63 ON `owner`.`id` = `me`.`owner`
65 GROUP BY (`me`.`id` / ?), `owner`.`id`
74 [ { sqlt_datatype => 'integer' } => 4 ],
79 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
82 ON `owner`.`id` = `me`.`owner`
84 GROUP BY (`me`.`id` / ?), `owner`.`id`
94 [ { sqlt_datatype => 'integer' } => 4 ],
95 [ { sqlt_datatype => 'integer' } => 3 ],
100 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
102 JOIN `owners` `owner`
103 ON `owner`.`id` = `me`.`owner`
105 GROUP BY (`me`.`id` / ?), `owner`.`id`
107 ORDER BY ? / ?, ?, `me`.`short desc`
116 [ { sqlt_datatype => 'integer' } => 4 ],
119 ordered_limit_offset => [
121 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
123 JOIN `owners` `owner`
124 ON `owner`.`id` = `me`.`owner`
126 GROUP BY (`me`.`id` / ?), `owner`.`id`
128 ORDER BY ? / ?, ?, `me`.`short desc`
138 [ { sqlt_datatype => 'integer' } => 4 ],
139 [ { sqlt_datatype => 'integer' } => 3 ],
142 limit_offset_prefetch => [
144 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
146 SELECT `me`.`name`, `me`.`id`
150 LEFT JOIN `books` `books`
151 ON `books`.`owner` = `me`.`id`
154 [ { sqlt_datatype => 'integer' } => 3 ],
155 [ { sqlt_datatype => 'integer' } => 1 ],
161 ordered_limit_offset => [
163 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
165 JOIN `owners` `owner`
166 ON `owner`.`id` = `me`.`owner`
168 GROUP BY (`me`.`id` / ?), `owner`.`id`
170 ORDER BY ? / ?, ?, `me`.`short desc`
179 [ { sqlt_datatype => 'integer' } => 3 ],
180 [ { sqlt_datatype => 'integer' } => 4 ],
183 limit_offset_prefetch => [
185 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
187 SELECT `me`.`name`, `me`.`id`
191 LEFT JOIN `books` `books`
192 ON `books`.`owner` = `me`.`id`
195 [ { sqlt_datatype => 'integer' } => 1 ],
196 [ { sqlt_datatype => 'integer' } => 3 ],
202 ordered_limit_offset => [
204 SELECT SKIP ? FIRST ? `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
206 JOIN `owners` `owner`
207 ON `owner`.`id` = `me`.`owner`
209 GROUP BY (`me`.`id` / ?), `owner`.`id`
211 ORDER BY ? / ?, ?, `me`.`short desc`
214 [ { sqlt_datatype => 'integer' } => 3 ],
215 [ { sqlt_datatype => 'integer' } => 4 ],
223 limit_offset_prefetch => [
225 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
227 SELECT SKIP ? FIRST ? `me`.`name`, `me`.`id`
230 LEFT JOIN `books` `books`
231 ON `books`.`owner` = `me`.`id`
234 [ { sqlt_datatype => 'integer' } => 1 ],
235 [ { sqlt_datatype => 'integer' } => 3 ],
241 ordered_limit_offset => [
243 SELECT FIRST ? SKIP ? `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
245 JOIN `owners` `owner`
246 ON `owner`.`id` = `me`.`owner`
248 GROUP BY (`me`.`id` / ?), `owner`.`id`
250 ORDER BY ? / ?, ?, `me`.`short desc`
253 [ { sqlt_datatype => 'integer' } => 4 ],
254 [ { sqlt_datatype => 'integer' } => 3 ],
262 limit_offset_prefetch => [
264 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
266 SELECT FIRST ? SKIP ? `me`.`name`, `me`.`id`
269 LEFT JOIN `books` `books`
270 ON `books`.`owner` = `me`.`id`
273 [ { sqlt_datatype => 'integer' } => 3 ],
274 [ { sqlt_datatype => 'integer' } => 1 ],
279 RowNumberOver => do {
280 my $unordered_sql = "(
281 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
283 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROW_NUMBER() OVER() AS `rno__row__index`
285 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
287 JOIN `owners` `owner`
288 ON `owner`.`id` = `me`.`owner`
290 GROUP BY (`me`.`id` / ?), `owner`.`id`
294 WHERE `rno__row__index` >= ? AND `rno__row__index` <= ?
298 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
300 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROW_NUMBER() OVER( ORDER BY `ORDER__BY__001`, `ORDER__BY__002`, `ORDER__BY__003` ) AS `rno__row__index`
302 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`,
303 ? / ? AS `ORDER__BY__001`, ? AS `ORDER__BY__002`, `me`.`short desc` AS `ORDER__BY__003`
305 JOIN `owners` `owner`
306 ON `owner`.`id` = `me`.`owner`
308 GROUP BY (`me`.`id` / ?), `owner`.`id`
312 WHERE `rno__row__index` >= ? AND `rno__row__index` <= ?
316 limit => [$unordered_sql,
322 [ { sqlt_datatype => 'integer' } => 1 ],
323 [ { sqlt_datatype => 'integer' } => 4 ],
326 limit_offset => [$unordered_sql,
332 [ { sqlt_datatype => 'integer' } => 4 ],
333 [ { sqlt_datatype => 'integer' } => 7 ],
336 ordered_limit => [$ordered_sql,
343 [ { sqlt_datatype => 'integer' } => 1 ],
344 [ { sqlt_datatype => 'integer' } => 4 ],
347 ordered_limit_offset => [$ordered_sql,
354 [ { sqlt_datatype => 'integer' } => 4 ],
355 [ { sqlt_datatype => 'integer' } => 7 ],
358 limit_offset_prefetch => [
360 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
362 SELECT `me`.`name`, `me`.`id`
364 SELECT `me`.`name`, `me`.`id`, ROW_NUMBER() OVER() AS `rno__row__index`
366 SELECT `me`.`name`, `me`.`id` FROM `owners` `me`
369 WHERE `rno__row__index` >= ? AND `rno__row__index` <= ?
371 LEFT JOIN `books` `books`
372 ON `books`.`owner` = `me`.`id`
375 [ { sqlt_datatype => 'integer' } => 2 ],
376 [ { sqlt_datatype => 'integer' } => 4 ],
383 my $limit_sql = sub {
385 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
387 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
389 JOIN `owners` `owner`
390 ON `owner`.`id` = `me`.`owner`
392 GROUP BY (`me`.`id` / ?), `owner`.`id`
401 limit => [ $limit_sql->(),
407 [ { sqlt_datatype => 'integer' } => 4 ],
412 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
414 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROWNUM AS `rownum__index`
416 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
418 JOIN `owners` `owner`
419 ON `owner`.`id` = `me`.`owner`
421 GROUP BY (`me`.`id` / ?), `owner`.`id`
425 WHERE `rownum__index` BETWEEN ? AND ?
432 [ { sqlt_datatype => 'integer' } => 4 ],
433 [ { sqlt_datatype => 'integer' } => 7 ],
436 ordered_limit => [ $limit_sql->('ORDER BY ? / ?, ?, `me`.`short desc`'),
443 [ { sqlt_datatype => 'integer' } => 4 ],
446 ordered_limit_offset => [
448 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
450 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, ROWNUM AS `rownum__index`
452 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
454 JOIN `owners` `owner`
455 ON `owner`.`id` = `me`.`owner`
457 GROUP BY (`me`.`id` / ?), `owner`.`id`
459 ORDER BY ? / ?, ?, `me`.`short desc`
463 WHERE `rownum__index` >= ?
471 [ { sqlt_datatype => 'integer' } => 7 ],
472 [ { sqlt_datatype => 'integer' } => 4 ],
475 limit_offset_prefetch => [
477 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
479 SELECT `me`.`name`, `me`.`id`
481 SELECT `me`.`name`, `me`.`id`, ROWNUM AS `rownum__index`
483 SELECT `me`.`name`, `me`.`id`
486 ) `me` WHERE `rownum__index` BETWEEN ? AND ?
488 LEFT JOIN `books` `books`
489 ON `books`.`owner` = `me`.`id`
492 [ { sqlt_datatype => 'integer' } => 2 ],
493 [ { sqlt_datatype => 'integer' } => 4 ],
502 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
504 JOIN `owners` `owner`
505 ON `owner`.`id` = `me`.`owner`
507 GROUP BY (`me`.`id` / ?), `owner`.`id`
509 FETCH FIRST 4 ROWS ONLY
520 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
522 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
524 JOIN `owners` `owner`
525 ON `owner`.`id` = `me`.`owner`
527 GROUP BY (`me`.`id` / ?), `owner`.`id`
530 FETCH FIRST 7 ROWS ONLY
532 ORDER BY `me`.`id` DESC
533 FETCH FIRST 4 ROWS ONLY
544 SELECT `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
546 JOIN `owners` `owner`
547 ON `owner`.`id` = `me`.`owner`
549 GROUP BY (`me`.`id` / ?), `owner`.`id`
551 ORDER BY ? / ?, ?, `me`.`short desc`
552 FETCH FIRST 4 ROWS ONLY
562 ordered_limit_offset => [
564 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
566 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, `ORDER__BY__001`, `ORDER__BY__002`, `ORDER__BY__003`
568 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`, `me`.`short desc` AS `ORDER__BY__003`
570 JOIN `owners` `owner`
571 ON `owner`.`id` = `me`.`owner`
573 GROUP BY (`me`.`id` / ?), `owner`.`id`
575 ORDER BY ? / ?, ?, `me`.`short desc`
576 FETCH FIRST 7 ROWS ONLY
578 ORDER BY `ORDER__BY__001` DESC, `ORDER__BY__002` DESC, `ORDER__BY__003` DESC
579 FETCH FIRST 4 ROWS ONLY
581 ORDER BY `ORDER__BY__001`, `ORDER__BY__002`, `ORDER__BY__003`
589 @{ dclone \@order_bind }, # without this is_deeply throws a fit
592 limit_offset_prefetch => [
594 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
596 SELECT `me`.`name`, `me`.`id`
598 SELECT `me`.`name`, `me`.`id`
601 FETCH FIRST 4 ROWS ONLY
603 ORDER BY `me`.`id` DESC
604 FETCH FIRST 3 ROWS ONLY
606 LEFT JOIN `books` `books`
607 ON `books`.`owner` = `me`.`id`
616 SELECT TOP 4 `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
618 JOIN `owners` `owner`
619 ON `owner`.`id` = `me`.`owner`
621 GROUP BY (`me`.`id` / ?), `owner`.`id`
633 SELECT TOP 4 `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
635 SELECT TOP 7 `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`
637 JOIN `owners` `owner`
638 ON `owner`.`id` = `me`.`owner`
640 GROUP BY (`me`.`id` / ?), `owner`.`id`
644 ORDER BY `me`.`id` DESC
655 SELECT TOP 4 `me`.`id`, `owner`.`id`, `owner`.`name`, ? * ?, ?
657 JOIN `owners` `owner`
658 ON `owner`.`id` = `me`.`owner`
660 GROUP BY (`me`.`id` / ?), `owner`.`id`
662 ORDER BY ? / ?, ?, `me`.`short desc`
672 ordered_limit_offset => [
674 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
676 SELECT TOP 4 `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`, `ORDER__BY__001`, `ORDER__BY__002`, `ORDER__BY__003`
678 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`, `me`.`short desc` AS `ORDER__BY__003`
680 JOIN `owners` `owner`
681 ON `owner`.`id` = `me`.`owner`
683 GROUP BY (`me`.`id` / ?), `owner`.`id`
685 ORDER BY ? / ?, ?, `me`.`short desc`
687 ORDER BY `ORDER__BY__001` DESC, `ORDER__BY__002` DESC, `ORDER__BY__003` DESC
689 ORDER BY `ORDER__BY__001`, `ORDER__BY__002`, `ORDER__BY__003`
697 @{ dclone \@order_bind }, # without this is_deeply throws a fit
700 limit_offset_prefetch => [
702 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
704 SELECT TOP 3 `me`.`name`, `me`.`id`
706 SELECT TOP 4 `me`.`name`, `me`.`id`
710 ORDER BY `me`.`id` DESC
712 LEFT JOIN `books` `books`
713 ON `books`.`owner` = `me`.`id`
722 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
724 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`, `me`.`price (#)`
726 JOIN `owners` `owner`
727 ON `owner`.`id` = `me`.`owner`
729 GROUP BY (`me`.`id` / ?), `owner`.`id`
734 FROM `books` `rownum__emulation`
736 ( `me`.`price (#)` IS NULL AND `rownum__emulation`.`price (#)` IS NOT NULL )
739 `rownum__emulation`.`price (#)` > `me`.`price (#)`
741 `me`.`price (#)` IS NOT NULL
743 `rownum__emulation`.`price (#)` IS NOT NULL
748 `me`.`price (#)` = `rownum__emulation`.`price (#)`
750 ( `me`.`price (#)` IS NULL AND `rownum__emulation`.`price (#)` IS NULL )
753 `rownum__emulation`.`id` < `me`.`id`
756 ORDER BY `me`.`price (#)` DESC, `me`.`id` ASC
763 [ { sqlt_datatype => 'integer' } => 4 ],
766 ordered_limit_offset => [
768 SELECT `me`.`id`, `owner__id`, `owner__name`, `bar`, `baz`
770 SELECT `me`.`id`, `owner`.`id` AS `owner__id`, `owner`.`name` AS `owner__name`, ? * ? AS `bar`, ? AS `baz`, `me`.`price (#)`
772 JOIN `owners` `owner`
773 ON `owner`.`id` = `me`.`owner`
775 GROUP BY (`me`.`id` / ?), `owner`.`id`
780 FROM `books` `rownum__emulation`
782 ( `me`.`price (#)` IS NULL AND `rownum__emulation`.`price (#)` IS NOT NULL )
785 `rownum__emulation`.`price (#)` > `me`.`price (#)`
787 `me`.`price (#)` IS NOT NULL
789 `rownum__emulation`.`price (#)` IS NOT NULL
794 `me`.`price (#)` = `rownum__emulation`.`price (#)`
796 ( `me`.`price (#)` IS NULL AND `rownum__emulation`.`price (#)` IS NULL )
799 `rownum__emulation`.`id` < `me`.`id`
802 ORDER BY `me`.`price (#)` DESC, `me`.`id` ASC
809 [ { sqlt_datatype => 'integer' } => 3 ],
810 [ { sqlt_datatype => 'integer' } => 6 ],
813 limit_offset_prefetch => [
815 SELECT `me`.`name`, `books`.`id`, `books`.`source`, `books`.`owner`, `books`.`title`, `books`.`price (#)`, `books`.`short desc`
817 SELECT `me`.`name`, `me`.`id`
819 SELECT `me`.`name`, `me`.`id`
825 FROM `owners` `rownum__emulation`
827 `rownum__emulation`.`name` < `me`.`name`
830 `me`.`name` = `rownum__emulation`.`name`
832 `rownum__emulation`.`id` > `me`.`id`
836 ORDER BY `me`.`name` ASC, `me`.`id` DESC
838 LEFT JOIN `books` `books`
839 ON `books`.`owner` = `me`.`id`
840 ORDER BY `me`.`name` ASC, `me`.`id` DESC
843 [ { sqlt_datatype => 'integer' } => 1 ],
844 [ { sqlt_datatype => 'integer' } => 3 ],
850 for my $limtype (sort keys %$tests) {
852 Test::Builder->new->is_passing or exit;
854 delete $schema->storage->_sql_maker->{_cached_syntax};
855 $schema->storage->_sql_maker->limit_dialect ($limtype);
857 # not deploying, so can't run
858 my $can_run = 0 && ($limtype eq $native_limit_dialect or $limtype eq 'GenericSubQ');
860 # chained search is necessary to exercise the recursive {where} parser
861 my $rs = $schema->resultset('BooksInLibrary')->search(
862 { 'me.title' => { '=' => \[ '?', 'kama sutra' ] } }
864 { source => { '!=', \[ '?', [ {} => 'Study' ] ] } },
866 columns => [ { identifier => 'me.id' }, 'owner.id', 'owner.name' ], # people actually do that. BLEH!!! :)
867 join => 'owner', # single-rel manual prefetch
869 '+columns' => { bar => \['? * ?', [ \ 'numeric' => 11 ], 12 ], baz => \[ '?', [ 'me.id' => 13 ] ] },
870 group_by => \[ '(`me`.`id` / ?), `owner`.`id`', 21 ],
871 having => \[ '?', 31 ],
876 # not all tests run on all dialects (somewhere impossible, somewhere makes no sense)
879 # only limit, no offset, no order
880 if ($tests->{$limtype}{limit}) {
884 @{$tests->{$limtype}{limit}},
885 "$limtype: Unordered limit with select/group/having",
888 $rs->all if $can_run;
889 } "Grouped limit under $limtype";
892 # limit + offset, no order
893 if ($tests->{$limtype}{limit_offset}) {
896 my $subrs = $rs->search({}, { offset => 3 });
900 @{$tests->{$limtype}{limit_offset}},
901 "$limtype: Unordered limit+offset with select/group/having",
904 $subrs->all if $can_run;
905 } "Grouped limit+offset runs under $limtype";
908 # order + limit, no offset
909 $rs = $rs->search(undef, {
910 order_by => ( $limtype =~ /GenericSubQ/
911 ? [ { -desc => 'price (#)' }, 'me.id', \[ '`owner`.`name` + ?', 'bah' ], \'`me`.`short desc`' ] # needs a same-table stable order to be happy
912 : [ \['? / ?', [ \ 'int' => 1 ], [ name => 2 ]], \[ '?', 3 ], \'`me`.`short desc`' ]
916 if ($tests->{$limtype}{ordered_limit}) {
921 @{$tests->{$limtype}{ordered_limit}},
922 "$limtype: Ordered limit with select/group/having",
925 $rs->all if $can_run;
926 } "Grouped ordered limit runs under $limtype"
929 # order + limit + offset
930 if ($tests->{$limtype}{ordered_limit_offset}) {
932 my $subrs = $rs->search({}, { offset => 3 });
936 @{$tests->{$limtype}{ordered_limit_offset}},
937 "$limtype: Ordered limit+offset with select/group/having",
940 $subrs->all if $can_run;
941 } "Grouped ordered limit+offset runs under $limtype";
944 # complex prefetch on partial-fetch root with limit
945 my $pref_rs = $schema->resultset('Owners')->search({}, {
948 columns => 'name', # only the owner name, still prefetch all the books
950 ($limtype !~ /GenericSubQ/ ? () : (
951 # needs a same-table stable order to be happy
952 order_by => [ { -asc => 'me.name' }, \ '`me`.`id` DESC' ]
959 @{$tests->{$limtype}{limit_offset_prefetch}},
960 "$limtype: Prefetch with limit+offset",
961 ) if $tests->{$limtype}{limit_offset_prefetch};
963 is ($pref_rs->all, 1, 'Expected count of objects on limited prefetch')
965 } "Complex limited prefetch runs under $limtype";