Fix silent failures on autoinc PK without an is_auto_increment attribute
[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     # FIXME - for some reason column_info_from_storage does not properly find
200     # the is_auto_increment setting...
201     __PACKAGE__->column_info('id')->{is_auto_increment} = 1;
202   }
203   SKIP: {
204     skip "Need DBD::Pg 2.9.2 or newer for array tests", 4 if $DBD::Pg::VERSION < 2.009002;
205
206     my $arr_rs = $schema->resultset('ArrayTest');
207
208     lives_ok {
209       $arr_rs->create({
210         arrayfield => [1, 2],
211       });
212     } 'inserting arrayref as pg array data';
213
214     lives_ok {
215       $arr_rs->update({
216         arrayfield => [3, 4],
217       });
218     } 'updating arrayref as pg array data';
219
220     $arr_rs->create({
221       arrayfield => [5, 6],
222     });
223
224     lives_ok {
225       $schema->populate('ArrayTest', [
226         [ qw/arrayfield/ ],
227         [ [0,0]          ],
228       ]);
229     } 'inserting arrayref using void ctx populate';
230
231     # Search using arrays
232     lives_ok {
233       is_deeply (
234         $arr_rs->search({ arrayfield => { -value => [3,4] } })->first->arrayfield,
235         [3,4],
236         'Array value matches'
237       );
238     } 'searching by arrayref';
239
240     lives_ok {
241       is_deeply (
242         $arr_rs->search({ arrayfield => { '=' => { -value => [3,4] }} })->first->arrayfield,
243         [3,4],
244         'Array value matches explicit equal'
245       );
246     } 'searching by arrayref (explicit equal sign)';
247
248     lives_ok {
249       is_deeply (
250         $arr_rs->search({ arrayfield => { '>' => { -value => [3,1] }} })->first->arrayfield,
251         [3,4],
252         'Array value matches greater than'
253       );
254     } 'searching by arrayref (greater than)';
255
256     lives_ok {
257       is (
258         $arr_rs->search({ arrayfield => { '>' => { -value => [3,7] }} })->count,
259         1,
260         'Greater than search found [5,6]',
261       );
262     } 'searching by arrayref (greater than)';
263
264     # Find using arrays
265     lives_ok {
266       is_deeply (
267         $arr_rs->find({ arrayfield => { -value => [3,4] } })->arrayfield,
268         [3,4],
269         'Array value matches implicit equal'
270       );
271     } 'find by arrayref';
272
273     lives_ok {
274       is_deeply (
275         $arr_rs->find({ arrayfield => { '=' => { -value => [3,4] }} })->arrayfield,
276         [3,4],
277         'Array value matches explicit equal'
278       );
279     } 'find by arrayref (equal)';
280
281     # test inferred condition for creation
282     for my $cond (
283       { -value => [3,4] },
284       \[ '= ?' => [3, 4] ],
285     ) {
286       local $TODO = 'No introspection of complex literal conditions :('
287         if is_literal_value $cond;
288
289
290       my $arr_rs_cond = $arr_rs->search({ arrayfield => $cond });
291
292       my $row = $arr_rs_cond->create({});
293       is_deeply ($row->arrayfield, [3,4], 'Array value taken from $rs condition');
294       $row->discard_changes;
295       is_deeply ($row->arrayfield, [3,4], 'Array value made it to storage');
296     }
297
298     my $arr = [ 1..10 ];
299     # exercise the creation-logic even more (akin to t/100populate.t)
300     for my $insert_value (
301       $arr,
302       { -value => $arr },
303       \[ '?', $arr ],
304     ) {
305       $arr_rs->delete;
306
307       my @objs = (
308         $arr_rs->create({ arrayfield => $insert_value }),
309         $arr_rs->populate([ { arrayfield => $insert_value } ]),
310         $arr_rs->populate([ ['arrayfield'], [ $insert_value ] ]),
311       );
312
313       my $loose_obj = $arr_rs->new({ arrayfield => $insert_value });
314
315       unless (is_literal_value $insert_value) {
316         is_deeply( $_->arrayfield, $arr, 'array value preserved during set_columns' )
317           for ($loose_obj, @objs)
318       }
319
320       push @objs, $loose_obj->insert;
321
322       $_->discard_changes for @objs;
323       is_deeply( $_->arrayfield, $arr, 'array value correct after discard_changes' )
324         for (@objs);
325
326       # insert couple more in void ctx
327       $arr_rs->populate([ { arrayfield => $insert_value } ]);
328       $arr_rs->populate([ ['arrayfield'], [ $insert_value ] ]);
329
330       # should have a total of 6 now, all pristine
331       my @retrieved_objs = $arr_rs->search({
332         arrayfield => ref $insert_value eq 'ARRAY'
333           ? { -value => $insert_value }
334           : { '=' => $insert_value }
335       })->all;
336       is scalar @retrieved_objs, 6, 'Correct count of inserted rows';
337       is_deeply( $_->arrayfield, $arr, 'array value correct after storage retrieval' )
338         for (@retrieved_objs);
339     }
340   }
341
342 ########## Case check
343
344   BEGIN {
345     package DBICTest::Schema::Casecheck;
346     push @main::test_classes, __PACKAGE__;
347
348     use strict;
349     use warnings;
350     use base 'DBIx::Class::Core';
351
352     __PACKAGE__->table('dbic_t_schema.casecheck');
353     __PACKAGE__->add_columns(qw/id name NAME uc_name/);
354     __PACKAGE__->column_info_from_storage(1);
355     __PACKAGE__->set_primary_key('id');
356   }
357
358   my $name_info = $schema->source('Casecheck')->column_info( 'name' );
359   is( $name_info->{size}, 1, "Case sensitive matching info for 'name'" );
360
361   my $NAME_info = $schema->source('Casecheck')->column_info( 'NAME' );
362   is( $NAME_info->{size}, 2, "Case sensitive matching info for 'NAME'" );
363
364   my $uc_name_info = $schema->source('Casecheck')->column_info( 'uc_name' );
365   is( $uc_name_info->{size}, 3, "Case insensitive matching info for 'uc_name'" );
366
367
368 ## Test ResultSet->update
369 my $artist = $schema->resultset('Artist')->first;
370 my $cds = $artist->cds_unordered->search({
371     year => { '!=' => 2010 }
372 }, { prefetch => 'liner_notes' });
373 lives_ok { $cds->update({ year => '2010' }) } 'Update on prefetched rs';
374
375 ## Test SELECT ... FOR UPDATE
376   SKIP: {
377       skip "Your system does not support unsafe signals (d_sigaction) - unable to run deadlock test", 1
378         unless eval { $Config{d_sigaction} and require POSIX };
379
380       my ($timed_out, $artist2);
381
382       for my $t (
383         {
384           # Make sure that an error was raised, and that the update failed
385           update_lock => 1,
386           test_sub => sub {
387             ok($timed_out, "update from second schema times out");
388             ok($artist2->is_column_changed('name'), "'name' column is still dirty from second schema");
389           },
390         },
391         {
392           # Make sure that an error was NOT raised, and that the update succeeded
393           update_lock => 0,
394           test_sub => sub {
395             ok(! $timed_out, "update from second schema DOES NOT timeout");
396             ok(! $artist2->is_column_changed('name'), "'name' column is NOT dirty from second schema");
397           },
398         },
399       ) {
400         # create a new schema
401         my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
402         $schema2->source("Artist")->name("dbic_t_schema.artist");
403
404         $schema->txn_do( sub {
405           my $rs = $schema->resultset('Artist')->search(
406               {
407                   artistid => 1
408               },
409               $t->{update_lock} ? { for => 'update' } : {}
410           );
411           ok ($rs->count, 'Count works');
412
413           my $artist = $rs->next;
414           is($artist->artistid, 1, "select returns artistid = 1");
415
416           $timed_out = 0;
417
418           eval {
419               # can not use %SIG assignment directly - we need sigaction below
420               # localization to a block still works however
421               local $SIG{ALRM};
422
423               POSIX::sigaction( POSIX::SIGALRM() => POSIX::SigAction->new(
424                 sub { die "DBICTestTimeout" },
425               ));
426
427               $artist2 = $schema2->resultset('Artist')->find(1);
428               $artist2->name('fooey');
429
430               # FIXME - this needs to go away in lieu of a non-retrying runner
431               # ( i.e. after solving RT#47005 )
432               local *DBIx::Class::Storage::DBI::_ping = sub { 1 }, DBIx::Class::_ENV_::OLD_MRO && Class::C3->reinitialize()
433                 if DBIx::Class::_Util::modver_gt_or_eq( 'DBD::Pg' => '3.5.0' );
434
435               alarm(1);
436               $artist2->update;
437           };
438
439           alarm(0);
440
441           if (is_exception($@)) {
442             $timed_out = $@ =~ /DBICTestTimeout/
443               or die $@;
444           }
445         });
446
447         $t->{test_sub}->();
448       }
449   }
450
451
452 ######## other older Auto-pk tests
453
454   $schema->source("SequenceTest")->name("dbic_t_schema.sequence_test");
455   for (1..5) {
456       my $st = $schema->resultset('SequenceTest')->create({ name => 'foo' });
457       is($st->pkid1, $_, "Auto-PK for sequence without default: First primary key");
458       is($st->pkid2, $_ + 9, "Auto-PK for sequence without default: Second primary key");
459       is($st->nonpkid, $_ + 19, "Auto-PK for sequence without default: Non-primary key");
460   }
461   my $st = $schema->resultset('SequenceTest')->create({ name => 'foo', pkid1 => 55 });
462   is($st->pkid1, 55, "Auto-PK for sequence without default: First primary key set manually");
463
464
465 ######## test non-serial auto-pk
466
467   if ($schema->storage->_use_insert_returning) {
468     $schema->source('TimestampPrimaryKey')->name('dbic_t_schema.timestamp_primary_key_test');
469     my $row = $schema->resultset('TimestampPrimaryKey')->create({});
470     ok $row->id;
471   }
472
473 ######## test with_deferred_fk_checks
474
475   $schema->source('CD')->name('dbic_t_schema.cd');
476   $schema->source('Track')->name('dbic_t_schema.track');
477   lives_ok {
478     $schema->storage->with_deferred_fk_checks(sub {
479       $schema->resultset('Track')->create({
480         trackid => 999, cd => 999, position => 1, title => 'deferred FK track'
481       });
482       $schema->resultset('CD')->create({
483         artist => 1, cdid => 999, year => '2003', title => 'deferred FK cd'
484       });
485     });
486   } 'with_deferred_fk_checks code survived';
487
488   is eval { $schema->resultset('Track')->find(999)->title }, 'deferred FK track',
489      'code in with_deferred_fk_checks worked';
490
491   throws_ok {
492     $schema->resultset('Track')->create({
493       trackid => 1, cd => 9999, position => 1, title => 'Track1'
494     });
495   } qr/violates foreign key constraint/i, 'with_deferred_fk_checks is off outside of TXN';
496
497   # rerun the same under with_deferred_fk_checks
498   # it is expected to fail, hence the eval
499   # but it also should not warn
500   warnings_like {
501     eval {
502       $schema->storage->with_deferred_fk_checks(sub {
503         $schema->resultset('Track')->create({
504           trackid => 1, cd => 9999, position => 1, title => 'Track1'
505         });
506       } )
507     };
508
509     like $@, qr/violates foreign key constraint/i,
510       "Still expected exception on deferred failure at commit time";
511
512   } [], 'No warnings on deferred rollback';
513 }
514
515 done_testing;
516
517 END {
518     return unless $schema;
519     drop_test_schema($schema);
520     eapk_drop_all($schema);
521     undef $schema;
522 };
523
524
525 ######### SUBROUTINES
526
527 sub create_test_schema {
528     my $schema = shift;
529     $schema->storage->dbh_do(sub {
530       my (undef,$dbh) = @_;
531
532       local $dbh->{Warn} = 0;
533
534       my $std_artist_table = <<EOS;
535 (
536   artistid serial PRIMARY KEY
537   , name VARCHAR(100)
538   , rank INTEGER NOT NULL DEFAULT '13'
539   , charfield CHAR(10)
540   , arrayfield INTEGER[]
541 )
542 EOS
543
544       $dbh->do("CREATE SCHEMA dbic_t_schema");
545       $dbh->do("CREATE TABLE dbic_t_schema.artist $std_artist_table");
546
547       $dbh->do(<<EOS);
548 CREATE TABLE dbic_t_schema.timestamp_primary_key_test (
549   id timestamp default current_timestamp
550 )
551 EOS
552       $dbh->do(<<EOS);
553 CREATE TABLE dbic_t_schema.cd (
554   cdid int PRIMARY KEY,
555   artist int,
556   title varchar(255),
557   year varchar(4),
558   genreid int,
559   single_track int
560 )
561 EOS
562       $dbh->do(<<EOS);
563 CREATE TABLE dbic_t_schema.track (
564   trackid int,
565   cd int REFERENCES dbic_t_schema.cd(cdid) DEFERRABLE,
566   position int,
567   title varchar(255),
568   last_updated_on date,
569   last_updated_at date
570 )
571 EOS
572
573       $dbh->do(<<EOS);
574 CREATE TABLE dbic_t_schema.sequence_test (
575     pkid1 integer
576     , pkid2 integer
577     , nonpkid integer
578     , name VARCHAR(100)
579     , CONSTRAINT pk PRIMARY KEY(pkid1, pkid2)
580 )
581 EOS
582       $dbh->do("CREATE SEQUENCE pkid1_seq START 1 MAXVALUE 999999 MINVALUE 0");
583       $dbh->do("CREATE SEQUENCE pkid2_seq START 10 MAXVALUE 999999 MINVALUE 0");
584       $dbh->do("CREATE SEQUENCE nonpkid_seq START 20 MAXVALUE 999999 MINVALUE 0");
585       $dbh->do(<<EOS);
586 CREATE TABLE dbic_t_schema.casecheck (
587     id serial PRIMARY KEY
588     , "name" VARCHAR(1)
589     , "NAME" VARCHAR(2)
590     , "UC_NAME" VARCHAR(3)
591 )
592 EOS
593       $dbh->do(<<EOS);
594 CREATE TABLE dbic_t_schema.array_test (
595     id serial PRIMARY KEY
596     , arrayfield INTEGER[]
597 )
598 EOS
599       $dbh->do("CREATE SCHEMA dbic_t_schema_2");
600       $dbh->do("CREATE TABLE dbic_t_schema_2.artist $std_artist_table");
601       $dbh->do("CREATE SCHEMA dbic_t_schema_3");
602       $dbh->do("CREATE TABLE dbic_t_schema_3.artist $std_artist_table");
603       $dbh->do('set search_path=dbic_t_schema,public');
604       $dbh->do("CREATE SCHEMA dbic_t_schema_4");
605       $dbh->do("CREATE SCHEMA dbic_t_schema_5");
606       $dbh->do(<<EOS);
607  CREATE TABLE dbic_t_schema_4.artist
608  (
609    artistid integer not null default nextval('artist_artistid_seq'::regclass) PRIMARY KEY
610    , name VARCHAR(100)
611    , rank INTEGER NOT NULL DEFAULT '13'
612    , charfield CHAR(10)
613    , arrayfield INTEGER[]
614  );
615 EOS
616       $dbh->do('set search_path=public,dbic_t_schema,dbic_t_schema_3');
617       $dbh->do('create sequence public.artist_artistid_seq'); #< in the public schema
618       $dbh->do(<<EOS);
619  CREATE TABLE dbic_t_schema_5.artist
620  (
621    artistid integer not null default nextval('public.artist_artistid_seq'::regclass) PRIMARY KEY
622    , name VARCHAR(100)
623    , rank INTEGER NOT NULL DEFAULT '13'
624    , charfield CHAR(10)
625    , arrayfield INTEGER[]
626  );
627 EOS
628       $dbh->do('set search_path=dbic_t_schema,public');
629   });
630 }
631
632
633
634 sub drop_test_schema {
635     my ( $schema, $warn_exceptions ) = @_;
636
637     $schema->storage->dbh_do(sub {
638         my (undef,$dbh) = @_;
639
640         local $dbh->{Warn} = 0;
641
642         for my $stat (
643                       'DROP SCHEMA dbic_t_schema_5 CASCADE',
644                       'DROP SEQUENCE public.artist_artistid_seq CASCADE',
645                       'DROP SCHEMA dbic_t_schema_4 CASCADE',
646                       'DROP SCHEMA dbic_t_schema CASCADE',
647                       'DROP SEQUENCE pkid1_seq CASCADE',
648                       'DROP SEQUENCE pkid2_seq CASCADE',
649                       'DROP SEQUENCE nonpkid_seq CASCADE',
650                       'DROP SCHEMA dbic_t_schema_2 CASCADE',
651                       'DROP SCHEMA dbic_t_schema_3 CASCADE',
652                      ) {
653             eval { $dbh->do ($stat) };
654             diag $@ if $@ && $warn_exceptions;
655         }
656     });
657 }
658
659
660 ###  auto-pk / last_insert_id / sequence discovery
661 sub run_apk_tests {
662     my $schema = shift;
663
664     # This is in Core now, but it's here just to test that it doesn't break
665     $schema->class('Artist')->load_components('PK::Auto');
666     cmp_ok( $schema->resultset('Artist')->count, '==', 0, 'this should start with an empty artist table');
667
668     # test that auto-pk also works with the defined search path by
669     # un-schema-qualifying the table name
670     apk_t_set($schema,'artist');
671
672     my $unq_new;
673     lives_ok {
674         $unq_new = $schema->resultset('Artist')->create({ name => 'baz' });
675     } 'insert into unqualified, shadowed table succeeds';
676
677     is($unq_new && $unq_new->artistid, 1, "and got correct artistid");
678
679     my @test_schemas = ( [qw| dbic_t_schema_2    1  |],
680                          [qw| dbic_t_schema_3    1  |],
681                          [qw| dbic_t_schema_4    2  |],
682                          [qw| dbic_t_schema_5    1  |],
683                        );
684     foreach my $t ( @test_schemas ) {
685         my ($sch_name, $start_num) = @$t;
686         #test with dbic_t_schema_2
687         apk_t_set($schema,"$sch_name.artist");
688         my $another_new;
689         lives_ok {
690             $another_new = $schema->resultset('Artist')->create({ name => 'Tollbooth Willy'});
691             is( $another_new->artistid,$start_num, "got correct artistid for $sch_name")
692                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
693         } "$sch_name liid 1 did not die"
694             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
695         lives_ok {
696             $another_new = $schema->resultset('Artist')->create({ name => 'Adam Sandler'});
697             is( $another_new->artistid,$start_num+1, "got correct artistid for $sch_name")
698                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
699         } "$sch_name liid 2 did not die"
700             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
701
702     }
703
704     lives_ok {
705         apk_t_set($schema,'dbic_t_schema.artist');
706         my $new = $schema->resultset('Artist')->create({ name => 'foo' });
707         is($new->artistid, 4, "Auto-PK worked");
708         $new = $schema->resultset('Artist')->create({ name => 'bar' });
709         is($new->artistid, 5, "Auto-PK worked");
710     } 'old auto-pk tests did not die either';
711 }
712
713 # sets the artist table name and clears sequence name cache
714 sub apk_t_set {
715     my ( $s, $n ) = @_;
716     $s->source("Artist")->name($n);
717     $s->source('Artist')->column_info('artistid')->{sequence} = undef; #< clear sequence name cache
718 }
719
720
721 ######## EXTENDED AUTO-PK TESTS
722
723 my @eapk_id_columns;
724 BEGIN {
725   package DBICTest::Schema::ExtAPK;
726   push @main::test_classes, __PACKAGE__;
727
728   use strict;
729   use warnings;
730   use base 'DBIx::Class::Core';
731
732   __PACKAGE__->table('apk');
733
734   @eapk_id_columns = qw( id1 id2 id3 id4 );
735   __PACKAGE__->add_columns(
736     map { $_ => { data_type => 'integer', is_auto_increment => 1 } }
737        @eapk_id_columns
738   );
739
740   __PACKAGE__->set_primary_key('id2'); #< note the SECOND column is
741                                        #the primary key
742 }
743
744 my @eapk_schemas;
745 BEGIN{ @eapk_schemas = map "dbic_apk_$_", 0..5 }
746 my %seqs; #< hash of schema.table.col => currval of its (DBIC) primary key sequence
747
748 sub run_extended_apk_tests {
749   my $schema = shift;
750
751   #save the search path and reset it at the end
752   my $search_path_save = eapk_get_search_path($schema);
753
754   eapk_drop_all($schema);
755   %seqs = ();
756
757   # make the test schemas and sequences
758   $schema->storage->dbh_do(sub {
759     my ( undef, $dbh ) = @_;
760
761     $dbh->do("CREATE SCHEMA $_")
762         for @eapk_schemas;
763
764     $dbh->do("CREATE SEQUENCE $eapk_schemas[5].fooseq");
765     $dbh->do("SELECT setval('$eapk_schemas[5].fooseq',400)");
766     $seqs{"$eapk_schemas[1].apk.id2"} = 400;
767
768     $dbh->do("CREATE SEQUENCE $eapk_schemas[4].fooseq");
769     $dbh->do("SELECT setval('$eapk_schemas[4].fooseq',300)");
770     $seqs{"$eapk_schemas[3].apk.id2"} = 300;
771
772     $dbh->do("CREATE SEQUENCE $eapk_schemas[3].fooseq");
773     $dbh->do("SELECT setval('$eapk_schemas[3].fooseq',200)");
774     $seqs{"$eapk_schemas[4].apk.id2"} = 200;
775
776     $dbh->do("SET search_path = ".join ',', reverse @eapk_schemas );
777   });
778
779   # clear our search_path cache
780   $schema->storage->{_pg_search_path} = undef;
781
782   eapk_create( $schema,
783                with_search_path => [0,1],
784              );
785   eapk_create( $schema,
786                with_search_path => [1,0,'public'],
787                nextval => "$eapk_schemas[5].fooseq",
788              );
789   eapk_create( $schema,
790                with_search_path => ['public',0,1],
791                qualify_table => 2,
792              );
793   eapk_create( $schema,
794                with_search_path => [3,1,0,'public'],
795                nextval => "$eapk_schemas[4].fooseq",
796              );
797   eapk_create( $schema,
798                with_search_path => [3,1,0,'public'],
799                nextval => "$eapk_schemas[3].fooseq",
800                qualify_table => 4,
801              );
802
803   eapk_poke( $schema );
804   eapk_poke( $schema, 0 );
805   eapk_poke( $schema, 2 );
806   eapk_poke( $schema, 4 );
807   eapk_poke( $schema, 1 );
808   eapk_poke( $schema, 0 );
809   eapk_poke( $schema, 1 );
810   eapk_poke( $schema );
811   eapk_poke( $schema, 4 );
812   eapk_poke( $schema, 3 );
813   eapk_poke( $schema, 1 );
814   eapk_poke( $schema, 2 );
815   eapk_poke( $schema, 0 );
816
817   # set our search path back
818   eapk_set_search_path( $schema, @$search_path_save );
819 }
820
821 # do a DBIC create on the apk table in the given schema number (which is an
822 # index of @eapk_schemas)
823
824 sub eapk_poke {
825   my ($s, $schema_num) = @_;
826
827   my $schema_name = defined $schema_num
828       ? $eapk_schemas[$schema_num]
829       : '';
830
831   my $schema_name_actual = $schema_name || eapk_find_visible_schema($s);
832
833   $s->source('ExtAPK')->name($schema_name ? $schema_name.'.apk' : 'apk');
834   #< clear sequence name cache
835   $s->source('ExtAPK')->column_info($_)->{sequence} = undef
836       for @eapk_id_columns;
837
838   no warnings 'uninitialized';
839   lives_ok {
840     my $new;
841     for my $inc (1,2,3) {
842       $new = $schema->resultset('ExtAPK')->create({ id1 => 1});
843       my $proper_seqval = ++$seqs{"$schema_name_actual.apk.id2"};
844       is( $new->id2, $proper_seqval, "$schema_name_actual.apk.id2 correct inc $inc" )
845           or eapk_seq_diag($s,$schema_name);
846       $new->discard_changes;
847       is( $new->id1, 1 );
848       for my $id ('id3','id4') {
849         my $proper_seqval = ++$seqs{"$schema_name_actual.apk.$id"};
850         is( $new->$id, $proper_seqval, "$schema_name_actual.apk.$id correct inc $inc" )
851             or eapk_seq_diag($s,$schema_name);
852       }
853     }
854   } "create in schema '$schema_name' lives"
855       or eapk_seq_diag($s,$schema_name);
856 }
857
858 # print diagnostic info on which sequences were found in the ExtAPK
859 # class
860 sub eapk_seq_diag {
861     my $s = shift;
862     my $schema = shift || eapk_find_visible_schema($s);
863
864     diag "$schema.apk sequences: ",
865         join(', ',
866              map "$_:".($s->source('ExtAPK')->column_info($_)->{sequence} || '<none>'),
867              @eapk_id_columns
868             );
869 }
870
871 # get the postgres search path as an arrayref
872 sub eapk_get_search_path {
873     my ( $s ) = @_;
874     # cache the search path as ['schema','schema',...] in the storage
875     # obj
876
877     return $s->storage->dbh_do(sub {
878         my (undef, $dbh) = @_;
879         my @search_path;
880         my ($sp_string) = $dbh->selectrow_array('SHOW search_path');
881         while ( $sp_string =~ s/("[^"]+"|[^,]+),?// ) {
882             unless( defined $1 and length $1 ) {
883                 die "search path sanity check failed: '$1'";
884             }
885             push @search_path, $1;
886         }
887         \@search_path
888     });
889 }
890 sub eapk_set_search_path {
891     my ($s,@sp) = @_;
892     my $sp = join ',',@sp;
893     $s->storage->dbh_do( sub { $_[1]->do("SET search_path = $sp") } );
894 }
895
896 # create the apk table in the given schema, can set whether the table name is qualified, what the nextval is for the second ID
897 sub eapk_create {
898     my ($schema, %a) = @_;
899
900     $schema->storage->dbh_do(sub {
901         my (undef,$dbh) = @_;
902
903         my $searchpath_save;
904         if ( $a{with_search_path} ) {
905             ($searchpath_save) = $dbh->selectrow_array('SHOW search_path');
906
907             my $search_path = join ',',map {/\D/ ? $_ : $eapk_schemas[$_]} @{$a{with_search_path}};
908
909             $dbh->do("SET search_path = $search_path");
910         }
911
912         my $table_name = $a{qualify_table}
913             ? ($eapk_schemas[$a{qualify_table}] || die). ".apk"
914             : 'apk';
915         local $_[1]->{Warn} = 0;
916
917         my $id_def = $a{nextval}
918             ? "integer not null default nextval('$a{nextval}'::regclass)"
919             : 'serial';
920         $dbh->do(<<EOS);
921 CREATE TABLE $table_name (
922   id1 serial
923   , id2 $id_def
924   , id3 serial primary key
925   , id4 serial
926 )
927 EOS
928
929         if( $searchpath_save ) {
930             $dbh->do("SET search_path = $searchpath_save");
931         }
932     });
933 }
934
935 sub eapk_drop_all {
936     my ( $schema, $warn_exceptions ) = @_;
937
938     $schema->storage->dbh_do(sub {
939         my (undef,$dbh) = @_;
940
941         local $dbh->{Warn} = 0;
942
943         # drop the test schemas
944         for (@eapk_schemas ) {
945             eval{ $dbh->do("DROP SCHEMA $_ CASCADE") };
946             diag $@ if $@ && $warn_exceptions;
947         }
948
949
950     });
951 }
952
953 sub eapk_find_visible_schema {
954     my ($s) = @_;
955
956     my ($schema) =
957         $s->storage->dbh_do(sub {
958             $_[1]->selectrow_array(<<EOS);
959 SELECT n.nspname
960 FROM pg_catalog.pg_namespace n
961 JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid
962 WHERE c.relname = 'apk'
963   AND pg_catalog.pg_table_is_visible(c.oid)
964 EOS
965         });
966     return $schema;
967 }