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