fix some issues with multi-db_schema support
[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     data_types  => {
35         # http://www.postgresql.org/docs/7.4/interactive/datatype.html
36         #
37         # Numeric Types
38         boolean     => { data_type => 'boolean' },
39         bool        => { data_type => 'boolean' },
40         'bool default false'
41                     => { data_type => 'boolean', default_value => \'false' },
42         'bool default true'
43                     => { data_type => 'boolean', default_value => \'true' },
44         'bool default 0::bool'
45                     => { data_type => 'boolean', default_value => \'false' },
46         'bool default 1::bool'
47                     => { data_type => 'boolean', default_value => \'true' },
48
49         bigint      => { data_type => 'bigint' },
50         int8        => { data_type => 'bigint' },
51         bigserial   => { data_type => 'bigint', is_auto_increment => 1 },
52         serial8     => { data_type => 'bigint', is_auto_increment => 1 },
53         integer     => { data_type => 'integer' },
54         int         => { data_type => 'integer' },
55         int4        => { data_type => 'integer' },
56         serial      => { data_type => 'integer', is_auto_increment => 1 },
57         serial4     => { data_type => 'integer', is_auto_increment => 1 },
58         smallint    => { data_type => 'smallint' },
59         int2        => { data_type => 'smallint' },
60
61         money       => { data_type => 'money' },
62
63         'double precision' => { data_type => 'double precision' },
64         float8             => { data_type => 'double precision' },
65         real               => { data_type => 'real' },
66         float4             => { data_type => 'real' },
67         'float(24)'        => { data_type => 'real' },
68         'float(25)'        => { data_type => 'double precision' },
69         'float(53)'        => { data_type => 'double precision' },
70         float              => { data_type => 'double precision' },
71
72         numeric        => { data_type => 'numeric' },
73         decimal        => { data_type => 'numeric' },
74         'numeric(6,3)' => { data_type => 'numeric', size => [6,3] },
75         'decimal(6,3)' => { data_type => 'numeric', size => [6,3] },
76
77         # Bit String Types
78         'bit varying(2)' => { data_type => 'varbit', size => 2 },
79         'varbit(2)'      => { data_type => 'varbit', size => 2 },
80         'varbit'         => { data_type => 'varbit' },
81         bit              => { data_type => 'bit', size => 1 },
82         'bit(3)'         => { data_type => 'bit', size => 3 },
83
84         # Network Types
85         inet    => { data_type => 'inet' },
86         cidr    => { data_type => 'cidr' },
87         macaddr => { data_type => 'macaddr' },
88
89         # Geometric Types
90         point   => { data_type => 'point' },
91         line    => { data_type => 'line' },
92         lseg    => { data_type => 'lseg' },
93         box     => { data_type => 'box' },
94         path    => { data_type => 'path' },
95         polygon => { data_type => 'polygon' },
96         circle  => { data_type => 'circle' },
97
98         # Character Types
99         'character varying(2)'           => { data_type => 'varchar', size => 2 },
100         'varchar(2)'                     => { data_type => 'varchar', size => 2 },
101         'character(2)'                   => { data_type => 'char', size => 2 },
102         'char(2)'                        => { data_type => 'char', size => 2 },
103         # check that default null is correctly rewritten
104         'char(3) default null'           => { data_type => 'char', size => 3,
105                                               default_value => \'null' },
106         'character'                      => { data_type => 'char', size => 1 },
107         'char'                           => { data_type => 'char', size => 1 },
108         text                             => { data_type => 'text' },
109         # varchar with no size has unlimited size, we rewrite to 'text'
110         varchar                          => { data_type => 'text',
111                                               original => { data_type => 'varchar' } },
112         # check default null again (to make sure ref is safe)
113         'varchar(3) default null'        => { data_type => 'varchar', size => 3,
114                                               default_value => \'null' },
115
116         # Datetime Types
117         date                             => { data_type => 'date' },
118         interval                         => { data_type => 'interval' },
119         'interval(2)'                    => { data_type => 'interval', size => 2 },
120         time                             => { data_type => 'time' },
121         'time(2)'                        => { data_type => 'time', size => 2 },
122         'time without time zone'         => { data_type => 'time' },
123         'time(2) without time zone'      => { data_type => 'time', size => 2 },
124         'time with time zone'            => { data_type => 'time with time zone' },
125         'time(2) with time zone'         => { data_type => 'time with time zone', size => 2 },
126         timestamp                        => { data_type => 'timestamp' },
127         'timestamp default now()'
128                                          => { data_type => 'timestamp', default_value => \'current_timestamp',
129                                               original => { default_value => \'now()' } },
130         'timestamp(2)'                   => { data_type => 'timestamp', size => 2 },
131         'timestamp without time zone'    => { data_type => 'timestamp' },
132         'timestamp(2) without time zone' => { data_type => 'timestamp', size => 2 },
133
134         'timestamp with time zone'       => { data_type => 'timestamp with time zone' },
135         'timestamp(2) with time zone'    => { data_type => 'timestamp with time zone', size => 2 },
136
137         # Blob Types
138         bytea => { data_type => 'bytea' },
139
140         # Enum Types
141         pg_loader_test_enum => { data_type => 'enum', extra => { custom_type_name => 'pg_loader_test_enum',
142                                                                  list => [ qw/foo bar baz/] } },
143     },
144     pre_create => [
145         q{
146             CREATE TYPE pg_loader_test_enum AS ENUM (
147                 'foo', 'bar', 'baz'
148             )
149         },
150     ],
151     extra       => {
152         create => [
153             q{
154                 CREATE SCHEMA dbicsl_test
155             },
156             q{
157                 CREATE SEQUENCE dbicsl_test.myseq
158             },
159             q{
160                 CREATE TABLE pg_loader_test1 (
161                     id INTEGER NOT NULL DEFAULT nextval('dbicsl_test.myseq') PRIMARY KEY,
162                     value VARCHAR(100)
163                 )
164             },
165             qq{
166                 COMMENT ON TABLE pg_loader_test1 IS 'The\15\12Table ∑'
167             },
168             qq{
169                 COMMENT ON COLUMN pg_loader_test1.value IS 'The\15\12Column'
170             },
171             q{
172                 CREATE TABLE pg_loader_test2 (
173                     id SERIAL PRIMARY KEY,
174                     value VARCHAR(100)
175                 )
176             },
177             q{
178                 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'
179             },
180             q{
181                 CREATE SCHEMA "dbicsl-test"
182             },
183             q{
184                 CREATE TABLE "dbicsl-test".pg_loader_test4 (
185                     id SERIAL PRIMARY KEY,
186                     value VARCHAR(100)
187                 )
188             },
189             q{
190                 CREATE TABLE "dbicsl-test".pg_loader_test5 (
191                     id SERIAL PRIMARY KEY,
192                     value VARCHAR(100),
193                     four_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id),
194                     CONSTRAINT loader_test5_uniq UNIQUE (four_id)
195                 )
196             },
197             q{
198                 CREATE SCHEMA "dbicsl.test"
199             },
200             q{
201                 CREATE TABLE "dbicsl.test".pg_loader_test5 (
202                     pk SERIAL PRIMARY KEY,
203                     value VARCHAR(100),
204                     four_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id),
205                     CONSTRAINT loader_test5_uniq UNIQUE (four_id)
206                 )
207             },
208             q{
209                 CREATE TABLE "dbicsl.test".pg_loader_test6 (
210                     id SERIAL PRIMARY KEY,
211                     value VARCHAR(100),
212                     pg_loader_test4_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id)
213                 )
214             },
215             q{
216                 CREATE TABLE "dbicsl.test".pg_loader_test7 (
217                     id SERIAL PRIMARY KEY,
218                     value VARCHAR(100),
219                     six_id INTEGER UNIQUE REFERENCES "dbicsl.test".pg_loader_test6 (id)
220                 )
221             },
222             q{
223                 CREATE TABLE "dbicsl-test".pg_loader_test8 (
224                     id SERIAL PRIMARY KEY,
225                     value VARCHAR(100),
226                     pg_loader_test7_id INTEGER REFERENCES "dbicsl.test".pg_loader_test7 (id)
227                 )
228             },
229         ],
230         pre_drop_ddl => [
231             'DROP SCHEMA dbicsl_test CASCADE',
232             'DROP SCHEMA "dbicsl-test" CASCADE',
233             'DROP SCHEMA "dbicsl.test" CASCADE',
234             'DROP TYPE pg_loader_test_enum',
235         ],
236         drop  => [ qw/ pg_loader_test1 pg_loader_test2 / ],
237         count => 4 + 30 * 2,
238         run   => sub {
239             my ($schema, $monikers, $classes) = @_;
240
241             is $schema->source($monikers->{pg_loader_test1})->column_info('id')->{sequence},
242                 'dbicsl_test.myseq',
243                 'qualified sequence detected';
244
245             my $class    = $classes->{pg_loader_test1};
246             my $filename = $schema->loader->get_dump_filename($class);
247
248             my $code = slurp_file $filename;
249
250             like $code, qr/^=head1 NAME\n\n^$class - The\nTable ∑\n\n^=cut\n/m,
251                 'table comment';
252
253             like $code, qr/^=head2 value\n\n(.+:.+\n)+\nThe\nColumn\n\n/m,
254                 'column comment and attrs';
255
256             $class    = $classes->{pg_loader_test2};
257             $filename = $schema->loader->get_dump_filename($class);
258
259             $code = slurp_file $filename;
260
261             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,
262                 'long table comment is in DESCRIPTION';
263
264             foreach my $db_schema (['dbicsl-test', 'dbicsl.test'], '%') {
265                 lives_and {
266                     rmtree EXTRA_DUMP_DIR;
267
268                     my @warns;
269                     local $SIG{__WARN__} = sub {
270                         push @warns, $_[0] unless $_[0] =~ /\bcollides\b/;
271                     };
272
273                     make_schema_at(
274                         'PGMultiSchema',
275                         {
276                             naming => 'current',
277                             db_schema => $db_schema,
278                             moniker_parts => [qw/schema name/],
279                             preserve_case => 1,
280                             dump_directory => EXTRA_DUMP_DIR,
281                             quiet => 1,
282                         },
283                         [ $dsn, $user, $password, {
284                             on_connect_do  => [ 'SET client_min_messages=WARNING' ],
285                         } ],
286                     );
287
288                     diag join "\n", @warns if @warns;
289
290                     is @warns, 0;
291                 } 'dumped schema for "dbicsl-test" and "dbicsl.test" schemas with no warnings';
292
293                 my ($test_schema, $rsrc, $rs, $row, %uniqs, $rel_info);
294
295                 lives_and {
296                     ok $test_schema = PGMultiSchema->connect($dsn, $user, $password, {
297                         on_connect_do  => [ 'SET client_min_messages=WARNING' ],
298                     });
299                 } 'connected test schema';
300
301                 lives_and {
302                     ok $rsrc = $test_schema->source('DbicslDashTestPgLoaderTest4');
303                 } 'got source for table in schema name with dash';
304
305                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
306                     'column in schema name with dash';
307
308                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
309                     'column in schema name with dash';
310
311                 is try { $rsrc->column_info('value')->{size} }, 100,
312                     'column in schema name with dash';
313
314                 lives_and {
315                     ok $rs = $test_schema->resultset('DbicslDashTestPgLoaderTest4');
316                 } 'got resultset for table in schema name with dash';
317
318                 lives_and {
319                     ok $row = $rs->create({ value => 'foo' });
320                 } 'executed SQL on table in schema name with dash';
321
322                 $rel_info = try { $rsrc->relationship_info('dbicsl_dash_test_pg_loader_test5') };
323
324                 is_deeply $rel_info->{cond}, {
325                     'foreign.four_id' => 'self.id'
326                 }, 'relationship in schema name with dash';
327
328                 is $rel_info->{attrs}{accessor}, 'single',
329                     'relationship in schema name with dash';
330
331                 is $rel_info->{attrs}{join_type}, 'LEFT',
332                     'relationship in schema name with dash';
333
334                 lives_and {
335                     ok $rsrc = $test_schema->source('DbicslDashTestPgLoaderTest5');
336                 } 'got source for table in schema name with dash';
337
338                 %uniqs = try { $rsrc->unique_constraints };
339
340                 is keys %uniqs, 2,
341                     'got unique and primary constraint in schema name with dash';
342
343                 delete $uniqs{primary};
344
345                 is_deeply ((values %uniqs)[0], ['four_id'],
346                     'unique constraint is correct in schema name with dash');
347
348                 lives_and {
349                     ok $rsrc = $test_schema->source('DbicslDotTestPgLoaderTest6');
350                 } 'got source for table in schema name with dot';
351
352                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
353                     'column in schema name with dot introspected correctly';
354
355                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
356                     'column in schema name with dot introspected correctly';
357
358                 is try { $rsrc->column_info('value')->{size} }, 100,
359                     'column in schema name with dot introspected correctly';
360
361                 lives_and {
362                     ok $rs = $test_schema->resultset('DbicslDotTestPgLoaderTest6');
363                 } 'got resultset for table in schema name with dot';
364
365                 lives_and {
366                     ok $row = $rs->create({ value => 'foo' });
367                 } 'executed SQL on table in schema name with dot';
368
369                 $rel_info = try { $rsrc->relationship_info('pg_loader_test7') };
370
371                 is_deeply $rel_info->{cond}, {
372                     'foreign.six_id' => 'self.id'
373                 }, 'relationship in schema name with dot';
374
375                 is $rel_info->{attrs}{accessor}, 'single',
376                     'relationship in schema name with dot';
377
378                 is $rel_info->{attrs}{join_type}, 'LEFT',
379                     'relationship in schema name with dot';
380
381                 lives_and {
382                     ok $rsrc = $test_schema->source('DbicslDotTestPgLoaderTest7');
383                 } 'got source for table in schema name with dot';
384
385                 %uniqs = try { $rsrc->unique_constraints };
386
387                 is keys %uniqs, 2,
388                     'got unique and primary constraint in schema name with dot';
389
390                 delete $uniqs{primary};
391
392                 is_deeply ((values %uniqs)[0], ['six_id'],
393                     'unique constraint is correct in schema name with dot');
394
395                 lives_and {
396                     ok $test_schema->source('DbicslDotTestPgLoaderTest6')
397                         ->has_relationship('pg_loader_test4');
398                 } 'cross-schema relationship in multi-db_schema';
399
400                 lives_and {
401                     ok $test_schema->source('DbicslDashTestPgLoaderTest4')
402                         ->has_relationship('pg_loader_test6s');
403                 } 'cross-schema relationship in multi-db_schema';
404
405                 lives_and {
406                     ok $test_schema->source('DbicslDashTestPgLoaderTest8')
407                         ->has_relationship('pg_loader_test7');
408                 } 'cross-schema relationship in multi-db_schema';
409
410                 lives_and {
411                     ok $test_schema->source('DbicslDotTestPgLoaderTest7')
412                         ->has_relationship('pg_loader_test8s');
413                 } 'cross-schema relationship in multi-db_schema';
414             }
415         },
416     },
417 );
418
419 if( !$dsn || !$user ) {
420     $tester->skip_tests('You need to set the DBICTEST_PG_DSN, _USER, and _PASS environment variables');
421 }
422 else {
423     $tester->run_tests();
424 }
425
426 END {
427     rmtree EXTRA_DUMP_DIR unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP};
428 }
429 # vim:et sw=4 sts=4 tw=0: