342e771d05a9cd7acdda12b3ca4f3a853cf42b87
[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                     eleven_id int,
241                     foreign key (eleven_id) references pg_loader_test9(id)
242                         on delete restrict on update set null
243                 )
244             },
245
246         ],
247         pre_drop_ddl => [
248             'DROP SCHEMA dbicsl_test CASCADE',
249             'DROP SCHEMA "dbicsl-test" CASCADE',
250             'DROP SCHEMA "dbicsl.test" CASCADE',
251             'DROP TYPE pg_loader_test_enum',
252         ],
253         drop  => [ qw/pg_loader_test1 pg_loader_test2 pg_loader_test9 pg_loader_test10/ ],
254         count => 8 + 30 * 2,
255         run   => sub {
256             my ($schema, $monikers, $classes) = @_;
257
258             is $schema->source($monikers->{pg_loader_test1})->column_info('id')->{sequence},
259                 'dbicsl_test.myseq',
260                 'qualified sequence detected';
261
262             my $class    = $classes->{pg_loader_test1};
263             my $filename = $schema->loader->get_dump_filename($class);
264
265             my $code = slurp_file $filename;
266
267             like $code, qr/^=head1 NAME\n\n^$class - The\nTable ∑\n\n^=cut\n/m,
268                 'table comment';
269
270             like $code, qr/^=head2 value\n\n(.+:.+\n)+\nThe\nColumn\n\n/m,
271                 'column comment and attrs';
272
273             $class    = $classes->{pg_loader_test2};
274             $filename = $schema->loader->get_dump_filename($class);
275
276             $code = slurp_file $filename;
277
278             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,
279                 'long table comment is in DESCRIPTION';
280
281             # test on delete/update fk clause introspection
282             ok ((my $rel_info = $schema->source('PgLoaderTest10')->relationship_info('eleven')),
283                 'got rel info');
284
285             is $rel_info->{attrs}{on_delete}, 'RESTRICT',
286                 'ON DELETE clause introspected correctly';
287
288             is $rel_info->{attrs}{on_update}, 'SET NULL',
289                 'ON UPDATE clause introspected correctly';
290
291             is $rel_info->{attrs}{is_deferrable}, 0,
292                 'DEFERRABLE clause introspected correctly';
293
294             foreach my $db_schema (['dbicsl-test', 'dbicsl.test'], '%') {
295                 lives_and {
296                     rmtree EXTRA_DUMP_DIR;
297
298                     my @warns;
299                     local $SIG{__WARN__} = sub {
300                         push @warns, $_[0] unless $_[0] =~ /\bcollides\b/;
301                     };
302
303                     make_schema_at(
304                         'PGMultiSchema',
305                         {
306                             naming => 'current',
307                             db_schema => $db_schema,
308                             preserve_case => 1,
309                             dump_directory => EXTRA_DUMP_DIR,
310                             quiet => 1,
311                         },
312                         [ $dsn, $user, $password, {
313                             on_connect_do  => [ 'SET client_min_messages=WARNING' ],
314                         } ],
315                     );
316
317                     diag join "\n", @warns if @warns;
318
319                     is @warns, 0;
320                 } 'dumped schema for "dbicsl-test" and "dbicsl.test" schemas with no warnings';
321
322                 my ($test_schema, $rsrc, $rs, $row, %uniqs, $rel_info);
323
324                 lives_and {
325                     ok $test_schema = PGMultiSchema->connect($dsn, $user, $password, {
326                         on_connect_do  => [ 'SET client_min_messages=WARNING' ],
327                     });
328                 } 'connected test schema';
329
330                 lives_and {
331                     ok $rsrc = $test_schema->source('PgLoaderTest4');
332                 } 'got source for table in schema name with dash';
333
334                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
335                     'column in schema name with dash';
336
337                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
338                     'column in schema name with dash';
339
340                 is try { $rsrc->column_info('value')->{size} }, 100,
341                     'column in schema name with dash';
342
343                 lives_and {
344                     ok $rs = $test_schema->resultset('PgLoaderTest4');
345                 } 'got resultset for table in schema name with dash';
346
347                 lives_and {
348                     ok $row = $rs->create({ value => 'foo' });
349                 } 'executed SQL on table in schema name with dash';
350
351                 $rel_info = try { $rsrc->relationship_info('dbicsl_dash_test_pg_loader_test5') };
352
353                 is_deeply $rel_info->{cond}, {
354                     'foreign.four_id' => 'self.id'
355                 }, 'relationship in schema name with dash';
356
357                 is $rel_info->{attrs}{accessor}, 'single',
358                     'relationship in schema name with dash';
359
360                 is $rel_info->{attrs}{join_type}, 'LEFT',
361                     'relationship in schema name with dash';
362
363                 lives_and {
364                     ok $rsrc = $test_schema->source('DbicslDashTestPgLoaderTest5');
365                 } 'got source for table in schema name with dash';
366
367                 %uniqs = try { $rsrc->unique_constraints };
368
369                 is keys %uniqs, 2,
370                     'got unique and primary constraint in schema name with dash';
371
372                 delete $uniqs{primary};
373
374                 is_deeply ((values %uniqs)[0], ['four_id'],
375                     'unique constraint is correct in schema name with dash');
376
377                 lives_and {
378                     ok $rsrc = $test_schema->source('PgLoaderTest6');
379                 } 'got source for table in schema name with dot';
380
381                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
382                     'column in schema name with dot introspected correctly';
383
384                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
385                     'column in schema name with dot introspected correctly';
386
387                 is try { $rsrc->column_info('value')->{size} }, 100,
388                     'column in schema name with dot introspected correctly';
389
390                 lives_and {
391                     ok $rs = $test_schema->resultset('PgLoaderTest6');
392                 } 'got resultset for table in schema name with dot';
393
394                 lives_and {
395                     ok $row = $rs->create({ value => 'foo' });
396                 } 'executed SQL on table in schema name with dot';
397
398                 $rel_info = try { $rsrc->relationship_info('pg_loader_test7') };
399
400                 is_deeply $rel_info->{cond}, {
401                     'foreign.six_id' => 'self.id'
402                 }, 'relationship in schema name with dot';
403
404                 is $rel_info->{attrs}{accessor}, 'single',
405                     'relationship in schema name with dot';
406
407                 is $rel_info->{attrs}{join_type}, 'LEFT',
408                     'relationship in schema name with dot';
409
410                 lives_and {
411                     ok $rsrc = $test_schema->source('PgLoaderTest7');
412                 } 'got source for table in schema name with dot';
413
414                 %uniqs = try { $rsrc->unique_constraints };
415
416                 is keys %uniqs, 2,
417                     'got unique and primary constraint in schema name with dot';
418
419                 delete $uniqs{primary};
420
421                 is_deeply ((values %uniqs)[0], ['six_id'],
422                     'unique constraint is correct in schema name with dot');
423
424                 lives_and {
425                     ok $test_schema->source('PgLoaderTest6')
426                         ->has_relationship('pg_loader_test4');
427                 } 'cross-schema relationship in multi-db_schema';
428
429                 lives_and {
430                     ok $test_schema->source('PgLoaderTest4')
431                         ->has_relationship('pg_loader_test6s');
432                 } 'cross-schema relationship in multi-db_schema';
433
434                 lives_and {
435                     ok $test_schema->source('PgLoaderTest8')
436                         ->has_relationship('pg_loader_test7');
437                 } 'cross-schema relationship in multi-db_schema';
438
439                 lives_and {
440                     ok $test_schema->source('PgLoaderTest7')
441                         ->has_relationship('pg_loader_test8s');
442                 } 'cross-schema relationship in multi-db_schema';
443             }
444         },
445     },
446 );
447
448 if( !$dsn || !$user ) {
449     $tester->skip_tests('You need to set the DBICTEST_PG_DSN, _USER, and _PASS environment variables');
450 }
451 else {
452     $tester->run_tests();
453 }
454
455 END {
456     rmtree EXTRA_DUMP_DIR unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP};
457 }
458 # vim:et sw=4 sts=4 tw=0: