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