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