Merge 'trunk' into 'pg_unqualified_schema'
[dbsrgits/DBIx-Class.git] / t / 72pg.t
1 use strict;
2 use warnings;
3
4 use Test::More;
5 use Test::Exception;
6 use lib qw(t/lib);
7 use DBICTest;
8
9
10 my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_PG_${_}" } qw/DSN USER PASS/};
11
12 plan skip_all => <<EOM unless $dsn && $user;
13 Set \$ENV{DBICTEST_PG_DSN}, _USER and _PASS to run this test
14 ( NOTE: This test drops and creates tables called 'artist', 'casecheck',
15   'array_test' and 'sequence_test' as well as following sequences:
16   'pkid1_seq', 'pkid2_seq' and 'nonpkid_seq''.  as well as following
17   schemas: 'testschema', 'anothertestschema', 'yetanothertestschema',
18   'unq_nextval_schema', and 'unq_nextval_schema2'
19 )
20 EOM
21
22 ### load any test classes that are defined further down in the file
23
24 our @test_classes; #< array that will be pushed into by test classes defined in this file
25 DBICTest::Schema->load_classes( map {s/.+:://;$_} @test_classes ) if @test_classes;
26
27
28 ###  pre-connect tests
29 {
30   my $s = DBICTest::Schema->connect($dsn, $user, $pass);
31
32   ok (!$s->storage->_dbh, 'definitely not connected');
33
34   # Check that datetime_parser returns correctly before we explicitly connect.
35  SKIP: {
36       eval { require DateTime::Format::Pg };
37       skip "DateTime::Format::Pg required", 2 if $@;
38
39       my $store = ref $s->storage;
40       is($store, 'DBIx::Class::Storage::DBI', 'Started with generic storage');
41
42       my $parser = $s->storage->datetime_parser;
43       is( $parser, 'DateTime::Format::Pg', 'datetime_parser is as expected');
44   }
45
46
47   # make sure sqlt_type overrides work (::Storage::DBI::Pg does this)
48   is ($s->storage->sqlt_type, 'PostgreSQL', 'sqlt_type correct pre-connection');
49 }
50
51 ### connect, create postgres-specific test schema
52
53 my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
54 my $dbh = $schema->storage->dbh;
55
56 drop_test_schema($dbh, 'no warn');
57 create_test_schema($dbh);
58
59 ### begin main tests
60
61 ###  auto-pk / last_insert_id / sequence discovery
62 {
63     local $SIG{__WARN__} = sub {};
64     _cleanup ($schema);
65
66     $schema->source("Artist")->name("testschema.artist");
67
68     # This is in Core now, but it's here just to test that it doesn't break
69     $schema->class('Artist')->load_components('PK::Auto');
70     cmp_ok( $schema->resultset('Artist')->count, '==', 0, 'this should start with an empty artist table');
71
72     # test that auto-pk also works with the defined search path by
73     # un-schema-qualifying the table name
74     my $artist_name_save = $schema->source("Artist")->name;
75     $schema->source("Artist")->name("artist");
76
77     my $unq_new;
78     lives_ok {
79         $unq_new = $schema->resultset('Artist')->create({ name => 'baz' });
80     } 'insert into unqualified, shadowed table succeeds';
81
82     is($unq_new && $unq_new->artistid, 1, "and got correct artistid");
83
84     my @test_schemas = ( [qw| anothertestschema    1      |],
85                          [qw| yetanothertestschema 1      |],
86                        );
87     foreach my $t ( @test_schemas ) {
88         my ($sch_name, $start_num) = @$t;
89         #test with anothertestschema
90         $schema->source('Artist')->name("$sch_name.artist");
91         $schema->source('Artist')->column_info('artistid')->{sequence} = undef; #< clear sequence name cache
92         my $another_new;
93         lives_ok {
94             $another_new = $schema->resultset('Artist')->create({ name => 'Tollbooth Willy'});
95             is( $another_new->artistid,$start_num, "got correct artistid for $sch_name")
96                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
97         } "$sch_name liid 1 did not die"
98             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
99         lives_ok {
100             $another_new = $schema->resultset('Artist')->create({ name => 'Adam Sandler'});
101             is( $another_new->artistid,$start_num+1, "got correct artistid for $sch_name")
102                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
103         } "$sch_name liid 2 did not die"
104             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
105
106     }
107
108
109     my @todo_schemas = (
110                         [qw| unq_nextval_schema   2 |],
111                         [qw| unq_nextval_schema2  1 |],
112                        );
113
114     foreach my $t ( @todo_schemas ) {
115         my ($sch_name, $start_num) = @$t;
116
117         #test with anothertestschema
118         $schema->source('Artist')->name("$sch_name.artist");
119         $schema->source('Artist')->column_info('artistid')->{sequence} = undef; #< clear sequence name cache
120         my $another_new;
121         lives_ok {
122             $another_new = $schema->resultset('Artist')->create({ name => 'Tollbooth Willy'});
123             is( $another_new->artistid,$start_num, "got correct artistid for $sch_name")
124                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
125         } "$sch_name liid 1 did not die"
126             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
127
128         lives_ok {
129             $another_new = $schema->resultset('Artist')->create({ name => 'Adam Sandler'});
130             is( $another_new->artistid,$start_num+1, "got correct artistid for $sch_name")
131                 or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
132         } "$sch_name liid 2 did not die"
133             or diag "USED SEQUENCE: ".($schema->source('Artist')->column_info('artistid')->{sequence} || '<none>');
134     }
135
136     $schema->source('Artist')->column_info('artistid')->{sequence} = undef; #< clear sequence name cache
137     $schema->source("Artist")->name($artist_name_save);
138 }
139
140 lives_ok {
141     my $new = $schema->resultset('Artist')->create({ name => 'foo' });
142     is($new->artistid, 4, "Auto-PK worked");
143     $new = $schema->resultset('Artist')->create({ name => 'bar' });
144     is($new->artistid, 5, "Auto-PK worked");
145 } 'old auto-pk tests did not die either';
146
147
148
149 ### type_info tests
150
151 my $test_type_info = {
152     'artistid' => {
153         'data_type' => 'integer',
154         'is_nullable' => 0,
155         'size' => 4,
156     },
157     'name' => {
158         'data_type' => 'character varying',
159         'is_nullable' => 1,
160         'size' => 100,
161         'default_value' => undef,
162     },
163     'rank' => {
164         'data_type' => 'integer',
165         'is_nullable' => 0,
166         'size' => 4,
167         'default_value' => 13,
168
169     },
170     'charfield' => {
171         'data_type' => 'character',
172         'is_nullable' => 1,
173         'size' => 10,
174         'default_value' => undef,
175     },
176     'arrayfield' => {
177         'data_type' => 'integer[]',
178         'is_nullable' => 1,
179         'size' => undef,
180         'default_value' => undef,
181     },
182 };
183
184 my $type_info = $schema->storage->columns_info_for('testschema.artist');
185 my $artistid_defval = delete $type_info->{artistid}->{default_value};
186 like($artistid_defval,
187      qr/^nextval\('([^\.]*\.){0,1}artist_artistid_seq'::(?:text|regclass)\)/,
188      'columns_info_for - sequence matches Pg get_autoinc_seq expectations');
189 is_deeply($type_info, $test_type_info,
190           'columns_info_for - column data types');
191
192
193
194
195 ####### Array tests
196
197 BEGIN {
198   package DBICTest::Schema::ArrayTest;
199   push @main::test_classes, __PACKAGE__;
200
201   use strict;
202   use warnings;
203   use base 'DBIx::Class';
204
205   __PACKAGE__->load_components(qw/Core/);
206   __PACKAGE__->table('testschema.array_test');
207   __PACKAGE__->add_columns(qw/id arrayfield/);
208   __PACKAGE__->column_info_from_storage(1);
209   __PACKAGE__->set_primary_key('id');
210
211 }
212 SKIP: {
213   skip "Need DBD::Pg 2.9.2 or newer for array tests", 4 if $DBD::Pg::VERSION < 2.009002;
214
215   lives_ok {
216     $schema->resultset('ArrayTest')->create({
217       arrayfield => [1, 2],
218     });
219   } 'inserting arrayref as pg array data';
220
221   lives_ok {
222     $schema->resultset('ArrayTest')->update({
223       arrayfield => [3, 4],
224     });
225   } 'updating arrayref as pg array data';
226
227   $schema->resultset('ArrayTest')->create({
228     arrayfield => [5, 6],
229   });
230
231   my $count;
232   lives_ok {
233     $count = $schema->resultset('ArrayTest')->search({
234       arrayfield => \[ '= ?' => [arrayfield => [3, 4]] ],   #Todo anything less ugly than this?
235     })->count;
236   } 'comparing arrayref to pg array data does not blow up';
237   is($count, 1, 'comparing arrayref to pg array data gives correct result');
238 }
239
240
241
242 ########## Case check
243
244 BEGIN {
245   package DBICTest::Schema::Casecheck;
246   push @main::test_classes, __PACKAGE__;
247
248   use strict;
249   use warnings;
250   use base 'DBIx::Class';
251
252   __PACKAGE__->load_components(qw/Core/);
253   __PACKAGE__->table('testschema.casecheck');
254   __PACKAGE__->add_columns(qw/id name NAME uc_name storecolumn/);
255   __PACKAGE__->column_info_from_storage(1);
256   __PACKAGE__->set_primary_key('id');
257
258   sub store_column {
259     my ($self, $name, $value) = @_;
260     $value = '#'.$value if($name eq "storecolumn");
261     $self->maybe::next::method($name, $value);
262   }
263 }
264
265 # store_column is called once for create() for non sequence columns
266 ok(my $storecolumn = $schema->resultset('Casecheck')->create({'storecolumn' => 'a'}));
267 is($storecolumn->storecolumn, '#a'); # was '##a'
268
269 my $name_info = $schema->source('Casecheck')->column_info( 'name' );
270 is( $name_info->{size}, 1, "Case sensitive matching info for 'name'" );
271
272 my $NAME_info = $schema->source('Casecheck')->column_info( 'NAME' );
273 is( $NAME_info->{size}, 2, "Case sensitive matching info for 'NAME'" );
274
275 my $uc_name_info = $schema->source('Casecheck')->column_info( 'uc_name' );
276 is( $uc_name_info->{size}, 3, "Case insensitive matching info for 'uc_name'" );
277
278
279
280
281 ## Test SELECT ... FOR UPDATE
282
283 my $HaveSysSigAction = eval "require Sys::SigAction" && !$@;
284 if( $HaveSysSigAction ) {
285     Sys::SigAction->import( 'set_sig_handler' );
286 }
287 SKIP: {
288     skip "Sys::SigAction is not available", 3 unless $HaveSysSigAction;
289     # create a new schema
290     my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
291     $schema2->source("Artist")->name("testschema.artist");
292
293     $schema->txn_do( sub {
294         my $artist = $schema->resultset('Artist')->search(
295             {
296                 artistid => 1
297             },
298             {
299                 for => 'update'
300             }
301         )->first;
302         is($artist->artistid, 1, "select for update returns artistid = 1");
303
304         my $artist_from_schema2;
305         my $error_ok = 0;
306         eval {
307             my $h = set_sig_handler( 'ALRM', sub { die "DBICTestTimeout" } );
308             alarm(2);
309             $artist_from_schema2 = $schema2->resultset('Artist')->find(1);
310             $artist_from_schema2->name('fooey');
311             $artist_from_schema2->update;
312             alarm(0);
313         };
314         if (my $e = $@) {
315             $error_ok = $e =~ /DBICTestTimeout/;
316         }
317
318         # Make sure that an error was raised, and that the update failed
319         ok($error_ok, "update from second schema times out");
320         ok($artist_from_schema2->is_column_changed('name'), "'name' column is still dirty from second schema");
321     });
322 }
323
324 SKIP: {
325     skip "Sys::SigAction is not available", 3 unless $HaveSysSigAction;
326     # create a new schema
327     my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
328     $schema2->source("Artist")->name("testschema.artist");
329
330     $schema->txn_do( sub {
331         my $artist = $schema->resultset('Artist')->search(
332             {
333                 artistid => 1
334             },
335         )->first;
336         is($artist->artistid, 1, "select for update returns artistid = 1");
337
338         my $artist_from_schema2;
339         my $error_ok = 0;
340         eval {
341             my $h = set_sig_handler( 'ALRM', sub { die "DBICTestTimeout" } );
342             alarm(2);
343             $artist_from_schema2 = $schema2->resultset('Artist')->find(1);
344             $artist_from_schema2->name('fooey');
345             $artist_from_schema2->update;
346             alarm(0);
347         };
348         if (my $e = $@) {
349             $error_ok = $e =~ /DBICTestTimeout/;
350         }
351
352         # Make sure that an error was NOT raised, and that the update succeeded
353         ok(! $error_ok, "update from second schema DOES NOT timeout");
354         ok(! $artist_from_schema2->is_column_changed('name'), "'name' column is NOT dirty from second schema");
355     });
356 }
357
358
359 ######## other Auto-pk tests
360
361 $schema->source("SequenceTest")->name("testschema.sequence_test");
362 for (1..5) {
363     my $st = $schema->resultset('SequenceTest')->create({ name => 'foo' });
364     is($st->pkid1, $_, "Oracle Auto-PK without trigger: First primary key");
365     is($st->pkid2, $_ + 9, "Oracle Auto-PK without trigger: Second primary key");
366     is($st->nonpkid, $_ + 19, "Oracle Auto-PK without trigger: Non-primary key");
367 }
368 my $st = $schema->resultset('SequenceTest')->create({ name => 'foo', pkid1 => 55 });
369 is($st->pkid1, 55, "Oracle Auto-PK without trigger: First primary key set manually");
370
371 sub _cleanup {
372   my $schema = shift or return;
373   local $SIG{__WARN__} = sub {};
374
375 exit;
376 END { drop_test_schema($dbh) }
377
378
379 ######### SUBROUTINES
380
381 sub create_test_schema {
382     my $dbh = shift;
383
384     local $SIG{__WARN__} = sub {};
385
386     my $std_artist_table = <<EOS;
387 (
388   artistid serial PRIMARY KEY
389   , name VARCHAR(100)
390   , rank INTEGER NOT NULL DEFAULT '13'
391   , charfield CHAR(10)
392   , arrayfield INTEGER[]
393 )
394 EOS
395
396     $dbh->do("CREATE SCHEMA testschema");
397     $dbh->do("CREATE TABLE testschema.artist $std_artist_table");
398     $dbh->do(<<EOS);
399 CREATE TABLE testschema.sequence_test (
400     pkid1 integer
401     , pkid2 integer
402     , nonpkid integer
403     , name VARCHAR(100)
404     , CONSTRAINT pk PRIMARY KEY(pkid1, pkid2)
405 )
406 EOS
407     $dbh->do("CREATE SEQUENCE pkid1_seq START 1 MAXVALUE 999999 MINVALUE 0");
408     $dbh->do("CREATE SEQUENCE pkid2_seq START 10 MAXVALUE 999999 MINVALUE 0");
409     $dbh->do("CREATE SEQUENCE nonpkid_seq START 20 MAXVALUE 999999 MINVALUE 0");
410     $dbh->do(<<EOS);
411 CREATE TABLE testschema.casecheck (
412     id serial PRIMARY KEY
413     , "name" VARCHAR(1)
414     , "NAME" VARCHAR(2)
415     , "UC_NAME" VARCHAR(3)
416     , "storecolumn" VARCHAR(10)
417 )
418 EOS
419     $dbh->do(<<EOS);
420 CREATE TABLE testschema.array_test (
421     id serial PRIMARY KEY
422     , arrayfield INTEGER[]
423 )
424 EOS
425     $dbh->do("CREATE SCHEMA anothertestschema");
426     $dbh->do("CREATE TABLE anothertestschema.artist $std_artist_table");
427     $dbh->do("CREATE SCHEMA yetanothertestschema");
428     $dbh->do("CREATE TABLE yetanothertestschema.artist $std_artist_table");
429     $dbh->do('set search_path=testschema,public');
430     $dbh->do("CREATE SCHEMA unq_nextval_schema");
431     $dbh->do("CREATE SCHEMA unq_nextval_schema2");
432     $dbh->do(<<EOS);
433  CREATE TABLE unq_nextval_schema.artist
434  (
435    artistid integer not null default nextval('artist_artistid_seq'::regclass) PRIMARY KEY
436    , name VARCHAR(100)
437    , rank INTEGER NOT NULL DEFAULT '13'
438    , charfield CHAR(10)
439    , arrayfield INTEGER[]
440  );
441 EOS
442     $dbh->do('set search_path=public,testschema,yetanothertestschema');
443     $dbh->do('create sequence public.artist_artistid_seq'); #< in the public schema
444     $dbh->do(<<EOS);
445  CREATE TABLE unq_nextval_schema2.artist
446  (
447    artistid integer not null default nextval('public.artist_artistid_seq'::regclass) PRIMARY KEY
448    , name VARCHAR(100)
449    , rank INTEGER NOT NULL DEFAULT '13'
450    , charfield CHAR(10)
451    , arrayfield INTEGER[]
452  );
453 EOS
454     $dbh->do('set search_path=testschema,public');
455 }
456
457
458
459 sub drop_test_schema {
460   my ( $dbh, $no_warn ) = @_;
461
462   return unless $dbh->ping;
463
464   for my $stat (
465     'DROP SCHEMA testschema CASCADE',
466     'DROP SCHEMA anothertestschema CASCADE',
467     'DROP SCHEMA yetanothertestschema CASCADE',
468     'DROP SEQUENCE pkid1_seq',
469     'DROP SEQUENCE pkid2_seq',
470     'DROP SEQUENCE nonpkid_seq',
471   ) {
472     eval { $schema->storage->_do_query ($stat) };
473     diag $@ if $@ && !$no_warn;
474   }
475 }
476
477 done_testing;
478
479 END { _cleanup($schema) }