fix multiple DEFAULT NULLs
[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 UNIQUE REFERENCES "dbicsl-test".pg_loader_test4 (id)
194                 )
195             },
196             q{
197                 CREATE SCHEMA "dbicsl.test"
198             },
199             q{
200                 CREATE TABLE "dbicsl.test".pg_loader_test6 (
201                     id SERIAL PRIMARY KEY,
202                     value VARCHAR(100),
203                     pg_loader_test4_id INTEGER REFERENCES "dbicsl-test".pg_loader_test4 (id)
204                 )
205             },
206             q{
207                 CREATE TABLE "dbicsl.test".pg_loader_test7 (
208                     id SERIAL PRIMARY KEY,
209                     value VARCHAR(100),
210                     six_id INTEGER UNIQUE REFERENCES "dbicsl.test".pg_loader_test6 (id)
211                 )
212             },
213             q{
214                 CREATE TABLE "dbicsl-test".pg_loader_test8 (
215                     id SERIAL PRIMARY KEY,
216                     value VARCHAR(100),
217                     pg_loader_test7_id INTEGER REFERENCES "dbicsl.test".pg_loader_test7 (id)
218                 )
219             },
220         ],
221         pre_drop_ddl => [
222             'DROP SCHEMA dbicsl_test CASCADE',
223             'DROP SCHEMA "dbicsl-test" CASCADE',
224             'DROP SCHEMA "dbicsl.test" CASCADE',
225             'DROP TYPE pg_loader_test_enum',
226         ],
227         drop  => [ qw/ pg_loader_test1 pg_loader_test2 / ],
228         count => 4 + 28 * 2,
229         run   => sub {
230             my ($schema, $monikers, $classes) = @_;
231
232             is $schema->source($monikers->{pg_loader_test1})->column_info('id')->{sequence},
233                 'dbicsl_test.myseq',
234                 'qualified sequence detected';
235
236             my $class    = $classes->{pg_loader_test1};
237             my $filename = $schema->loader->get_dump_filename($class);
238
239             my $code = slurp_file $filename;
240
241             like $code, qr/^=head1 NAME\n\n^$class - The\nTable ∑\n\n^=cut\n/m,
242                 'table comment';
243
244             like $code, qr/^=head2 value\n\n(.+:.+\n)+\nThe\nColumn\n\n/m,
245                 'column comment and attrs';
246
247             $class    = $classes->{pg_loader_test2};
248             $filename = $schema->loader->get_dump_filename($class);
249
250             $code = slurp_file $filename;
251
252             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,
253                 'long table comment is in DESCRIPTION';
254
255             foreach my $db_schema (['dbicsl-test', 'dbicsl.test'], '%') {
256                 lives_and {
257                     rmtree EXTRA_DUMP_DIR;
258
259                     my @warns;
260                     local $SIG{__WARN__} = sub {
261                         push @warns, $_[0] unless $_[0] =~ /\bcollides\b/;
262                     };
263
264                     make_schema_at(
265                         'PGMultiSchema',
266                         {
267                             naming => 'current',
268                             db_schema => $db_schema,
269                             preserve_case => 1,
270                             dump_directory => EXTRA_DUMP_DIR,
271                             quiet => 1,
272                         },
273                         [ $dsn, $user, $password, {
274                             on_connect_do  => [ 'SET client_min_messages=WARNING' ],
275                         } ],
276                     );
277
278                     diag join "\n", @warns if @warns;
279
280                     is @warns, 0;
281                 } 'dumped schema for "dbicsl-test" and "dbicsl.test" schemas with no warnings';
282
283                 my ($test_schema, $rsrc, $rs, $row, %uniqs, $rel_info);
284
285                 lives_and {
286                     ok $test_schema = PGMultiSchema->connect($dsn, $user, $password, {
287                         on_connect_do  => [ 'SET client_min_messages=WARNING' ],
288                     });
289                 } 'connected test schema';
290
291                 lives_and {
292                     ok $rsrc = $test_schema->source('PgLoaderTest4');
293                 } 'got source for table in schema name with dash';
294
295                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
296                     'column in schema name with dash';
297
298                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
299                     'column in schema name with dash';
300
301                 is try { $rsrc->column_info('value')->{size} }, 100,
302                     'column in schema name with dash';
303
304                 lives_and {
305                     ok $rs = $test_schema->resultset('PgLoaderTest4');
306                 } 'got resultset for table in schema name with dash';
307
308                 lives_and {
309                     ok $row = $rs->create({ value => 'foo' });
310                 } 'executed SQL on table in schema name with dash';
311
312                 $rel_info = try { $rsrc->relationship_info('pg_loader_test5') };
313
314                 is_deeply $rel_info->{cond}, {
315                     'foreign.four_id' => 'self.id'
316                 }, 'relationship in schema name with dash';
317
318                 is $rel_info->{attrs}{accessor}, 'single',
319                     'relationship in schema name with dash';
320
321                 is $rel_info->{attrs}{join_type}, 'LEFT',
322                     'relationship in schema name with dash';
323
324                 lives_and {
325                     ok $rsrc = $test_schema->source('PgLoaderTest5');
326                 } 'got source for table in schema name with dash';
327
328                 %uniqs = try { $rsrc->unique_constraints };
329
330                 is keys %uniqs, 2,
331                     'got unique and primary constraint in schema name with dash';
332
333                 lives_and {
334                     ok $rsrc = $test_schema->source('PgLoaderTest6');
335                 } 'got source for table in schema name with dot';
336
337                 is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
338                     'column in schema name with dot introspected correctly';
339
340                 is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
341                     'column in schema name with dot introspected correctly';
342
343                 is try { $rsrc->column_info('value')->{size} }, 100,
344                     'column in schema name with dot introspected correctly';
345
346                 lives_and {
347                     ok $rs = $test_schema->resultset('PgLoaderTest6');
348                 } 'got resultset for table in schema name with dot';
349
350                 lives_and {
351                     ok $row = $rs->create({ value => 'foo' });
352                 } 'executed SQL on table in schema name with dot';
353
354                 $rel_info = try { $rsrc->relationship_info('pg_loader_test7') };
355
356                 is_deeply $rel_info->{cond}, {
357                     'foreign.six_id' => 'self.id'
358                 }, 'relationship in schema name with dot';
359
360                 is $rel_info->{attrs}{accessor}, 'single',
361                     'relationship in schema name with dot';
362
363                 is $rel_info->{attrs}{join_type}, 'LEFT',
364                     'relationship in schema name with dot';
365
366                 lives_and {
367                     ok $rsrc = $test_schema->source('PgLoaderTest7');
368                 } 'got source for table in schema name with dot';
369
370                 %uniqs = try { $rsrc->unique_constraints };
371
372                 is keys %uniqs, 2,
373                     'got unique and primary constraint in schema name with dot';
374
375                 lives_and {
376                     ok $test_schema->source('PgLoaderTest6')
377                         ->has_relationship('pg_loader_test4');
378                 } 'cross-schema relationship in multi-db_schema';
379
380                 lives_and {
381                     ok $test_schema->source('PgLoaderTest4')
382                         ->has_relationship('pg_loader_test6s');
383                 } 'cross-schema relationship in multi-db_schema';
384
385                 lives_and {
386                     ok $test_schema->source('PgLoaderTest8')
387                         ->has_relationship('pg_loader_test7');
388                 } 'cross-schema relationship in multi-db_schema';
389
390                 lives_and {
391                     ok $test_schema->source('PgLoaderTest7')
392                         ->has_relationship('pg_loader_test8s');
393                 } 'cross-schema relationship in multi-db_schema';
394             }
395         },
396     },
397 );
398
399 if( !$dsn || !$user ) {
400     $tester->skip_tests('You need to set the DBICTEST_PG_DSN, _USER, and _PASS environment variables');
401 }
402 else {
403     $tester->run_tests();
404 }
405
406 END {
407     rmtree EXTRA_DUMP_DIR unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP};
408 }
409 # vim:et sw=4 sts=4 tw=0: