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