Some test suite corrections ahead of next commits
[dbsrgits/DBIx-Class.git] / t / 72pg.t
1 BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
2 use DBIx::Class::Optional::Dependencies -skip_all_without => 'test_rdbms_pg';
3
4 use strict;
5 use warnings;
6
7 use Test::More;
8 use Test::Exception;
9 use Test::Warn;
10 use Config;
11 use DBICTest;
12 use SQL::Abstract 'is_literal_value';
13 use DBIx::Class::_Util qw( is_exception set_subname );
14
15 my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_PG_${_}" } qw/DSN USER PASS/};
16
17 ### load any test classes that are defined further down in the file via BEGIN blocks
18 our @test_classes; #< array that will be pushed into by test classes defined in this file
19 DBICTest::Schema->load_classes( map {s/.+:://;$_} @test_classes ) if @test_classes;
20
21 ###  pre-connect tests (keep each test separate as to make sure rebless() runs)
22   {
23     my $s = DBICTest::Schema->connect($dsn, $user, $pass);
24     # make sure sqlt_type overrides work (::Storage::DBI::Pg does this)
25     ok (!$s->storage->_dbh, 'definitely not connected');
26     is ($s->storage->sqlt_type, 'PostgreSQL', 'sqlt_type correct pre-connection');
27     ok (!$s->storage->_dbh, 'still not connected');
28   }
29
30 # test LIMIT support
31 {
32   my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
33   drop_test_schema($schema);
34   create_test_schema($schema);
35   for (1..6) {
36     $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
37   }
38   my $it = $schema->resultset('Artist')->search( {},
39     { rows => 3,
40       offset => 2,
41       order_by => 'artistid' }
42   );
43   is( $it->count, 3, "LIMIT count ok" );  # ask for 3 rows out of 6 artists
44   is( $it->next->name, "Artist 3", "iterator->next ok" );
45   $it->next;
46   $it->next;
47   $it->next;
48   is( $it->next, undef, "next past end of resultset ok" );
49
50   # Limit with select-lock
51   lives_ok {
52     $schema->txn_do (sub {
53       isa_ok (
54         $schema->resultset('Artist')->find({artistid => 1}, {for => 'update', rows => 1}),
55         'DBICTest::Schema::Artist',
56       );
57     });
58   } 'Limited FOR UPDATE select works';
59 }
60
61 # check if we indeed do support stuff
62 my $test_server_supports_insert_returning = do {
63
64   my $si = DBICTest::Schema->connect($dsn, $user, $pass)->storage->_server_info;
65   die "Unparseable Pg server version: $si->{dbms_version}\n"
66     unless $si->{normalized_dbms_version};
67
68   $si->{normalized_dbms_version} < 8.002 ? 0 : 1;
69 };
70 is (
71   DBICTest::Schema->connect($dsn, $user, $pass)->storage->_use_insert_returning,
72   $test_server_supports_insert_returning,
73   'insert returning capability guessed correctly'
74 );
75
76 my $schema;
77 for my $use_insert_returning ($test_server_supports_insert_returning
78   ? (0,1)
79   : (0)
80 ) {
81
82   # doing it here instead of the actual class to keep the main thing under dfs
83   # and thus keep catching false positives (so far none, but one never knows)
84   mro::set_mro("DBICTest::Schema", "c3");
85
86   my $old_connection = DBICTest::Schema->can('connection');
87
88   no warnings qw/once redefine/;
89   local *DBICTest::Schema::connection = set_subname 'DBICTest::Schema::connection' => sub {
90     my $s = shift->$old_connection(@_);
91     $s->storage->_use_insert_returning ($use_insert_returning);
92     $s;
93   };
94
95 ### test capability override
96   {
97     my $s = DBICTest::Schema->connect($dsn, $user, $pass);
98
99     ok (!$s->storage->_dbh, 'definitely not connected');
100
101     ok (
102       ! ($s->storage->_use_insert_returning xor $use_insert_returning),
103       'insert returning capability set correctly',
104     );
105     ok (!$s->storage->_dbh, 'still not connected (capability override works)');
106   }
107
108 ### connect, create postgres-specific test schema
109
110   $schema = DBICTest::Schema->connect($dsn, $user, $pass);
111   $schema->storage->ensure_connected;
112
113   drop_test_schema($schema);
114   create_test_schema($schema);
115
116 ### begin main tests
117
118 # run a BIG bunch of tests for last-insert-id / Auto-PK / sequence
119 # discovery
120   run_apk_tests($schema); #< older set of auto-pk tests
121   run_extended_apk_tests($schema); #< new extended set of auto-pk tests
122
123
124 ######## test the pg-specific syntax from https://rt.cpan.org/Ticket/Display.html?id=99503
125   lives_ok {
126     is(
127       $schema->resultset('Artist')->search({ artistid => { -in => \ '(select 4) union (select 5)' } })->count,
128       2,
129       'Two expected artists found on subselect union within IN',
130     );
131   };
132
133 ### type_info tests
134
135   my $test_type_info = {
136       'artistid' => {
137           'data_type' => 'integer',
138           'is_nullable' => 0,
139           'size' => 4,
140       },
141       'name' => {
142           'data_type' => 'character varying',
143           'is_nullable' => 1,
144           'size' => 100,
145           'default_value' => undef,
146       },
147       'rank' => {
148           'data_type' => 'integer',
149           'is_nullable' => 0,
150           'size' => 4,
151           'default_value' => 13,
152
153       },
154       'charfield' => {
155           'data_type' => 'character',
156           'is_nullable' => 1,
157           'size' => 10,
158           'default_value' => undef,
159       },
160       'arrayfield' => {
161           'data_type' => 'integer[]',
162           'is_nullable' => 1,
163           'size' => undef,
164           'default_value' => undef,
165       },
166   };
167
168   my $type_info = $schema->storage->columns_info_for('dbic_t_schema.artist');
169   my $artistid_defval = delete $type_info->{artistid}->{default_value};
170
171   # The curor info is too radically different from what is in the column_info
172   # call - just punt it (DBD::SQLite tests the codepath plenty enough)
173   unless (DBIx::Class::_ENV_::STRESSTEST_COLUMN_INFO_UNAWARE_STORAGE) {
174     like(
175       $artistid_defval,
176       qr/^nextval\('([^\.]*\.){0,1}artist_artistid_seq'::(?:text|regclass)\)/,
177       'columns_info_for - sequence matches Pg get_autoinc_seq expectations'
178     );
179
180     is_deeply($type_info, $test_type_info,
181             'columns_info_for - column data types');
182   }
183
184 ####### Array tests
185
186   BEGIN {
187     package DBICTest::Schema::ArrayTest;
188     push @main::test_classes, __PACKAGE__;
189
190     use strict;
191     use warnings;
192     use base 'DBICTest::BaseResult';
193
194     __PACKAGE__->table('dbic_t_schema.array_test');
195     __PACKAGE__->add_columns(qw/id arrayfield/);
196     __PACKAGE__->column_info_from_storage(1);
197     __PACKAGE__->set_primary_key('id');
198
199   }
200   SKIP: {
201     skip "Need DBD::Pg 2.9.2 or newer for array tests", 4 if $DBD::Pg::VERSION < 2.009002;
202
203     my $arr_rs = $schema->resultset('ArrayTest');
204
205     lives_ok {
206       $arr_rs->create({
207         arrayfield => [1, 2],
208       });
209     } 'inserting arrayref as pg array data';
210
211     lives_ok {
212       $arr_rs->update({
213         arrayfield => [3, 4],
214       });
215     } 'updating arrayref as pg array data';
216
217     $arr_rs->create({
218       arrayfield => [5, 6],
219     });
220
221     lives_ok {
222       $schema->populate('ArrayTest', [
223         [ qw/arrayfield/ ],
224         [ [0,0]          ],
225       ]);
226     } 'inserting arrayref using void ctx populate';
227
228     # Search using arrays
229     lives_ok {
230       is_deeply (
231         $arr_rs->search({ arrayfield => { -value => [3,4] } })->first->arrayfield,
232         [3,4],
233         'Array value matches'
234       );
235     } 'searching by arrayref';
236
237     lives_ok {
238       is_deeply (
239         $arr_rs->search({ arrayfield => { '=' => { -value => [3,4] }} })->first->arrayfield,
240         [3,4],
241         'Array value matches explicit equal'
242       );
243     } 'searching by arrayref (explicit equal sign)';
244
245     lives_ok {
246       is_deeply (
247         $arr_rs->search({ arrayfield => { '>' => { -value => [3,1] }} })->first->arrayfield,
248         [3,4],
249         'Array value matches greater than'
250       );
251     } 'searching by arrayref (greater than)';
252
253     lives_ok {
254       is (
255         $arr_rs->search({ arrayfield => { '>' => { -value => [3,7] }} })->count,
256         1,
257         'Greater than search found [5,6]',
258       );
259     } 'searching by arrayref (greater than)';
260
261     # Find using arrays
262     lives_ok {
263       is_deeply (
264         $arr_rs->find({ arrayfield => { -value => [3,4] } })->arrayfield,
265         [3,4],
266         'Array value matches implicit equal'
267       );
268     } 'find by arrayref';
269
270     lives_ok {
271       is_deeply (
272         $arr_rs->find({ arrayfield => { '=' => { -value => [3,4] }} })->arrayfield,
273         [3,4],
274         'Array value matches explicit equal'
275       );
276     } 'find by arrayref (equal)';
277
278     # test inferred condition for creation
279     for my $cond (
280       { -value => [3,4] },
281       \[ '= ?' => [3, 4] ],
282     ) {
283       local $TODO = 'No introspection of complex literal conditions :('
284         if is_literal_value $cond;
285
286
287       my $arr_rs_cond = $arr_rs->search({ arrayfield => $cond });
288
289       my $row = $arr_rs_cond->create({});
290       is_deeply ($row->arrayfield, [3,4], 'Array value taken from $rs condition');
291       $row->discard_changes;
292       is_deeply ($row->arrayfield, [3,4], 'Array value made it to storage');
293     }
294
295     my $arr = [ 1..10 ];
296     # exercise the creation-logic even more (akin to t/100populate.t)
297     for my $insert_value (
298       $arr,
299       { -value => $arr },
300       \[ '?', $arr ],
301     ) {
302       $arr_rs->delete;
303
304       my @objs = (
305         $arr_rs->create({ arrayfield => $insert_value }),
306         $arr_rs->populate([ { arrayfield => $insert_value } ]),
307         $arr_rs->populate([ ['arrayfield'], [ $insert_value ] ]),
308       );
309
310       my $loose_obj = $arr_rs->new({ arrayfield => $insert_value });
311
312       unless (is_literal_value $insert_value) {
313         is_deeply( $_->arrayfield, $arr, 'array value preserved during set_columns' )
314           for ($loose_obj, @objs)
315       }
316
317       push @objs, $loose_obj->insert;
318
319       $_->discard_changes for @objs;
320       is_deeply( $_->arrayfield, $arr, 'array value correct after discard_changes' )
321         for (@objs);
322
323       # insert couple more in void ctx
324       $arr_rs->populate([ { arrayfield => $insert_value } ]);
325       $arr_rs->populate([ ['arrayfield'], [ $insert_value ] ]);
326
327       # should have a total of 6 now, all pristine
328       my @retrieved_objs = $arr_rs->search({
329         arrayfield => ref $insert_value eq 'ARRAY'
330           ? { -value => $insert_value }
331           : { '=' => $insert_value }
332       })->all;
333       is scalar @retrieved_objs, 6, 'Correct count of inserted rows';
334       is_deeply( $_->arrayfield, $arr, 'array value correct after storage retrieval' )
335         for (@retrieved_objs);
336     }
337   }
338
339 ########## Case check
340
341   BEGIN {
342     package DBICTest::Schema::Casecheck;
343     push @main::test_classes, __PACKAGE__;
344
345     use strict;
346     use warnings;
347     use base 'DBIx::Class::Core';
348
349     __PACKAGE__->table('dbic_t_schema.casecheck');
350     __PACKAGE__->add_columns(qw/id name NAME uc_name/);
351     __PACKAGE__->column_info_from_storage(1);
352     __PACKAGE__->set_primary_key('id');
353   }
354
355   my $name_info = $schema->source('Casecheck')->column_info( 'name' );
356   is( $name_info->{size}, 1, "Case sensitive matching info for 'name'" );
357
358   my $NAME_info = $schema->source('Casecheck')->column_info( 'NAME' );
359   is( $NAME_info->{size}, 2, "Case sensitive matching info for 'NAME'" );
360
361   my $uc_name_info = $schema->source('Casecheck')->column_info( 'uc_name' );
362   is( $uc_name_info->{size}, 3, "Case insensitive matching info for 'uc_name'" );
363
364
365 ## Test ResultSet->update
366 my $artist = $schema->resultset('Artist')->first;
367 my $cds = $artist->cds_unordered->search({
368     year => { '!=' => 2010 }
369 }, { prefetch => 'liner_notes' });
370 lives_ok { $cds->update({ year => '2010' }) } 'Update on prefetched rs';
371
372 ## Test SELECT ... FOR UPDATE
373   SKIP: {
374       skip "Your system does not support unsafe signals (d_sigaction) - unable to run deadlock test", 1
375         unless eval { $Config{d_sigaction} and require POSIX };
376
377       my ($timed_out, $artist2);
378
379       for my $t (
380         {
381           # Make sure that an error was raised, and that the update failed
382           update_lock => 1,
383           test_sub => sub {
384             ok($timed_out, "update from second schema times out");
385             ok($artist2->is_column_changed('name'), "'name' column is still dirty from second schema");
386           },
387         },
388         {
389           # Make sure that an error was NOT raised, and that the update succeeded
390           update_lock => 0,
391           test_sub => sub {
392             ok(! $timed_out, "update from second schema DOES NOT timeout");
393             ok(! $artist2->is_column_changed('name'), "'name' column is NOT dirty from second schema");
394           },
395         },
396       ) {
397         # create a new schema
398         my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
399         $schema2->source("Artist")->name("dbic_t_schema.artist");
400
401         $schema->txn_do( sub {
402           my $rs = $schema->resultset('Artist')->search(
403               {
404                   artistid => 1
405               },
406               $t->{update_lock} ? { for => 'update' } : {}
407           );
408           ok ($rs->count, 'Count works');
409
410           my $artist = $rs->next;
411           is($artist->artistid, 1, "select returns artistid = 1");
412
413           $timed_out = 0;
414
415           eval {
416               # can not use %SIG assignment directly - we need sigaction below
417               # localization to a block still works however
418               local $SIG{ALRM};
419
420               POSIX::sigaction( POSIX::SIGALRM() => POSIX::SigAction->new(
421                 sub { die "DBICTestTimeout" },
422               ));
423
424               $artist2 = $schema2->resultset('Artist')->find(1);
425               $artist2->name('fooey');
426
427               # FIXME - this needs to go away in lieu of a non-retrying runner
428               # ( i.e. after solving RT#47005 )
429               local *DBIx::Class::Storage::DBI::_ping = sub { 1 }, DBIx::Class::_ENV_::OLD_MRO && Class::C3->reinitialize()
430                 if DBIx::Class::_Util::modver_gt_or_eq( 'DBD::Pg' => '3.5.0' );
431
432               alarm(1);
433               $artist2->update;
434           };
435
436           alarm(0);
437
438           if (is_exception($@)) {
439             $timed_out = $@ =~ /DBICTestTimeout/
440               or die $@;
441           }
442         });
443
444         $t->{test_sub}->();
445       }
446   }
447
448
449 ######## other older Auto-pk tests
450
451   $schema->source("SequenceTest")->name("dbic_t_schema.sequence_test");
452   for (1..5) {
453       my $st = $schema->resultset('SequenceTest')->create({ name => 'foo' });
454       is($st->pkid1, $_, "Auto-PK for sequence without default: First primary key");
455       is($st->pkid2, $_ + 9, "Auto-PK for sequence without default: Second primary key");
456       is($st->nonpkid, $_ + 19, "Auto-PK for sequence without default: Non-primary key");
457   }
458   my $st = $schema->resultset('SequenceTest')->create({ name => 'foo', pkid1 => 55 });
459   is($st->pkid1, 55, "Auto-PK for sequence without default: First primary key set manually");
460
461
462 ######## test non-serial auto-pk
463
464   if ($schema->storage->_use_insert_returning) {
465     $schema->source('TimestampPrimaryKey')->name('dbic_t_schema.timestamp_primary_key_test');
466     my $row = $schema->resultset('TimestampPrimaryKey')->create({});
467     ok $row->id;
468   }
469
470 ######## test with_deferred_fk_checks
471
472   $schema->source('CD')->name('dbic_t_schema.cd');
473   $schema->source('Track')->name('dbic_t_schema.track');
474   lives_ok {
475     $schema->storage->with_deferred_fk_checks(sub {
476       $schema->resultset('Track')->create({
477         trackid => 999, cd => 999, position => 1, title => 'deferred FK track'
478       });
479       $schema->resultset('CD')->create({
480         artist => 1, cdid => 999, year => '2003', title => 'deferred FK cd'
481       });
482     });
483   } 'with_deferred_fk_checks code survived';
484
485   is eval { $schema->resultset('Track')->find(999)->title }, 'deferred FK track',
486      'code in with_deferred_fk_checks worked';
487
488   throws_ok {
489     $schema->resultset('Track')->create({
490       trackid => 1, cd => 9999, position => 1, title => 'Track1'
491     });
492   } qr/violates foreign key constraint/i, 'with_deferred_fk_checks is off outside of TXN';
493
494   # rerun the same under with_deferred_fk_checks
495   # it is expected to fail, hence the eval
496   # but it also should not warn
497   warnings_like {
498     eval {
499       $schema->storage->with_deferred_fk_checks(sub {
500         $schema->resultset('Track')->create({
501           trackid => 1, cd => 9999, position => 1, title => 'Track1'
502         });
503       } )
504     };
505
506     like $@, qr/violates foreign key constraint/i,
507       "Still expected exception on deferred failure at commit time";
508
509   } [], 'No warnings on deferred rollback';
510 }
511
512 done_testing;
513
514 END {
515     return unless $schema;
516     drop_test_schema($schema);
517     eapk_drop_all($schema);
518     undef $schema;
519 };
520
521
522 ######### SUBROUTINES
523
524 sub create_test_schema {
525     my $schema = shift;
526     $schema->storage->dbh_do(sub {
527       my (undef,$dbh) = @_;
528
529       local $dbh->{Warn} = 0;
530
531       my $std_artist_table = <<EOS;
532 (
533   artistid serial PRIMARY KEY
534   , name VARCHAR(100)
535   , rank INTEGER NOT NULL DEFAULT '13'
536   , charfield CHAR(10)
537   , arrayfield INTEGER[]
538 )
539 EOS
540
541       $dbh->do("CREATE SCHEMA dbic_t_schema");
542       $dbh->do("CREATE TABLE dbic_t_schema.artist $std_artist_table");
543
544       $dbh->do(<<EOS);
545 CREATE TABLE dbic_t_schema.timestamp_primary_key_test (
546   id timestamp default current_timestamp
547 )
548 EOS
549       $dbh->do(<<EOS);
550 CREATE TABLE dbic_t_schema.cd (
551   cdid int PRIMARY KEY,
552   artist int,
553   title varchar(255),
554   year varchar(4),
555   genreid int,
556   single_track int
557 )
558 EOS
559       $dbh->do(<<EOS);
560 CREATE TABLE dbic_t_schema.track (
561   trackid int,
562   cd int REFERENCES dbic_t_schema.cd(cdid) DEFERRABLE,
563   position int,
564   title varchar(255),
565   last_updated_on date,
566   last_updated_at date
567 )
568 EOS
569
570       $dbh->do(<<EOS);
571 CREATE TABLE dbic_t_schema.sequence_test (
572     pkid1 integer
573     , pkid2 integer
574     , nonpkid integer
575     , name VARCHAR(100)
576     , CONSTRAINT pk PRIMARY KEY(pkid1, pkid2)
577 )
578 EOS
579       $dbh->do("CREATE SEQUENCE pkid1_seq START 1 MAXVALUE 999999 MINVALUE 0");
580       $dbh->do("CREATE SEQUENCE pkid2_seq START 10 MAXVALUE 999999 MINVALUE 0");
581       $dbh->do("CREATE SEQUENCE nonpkid_seq START 20 MAXVALUE 999999 MINVALUE 0");
582       $dbh->do(<<EOS);
583 CREATE TABLE dbic_t_schema.casecheck (
584     id serial PRIMARY KEY
585     , "name" VARCHAR(1)
586     , "NAME" VARCHAR(2)
587     , "UC_NAME" VARCHAR(3)
588 )
589 EOS
590       $dbh->do(<<EOS);
591 CREATE TABLE dbic_t_schema.array_test (
592     id serial PRIMARY KEY
593     , arrayfield INTEGER[]
594 )
595 EOS
596       $dbh->do("CREATE SCHEMA dbic_t_schema_2");
597       $dbh->do("CREATE TABLE dbic_t_schema_2.artist $std_artist_table");
598       $dbh->do("CREATE SCHEMA dbic_t_schema_3");
599       $dbh->do("CREATE TABLE dbic_t_schema_3.artist $std_artist_table");
600       $dbh->do('set search_path=dbic_t_schema,public');
601       $dbh->do("CREATE SCHEMA dbic_t_schema_4");
602       $dbh->do("CREATE SCHEMA dbic_t_schema_5");
603       $dbh->do(<<EOS);
604  CREATE TABLE dbic_t_schema_4.artist
605  (
606    artistid integer not null default nextval('artist_artistid_seq'::regclass) PRIMARY KEY
607    , name VARCHAR(100)
608    , rank INTEGER NOT NULL DEFAULT '13'
609    , charfield CHAR(10)
610    , arrayfield INTEGER[]
611  );
612 EOS
613       $dbh->do('set search_path=public,dbic_t_schema,dbic_t_schema_3');
614       $dbh->do('create sequence public.artist_artistid_seq'); #< in the public schema
615       $dbh->do(<<EOS);
616  CREATE TABLE dbic_t_schema_5.artist
617  (
618    artistid integer not null default nextval('public.artist_artistid_seq'::regclass) PRIMARY KEY
619    , name VARCHAR(100)
620    , rank INTEGER NOT NULL DEFAULT '13'
621    , charfield CHAR(10)
622    , arrayfield INTEGER[]
623  );
624 EOS
625       $dbh->do('set search_path=dbic_t_schema,public');
626   });
627 }
628
629
630
631 sub drop_test_schema {
632     my ( $schema, $warn_exceptions ) = @_;
633
634     $schema->storage->dbh_do(sub {
635         my (undef,$dbh) = @_;
636
637         local $dbh->{Warn} = 0;
638
639         for my $stat (
640                       'DROP SCHEMA dbic_t_schema_5 CASCADE',
641                       'DROP SEQUENCE public.artist_artistid_seq CASCADE',
642                       'DROP SCHEMA dbic_t_schema_4 CASCADE',
643                       'DROP SCHEMA dbic_t_schema CASCADE',
644                       'DROP SEQUENCE pkid1_seq CASCADE',
645                       'DROP SEQUENCE pkid2_seq CASCADE',
646                       'DROP SEQUENCE nonpkid_seq CASCADE',
647                       'DROP SCHEMA dbic_t_schema_2 CASCADE',
648                       'DROP SCHEMA dbic_t_schema_3 CASCADE',
649                      ) {
650             eval { $dbh->do ($stat) };
651             diag $@ if $@ && $warn_exceptions;
652         }
653     });
654 }
655
656
657 ###  auto-pk / last_insert_id / sequence discovery
658 sub run_apk_tests {
659     my $schema = shift;
660
661     # This is in Core now, but it's here just to test that it doesn't break
662     $schema->class('Artist')->load_components('PK::Auto');
663     cmp_ok( $schema->resultset('Artist')->count, '==', 0, 'this should start with an empty artist table');
664
665     # test that auto-pk also works with the defined search path by
666     # un-schema-qualifying the table name
667     apk_t_set($schema,'artist');
668
669     my $unq_new;
670     lives_ok {
671         $unq_new = $schema->resultset('Artist')->create({ name => 'baz' });
672     } 'insert into unqualified, shadowed table succeeds';
673
674     is($unq_new && $unq_new->artistid, 1, "and got correct artistid");
675
676     my @test_schemas = ( [qw| dbic_t_schema_2    1  |],
677                          [qw| dbic_t_schema_3    1  |],
678                          [qw| dbic_t_schema_4    2  |],
679                          [qw| dbic_t_schema_5    1  |],
680                        );
681     foreach my $t ( @test_schemas ) {
682         my ($sch_name, $start_num) = @$t;
683         #test with dbic_t_schema_2
684         apk_t_set($schema,"$sch_name.artist");
685         my $another_new;
686         lives_ok {
687             $another_new = $schema->resultset('Artist')->create({ name => 'Tollbooth Willy'});
688             is( $another_new->artistid,$start_num, "got correct artistid for $sch_name")
689                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
690         } "$sch_name liid 1 did not die"
691             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
692         lives_ok {
693             $another_new = $schema->resultset('Artist')->create({ name => 'Adam Sandler'});
694             is( $another_new->artistid,$start_num+1, "got correct artistid for $sch_name")
695                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
696         } "$sch_name liid 2 did not die"
697             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
698
699     }
700
701     lives_ok {
702         apk_t_set($schema,'dbic_t_schema.artist');
703         my $new = $schema->resultset('Artist')->create({ name => 'foo' });
704         is($new->artistid, 4, "Auto-PK worked");
705         $new = $schema->resultset('Artist')->create({ name => 'bar' });
706         is($new->artistid, 5, "Auto-PK worked");
707     } 'old auto-pk tests did not die either';
708 }
709
710 # sets the artist table name and clears sequence name cache
711 sub apk_t_set {
712     my ( $s, $n ) = @_;
713     $s->source("Artist")->name($n);
714     $s->source('Artist')->column_info('artistid')->{sequence} = undef; #< clear sequence name cache
715 }
716
717
718 ######## EXTENDED AUTO-PK TESTS
719
720 my @eapk_id_columns;
721 BEGIN {
722   package DBICTest::Schema::ExtAPK;
723   push @main::test_classes, __PACKAGE__;
724
725   use strict;
726   use warnings;
727   use base 'DBIx::Class::Core';
728
729   __PACKAGE__->table('apk');
730
731   @eapk_id_columns = qw( id1 id2 id3 id4 );
732   __PACKAGE__->add_columns(
733     map { $_ => { data_type => 'integer', is_auto_increment => 1 } }
734        @eapk_id_columns
735   );
736
737   __PACKAGE__->set_primary_key('id2'); #< note the SECOND column is
738                                        #the primary key
739 }
740
741 my @eapk_schemas;
742 BEGIN{ @eapk_schemas = map "dbic_apk_$_", 0..5 }
743 my %seqs; #< hash of schema.table.col => currval of its (DBIC) primary key sequence
744
745 sub run_extended_apk_tests {
746   my $schema = shift;
747
748   #save the search path and reset it at the end
749   my $search_path_save = eapk_get_search_path($schema);
750
751   eapk_drop_all($schema);
752   %seqs = ();
753
754   # make the test schemas and sequences
755   $schema->storage->dbh_do(sub {
756     my ( undef, $dbh ) = @_;
757
758     $dbh->do("CREATE SCHEMA $_")
759         for @eapk_schemas;
760
761     $dbh->do("CREATE SEQUENCE $eapk_schemas[5].fooseq");
762     $dbh->do("SELECT setval('$eapk_schemas[5].fooseq',400)");
763     $seqs{"$eapk_schemas[1].apk.id2"} = 400;
764
765     $dbh->do("CREATE SEQUENCE $eapk_schemas[4].fooseq");
766     $dbh->do("SELECT setval('$eapk_schemas[4].fooseq',300)");
767     $seqs{"$eapk_schemas[3].apk.id2"} = 300;
768
769     $dbh->do("CREATE SEQUENCE $eapk_schemas[3].fooseq");
770     $dbh->do("SELECT setval('$eapk_schemas[3].fooseq',200)");
771     $seqs{"$eapk_schemas[4].apk.id2"} = 200;
772
773     $dbh->do("SET search_path = ".join ',', reverse @eapk_schemas );
774   });
775
776   # clear our search_path cache
777   $schema->storage->{_pg_search_path} = undef;
778
779   eapk_create( $schema,
780                with_search_path => [0,1],
781              );
782   eapk_create( $schema,
783                with_search_path => [1,0,'public'],
784                nextval => "$eapk_schemas[5].fooseq",
785              );
786   eapk_create( $schema,
787                with_search_path => ['public',0,1],
788                qualify_table => 2,
789              );
790   eapk_create( $schema,
791                with_search_path => [3,1,0,'public'],
792                nextval => "$eapk_schemas[4].fooseq",
793              );
794   eapk_create( $schema,
795                with_search_path => [3,1,0,'public'],
796                nextval => "$eapk_schemas[3].fooseq",
797                qualify_table => 4,
798              );
799
800   eapk_poke( $schema );
801   eapk_poke( $schema, 0 );
802   eapk_poke( $schema, 2 );
803   eapk_poke( $schema, 4 );
804   eapk_poke( $schema, 1 );
805   eapk_poke( $schema, 0 );
806   eapk_poke( $schema, 1 );
807   eapk_poke( $schema );
808   eapk_poke( $schema, 4 );
809   eapk_poke( $schema, 3 );
810   eapk_poke( $schema, 1 );
811   eapk_poke( $schema, 2 );
812   eapk_poke( $schema, 0 );
813
814   # set our search path back
815   eapk_set_search_path( $schema, @$search_path_save );
816 }
817
818 # do a DBIC create on the apk table in the given schema number (which is an
819 # index of @eapk_schemas)
820
821 sub eapk_poke {
822   my ($s, $schema_num) = @_;
823
824   my $schema_name = defined $schema_num
825       ? $eapk_schemas[$schema_num]
826       : '';
827
828   my $schema_name_actual = $schema_name || eapk_find_visible_schema($s);
829
830   $s->source('ExtAPK')->name($schema_name ? $schema_name.'.apk' : 'apk');
831   #< clear sequence name cache
832   $s->source('ExtAPK')->column_info($_)->{sequence} = undef
833       for @eapk_id_columns;
834
835   no warnings 'uninitialized';
836   lives_ok {
837     my $new;
838     for my $inc (1,2,3) {
839       $new = $schema->resultset('ExtAPK')->create({ id1 => 1});
840       my $proper_seqval = ++$seqs{"$schema_name_actual.apk.id2"};
841       is( $new->id2, $proper_seqval, "$schema_name_actual.apk.id2 correct inc $inc" )
842           or eapk_seq_diag($s,$schema_name);
843       $new->discard_changes;
844       is( $new->id1, 1 );
845       for my $id ('id3','id4') {
846         my $proper_seqval = ++$seqs{"$schema_name_actual.apk.$id"};
847         is( $new->$id, $proper_seqval, "$schema_name_actual.apk.$id correct inc $inc" )
848             or eapk_seq_diag($s,$schema_name);
849       }
850     }
851   } "create in schema '$schema_name' lives"
852       or eapk_seq_diag($s,$schema_name);
853 }
854
855 # print diagnostic info on which sequences were found in the ExtAPK
856 # class
857 sub eapk_seq_diag {
858     my $s = shift;
859     my $schema = shift || eapk_find_visible_schema($s);
860
861     diag "$schema.apk sequences: ",
862         join(', ',
863              map "$_:".($s->source('ExtAPK')->column_info($_)->{sequence} || '<none>'),
864              @eapk_id_columns
865             );
866 }
867
868 # get the postgres search path as an arrayref
869 sub eapk_get_search_path {
870     my ( $s ) = @_;
871     # cache the search path as ['schema','schema',...] in the storage
872     # obj
873
874     return $s->storage->dbh_do(sub {
875         my (undef, $dbh) = @_;
876         my @search_path;
877         my ($sp_string) = $dbh->selectrow_array('SHOW search_path');
878         while ( $sp_string =~ s/("[^"]+"|[^,]+),?// ) {
879             unless( defined $1 and length $1 ) {
880                 die "search path sanity check failed: '$1'";
881             }
882             push @search_path, $1;
883         }
884         \@search_path
885     });
886 }
887 sub eapk_set_search_path {
888     my ($s,@sp) = @_;
889     my $sp = join ',',@sp;
890     $s->storage->dbh_do( sub { $_[1]->do("SET search_path = $sp") } );
891 }
892
893 # create the apk table in the given schema, can set whether the table name is qualified, what the nextval is for the second ID
894 sub eapk_create {
895     my ($schema, %a) = @_;
896
897     $schema->storage->dbh_do(sub {
898         my (undef,$dbh) = @_;
899
900         my $searchpath_save;
901         if ( $a{with_search_path} ) {
902             ($searchpath_save) = $dbh->selectrow_array('SHOW search_path');
903
904             my $search_path = join ',',map {/\D/ ? $_ : $eapk_schemas[$_]} @{$a{with_search_path}};
905
906             $dbh->do("SET search_path = $search_path");
907         }
908
909         my $table_name = $a{qualify_table}
910             ? ($eapk_schemas[$a{qualify_table}] || die). ".apk"
911             : 'apk';
912         local $_[1]->{Warn} = 0;
913
914         my $id_def = $a{nextval}
915             ? "integer not null default nextval('$a{nextval}'::regclass)"
916             : 'serial';
917         $dbh->do(<<EOS);
918 CREATE TABLE $table_name (
919   id1 serial
920   , id2 $id_def
921   , id3 serial primary key
922   , id4 serial
923 )
924 EOS
925
926         if( $searchpath_save ) {
927             $dbh->do("SET search_path = $searchpath_save");
928         }
929     });
930 }
931
932 sub eapk_drop_all {
933     my ( $schema, $warn_exceptions ) = @_;
934
935     $schema->storage->dbh_do(sub {
936         my (undef,$dbh) = @_;
937
938         local $dbh->{Warn} = 0;
939
940         # drop the test schemas
941         for (@eapk_schemas ) {
942             eval{ $dbh->do("DROP SCHEMA $_ CASCADE") };
943             diag $@ if $@ && $warn_exceptions;
944         }
945
946
947     });
948 }
949
950 sub eapk_find_visible_schema {
951     my ($s) = @_;
952
953     my ($schema) =
954         $s->storage->dbh_do(sub {
955             $_[1]->selectrow_array(<<EOS);
956 SELECT n.nspname
957 FROM pg_catalog.pg_namespace n
958 JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid
959 WHERE c.relname = 'apk'
960   AND pg_catalog.pg_table_is_visible(c.oid)
961 EOS
962         });
963     return $schema;
964 }