c1b75b3354b8f6749698780cbf967baa4e5b2962
[dbsrgits/DBIx-Class-Schema-Loader.git] / t / 10_03pg_common.t
1 use strict;
2 use warnings;
3 use utf8;
4 use DBIx::Class::Schema::Loader 'make_schema_at';
5 use DBIx::Class::Schema::Loader::Utils qw/no_warnings slurp_file/;
6 use Test::More;
7 use Test::Exception;
8 use Try::Tiny;
9 use File::Path 'rmtree';
10 use namespace::clean;
11
12 use lib qw(t/lib);
13 use dbixcsl_common_tests ();
14 use dbixcsl_test_dir '$tdir';
15
16 use constant EXTRA_DUMP_DIR => "$tdir/pg_extra_dump";
17
18 my $dsn      = $ENV{DBICTEST_PG_DSN} || '';
19 my $user     = $ENV{DBICTEST_PG_USER} || '';
20 my $password = $ENV{DBICTEST_PG_PASS} || '';
21
22 my $tester = dbixcsl_common_tests->new(
23     vendor      => 'Pg',
24     auto_inc_pk => 'SERIAL NOT NULL PRIMARY KEY',
25     dsn         => $dsn,
26     user        => $user,
27     password    => $password,
28     loader_options  => { preserve_case => 1 },
29     connect_info_opts => {
30         pg_enable_utf8 => 1,
31         on_connect_do  => [ 'SET client_min_messages=WARNING' ],
32     },
33     quote_char  => '"',
34     default_is_deferrable => 0,
35     default_on_clause => 'NO ACTION',
36     data_types  => {
37         # http://www.postgresql.org/docs/7.4/interactive/datatype.html
38         #
39         # Numeric Types
40         boolean     => { data_type => 'boolean' },
41         bool        => { data_type => 'boolean' },
42         'bool default false'
43                     => { data_type => 'boolean', default_value => \'false' },
44         'bool default true'
45                     => { data_type => 'boolean', default_value => \'true' },
46         'bool default 0::bool'
47                     => { data_type => 'boolean', default_value => \'false' },
48         'bool default 1::bool'
49                     => { data_type => 'boolean', default_value => \'true' },
50
51         bigint      => { data_type => 'bigint' },
52         int8        => { data_type => 'bigint' },
53         bigserial   => { data_type => 'bigint', is_auto_increment => 1 },
54         serial8     => { data_type => 'bigint', is_auto_increment => 1 },
55         integer     => { data_type => 'integer' },
56         int         => { data_type => 'integer' },
57         int4        => { data_type => 'integer' },
58         serial      => { data_type => 'integer', is_auto_increment => 1 },
59         serial4     => { data_type => 'integer', is_auto_increment => 1 },
60         smallint    => { data_type => 'smallint' },
61         int2        => { data_type => 'smallint' },
62
63         money       => { data_type => 'money' },
64
65         'double precision' => { data_type => 'double precision' },
66         float8             => { data_type => 'double precision' },
67         real               => { data_type => 'real' },
68         float4             => { data_type => 'real' },
69         'float(24)'        => { data_type => 'real' },
70         'float(25)'        => { data_type => 'double precision' },
71         'float(53)'        => { data_type => 'double precision' },
72         float              => { data_type => 'double precision' },
73
74         numeric        => { data_type => 'numeric' },
75         decimal        => { data_type => 'numeric' },
76         'numeric(6,3)' => { data_type => 'numeric', size => [6,3] },
77         'decimal(6,3)' => { data_type => 'numeric', size => [6,3] },
78
79         # Bit String Types
80         'bit varying(2)' => { data_type => 'varbit', size => 2 },
81         'varbit(2)'      => { data_type => 'varbit', size => 2 },
82         'varbit'         => { data_type => 'varbit' },
83         bit              => { data_type => 'bit', size => 1 },
84         'bit(3)'         => { data_type => 'bit', size => 3 },
85
86         # Network Types
87         inet    => { data_type => 'inet' },
88         cidr    => { data_type => 'cidr' },
89         macaddr => { data_type => 'macaddr' },
90
91         # Geometric Types
92         point   => { data_type => 'point' },
93         line    => { data_type => 'line' },
94         lseg    => { data_type => 'lseg' },
95         box     => { data_type => 'box' },
96         path    => { data_type => 'path' },
97         polygon => { data_type => 'polygon' },
98         circle  => { data_type => 'circle' },
99
100         # Character Types
101         'character varying(2)'           => { data_type => 'varchar', size => 2 },
102         'varchar(2)'                     => { data_type => 'varchar', size => 2 },
103         'character(2)'                   => { data_type => 'char', size => 2 },
104         'char(2)'                        => { data_type => 'char', size => 2 },
105         # check that default null is correctly rewritten
106         'char(3) default null'           => { data_type => 'char', size => 3,
107                                               default_value => \'null' },
108         'character'                      => { data_type => 'char', size => 1 },
109         'char'                           => { data_type => 'char', size => 1 },
110         text                             => { data_type => 'text' },
111         # varchar with no size has unlimited size, we rewrite to 'text'
112         varchar                          => { data_type => 'text',
113                                               original => { data_type => 'varchar' } },
114         # check default null again (to make sure ref is safe)
115         'varchar(3) default null'        => { data_type => 'varchar', size => 3,
116                                               default_value => \'null' },
117
118         # Datetime Types
119         date                             => { data_type => 'date' },
120         interval                         => { data_type => 'interval' },
121         'interval(2)'                    => { data_type => 'interval', size => 2 },
122         time                             => { data_type => 'time' },
123         'time(2)'                        => { data_type => 'time', size => 2 },
124         'time without time zone'         => { data_type => 'time' },
125         'time(2) without time zone'      => { data_type => 'time', size => 2 },
126         'time with time zone'            => { data_type => 'time with time zone' },
127         'time(2) with time zone'         => { data_type => 'time with time zone', size => 2 },
128         timestamp                        => { data_type => 'timestamp' },
129         'timestamp default now()'
130                                          => { data_type => 'timestamp', default_value => \'current_timestamp',
131                                               original => { default_value => \'now()' } },
132         'timestamp(2)'                   => { data_type => 'timestamp', size => 2 },
133         'timestamp without time zone'    => { data_type => 'timestamp' },
134         'timestamp(2) without time zone' => { data_type => 'timestamp', size => 2 },
135
136         'timestamp with time zone'       => { data_type => 'timestamp with time zone' },
137         'timestamp(2) with time zone'    => { data_type => 'timestamp with time zone', size => 2 },
138
139         # Blob Types
140         bytea => { data_type => 'bytea' },
141
142         # Enum Types
143         pg_loader_test_enum => { data_type => 'enum', extra => { custom_type_name => 'pg_loader_test_enum',
144                                                                  list => [ qw/foo bar baz/] } },
145     },
146     pre_create => [
147         q{
148             CREATE TYPE pg_loader_test_enum AS ENUM (
149                 'foo', 'bar', 'baz'
150             )
151         },
152     ],
153     extra       => {
154         create => [
155             q{
156                 CREATE SCHEMA dbicsl_test
157             },
158             q{
159                 CREATE SEQUENCE dbicsl_test.myseq
160             },
161             q{
162                 CREATE TABLE pg_loader_test1 (
163                     id INTEGER NOT NULL DEFAULT nextval('dbicsl_test.myseq') PRIMARY KEY,
164                     value VARCHAR(100)
165                 )
166             },
167             qq{
168                 COMMENT ON TABLE pg_loader_test1 IS 'The\15\12Table ∑'
169             },
170             qq{
171                 COMMENT ON COLUMN pg_loader_test1.value IS 'The\15\12Column'
172             },
173             q{
174                 CREATE TABLE pg_loader_test2 (
175                     id SERIAL PRIMARY KEY,
176                     value VARCHAR(100)
177                 )
178             },
179             q{
180                 COMMENT ON TABLE pg_loader_test2 IS 'very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long comment'
181             },
182             q{
183                 CREATE SCHEMA "dbicsl-test"
184             },
185             q{
186                 CREATE TABLE "dbicsl-test".pg_loader_test4 (
187                     id SERIAL PRIMARY KEY,
188                     value VARCHAR(100)
189                 )
190             },
191             q{
192                 CREATE TABLE "dbicsl-test".pg_loader_test5 (
193                     id SERIAL PRIMARY KEY,
194                     value VARCHAR(100),
195                     four_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id),
196                     CONSTRAINT loader_test5_uniq UNIQUE (four_id)
197                 )
198             },
199             q{
200                 CREATE SCHEMA "dbicsl.test"
201             },
202             q{
203                 CREATE TABLE "dbicsl.test".pg_loader_test5 (
204                     pk SERIAL PRIMARY KEY,
205                     value VARCHAR(100),
206                     four_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id),
207                     CONSTRAINT loader_test5_uniq UNIQUE (four_id)
208                 )
209             },
210             q{
211                 CREATE TABLE "dbicsl.test".pg_loader_test6 (
212                     id SERIAL PRIMARY KEY,
213                     value VARCHAR(100),
214                     pg_loader_test4_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id)
215                 )
216             },
217             q{
218                 CREATE TABLE "dbicsl.test".pg_loader_test7 (
219                     id SERIAL PRIMARY KEY,
220                     value VARCHAR(100),
221                     six_id INTEGER UNIQUE REFERENCES "dbicsl.test".pg_loader_test6 (id)
222                 )
223             },
224             q{
225                 CREATE TABLE "dbicsl-test".pg_loader_test8 (
226                     id SERIAL PRIMARY KEY,
227                     value VARCHAR(100),
228                     pg_loader_test7_id INTEGER REFERENCES "dbicsl.test".pg_loader_test7 (id)
229                 )
230             },
231             # 4 through 8 are used for the multi-schema tests
232             q{
233                 create table pg_loader_test9 (
234                     id bigserial primary key
235                 )
236             },
237             q{
238                 create table pg_loader_test10 (
239                     id bigserial primary key,
240                     nine_id int,
241                     foreign key (nine_id) references pg_loader_test9(id)
242                         on delete restrict on update set null deferrable
243                 )
244             },
245             q{
246                 create view pg_loader_test11 as
247                     select * from pg_loader_test1
248             },
249             q{
250                 create table pg_loader_test12 (
251                     id integer not null,
252                     value integer,
253                     active boolean,
254                     name text
255                 )
256             },
257             q{
258                 create unique index uniq_id_lc_name on pg_loader_test12 (
259                     id, lower(name)
260                 )
261             },
262             q{
263                 create unique index uniq_uc_name_id on pg_loader_test12 (
264                     upper(name), id
265                 )
266             },
267             q{
268                 create unique index pg_loader_test12_value on pg_loader_test12 (
269                     value
270                 )
271             },
272             q{
273                 create unique index pg_loader_test12_name_active on pg_loader_test12 (
274                     name
275                 ) where active
276             },
277         ],
278         pre_drop_ddl => [
279             'DROP SCHEMA dbicsl_test CASCADE',
280             'DROP SCHEMA "dbicsl-test" CASCADE',
281             'DROP SCHEMA "dbicsl.test" CASCADE',
282             'DROP TYPE pg_loader_test_enum',
283             'DROP VIEW pg_loader_test11',
284         ],
285         drop  => [ qw/pg_loader_test1 pg_loader_test2 pg_loader_test9 pg_loader_test10 pg_loader_test12/ ],
286         count => 10 + 30 * 2,
287         run   => sub {
288             my ($schema, $monikers, $classes) = @_;
289
290             is $schema->source($monikers->{pg_loader_test1})->column_info('id')->{sequence},
291                 'dbicsl_test.myseq',
292                 'qualified sequence detected';
293
294             my $class    = $classes->{pg_loader_test1};
295             my $filename = $schema->loader->get_dump_filename($class);
296
297             my $code = slurp_file $filename;
298
299             like $code, qr/^=head1 NAME\n\n^$class - The\nTable ∑\n\n^=cut\n/m,
300                 'table comment';
301
302             like $code, qr/^=head2 value\n\n(.+:.+\n)+\nThe\nColumn\n\n/m,
303                 'column comment and attrs';
304
305             $class    = $classes->{pg_loader_test2};
306             $filename = $schema->loader->get_dump_filename($class);
307
308             $code = slurp_file $filename;
309
310             like $code, qr/^=head1 NAME\n\n^$class\n\n=head1 DESCRIPTION\n\n^very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long comment\n\n^=cut\n/m,
311                 'long table comment is in DESCRIPTION';
312
313             # test on delete/update fk clause introspection
314             ok ((my $rel_info = $schema->source('PgLoaderTest10')->relationship_info('nine')),
315                 'got rel info');
316
317             is $rel_info->{attrs}{on_delete}, 'RESTRICT',
318                 'ON DELETE clause introspected correctly';
319
320             is $rel_info->{attrs}{on_update}, 'SET NULL',
321                 'ON UPDATE clause introspected correctly';
322
323             is $rel_info->{attrs}{is_deferrable}, 1,
324                 'DEFERRABLE clause introspected correctly';
325
326             foreach my $db_schema (['dbicsl-test', 'dbicsl.test'], '%') {
327                 lives_and {
328                     rmtree EXTRA_DUMP_DIR;
329
330                     my @warns;
331                     local $SIG{__WARN__} = sub {
332                         push @warns, $_[0] unless $_[0] =~ /\bcollides\b/;
333                     };
334
335                     make_schema_at(
336                         'PGMultiSchema',
337                         {
338                             naming => 'current',
339                             db_schema => $db_schema,
340                             preserve_case => 1,
341                             dump_directory => EXTRA_DUMP_DIR,
342                             quiet => 1,
343                         },
344                         [ $dsn, $user, $password, {
345                             on_connect_do  => [ 'SET client_min_messages=WARNING' ],
346                         } ],
347                     );
348
349                     diag join "\n", @warns if @warns;
350
351                     is @warns, 0;
352                 } 'dumped schema for "dbicsl-test" and "dbicsl.test" schemas with no warnings';
353
354                 my ($test_schema, $rsrc, $rs, $row, %uniqs, $rel_info);
355
356                 lives_and {
357                     ok $test_schema = PGMultiSchema->connect($dsn, $user, $password, {
358                         on_connect_do  => [ 'SET client_min_messages=WARNING' ],
359                     });
360                 } 'connected test schema';
361
362                 lives_and {
363                     ok $rsrc = $test_schema->source('PgLoaderTest4');
364                 } 'got source for table in schema name with dash';
365
366                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
367                     'column in schema name with dash';
368
369                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
370                     'column in schema name with dash';
371
372                 is try { $rsrc->column_info('value')->{size} }, 100,
373                     'column in schema name with dash';
374
375                 lives_and {
376                     ok $rs = $test_schema->resultset('PgLoaderTest4');
377                 } 'got resultset for table in schema name with dash';
378
379                 lives_and {
380                     ok $row = $rs->create({ value => 'foo' });
381                 } 'executed SQL on table in schema name with dash';
382
383                 $rel_info = try { $rsrc->relationship_info('dbicsl_dash_test_pg_loader_test5') };
384
385                 is_deeply $rel_info->{cond}, {
386                     'foreign.four_id' => 'self.id'
387                 }, 'relationship in schema name with dash';
388
389                 is $rel_info->{attrs}{accessor}, 'single',
390                     'relationship in schema name with dash';
391
392                 is $rel_info->{attrs}{join_type}, 'LEFT',
393                     'relationship in schema name with dash';
394
395                 lives_and {
396                     ok $rsrc = $test_schema->source('DbicslDashTestPgLoaderTest5');
397                 } 'got source for table in schema name with dash';
398
399                 %uniqs = try { $rsrc->unique_constraints };
400
401                 is keys %uniqs, 2,
402                     'got unique and primary constraint in schema name with dash';
403
404                 delete $uniqs{primary};
405
406                 is_deeply ((values %uniqs)[0], ['four_id'],
407                     'unique constraint is correct in schema name with dash');
408
409                 lives_and {
410                     ok $rsrc = $test_schema->source('PgLoaderTest6');
411                 } 'got source for table in schema name with dot';
412
413                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
414                     'column in schema name with dot introspected correctly';
415
416                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
417                     'column in schema name with dot introspected correctly';
418
419                 is try { $rsrc->column_info('value')->{size} }, 100,
420                     'column in schema name with dot introspected correctly';
421
422                 lives_and {
423                     ok $rs = $test_schema->resultset('PgLoaderTest6');
424                 } 'got resultset for table in schema name with dot';
425
426                 lives_and {
427                     ok $row = $rs->create({ value => 'foo' });
428                 } 'executed SQL on table in schema name with dot';
429
430                 $rel_info = try { $rsrc->relationship_info('pg_loader_test7') };
431
432                 is_deeply $rel_info->{cond}, {
433                     'foreign.six_id' => 'self.id'
434                 }, 'relationship in schema name with dot';
435
436                 is $rel_info->{attrs}{accessor}, 'single',
437                     'relationship in schema name with dot';
438
439                 is $rel_info->{attrs}{join_type}, 'LEFT',
440                     'relationship in schema name with dot';
441
442                 lives_and {
443                     ok $rsrc = $test_schema->source('PgLoaderTest7');
444                 } 'got source for table in schema name with dot';
445
446                 %uniqs = try { $rsrc->unique_constraints };
447
448                 is keys %uniqs, 2,
449                     'got unique and primary constraint in schema name with dot';
450
451                 delete $uniqs{primary};
452
453                 is_deeply ((values %uniqs)[0], ['six_id'],
454                     'unique constraint is correct in schema name with dot');
455
456                 lives_and {
457                     ok $test_schema->source('PgLoaderTest6')
458                         ->has_relationship('pg_loader_test4');
459                 } 'cross-schema relationship in multi-db_schema';
460
461                 lives_and {
462                     ok $test_schema->source('PgLoaderTest4')
463                         ->has_relationship('pg_loader_test6s');
464                 } 'cross-schema relationship in multi-db_schema';
465
466                 lives_and {
467                     ok $test_schema->source('PgLoaderTest8')
468                         ->has_relationship('pg_loader_test7');
469                 } 'cross-schema relationship in multi-db_schema';
470
471                 lives_and {
472                     ok $test_schema->source('PgLoaderTest7')
473                         ->has_relationship('pg_loader_test8s');
474                 } 'cross-schema relationship in multi-db_schema';
475             }
476
477             # test that views are marked as such
478             isa_ok $schema->resultset($monikers->{pg_loader_test11})->result_source, 'DBIx::Class::ResultSource::View',
479                 'views have table_class set correctly';
480
481             is_deeply
482                 { $schema->source($monikers->{pg_loader_test12})->unique_constraints },
483                 { pg_loader_test12_value => ['value'] },
484                 'unique indexes are dumped correctly';
485         },
486     },
487 );
488
489 if( !$dsn || !$user ) {
490     $tester->skip_tests('You need to set the DBICTEST_PG_DSN, _USER, and _PASS environment variables');
491 }
492 else {
493     $tester->run_tests();
494 }
495
496 END {
497     rmtree EXTRA_DUMP_DIR unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP};
498 }
499 # vim:et sw=4 sts=4 tw=0: