handle duplicate relationship names (RT#64041)
[dbsrgits/DBIx-Class-Schema-Loader.git] / t / lib / dbixcsl_common_tests.pm
1 package dbixcsl_common_tests;
2
3 use strict;
4 use warnings;
5
6 use Test::More;
7 use Test::Exception;
8 use DBIx::Class::Schema::Loader;
9 use Class::Unload;
10 use File::Path;
11 use DBI;
12 use Digest::MD5;
13 use File::Find 'find';
14 use Class::Unload ();
15 use DBIx::Class::Schema::Loader::Utils 'dumper_squashed';
16 use List::MoreUtils 'apply';
17 use DBIx::Class::Schema::Loader::Optional::Dependencies ();
18 use Try::Tiny;
19 use File::Slurp 'slurp';
20 use namespace::clean;
21
22 use dbixcsl_test_dir qw/$tdir/;
23
24 my $DUMP_DIR = "$tdir/common_dump";
25 rmtree $DUMP_DIR;
26
27 use constant RESCAN_WARNINGS => qr/(?i:loader_test|LoaderTest)\d+s? has no primary key|^Dumping manual schema|^Schema dump completed|collides with an inherited method|invalidates \d+ active statement|^Bad table or view/;
28
29 sub new {
30     my $class = shift;
31
32     my $self;
33
34     if( ref($_[0]) eq 'HASH') {
35        my $args = shift;
36        $self = { (%$args) };
37     }
38     else {
39        $self = { @_ };
40     }
41
42     # Only MySQL uses this
43     $self->{innodb} ||= '';
44
45     # DB2 and Firebird don't support 'field type NULL'
46     $self->{null} = 'NULL' unless defined $self->{null};
47     
48     $self->{verbose} = $ENV{TEST_VERBOSE} || 0;
49
50     # Optional extra tables and tests
51     $self->{extra} ||= {};
52
53     $self->{basic_date_datatype} ||= 'DATE';
54
55     # Not all DBS do SQL-standard CURRENT_TIMESTAMP
56     $self->{default_function} ||= "current_timestamp";
57     $self->{default_function_def} ||= "timestamp default $self->{default_function}";
58
59     $self = bless $self, $class;
60
61     $self->setup_data_type_tests;
62
63     return $self;
64 }
65
66 sub skip_tests {
67     my ($self, $why) = @_;
68
69     plan skip_all => $why;
70 }
71
72 sub _monikerize {
73     my $name = shift;
74     return 'LoaderTest2X' if $name =~ /^loader_test2$/i;
75     return undef;
76 }
77
78 sub run_tests {
79     my $self = shift;
80
81     my @connect_info;
82
83     if ($self->{dsn}) {
84         push @connect_info, [ @{$self}{qw/dsn user password connect_info_opts/ } ];
85     }
86     else {
87         foreach my $info (@{ $self->{connect_info} || [] }) {
88             push @connect_info, [ @{$info}{qw/dsn user password connect_info_opts/ } ];
89         }
90     }
91     
92     if ($ENV{SCHEMA_LOADER_TESTS_EXTRA_ONLY}) {
93         $self->run_only_extra_tests(\@connect_info);
94         return;
95     }
96
97     my $extra_count = $self->{extra}{count} || 0;
98
99     my $col_accessor_map_tests = 5;
100     my $num_rescans = 5;
101     $num_rescans-- if $self->{vendor} eq 'Mysql';
102     $num_rescans++ if $self->{vendor} eq 'mssql';
103     $num_rescans++ if $self->{vendor} eq 'Firebird';
104
105     plan tests => @connect_info *
106         (203 + $num_rescans * $col_accessor_map_tests + $extra_count + ($self->{data_type_tests}{test_count} || 0));
107
108     foreach my $info_idx (0..$#connect_info) {
109         my $info = $connect_info[$info_idx];
110
111         @{$self}{qw/dsn user password connect_info_opts/} = @$info;
112
113         $self->create();
114
115         my $schema_class = $self->setup_schema($info);
116         $self->test_schema($schema_class);
117
118         rmtree $DUMP_DIR
119             unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP} && $info_idx == $#connect_info;
120     }
121 }
122
123 sub run_only_extra_tests {
124     my ($self, $connect_info) = @_;
125
126     plan tests => @$connect_info * (4 + ($self->{extra}{count} || 0) + ($self->{data_type_tests}{test_count} || 0));
127
128     rmtree $DUMP_DIR;
129
130     foreach my $info_idx (0..$#$connect_info) {
131         my $info = $connect_info->[$info_idx];
132
133         @{$self}{qw/dsn user password connect_info_opts/} = @$info;
134
135         $self->drop_extra_tables_only;
136
137         my $dbh = $self->dbconnect(1);
138         $dbh->do($_) for @{ $self->{pre_create} || [] };
139         $dbh->do($_) for @{ $self->{extra}{create} || [] };
140
141         if (not ($self->{vendor} eq 'mssql' && $dbh->{Driver}{Name} eq 'Sybase')) {
142             $dbh->do($_) for @{ $self->{data_type_tests}{ddl} || []};
143         }
144
145         $self->{_created} = 1;
146
147         my $file_count = grep /CREATE (?:TABLE|VIEW)/i, @{ $self->{extra}{create} || [] };
148         $file_count++; # schema
149         
150         if (not ($self->{vendor} eq 'mssql' && $dbh->{Driver}{Name} eq 'Sybase')) {
151             $file_count++ for @{ $self->{data_type_tests}{table_names} || [] };
152         }
153
154         my $schema_class = $self->setup_schema($info, $file_count);
155         my ($monikers, $classes) = $self->monikers_and_classes($schema_class);
156         my $conn = $schema_class->clone;
157
158         $self->test_data_types($conn);
159         $self->{extra}{run}->($conn, $monikers, $classes, $self) if $self->{extra}{run};
160
161         if (not ($ENV{SCHEMA_LOADER_TESTS_NOCLEANUP} && $info_idx == $#$connect_info)) {
162             $self->drop_extra_tables_only;
163             rmtree $DUMP_DIR;
164         }
165     }
166 }
167
168 sub drop_extra_tables_only {
169     my $self = shift;
170
171     my $dbh = $self->dbconnect(0);
172
173     local $^W = 0; # for ADO
174
175     $dbh->do($_) for @{ $self->{extra}{pre_drop_ddl} || [] };
176     $self->drop_table($dbh, $_) for @{ $self->{extra}{drop} || [] };
177
178     if (not ($self->{vendor} eq 'mssql' && $dbh->{Driver}{Name} eq 'Sybase')) {
179         foreach my $data_type_table (@{ $self->{data_type_tests}{table_names} || [] }) {
180             $self->drop_table($dbh, $data_type_table);
181         }
182     }
183 }
184
185 # defined in sub create
186 my (@statements, @statements_reltests, @statements_advanced,
187     @statements_advanced_sqlite, @statements_inline_rels,
188     @statements_implicit_rels);
189
190 sub setup_schema {
191     my ($self, $connect_info, $expected_count) = @_;
192
193     my $schema_class = 'DBIXCSL_Test::Schema';
194
195     my $debug = ($self->{verbose} > 1) ? 1 : 0;
196
197     if ($ENV{SCHEMA_LOADER_TESTS_USE_MOOSE}) {
198         if (not DBIx::Class::Schema::Loader::Optional::Dependencies->req_ok_for('use_moose')) {
199             die sprintf ("Missing dependencies for SCHEMA_LOADER_TESTS_USE_MOOSE: %s\n",
200                 DBIx::Class::Schema::Loader::Optional::Dependencies->req_missing_for('use_moose'));
201         }
202
203         $self->{use_moose} = 1;
204     }
205
206     my %loader_opts = (
207         constraint              =>
208           qr/^(?:\S+\.)?(?:(?:$self->{vendor}|extra)_?)?loader_?test[0-9]+(?!.*_)/i,
209         relationships           => 1,
210         additional_classes      => 'TestAdditional',
211         additional_base_classes => 'TestAdditionalBase',
212         left_base_classes       => [ qw/TestLeftBase/ ],
213         components              => [ qw/TestComponent +TestComponentFQN/ ],
214         inflect_plural          => { loader_test4_fkid => 'loader_test4zes' },
215         inflect_singular        => { fkid => 'fkid_singular' },
216         moniker_map             => \&_monikerize,
217         custom_column_info      => \&_custom_column_info,
218         debug                   => $debug,
219         use_namespaces          => 0,
220         dump_directory          => $DUMP_DIR,
221         datetime_timezone       => 'Europe/Berlin',
222         datetime_locale         => 'de_DE',
223         $self->{use_moose} ? (
224             use_moose        => 1,
225             result_roles     => 'TestRole',
226             result_roles_map => { LoaderTest2X => 'TestRoleForMap' },
227         ) : (),
228         col_collision_map       => { '^(can)\z' => 'caught_collision_%s' },
229         rel_collision_map       => { '^(set_primary_key)\z' => 'caught_rel_collision_%s' },
230         col_accessor_map        => \&test_col_accessor_map,
231         result_components_map   => { LoaderTest2X => 'TestComponentForMap', LoaderTest1 => '+TestComponentForMapFQN' },
232         %{ $self->{loader_options} || {} },
233     );
234
235     $loader_opts{db_schema} = $self->{db_schema} if $self->{db_schema};
236
237     Class::Unload->unload($schema_class);
238
239     my $file_count;
240     {
241         my @loader_warnings;
242         local $SIG{__WARN__} = sub { push(@loader_warnings, @_); };
243          eval qq{
244              package $schema_class;
245              use base qw/DBIx::Class::Schema::Loader/;
246      
247              __PACKAGE__->loader_options(\%loader_opts);
248              __PACKAGE__->connection(\@\$connect_info);
249          };
250  
251         ok(!$@, "Loader initialization") or diag $@;
252
253         find sub { return if -d; $file_count++ }, $DUMP_DIR;
254
255         my $standard_sources = not defined $expected_count;
256
257         if ($standard_sources) {
258             $expected_count = 36;
259
260             if (not ($self->{vendor} eq 'mssql' && $connect_info->[0] =~ /Sybase/)) {
261                 $expected_count++ for @{ $self->{data_type_tests}{table_names} || [] };
262             }
263
264             $expected_count += grep /CREATE (?:TABLE|VIEW)/i,
265                 @{ $self->{extra}{create} || [] };
266      
267             $expected_count -= grep /CREATE TABLE/, @statements_inline_rels
268                 if $self->{skip_rels} || $self->{no_inline_rels};
269      
270             $expected_count -= grep /CREATE TABLE/, @statements_implicit_rels
271                 if $self->{skip_rels} || $self->{no_implicit_rels};
272      
273             $expected_count -= grep /CREATE TABLE/, ($self->{vendor} =~ /sqlite/ ? @statements_advanced_sqlite : @statements_advanced), @statements_reltests
274                 if $self->{skip_rels};
275         }
276  
277         is $file_count, $expected_count, 'correct number of files generated';
278  
279         my $warn_count = 2;
280  
281         $warn_count++ for grep /^Bad table or view/, @loader_warnings;
282  
283         $warn_count++ for grep /renaming \S+ relation/, @loader_warnings;
284  
285         $warn_count++ for grep /\b(?!loader_test9)\w+ has no primary key/i, @loader_warnings;
286
287         $warn_count++ for grep /^Column '\w+' in table '\w+' collides with an inherited method\./, @loader_warnings;
288
289         $warn_count++ for grep /^Relationship '\w+' in source '\w+' for columns '[^']+' collides with an inherited method\./, @loader_warnings;
290
291         $warn_count++ for grep { my $w = $_; grep $w =~ $_, @{ $self->{warnings} || [] } } @loader_warnings;
292
293         $warn_count-- for grep { my $w = $_; grep $w =~ $_, @{ $self->{failtrigger_warnings} || [] } } @loader_warnings;
294
295         if ($standard_sources) {
296             if($self->{skip_rels}) {
297                 SKIP: {
298                     is(scalar(@loader_warnings), $warn_count, "No loader warnings")
299                         or diag @loader_warnings;
300                     skip "No missing PK warnings without rels", 1;
301                 }
302             }
303             else {
304                 $warn_count++;
305                 is(scalar(@loader_warnings), $warn_count, "Expected loader warning")
306                     or diag @loader_warnings;
307                 is(grep(/loader_test9 has no primary key/i, @loader_warnings), 1,
308                      "Missing PK warning");
309             }
310         }
311         else {
312             SKIP: {
313                 is scalar(@loader_warnings), $warn_count, 'Correct number of warnings'
314                     or diag @loader_warnings;
315                 skip "not testing standard sources", 1;
316             }
317         }
318     }
319
320     exit if ($file_count||0) != $expected_count;
321    
322     return $schema_class;
323 }
324
325 sub test_schema {
326     my $self = shift;
327     my $schema_class = shift;
328
329     my $conn = $schema_class->clone;
330
331     ($self->{before_tests_run} || sub {})->($conn);
332
333     my ($monikers, $classes) = $self->monikers_and_classes($schema_class);
334
335     my $moniker1 = $monikers->{loader_test1s};
336     my $class1   = $classes->{loader_test1s};
337     my $rsobj1   = $conn->resultset($moniker1);
338     check_no_duplicate_unique_constraints($class1);
339
340     my $moniker2 = $monikers->{loader_test2};
341     my $class2   = $classes->{loader_test2};
342     my $rsobj2   = $conn->resultset($moniker2);
343     check_no_duplicate_unique_constraints($class2);
344
345     my $moniker23 = $monikers->{LOADER_test23} || $monikers->{loader_test23};
346     my $class23   = $classes->{LOADER_test23}  || $classes->{loader_test23};
347     my $rsobj23   = $conn->resultset($moniker1);
348
349     my $moniker24 = $monikers->{LoAdEr_test24} || $monikers->{loader_test24};
350     my $class24   = $classes->{LoAdEr_test24}  || $classes->{loader_test24};
351     my $rsobj24   = $conn->resultset($moniker2);
352
353     my $moniker35 = $monikers->{loader_test35};
354     my $class35   = $classes->{loader_test35};
355     my $rsobj35   = $conn->resultset($moniker35);
356
357     isa_ok( $rsobj1, "DBIx::Class::ResultSet" );
358     isa_ok( $rsobj2, "DBIx::Class::ResultSet" );
359     isa_ok( $rsobj23, "DBIx::Class::ResultSet" );
360     isa_ok( $rsobj24, "DBIx::Class::ResultSet" );
361     isa_ok( $rsobj35, "DBIx::Class::ResultSet" );
362
363     my @columns_lt2 = $class2->columns;
364     is_deeply( \@columns_lt2, [ qw/id dat dat2 set_primary_key can dbix_class_testcomponent dbix_class_testcomponentmap testcomponent_fqn meta test_role_method test_role_for_map_method/ ], "Column Ordering" );
365
366     is $class2->column_info('can')->{accessor}, 'caught_collision_can',
367         'accessor for column name that conflicts with a UNIVERSAL method renamed based on col_collision_map';
368
369     ok (exists $class2->column_info('set_primary_key')->{accessor}
370         && (not defined $class2->column_info('set_primary_key')->{accessor}),
371         'accessor for column name that conflicts with a result base class method removed');
372
373     ok (exists $class2->column_info('dbix_class_testcomponent')->{accessor}
374         && (not defined $class2->column_info('dbix_class_testcomponent')->{accessor}),
375         'accessor for column name that conflicts with a component class method removed');
376
377     ok (exists $class2->column_info('dbix_class_testcomponentmap')->{accessor}
378         && (not defined $class2->column_info('dbix_class_testcomponentmap')->{accessor}),
379         'accessor for column name that conflicts with a component class method removed');
380
381     ok (exists $class2->column_info('testcomponent_fqn')->{accessor}
382         && (not defined $class2->column_info('testcomponent_fqn')->{accessor}),
383         'accessor for column name that conflicts with a fully qualified component class method removed');
384
385     if ($self->{use_moose}) {
386         ok (exists $class2->column_info('meta')->{accessor}
387             && (not defined $class2->column_info('meta')->{accessor}),
388             'accessor for column name that conflicts with Moose removed');
389
390         ok (exists $class2->column_info('test_role_for_map_method')->{accessor}
391             && (not defined $class2->column_info('test_role_for_map_method')->{accessor}),
392             'accessor for column name that conflicts with a Result role removed');
393
394         ok (exists $class2->column_info('test_role_method')->{accessor}
395             && (not defined $class2->column_info('test_role_method')->{accessor}),
396             'accessor for column name that conflicts with a Result role removed');
397     }
398     else {
399         ok ((not exists $class2->column_info('meta')->{accessor}),
400             "not removing 'meta' accessor with use_moose disabled");
401
402         ok ((not exists $class2->column_info('test_role_for_map_method')->{accessor}),
403             'no role method conflicts with use_moose disabled');
404
405         ok ((not exists $class2->column_info('test_role_method')->{accessor}),
406             'no role method conflicts with use_moose disabled');
407     }
408
409     my %uniq1 = $class1->unique_constraints;
410     my $uniq1_test = 0;
411     foreach my $ucname (keys %uniq1) {
412         my $cols_arrayref = $uniq1{$ucname};
413         if(@$cols_arrayref == 1 && $cols_arrayref->[0] eq 'dat') {
414            $uniq1_test = 1;
415            last;
416         }
417     }
418     ok($uniq1_test, "Unique constraint");
419
420     is($moniker1, 'LoaderTest1', 'moniker singularisation');
421
422     my %uniq2 = $class2->unique_constraints;
423     my $uniq2_test = 0;
424     foreach my $ucname (keys %uniq2) {
425         my $cols_arrayref = $uniq2{$ucname};
426         if(@$cols_arrayref == 2
427            && $cols_arrayref->[0] eq 'dat2'
428            && $cols_arrayref->[1] eq 'dat') {
429             $uniq2_test = 2;
430             last;
431         }
432     }
433     ok($uniq2_test, "Multi-col unique constraint");
434
435     is($moniker2, 'LoaderTest2X', "moniker_map testing");
436
437     SKIP: {
438         can_ok( $class1, 'test_additional_base' )
439             or skip "Pre-requisite test failed", 1;
440         is( $class1->test_additional_base, "test_additional_base",
441             "Additional Base method" );
442     }
443
444     SKIP: {
445         can_ok( $class1, 'test_additional_base_override' )
446             or skip "Pre-requisite test failed", 1;
447         is( $class1->test_additional_base_override,
448             "test_left_base_override",
449             "Left Base overrides Additional Base method" );
450     }
451
452     SKIP: {
453         can_ok( $class1, 'test_additional_base_additional' )
454             or skip "Pre-requisite test failed", 1;
455         is( $class1->test_additional_base_additional, "test_additional",
456             "Additional Base can use Additional package method" );
457     }
458
459     SKIP: {
460         can_ok( $class1, 'dbix_class_testcomponent' )
461             or skip "Pre-requisite test failed", 1;
462         is( $class1->dbix_class_testcomponent,
463             'dbix_class_testcomponent works',
464             'Additional Component' );
465     }
466
467     is try { $class2->dbix_class_testcomponentmap }, 'dbix_class_testcomponentmap works',
468         'component from result_component_map';
469
470     isnt try { $class1->dbix_class_testcomponentmap }, 'dbix_class_testcomponentmap works',
471         'component from result_component_map not added to not mapped Result';
472
473     is try { $class1->testcomponent_fqn }, 'TestComponentFQN works',
474         'fully qualified component class';
475
476     is try { $class1->testcomponentformap_fqn }, 'TestComponentForMapFQN works',
477         'fully qualified component class from result_component_map';
478
479     isnt try { $class2->testcomponentformap_fqn }, 'TestComponentForMapFQN works',
480         'fully qualified component class from result_component_map not added to not mapped Result';
481
482     SKIP: {
483         skip 'not testing role methods with use_moose disabled', 2
484             unless $self->{use_moose};
485
486         is try { $class1->test_role_method }, 'test_role_method works',
487             'role from result_roles applied';
488
489         is try { $class2->test_role_for_map_method },
490             'test_role_for_map_method works',
491             'role from result_roles_map applied';
492     }
493
494     SKIP: {
495         can_ok( $class1, 'loader_test1_classmeth' )
496             or skip "Pre-requisite test failed", 1;
497         is( $class1->loader_test1_classmeth, 'all is well', 'Class method' );
498     }
499
500     ok( $class1->column_info('id')->{is_auto_increment}, 'is_auto_increment detection' );
501
502     my $obj = try { $rsobj1->find(1) };
503
504     is( try { $obj->id },  1, "Find got the right row" );
505     is( try { $obj->dat }, "foo", "Column value" );
506     is( $rsobj2->count, 4, "Count" );
507     my $saved_id;
508     eval {
509         my $new_obj1 = $rsobj1->create({ dat => 'newthing' });
510         $saved_id = $new_obj1->id;
511     };
512     ok(!$@, "Inserting new record using a PK::Auto key didn't die") or diag $@;
513     ok($saved_id, "Got PK::Auto-generated id");
514
515     my $new_obj1 = $rsobj1->search({ dat => 'newthing' })->first;
516     ok($new_obj1, "Found newly inserted PK::Auto record");
517     is($new_obj1->id, $saved_id, "Correct PK::Auto-generated id");
518
519     my ($obj2) = $rsobj2->search({ dat => 'bbb' })->first;
520     is( $obj2->id, 2 );
521
522     SKIP: {
523         skip 'no DEFAULT on Access', 7 if $self->{vendor} eq 'Access';
524
525         is(
526             $class35->column_info('a_varchar')->{default_value}, 'foo',
527             'constant character default',
528         );
529
530         is(
531             $class35->column_info('an_int')->{default_value}, 42,
532             'constant integer default',
533         );
534
535         is(
536             $class35->column_info('a_negative_int')->{default_value}, -42,
537             'constant negative integer default',
538         );
539
540         is(
541             sprintf("%.3f", $class35->column_info('a_double')->{default_value}), '10.555',
542             'constant numeric default',
543         );
544
545         is(
546             sprintf("%.3f", $class35->column_info('a_negative_double')->{default_value}), -10.555,
547             'constant negative numeric default',
548         );
549
550         my $function_default = $class35->column_info('a_function')->{default_value};
551
552         isa_ok( $function_default, 'SCALAR', 'default_value for function default' );
553         is_deeply(
554             $function_default, \$self->{default_function},
555             'default_value for function default is correct'
556         );
557     }
558
559     SKIP: {
560         skip $self->{skip_rels}, 124 if $self->{skip_rels};
561
562         my $moniker3 = $monikers->{loader_test3};
563         my $class3   = $classes->{loader_test3};
564         my $rsobj3   = $conn->resultset($moniker3);
565
566         my $moniker4 = $monikers->{loader_test4};
567         my $class4   = $classes->{loader_test4};
568         my $rsobj4   = $conn->resultset($moniker4);
569
570         my $moniker5 = $monikers->{loader_test5};
571         my $class5   = $classes->{loader_test5};
572         my $rsobj5   = $conn->resultset($moniker5);
573
574         my $moniker6 = $monikers->{loader_test6};
575         my $class6   = $classes->{loader_test6};
576         my $rsobj6   = $conn->resultset($moniker6);
577
578         my $moniker7 = $monikers->{loader_test7};
579         my $class7   = $classes->{loader_test7};
580         my $rsobj7   = $conn->resultset($moniker7);
581
582         my $moniker8 = $monikers->{loader_test8};
583         my $class8   = $classes->{loader_test8};
584         my $rsobj8   = $conn->resultset($moniker8);
585
586         my $moniker9 = $monikers->{loader_test9};
587         my $class9   = $classes->{loader_test9};
588         my $rsobj9   = $conn->resultset($moniker9);
589
590         my $moniker16 = $monikers->{loader_test16};
591         my $class16   = $classes->{loader_test16};
592         my $rsobj16   = $conn->resultset($moniker16);
593
594         my $moniker17 = $monikers->{loader_test17};
595         my $class17   = $classes->{loader_test17};
596         my $rsobj17   = $conn->resultset($moniker17);
597
598         my $moniker18 = $monikers->{loader_test18};
599         my $class18   = $classes->{loader_test18};
600         my $rsobj18   = $conn->resultset($moniker18);
601
602         my $moniker19 = $monikers->{loader_test19};
603         my $class19   = $classes->{loader_test19};
604         my $rsobj19   = $conn->resultset($moniker19);
605
606         my $moniker20 = $monikers->{loader_test20};
607         my $class20   = $classes->{loader_test20};
608         my $rsobj20   = $conn->resultset($moniker20);
609
610         my $moniker21 = $monikers->{loader_test21};
611         my $class21   = $classes->{loader_test21};
612         my $rsobj21   = $conn->resultset($moniker21);
613
614         my $moniker22 = $monikers->{loader_test22};
615         my $class22   = $classes->{loader_test22};
616         my $rsobj22   = $conn->resultset($moniker22);
617
618         my $moniker25 = $monikers->{loader_test25};
619         my $class25   = $classes->{loader_test25};
620         my $rsobj25   = $conn->resultset($moniker25);
621
622         my $moniker26 = $monikers->{loader_test26};
623         my $class26   = $classes->{loader_test26};
624         my $rsobj26   = $conn->resultset($moniker26);
625
626         my $moniker27 = $monikers->{loader_test27};
627         my $class27   = $classes->{loader_test27};
628         my $rsobj27   = $conn->resultset($moniker27);
629
630         my $moniker28 = $monikers->{loader_test28};
631         my $class28   = $classes->{loader_test28};
632         my $rsobj28   = $conn->resultset($moniker28);
633
634         my $moniker29 = $monikers->{loader_test29};
635         my $class29   = $classes->{loader_test29};
636         my $rsobj29   = $conn->resultset($moniker29);
637
638         my $moniker31 = $monikers->{loader_test31};
639         my $class31   = $classes->{loader_test31};
640         my $rsobj31   = $conn->resultset($moniker31);
641
642         my $moniker32 = $monikers->{loader_test32};
643         my $class32   = $classes->{loader_test32};
644         my $rsobj32   = $conn->resultset($moniker32);
645
646         my $moniker33 = $monikers->{loader_test33};
647         my $class33   = $classes->{loader_test33};
648         my $rsobj33   = $conn->resultset($moniker33);
649
650         my $moniker34 = $monikers->{loader_test34};
651         my $class34   = $classes->{loader_test34};
652         my $rsobj34   = $conn->resultset($moniker34);
653
654         my $moniker36 = $monikers->{loader_test36};
655         my $class36   = $classes->{loader_test36};
656         my $rsobj36   = $conn->resultset($moniker36);
657         
658         isa_ok( $rsobj3, "DBIx::Class::ResultSet" );
659         isa_ok( $rsobj4, "DBIx::Class::ResultSet" );
660         isa_ok( $rsobj5, "DBIx::Class::ResultSet" );
661         isa_ok( $rsobj6, "DBIx::Class::ResultSet" );
662         isa_ok( $rsobj7, "DBIx::Class::ResultSet" );
663         isa_ok( $rsobj8, "DBIx::Class::ResultSet" );
664         isa_ok( $rsobj9, "DBIx::Class::ResultSet" );
665         isa_ok( $rsobj16, "DBIx::Class::ResultSet" );
666         isa_ok( $rsobj17, "DBIx::Class::ResultSet" );
667         isa_ok( $rsobj18, "DBIx::Class::ResultSet" );
668         isa_ok( $rsobj19, "DBIx::Class::ResultSet" );
669         isa_ok( $rsobj20, "DBIx::Class::ResultSet" );
670         isa_ok( $rsobj21, "DBIx::Class::ResultSet" );
671         isa_ok( $rsobj22, "DBIx::Class::ResultSet" );
672         isa_ok( $rsobj25, "DBIx::Class::ResultSet" );
673         isa_ok( $rsobj26, "DBIx::Class::ResultSet" );
674         isa_ok( $rsobj27, "DBIx::Class::ResultSet" );
675         isa_ok( $rsobj28, "DBIx::Class::ResultSet" );
676         isa_ok( $rsobj29, "DBIx::Class::ResultSet" );
677         isa_ok( $rsobj31, "DBIx::Class::ResultSet" );
678         isa_ok( $rsobj32, "DBIx::Class::ResultSet" );
679         isa_ok( $rsobj33, "DBIx::Class::ResultSet" );
680         isa_ok( $rsobj34, "DBIx::Class::ResultSet" );
681         isa_ok( $rsobj36, "DBIx::Class::ResultSet" );
682
683         # basic rel test
684         my $obj4 = try { $rsobj4->find(123) } || $rsobj4->search({ id => 123 })->first;
685         isa_ok( try { $obj4->fkid_singular }, $class3);
686
687         # test renaming rel that conflicts with a class method
688         ok ($obj4->has_relationship('belongs_to_rel'), 'relationship name that conflicts with a method renamed');
689
690         isa_ok( try { $obj4->belongs_to_rel }, $class3);
691
692         ok ($obj4->has_relationship('caught_rel_collision_set_primary_key'),
693             'relationship name that conflicts with a method renamed based on rel_collision_map');
694         isa_ok( try { $obj4->caught_rel_collision_set_primary_key }, $class3);
695
696         ok($class4->column_info('fkid')->{is_foreign_key}, 'Foreign key detected');
697
698         my $obj3 = try { $rsobj3->find(1) } || $rsobj3->search({ id => 1 })->first;
699         my $rs_rel4 = try { $obj3->search_related('loader_test4zes') };
700         isa_ok( try { $rs_rel4->first }, $class4);
701
702         is( $class4->column_info('crumb_crisp_coating')->{accessor},  'trivet',
703             'col_accessor_map is being run' );
704
705         # check rel naming with prepositions
706         ok ($rsobj4->result_source->has_relationship('loader_test5s_to'),
707             "rel with preposition 'to' pluralized correctly");
708
709         ok ($rsobj4->result_source->has_relationship('loader_test5s_from'),
710             "rel with preposition 'from' pluralized correctly");
711
712         # check default relationship attributes
713         is try { $rsobj3->result_source->relationship_info('loader_test4zes')->{attrs}{cascade_delete} }, 0,
714             'cascade_delete => 0 on has_many by default';
715
716         is try { $rsobj3->result_source->relationship_info('loader_test4zes')->{attrs}{cascade_copy} }, 0,
717             'cascade_copy => 0 on has_many by default';
718
719         ok ((not try { exists $rsobj3->result_source->relationship_info('loader_test4zes')->{attrs}{on_delete} }),
720             'has_many does not have on_delete');
721
722         ok ((not try { exists $rsobj3->result_source->relationship_info('loader_test4zes')->{attrs}{on_update} }),
723             'has_many does not have on_update');
724
725         ok ((not try { exists $rsobj3->result_source->relationship_info('loader_test4zes')->{attrs}{is_deferrable} }),
726             'has_many does not have is_deferrable');
727
728         is try { $rsobj4->result_source->relationship_info('fkid_singular')->{attrs}{on_delete} }, 'CASCADE',
729             "on_delete => 'CASCADE' on belongs_to by default";
730
731         is try { $rsobj4->result_source->relationship_info('fkid_singular')->{attrs}{on_update} }, 'CASCADE',
732             "on_update => 'CASCADE' on belongs_to by default";
733
734         is try { $rsobj4->result_source->relationship_info('fkid_singular')->{attrs}{is_deferrable} }, 1,
735             "is_deferrable => 1 on belongs_to by default";
736
737         ok ((not try { exists $rsobj4->result_source->relationship_info('fkid_singular')->{attrs}{cascade_delete} }),
738             'belongs_to does not have cascade_delete');
739
740         ok ((not try { exists $rsobj4->result_source->relationship_info('fkid_singular')->{attrs}{cascade_copy} }),
741             'belongs_to does not have cascade_copy');
742
743         is try { $rsobj27->result_source->relationship_info('loader_test28')->{attrs}{cascade_delete} }, 0,
744             'cascade_delete => 0 on might_have by default';
745
746         is try { $rsobj27->result_source->relationship_info('loader_test28')->{attrs}{cascade_copy} }, 0,
747             'cascade_copy => 0 on might_have by default';
748
749         ok ((not try { exists $rsobj27->result_source->relationship_info('loader_test28')->{attrs}{on_delete} }),
750             'might_have does not have on_delete');
751
752         ok ((not try { exists $rsobj27->result_source->relationship_info('loader_test28')->{attrs}{on_update} }),
753             'might_have does not have on_update');
754
755         ok ((not try { exists $rsobj27->result_source->relationship_info('loader_test28')->{attrs}{is_deferrable} }),
756             'might_have does not have is_deferrable');
757
758         # find on multi-col pk
759         if ($conn->_loader->preserve_case) {
760             my $obj5 = $rsobj5->find({id1 => 1, iD2 => 1});
761             is $obj5->i_d2, 1, 'Find on multi-col PK';
762         }
763         else {
764             my $obj5 = $rsobj5->find({id1 => 1, id2 => 1});
765             is $obj5->id2, 1, 'Find on multi-col PK';
766         }
767
768         # mulit-col fk def
769         my $obj6 = try { $rsobj6->find(1) } || $rsobj6->search({ id => 1 })->first;
770         isa_ok( try { $obj6->loader_test2 }, $class2);
771         isa_ok( try { $obj6->loader_test5 }, $class5);
772
773         ok($class6->column_info('loader_test2_id')->{is_foreign_key}, 'Foreign key detected');
774         ok($class6->column_info('id')->{is_foreign_key}, 'Foreign key detected');
775
776         my $id2_info = try { $class6->column_info('id2') } ||
777                         $class6->column_info('Id2');
778         ok($id2_info->{is_foreign_key}, 'Foreign key detected');
779
780         unlike slurp($conn->_loader->get_dump_filename($class6)),
781 qr/\n__PACKAGE__->(?:belongs_to|has_many|might_have|has_one|many_to_many)\(
782     \s+ "(\w+?)"
783     .*?
784    \n__PACKAGE__->(?:belongs_to|has_many|might_have|has_one|many_to_many)\(
785     \s+ "\1"/xs,
786 'did not create two relationships with the same name';
787
788        unlike slurp($conn->_loader->get_dump_filename($class8)),
789 qr/\n__PACKAGE__->(?:belongs_to|has_many|might_have|has_one|many_to_many)\(
790     \s+ "(\w+?)"
791     .*?
792    \n__PACKAGE__->(?:belongs_to|has_many|might_have|has_one|many_to_many)\(
793     \s+ "\1"/xs,
794 'did not create two relationships with the same name';
795
796         # check naming of ambiguous relationships
797         my $rel_info = $class6->relationship_info('lovely_loader_test7') || {};
798
799         ok (($class6->has_relationship('lovely_loader_test7')
800             && $rel_info->{cond}{'foreign.lovely_loader_test6'} eq 'self.id'
801             && $rel_info->{class} eq $class7
802             && $rel_info->{attrs}{accessor} eq 'single'),
803             'ambiguous relationship named correctly');
804
805         $rel_info = $class8->relationship_info('active_loader_test16') || {};
806
807         ok (($class8->has_relationship('active_loader_test16')
808             && $rel_info->{cond}{'foreign.loader_test8_id'} eq 'self.id'
809             && $rel_info->{class} eq $class16
810             && $rel_info->{attrs}{accessor} eq 'single'),
811             'ambiguous relationship named correctly');
812
813         # fk that references a non-pk key (UNIQUE)
814         my $obj8 = try { $rsobj8->find(1) } || $rsobj8->search({ id => 1 })->first;
815         isa_ok( try { $obj8->loader_test7 }, $class7);
816
817         ok($class8->column_info('loader_test7')->{is_foreign_key}, 'Foreign key detected');
818
819         # test double-fk 17 ->-> 16
820         my $obj17 = try { $rsobj17->find(33) } || $rsobj17->search({ id => 33 })->first;
821
822         my $rs_rel16_one = try { $obj17->loader16_one };
823         isa_ok($rs_rel16_one, $class16);
824         is(try { $rs_rel16_one->dat }, 'y16', "Multiple FKs to same table");
825
826         ok($class17->column_info('loader16_one')->{is_foreign_key}, 'Foreign key detected');
827
828         my $rs_rel16_two = try { $obj17->loader16_two };
829         isa_ok($rs_rel16_two, $class16);
830         is(try { $rs_rel16_two->dat }, 'z16', "Multiple FKs to same table");
831
832         ok($class17->column_info('loader16_two')->{is_foreign_key}, 'Foreign key detected');
833
834         my $obj16 = try { $rsobj16->find(2) } || $rsobj16->search({ id => 2 })->first;
835         my $rs_rel17 = try { $obj16->search_related('loader_test17_loader16_ones') };
836         isa_ok(try { $rs_rel17->first }, $class17);
837         is(try { $rs_rel17->first->id }, 3, "search_related with multiple FKs from same table");
838         
839         # XXX test m:m 18 <- 20 -> 19
840         ok($class20->column_info('parent')->{is_foreign_key}, 'Foreign key detected');
841         ok($class20->column_info('child')->{is_foreign_key}, 'Foreign key detected');
842         
843         # XXX test double-fk m:m 21 <- 22 -> 21
844         ok($class22->column_info('parent')->{is_foreign_key}, 'Foreign key detected');
845         ok($class22->column_info('child')->{is_foreign_key}, 'Foreign key detected');
846
847         # test double multi-col fk 26 -> 25
848         my $obj26 = try { $rsobj26->find(33) } || $rsobj26->search({ id => 33 })->first;
849
850         my $rs_rel25_one = try { $obj26->loader_test25_id_rel1 };
851         isa_ok($rs_rel25_one, $class25);
852         is(try { $rs_rel25_one->dat }, 'x25', "Multiple multi-col FKs to same table");
853
854         ok($class26->column_info('id')->{is_foreign_key}, 'Foreign key detected');
855         ok($class26->column_info('rel1')->{is_foreign_key}, 'Foreign key detected');
856         ok($class26->column_info('rel2')->{is_foreign_key}, 'Foreign key detected');
857
858         my $rs_rel25_two = try { $obj26->loader_test25_id_rel2 };
859         isa_ok($rs_rel25_two, $class25);
860         is(try { $rs_rel25_two->dat }, 'y25', "Multiple multi-col FKs to same table");
861
862         my $obj25 = try { $rsobj25->find(3,42) } || $rsobj25->search({ id1 => 3, id2 => 42 })->first;
863         my $rs_rel26 = try { $obj25->search_related('loader_test26_id_rel1s') };
864         isa_ok(try { $rs_rel26->first }, $class26);
865         is(try { $rs_rel26->first->id }, 3, "search_related with multiple multi-col FKs from same table");
866
867         # test one-to-one rels
868         my $obj27 = try { $rsobj27->find(1) } || $rsobj27->search({ id => 1 })->first;
869         my $obj28 = try { $obj27->loader_test28 };
870         isa_ok($obj28, $class28);
871         is(try { $obj28->get_column('id') }, 1, "One-to-one relationship with PRIMARY FK");
872
873         ok($class28->column_info('id')->{is_foreign_key}, 'Foreign key detected');
874
875         my $obj29 = try { $obj27->loader_test29 };
876         isa_ok($obj29, $class29);
877         is(try { $obj29->id }, 1, "One-to-one relationship with UNIQUE FK");
878
879         ok($class29->column_info('fk')->{is_foreign_key}, 'Foreign key detected');
880
881         $obj27 = try { $rsobj27->find(2) } || $rsobj27->search({ id => 2 })->first;
882         is(try { $obj27->loader_test28 }, undef, "Undef for missing one-to-one row");
883         is(try { $obj27->loader_test29 }, undef, "Undef for missing one-to-one row");
884
885         # test outer join for nullable referring columns:
886         is $class32->column_info('rel2')->{is_nullable}, 1,
887           'is_nullable detection';
888
889         ok($class32->column_info('rel1')->{is_foreign_key}, 'Foreign key detected');
890         ok($class32->column_info('rel2')->{is_foreign_key}, 'Foreign key detected');
891         
892         my $obj32 = try { $rsobj32->find(1, { prefetch => [qw/rel1 rel2/] }) }
893             || try { $rsobj32->search({ id => 1 }, { prefetch => [qw/rel1 rel2/] })->first }
894             || $rsobj32->search({ id => 1 })->first;
895
896         my $obj34 = eval { $rsobj34->find(1, { prefetch => [qw/loader_test33_id_rel1 loader_test33_id_rel2/] }) }
897             || eval { $rsobj34->search({ id => 1 }, { prefetch => [qw/loader_test33_id_rel1 loader_test33_id_rel2/] })->first }
898             || $rsobj34->search({ id => 1 })->first;
899         diag $@ if $@;
900
901         isa_ok($obj32,$class32);
902         isa_ok($obj34,$class34);
903
904         ok($class34->column_info('id')->{is_foreign_key}, 'Foreign key detected');
905         ok($class34->column_info('rel1')->{is_foreign_key}, 'Foreign key detected');
906         ok($class34->column_info('rel2')->{is_foreign_key}, 'Foreign key detected');
907
908         my $rs_rel31_one = try { $obj32->rel1 };
909         my $rs_rel31_two = try { $obj32->rel2 };
910         isa_ok($rs_rel31_one, $class31);
911         is($rs_rel31_two, undef);
912
913         my $rs_rel33_one = try { $obj34->loader_test33_id_rel1 };
914         my $rs_rel33_two = try { $obj34->loader_test33_id_rel2 };
915
916         isa_ok($rs_rel33_one, $class33);
917         isa_ok($rs_rel33_two, $class33);
918
919         # from Chisel's tests...
920         my $moniker10 = $monikers->{loader_test10};
921         my $class10   = $classes->{loader_test10};
922         my $rsobj10   = $conn->resultset($moniker10);
923
924         my $moniker11 = $monikers->{loader_test11};
925         my $class11   = $classes->{loader_test11};
926         my $rsobj11   = $conn->resultset($moniker11);
927
928         isa_ok( $rsobj10, "DBIx::Class::ResultSet" );
929         isa_ok( $rsobj11, "DBIx::Class::ResultSet" );
930
931         ok($class10->column_info('loader_test11')->{is_foreign_key}, 'Foreign key detected');
932         ok($class11->column_info('loader_test10')->{is_foreign_key}, 'Foreign key detected');
933
934         my $obj10 = $rsobj10->create({ subject => 'xyzzy' });
935
936         $obj10->update();
937         ok( defined $obj10, 'Create row' );
938
939         my $obj11 = $rsobj11->create({ loader_test10 => (try { $obj10->id() } || $obj10->id10) });
940         $obj11->update();
941         ok( defined $obj11, 'Create related row' );
942
943         eval {
944             my $obj10_2 = $obj11->loader_test10;
945             $obj10_2->update({ loader_test11 => $obj11->id11 });
946         };
947         diag $@ if $@;
948         ok(!$@, "Setting up circular relationship");
949
950         SKIP: {
951             skip 'Previous eval block failed', 3 if $@;
952     
953             my $results = $rsobj10->search({ subject => 'xyzzy' });
954             is( $results->count(), 1, 'No duplicate row created' );
955
956             my $obj10_3 = $results->first();
957             isa_ok( $obj10_3, $class10 );
958             is( $obj10_3->loader_test11()->id(), $obj11->id(),
959                 'Circular rel leads back to same row' );
960         }
961
962         SKIP: {
963             skip 'This vendor cannot do inline relationship definitions', 11
964                 if $self->{no_inline_rels};
965
966             my $moniker12 = $monikers->{loader_test12};
967             my $class12   = $classes->{loader_test12};
968             my $rsobj12   = $conn->resultset($moniker12);
969
970             my $moniker13 = $monikers->{loader_test13};
971             my $class13   = $classes->{loader_test13};
972             my $rsobj13   = $conn->resultset($moniker13);
973
974             isa_ok( $rsobj12, "DBIx::Class::ResultSet" ); 
975             isa_ok( $rsobj13, "DBIx::Class::ResultSet" );
976
977             ok($class13->column_info('id')->{is_foreign_key}, 'Foreign key detected');
978             ok($class13->column_info('loader_test12')->{is_foreign_key}, 'Foreign key detected');
979             ok($class13->column_info('dat')->{is_foreign_key}, 'Foreign key detected');
980
981             my $obj13 = try { $rsobj13->find(1) } || $rsobj13->search({ id => 1 })->first;
982             isa_ok( $obj13->id, $class12 );
983             isa_ok( $obj13->loader_test12, $class12);
984             isa_ok( $obj13->dat, $class12);
985
986             my $obj12 = try { $rsobj12->find(1) } || $rsobj12->search({ id => 1 })->first;
987             isa_ok( try { $obj12->loader_test13 }, $class13 );
988
989             # relname is preserved when another fk is added
990
991             {
992                 local $SIG{__WARN__} = sub { warn @_ unless $_[0] =~ /invalidates \d+ active statement/ };
993                 $conn->storage->disconnect; # for mssql and access
994             }
995
996             isa_ok try { $rsobj3->find(1)->loader_test4zes }, 'DBIx::Class::ResultSet';
997
998             $conn->storage->disconnect; # for access
999
1000             if (lc($self->{vendor}) ne 'sybase') {
1001                 $conn->storage->dbh->do('ALTER TABLE loader_test4 ADD fkid2 INTEGER REFERENCES loader_test3 (id)');
1002             }
1003             else {
1004                 $conn->storage->dbh->do(<<"EOF");
1005                 ALTER TABLE loader_test4 ADD fkid2 INTEGER $self->{null}
1006                 ALTER TABLE loader_test4 ADD CONSTRAINT loader_test4_to_3_fk FOREIGN KEY (fkid2) REFERENCES loader_test3 (id)
1007 EOF
1008             }
1009
1010             $conn->storage->disconnect; # for firebird
1011
1012             $self->rescan_without_warnings($conn);
1013
1014             isa_ok try { $rsobj3->find(1)->loader_test4zes }, 'DBIx::Class::ResultSet',
1015                 'relationship name preserved when another foreign key is added in remote table';
1016         }
1017
1018         SKIP: {
1019             skip 'This vendor cannot do out-of-line implicit rel defs', 4
1020                 if $self->{no_implicit_rels};
1021             my $moniker14 = $monikers->{loader_test14};
1022             my $class14   = $classes->{loader_test14};
1023             my $rsobj14   = $conn->resultset($moniker14);
1024
1025             my $moniker15 = $monikers->{loader_test15};
1026             my $class15   = $classes->{loader_test15};
1027             my $rsobj15   = $conn->resultset($moniker15);
1028
1029             isa_ok( $rsobj14, "DBIx::Class::ResultSet" ); 
1030             isa_ok( $rsobj15, "DBIx::Class::ResultSet" );
1031
1032             ok($class15->column_info('loader_test14')->{is_foreign_key}, 'Foreign key detected');
1033
1034             my $obj15 = try { $rsobj15->find(1) } || $rsobj15->search({ id => 1 })->first;
1035             isa_ok( $obj15->loader_test14, $class14 );
1036         }
1037     }
1038
1039     # test custom_column_info and datetime_timezone/datetime_locale
1040     {
1041         my $class35 = $classes->{loader_test35};
1042         my $class36 = $classes->{loader_test36};
1043
1044         ok($class35->column_info('an_int')->{is_numeric}, 'custom_column_info');
1045
1046         is($class36->column_info('a_date')->{locale},'de_DE','datetime_locale');
1047         is($class36->column_info('a_date')->{timezone},'Europe/Berlin','datetime_timezone');
1048
1049         ok($class36->column_info('b_char_as_data')->{inflate_datetime},'custom_column_info');
1050         is($class36->column_info('b_char_as_data')->{locale},'de_DE','datetime_locale');
1051         is($class36->column_info('b_char_as_data')->{timezone},'Europe/Berlin','datetime_timezone');
1052
1053         ok($class36->column_info('c_char_as_data')->{inflate_date},'custom_column_info');
1054         is($class36->column_info('c_char_as_data')->{locale},'de_DE','datetime_locale');
1055         is($class36->column_info('c_char_as_data')->{timezone},'Europe/Berlin','datetime_timezone');
1056     }
1057
1058     # rescan and norewrite test
1059     {
1060         my @statements_rescan = (
1061             qq{
1062                 CREATE TABLE loader_test30 (
1063                     id INTEGER NOT NULL PRIMARY KEY,
1064                     loader_test2 INTEGER NOT NULL,
1065                     FOREIGN KEY (loader_test2) REFERENCES loader_test2 (id)
1066                 ) $self->{innodb}
1067             },
1068             q{ INSERT INTO loader_test30 (id,loader_test2) VALUES(123, 1) },
1069             q{ INSERT INTO loader_test30 (id,loader_test2) VALUES(321, 2) },
1070         );
1071
1072         # get md5
1073         my $digest  = Digest::MD5->new;
1074
1075         my $find_cb = sub {
1076             return if -d;
1077             return if /^(?:LoaderTest30|LoaderTest1|LoaderTest2X)\.pm\z/;
1078
1079             open my $fh, '<', $_ or die "Could not open $_ for reading: $!";
1080             binmode $fh;
1081             $digest->addfile($fh);
1082         };
1083
1084         find $find_cb, $DUMP_DIR;
1085
1086 #        system "rm -f /tmp/before_rescan/* /tmp/after_rescan/*";
1087 #        system "cp $tdir/common_dump/DBIXCSL_Test/Schema/*.pm /tmp/before_rescan";
1088
1089         my $before_digest = $digest->b64digest;
1090
1091         $conn->storage->disconnect; # needed for Firebird and Informix
1092         my $dbh = $self->dbconnect(1);
1093         $dbh->do($_) for @statements_rescan;
1094         $dbh->disconnect;
1095
1096         sleep 1;
1097
1098         my @new = $self->rescan_without_warnings($conn);
1099
1100         is_deeply(\@new, [ qw/LoaderTest30/ ], "Rescan");
1101
1102 #        system "cp t/_common_dump/DBIXCSL_Test/Schema/*.pm /tmp/after_rescan";
1103
1104         $digest = Digest::MD5->new;
1105         find $find_cb, $DUMP_DIR;
1106         my $after_digest = $digest->b64digest;
1107
1108         is $before_digest, $after_digest,
1109             'dumped files are not rewritten when there is no modification';
1110
1111         my $rsobj30   = $conn->resultset('LoaderTest30');
1112         isa_ok($rsobj30, 'DBIx::Class::ResultSet');
1113
1114         SKIP: {
1115             skip 'no rels', 2 if $self->{skip_rels};
1116
1117             my $obj30 = try { $rsobj30->find(123) } || $rsobj30->search({ id => 123 })->first;
1118             isa_ok( $obj30->loader_test2, $class2);
1119
1120             ok($rsobj30->result_source->column_info('loader_test2')->{is_foreign_key},
1121                'Foreign key detected');
1122         }
1123
1124         $conn->storage->disconnect; # for Firebird
1125         $self->drop_table($conn->storage->dbh, 'loader_test30');
1126
1127         @new = $self->rescan_without_warnings($conn);
1128
1129         is_deeply(\@new, [], 'no new tables on rescan');
1130
1131         throws_ok { $conn->resultset('LoaderTest30') }
1132             qr/Can't find source/,
1133             'source unregistered for dropped table after rescan';
1134     }
1135
1136     $self->test_data_types($conn);
1137
1138     # run extra tests
1139     $self->{extra}{run}->($conn, $monikers, $classes, $self) if $self->{extra}{run};
1140
1141     $self->test_preserve_case($conn);
1142
1143     $self->drop_tables unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP};
1144
1145     $conn->storage->disconnect;
1146 }
1147
1148 sub test_data_types {
1149     my ($self, $conn) = @_;
1150
1151     SKIP: {
1152         if (my $test_count = $self->{data_type_tests}{test_count}) {
1153             if ($self->{vendor} eq 'mssql' && $conn->storage->dbh->{Driver}{Name} eq 'Sybase') {
1154                 skip 'DBD::Sybase does not work with the data_type tests on latest SQL Server', $test_count;
1155             }
1156
1157             my $data_type_tests = $self->{data_type_tests};
1158
1159             foreach my $moniker (@{ $data_type_tests->{table_monikers} }) {
1160                 my $columns = $data_type_tests->{columns}{$moniker};
1161
1162                 my $rsrc = $conn->resultset($moniker)->result_source;
1163
1164                 while (my ($col_name, $expected_info) = each %$columns) {
1165                     my %info = %{ $rsrc->column_info($col_name) };
1166                     delete @info{qw/is_nullable timezone locale sequence/};
1167
1168                     my $text_col_def = dumper_squashed \%info;
1169
1170                     my $text_expected_info = dumper_squashed $expected_info;
1171
1172                     is_deeply \%info, $expected_info,
1173                         "test column $col_name has definition: $text_col_def expecting: $text_expected_info";
1174                 }
1175             }
1176         }
1177     }
1178 }
1179
1180 sub test_preserve_case {
1181     my ($self, $conn) = @_;
1182
1183     my ($oqt, $cqt) = $self->get_oqt_cqt(always => 1); # open quote, close quote
1184
1185     my $dbh = $self->dbconnect;
1186
1187     $dbh->do($_) for (
1188 qq|
1189     CREATE TABLE ${oqt}LoaderTest40${cqt} (
1190         ${oqt}Id${cqt} INTEGER NOT NULL PRIMARY KEY,
1191         ${oqt}Foo3Bar${cqt} VARCHAR(100) NOT NULL
1192     ) $self->{innodb}
1193 |,
1194 qq|
1195     CREATE TABLE ${oqt}LoaderTest41${cqt} (
1196         ${oqt}Id${cqt} INTEGER NOT NULL PRIMARY KEY,
1197         ${oqt}LoaderTest40Id${cqt} INTEGER,
1198         FOREIGN KEY (${oqt}LoaderTest40Id${cqt}) REFERENCES ${oqt}LoaderTest40${cqt} (${oqt}Id${cqt})
1199     ) $self->{innodb}
1200 |,
1201 qq| INSERT INTO ${oqt}LoaderTest40${cqt} VALUES (1, 'foo') |,
1202 qq| INSERT INTO ${oqt}LoaderTest41${cqt} VALUES (1, 1) |,
1203     );
1204     $conn->storage->disconnect;
1205
1206     local $conn->_loader->{preserve_case} = 1;
1207     $conn->_loader->_setup;
1208
1209     $self->rescan_without_warnings($conn);
1210
1211     if (not $self->{skip_rels}) {
1212         ok my $row = try { $conn->resultset('LoaderTest41')->find(1) },
1213             'row in mixed-case table';
1214         ok my $related_row = try { $row->loader_test40 },
1215             'rel in mixed-case table';
1216         is try { $related_row->foo3_bar }, 'foo',
1217             'accessor for mixed-case column name in mixed case table';
1218     }
1219     else {
1220         SKIP: { skip 'not testing mixed-case rels with skip_rels', 2 }
1221
1222         is try { $conn->resultset('LoaderTest40')->find(1)->foo3_bar }, 'foo',
1223             'accessor for mixed-case column name in mixed case table';
1224     }
1225 }
1226
1227 sub monikers_and_classes {
1228     my ($self, $schema_class) = @_;
1229     my ($monikers, $classes);
1230
1231     foreach my $source_name ($schema_class->sources) {
1232         my $table_name = $schema_class->source($source_name)->from;
1233
1234         $table_name = $$table_name if ref $table_name;
1235
1236         $monikers->{$table_name} = $source_name;
1237         $classes->{$table_name} = $schema_class . q{::} . $source_name;
1238
1239         # some DBs (Firebird) uppercase everything
1240         $monikers->{lc $table_name} = $source_name;
1241         $classes->{lc $table_name} = $schema_class . q{::} . $source_name;
1242     }
1243
1244     return ($monikers, $classes);
1245 }
1246
1247 sub check_no_duplicate_unique_constraints {
1248     my ($class) = @_;
1249
1250     # unique_constraints() automatically includes the PK, if any
1251     my %uc_cols;
1252     ++$uc_cols{ join ", ", @$_ }
1253         for values %{ { $class->unique_constraints } };
1254     my $dup_uc = grep { $_ > 1 } values %uc_cols;
1255
1256     is($dup_uc, 0, "duplicate unique constraints ($class)")
1257         or diag "uc_cols: @{[ %uc_cols ]}";
1258 }
1259
1260 sub dbconnect {
1261     my ($self, $complain) = @_;
1262
1263     require DBIx::Class::Storage::DBI;
1264     my $storage = DBIx::Class::Storage::DBI->new;
1265
1266     $complain = defined $complain ? $complain : 1;
1267
1268     $storage->connect_info([
1269         @{ $self }{qw/dsn user password/},
1270         {
1271             unsafe => 1,
1272             RaiseError => $complain,
1273             ShowErrorStatement => $complain,
1274             PrintError => 0,
1275             %{ $self->{connect_info_opts} || {} },
1276         },
1277     ]);
1278
1279     my $dbh = $storage->dbh;
1280     die "Failed to connect to database: $@" if !$dbh;
1281
1282     $self->{storage} = $storage; # storage DESTROY disconnects
1283
1284     return $dbh;
1285 }
1286
1287 sub get_oqt_cqt {
1288     my $self = shift;
1289     my %opts = @_;
1290
1291     if ((not $opts{always}) && $self->{preserve_case_mode_is_exclusive}) {
1292         return ('', '');
1293     }
1294
1295     # XXX should get quote_char from the storage of an initialized loader.
1296     my ($oqt, $cqt); # open quote, close quote
1297     if (ref $self->{quote_char}) {
1298         ($oqt, $cqt) = @{ $self->{quote_char} };
1299     }
1300     else {
1301         $oqt = $cqt = $self->{quote_char} || '';
1302     }
1303
1304     return ($oqt, $cqt);
1305 }
1306
1307 sub create {
1308     my $self = shift;
1309
1310     $self->{_created} = 1;
1311
1312     $self->drop_tables;
1313
1314     my $make_auto_inc = $self->{auto_inc_cb} || sub {};
1315     @statements = (
1316         qq{
1317             CREATE TABLE loader_test1s (
1318                 id $self->{auto_inc_pk},
1319                 dat VARCHAR(32) NOT NULL UNIQUE
1320             ) $self->{innodb}
1321         },
1322         $make_auto_inc->(qw/loader_test1s id/),
1323
1324         q{ INSERT INTO loader_test1s (dat) VALUES('foo') },
1325         q{ INSERT INTO loader_test1s (dat) VALUES('bar') }, 
1326         q{ INSERT INTO loader_test1s (dat) VALUES('baz') }, 
1327
1328         # also test method collision
1329         qq{ 
1330             CREATE TABLE loader_test2 (
1331                 id $self->{auto_inc_pk},
1332                 dat VARCHAR(32) NOT NULL,
1333                 dat2 VARCHAR(32) NOT NULL,
1334                 set_primary_key INTEGER $self->{null},
1335                 can INTEGER $self->{null},
1336                 dbix_class_testcomponent INTEGER $self->{null},
1337                 dbix_class_testcomponentmap INTEGER $self->{null},
1338                 testcomponent_fqn INTEGER $self->{null},
1339                 meta INTEGER $self->{null},
1340                 test_role_method INTEGER $self->{null},
1341                 test_role_for_map_method INTEGER $self->{null},
1342                 UNIQUE (dat2, dat)
1343             ) $self->{innodb}
1344         },
1345         $make_auto_inc->(qw/loader_test2 id/),
1346
1347         q{ INSERT INTO loader_test2 (dat, dat2) VALUES('aaa', 'zzz') }, 
1348         q{ INSERT INTO loader_test2 (dat, dat2) VALUES('bbb', 'yyy') }, 
1349         q{ INSERT INTO loader_test2 (dat, dat2) VALUES('ccc', 'xxx') }, 
1350         q{ INSERT INTO loader_test2 (dat, dat2) VALUES('ddd', 'www') }, 
1351
1352         qq{
1353             CREATE TABLE LOADER_test23 (
1354                 ID INTEGER NOT NULL PRIMARY KEY,
1355                 DAT VARCHAR(32) NOT NULL UNIQUE
1356             ) $self->{innodb}
1357         },
1358
1359         qq{
1360             CREATE TABLE LoAdEr_test24 (
1361                 iD INTEGER NOT NULL PRIMARY KEY,
1362                 DaT VARCHAR(32) NOT NULL UNIQUE
1363             ) $self->{innodb}
1364         },
1365
1366 # Access does not support DEFAULT
1367         $self->{vendor} ne 'Access' ? qq{
1368             CREATE TABLE loader_test35 (
1369                 id INTEGER NOT NULL PRIMARY KEY,
1370                 a_varchar VARCHAR(100) DEFAULT 'foo',
1371                 an_int INTEGER DEFAULT 42,
1372                 a_negative_int INTEGER DEFAULT -42,
1373                 a_double DOUBLE PRECISION DEFAULT 10.555,
1374                 a_negative_double DOUBLE PRECISION DEFAULT -10.555,
1375                 a_function $self->{default_function_def}
1376             ) $self->{innodb}
1377         } : qq{
1378             CREATE TABLE loader_test35 (
1379                 id INTEGER NOT NULL PRIMARY KEY,
1380                 a_varchar VARCHAR(100),
1381                 an_int INTEGER,
1382                 a_negative_int INTEGER,
1383                 a_double DOUBLE,
1384                 a_negative_double DOUBLE,
1385                 a_function DATETIME
1386             )
1387         },
1388
1389         qq{
1390             CREATE TABLE loader_test36 (
1391                 id INTEGER NOT NULL PRIMARY KEY,
1392                 a_date $self->{basic_date_datatype},
1393                 b_char_as_data VARCHAR(100),
1394                 c_char_as_data VARCHAR(100)
1395             ) $self->{innodb}
1396         },
1397     );
1398
1399     # some DBs require mixed case identifiers to be quoted
1400     my ($oqt, $cqt) = $self->get_oqt_cqt;
1401
1402     @statements_reltests = (
1403         qq{
1404             CREATE TABLE loader_test3 (
1405                 id INTEGER NOT NULL PRIMARY KEY,
1406                 dat VARCHAR(32)
1407             ) $self->{innodb}
1408         },
1409
1410         q{ INSERT INTO loader_test3 (id,dat) VALUES(1,'aaa') }, 
1411         q{ INSERT INTO loader_test3 (id,dat) VALUES(2,'bbb') }, 
1412         q{ INSERT INTO loader_test3 (id,dat) VALUES(3,'ccc') }, 
1413         q{ INSERT INTO loader_test3 (id,dat) VALUES(4,'ddd') }, 
1414
1415         qq{
1416             CREATE TABLE loader_test4 (
1417                 id INTEGER NOT NULL PRIMARY KEY,
1418                 fkid INTEGER NOT NULL,
1419                 dat VARCHAR(32),
1420                 crumb_crisp_coating VARCHAR(32) $self->{null},
1421                 belongs_to INTEGER $self->{null},
1422                 set_primary_key INTEGER $self->{null},
1423                 FOREIGN KEY( fkid ) REFERENCES loader_test3 (id),
1424                 FOREIGN KEY( belongs_to ) REFERENCES loader_test3 (id),
1425                 FOREIGN KEY( set_primary_key ) REFERENCES loader_test3 (id)
1426             ) $self->{innodb}
1427         },
1428
1429         q{ INSERT INTO loader_test4 (id,fkid,dat,belongs_to,set_primary_key) VALUES(123,1,'aaa',1,1) },
1430         q{ INSERT INTO loader_test4 (id,fkid,dat,belongs_to,set_primary_key) VALUES(124,2,'bbb',2,2) }, 
1431         q{ INSERT INTO loader_test4 (id,fkid,dat,belongs_to,set_primary_key) VALUES(125,3,'ccc',3,3) },
1432         q{ INSERT INTO loader_test4 (id,fkid,dat,belongs_to,set_primary_key) VALUES(126,4,'ddd',4,4) },
1433
1434         qq|
1435             CREATE TABLE loader_test5 (
1436                 id1 INTEGER NOT NULL,
1437                 ${oqt}iD2${cqt} INTEGER NOT NULL,
1438                 dat VARCHAR(8),
1439                 from_id INTEGER $self->{null},
1440                 to_id INTEGER $self->{null},
1441                 PRIMARY KEY (id1,${oqt}iD2${cqt}),
1442                 FOREIGN KEY (from_id) REFERENCES loader_test4 (id),
1443                 FOREIGN KEY (to_id) REFERENCES loader_test4 (id)
1444             ) $self->{innodb}
1445         |,
1446
1447         qq| INSERT INTO loader_test5 (id1,${oqt}iD2${cqt},dat) VALUES (1,1,'aaa') |,
1448
1449         qq|
1450             CREATE TABLE loader_test6 (
1451                 id INTEGER NOT NULL PRIMARY KEY,
1452                 ${oqt}Id2${cqt} INTEGER,
1453                 loader_test2_id INTEGER,
1454                 dat VARCHAR(8),
1455                 FOREIGN KEY (loader_test2_id)  REFERENCES loader_test2 (id),
1456                 FOREIGN KEY(id,${oqt}Id2${cqt}) REFERENCES loader_test5 (id1,${oqt}iD2${cqt})
1457             ) $self->{innodb}
1458         |,
1459
1460         (qq| INSERT INTO loader_test6 (id, ${oqt}Id2${cqt},loader_test2_id,dat) | .
1461          q{ VALUES (1, 1,1,'aaa') }),
1462
1463         # here we are testing adjective detection
1464
1465         qq{
1466             CREATE TABLE loader_test7 (
1467                 id INTEGER NOT NULL PRIMARY KEY,
1468                 id2 VARCHAR(8) NOT NULL UNIQUE,
1469                 dat VARCHAR(8),
1470                 lovely_loader_test6 INTEGER NOT NULL UNIQUE,
1471                 FOREIGN KEY (lovely_loader_test6) REFERENCES loader_test6 (id)
1472             ) $self->{innodb}
1473         },
1474
1475         q{ INSERT INTO loader_test7 (id,id2,dat,lovely_loader_test6) VALUES (1,'aaa','bbb',1) },
1476
1477         # for some DBs we need a named FK to drop later
1478         ($self->{vendor} =~ /^(mssql|sybase|access|mysql)\z/i ? (
1479             (q{ ALTER TABLE loader_test6 ADD } .
1480              qq{ loader_test7_id INTEGER $self->{null} }),
1481             (q{ ALTER TABLE loader_test6 ADD CONSTRAINT loader_test6_to_7_fk } .
1482              q{ FOREIGN KEY (loader_test7_id) } .
1483              q{ REFERENCES loader_test7 (id) })
1484         ) : (
1485             (q{ ALTER TABLE loader_test6 ADD } .
1486              qq{ loader_test7_id INTEGER $self->{null} REFERENCES loader_test7 (id) }),
1487         )),
1488
1489         qq{
1490             CREATE TABLE loader_test8 (
1491                 id INTEGER NOT NULL PRIMARY KEY,
1492                 loader_test7 VARCHAR(8) NOT NULL,
1493                 dat VARCHAR(8),
1494                 FOREIGN KEY (loader_test7) REFERENCES loader_test7 (id2)
1495             ) $self->{innodb}
1496         },
1497
1498         (q{ INSERT INTO loader_test8 (id,loader_test7,dat) VALUES (1,'aaa','bbb') }),
1499         (q{ INSERT INTO loader_test8 (id,loader_test7,dat) VALUES (2,'aaa','bbb') }),
1500         (q{ INSERT INTO loader_test8 (id,loader_test7,dat) VALUES (3,'aaa','bbb') }),
1501
1502         qq{
1503             CREATE TABLE loader_test9 (
1504                 loader_test9 VARCHAR(8) NOT NULL
1505             ) $self->{innodb}
1506         },
1507
1508         qq{
1509             CREATE TABLE loader_test16 (
1510                 id INTEGER NOT NULL PRIMARY KEY,
1511                 dat  VARCHAR(8),
1512                 loader_test8_id INTEGER NOT NULL UNIQUE,
1513                 FOREIGN KEY (loader_test8_id) REFERENCES loader_test8 (id)
1514             ) $self->{innodb}
1515         },
1516
1517         qq{ INSERT INTO loader_test16 (id,dat,loader_test8_id) VALUES (2,'x16',1) },
1518         qq{ INSERT INTO loader_test16 (id,dat,loader_test8_id) VALUES (4,'y16',2) },
1519         qq{ INSERT INTO loader_test16 (id,dat,loader_test8_id) VALUES (6,'z16',3) },
1520
1521         # for some DBs we need a named FK to drop later
1522         ($self->{vendor} =~ /^(mssql|sybase|access|mysql)\z/i ? (
1523             (q{ ALTER TABLE loader_test8 ADD } .
1524              qq{ loader_test16_id INTEGER $self->{null} }),
1525             (q{ ALTER TABLE loader_test8 ADD CONSTRAINT loader_test8_to_16_fk } .
1526              q{ FOREIGN KEY (loader_test16_id) } .
1527              q{ REFERENCES loader_test16 (id) })
1528         ) : (
1529             (q{ ALTER TABLE loader_test8 ADD } .
1530              qq{ loader_test16_id INTEGER $self->{null} REFERENCES loader_test16 (id) }),
1531         )),
1532
1533         qq{
1534             CREATE TABLE loader_test17 (
1535                 id INTEGER NOT NULL PRIMARY KEY,
1536                 loader16_one INTEGER,
1537                 loader16_two INTEGER,
1538                 FOREIGN KEY (loader16_one) REFERENCES loader_test16 (id),
1539                 FOREIGN KEY (loader16_two) REFERENCES loader_test16 (id)
1540             ) $self->{innodb}
1541         },
1542
1543         qq{ INSERT INTO loader_test17 (id, loader16_one, loader16_two) VALUES (3, 2, 4) },
1544         qq{ INSERT INTO loader_test17 (id, loader16_one, loader16_two) VALUES (33, 4, 6) },
1545
1546         qq{
1547             CREATE TABLE loader_test18 (
1548                 id INTEGER NOT NULL PRIMARY KEY,
1549                 dat  VARCHAR(8)
1550             ) $self->{innodb}
1551         },
1552
1553         qq{ INSERT INTO loader_test18 (id,dat) VALUES (1,'x18') },
1554         qq{ INSERT INTO loader_test18 (id,dat) VALUES (2,'y18') },
1555         qq{ INSERT INTO loader_test18 (id,dat) VALUES (3,'z18') },
1556
1557         qq{
1558             CREATE TABLE loader_test19 (
1559                 id INTEGER NOT NULL PRIMARY KEY,
1560                 dat  VARCHAR(8)
1561             ) $self->{innodb}
1562         },
1563
1564         qq{ INSERT INTO loader_test19 (id,dat) VALUES (4,'x19') },
1565         qq{ INSERT INTO loader_test19 (id,dat) VALUES (5,'y19') },
1566         qq{ INSERT INTO loader_test19 (id,dat) VALUES (6,'z19') },
1567
1568         qq{
1569             CREATE TABLE loader_test20 (
1570                 parent INTEGER NOT NULL,
1571                 child INTEGER NOT NULL,
1572                 PRIMARY KEY (parent, child),
1573                 FOREIGN KEY (parent) REFERENCES loader_test18 (id),
1574                 FOREIGN KEY (child) REFERENCES loader_test19 (id)
1575             ) $self->{innodb}
1576         },
1577
1578         q{ INSERT INTO loader_test20 (parent, child) VALUES (1,4) },
1579         q{ INSERT INTO loader_test20 (parent, child) VALUES (2,5) },
1580         q{ INSERT INTO loader_test20 (parent, child) VALUES (3,6) },
1581
1582         qq{
1583             CREATE TABLE loader_test21 (
1584                 id INTEGER NOT NULL PRIMARY KEY,
1585                 dat  VARCHAR(8)
1586             ) $self->{innodb}
1587         },
1588
1589         q{ INSERT INTO loader_test21 (id,dat) VALUES (7,'a21')},
1590         q{ INSERT INTO loader_test21 (id,dat) VALUES (11,'b21')},
1591         q{ INSERT INTO loader_test21 (id,dat) VALUES (13,'c21')},
1592         q{ INSERT INTO loader_test21 (id,dat) VALUES (17,'d21')},
1593
1594         qq{
1595             CREATE TABLE loader_test22 (
1596                 parent INTEGER NOT NULL,
1597                 child INTEGER NOT NULL,
1598                 PRIMARY KEY (parent, child),
1599                 FOREIGN KEY (parent) REFERENCES loader_test21 (id),
1600                 FOREIGN KEY (child) REFERENCES loader_test21 (id)
1601             ) $self->{innodb}
1602         },
1603
1604         q{ INSERT INTO loader_test22 (parent, child) VALUES (7,11)},
1605         q{ INSERT INTO loader_test22 (parent, child) VALUES (11,13)},
1606         q{ INSERT INTO loader_test22 (parent, child) VALUES (13,17)},
1607
1608         qq{
1609             CREATE TABLE loader_test25 (
1610                 id1 INTEGER NOT NULL,
1611                 id2 INTEGER NOT NULL,
1612                 dat VARCHAR(8),
1613                 PRIMARY KEY (id1,id2)
1614             ) $self->{innodb}
1615         },
1616
1617         q{ INSERT INTO loader_test25 (id1,id2,dat) VALUES (33,5,'x25') },
1618         q{ INSERT INTO loader_test25 (id1,id2,dat) VALUES (33,7,'y25') },
1619         q{ INSERT INTO loader_test25 (id1,id2,dat) VALUES (3,42,'z25') },
1620
1621         qq{
1622             CREATE TABLE loader_test26 (
1623                id INTEGER NOT NULL PRIMARY KEY,
1624                rel1 INTEGER NOT NULL,
1625                rel2 INTEGER NOT NULL,
1626                FOREIGN KEY (id, rel1) REFERENCES loader_test25 (id1, id2),
1627                FOREIGN KEY (id, rel2) REFERENCES loader_test25 (id1, id2)
1628             ) $self->{innodb}
1629         },
1630
1631         q{ INSERT INTO loader_test26 (id,rel1,rel2) VALUES (33,5,7) },
1632         q{ INSERT INTO loader_test26 (id,rel1,rel2) VALUES (3,42,42) },
1633
1634         qq{
1635             CREATE TABLE loader_test27 (
1636                 id INTEGER NOT NULL PRIMARY KEY
1637             ) $self->{innodb}
1638         },
1639
1640         q{ INSERT INTO loader_test27 (id) VALUES (1) },
1641         q{ INSERT INTO loader_test27 (id) VALUES (2) },
1642
1643         qq{
1644             CREATE TABLE loader_test28 (
1645                 id INTEGER NOT NULL PRIMARY KEY,
1646                 FOREIGN KEY (id) REFERENCES loader_test27 (id)
1647             ) $self->{innodb}
1648         },
1649
1650         q{ INSERT INTO loader_test28 (id) VALUES (1) },
1651
1652         qq{
1653             CREATE TABLE loader_test29 (
1654                 id INTEGER NOT NULL PRIMARY KEY,
1655                 fk INTEGER NOT NULL UNIQUE,
1656                 FOREIGN KEY (fk) REFERENCES loader_test27 (id)
1657             ) $self->{innodb}
1658         },
1659
1660         q{ INSERT INTO loader_test29 (id,fk) VALUES (1,1) },
1661
1662         qq{
1663           CREATE TABLE loader_test31 (
1664             id INTEGER NOT NULL PRIMARY KEY
1665           ) $self->{innodb}
1666         },
1667         q{ INSERT INTO loader_test31 (id) VALUES (1) },
1668
1669         qq{
1670           CREATE TABLE loader_test32 (
1671             id INTEGER NOT NULL PRIMARY KEY,
1672             rel1 INTEGER NOT NULL,
1673             rel2 INTEGER $self->{null},
1674             FOREIGN KEY (rel1) REFERENCES loader_test31(id),
1675             FOREIGN KEY (rel2) REFERENCES loader_test31(id)
1676           ) $self->{innodb}
1677         },
1678         q{ INSERT INTO loader_test32 (id,rel1) VALUES (1,1) },
1679
1680         qq{
1681           CREATE TABLE loader_test33 (
1682             id1 INTEGER NOT NULL,
1683             id2 INTEGER NOT NULL,
1684             PRIMARY KEY (id1,id2)
1685           ) $self->{innodb}
1686         },
1687         q{ INSERT INTO loader_test33 (id1,id2) VALUES (1,2) },
1688
1689         qq{
1690           CREATE TABLE loader_test34 (
1691             id INTEGER NOT NULL PRIMARY KEY,
1692             rel1 INTEGER NOT NULL,
1693             rel2 INTEGER $self->{null},
1694             FOREIGN KEY (id,rel1) REFERENCES loader_test33(id1,id2),
1695             FOREIGN KEY (id,rel2) REFERENCES loader_test33(id1,id2)
1696           ) $self->{innodb}
1697         },
1698         q{ INSERT INTO loader_test34 (id,rel1,rel2) VALUES (1,2,2) },
1699     );
1700
1701     @statements_advanced = (
1702         qq{
1703             CREATE TABLE loader_test10 (
1704                 id10 $self->{auto_inc_pk},
1705                 subject VARCHAR(8),
1706                 loader_test11 INTEGER $self->{null}
1707             ) $self->{innodb}
1708         },
1709         $make_auto_inc->(qw/loader_test10 id10/),
1710
1711 # Access does not support DEFAULT.
1712         qq{
1713             CREATE TABLE loader_test11 (
1714                 id11 $self->{auto_inc_pk},
1715                 a_message VARCHAR(8) @{[ $self->{vendor} ne 'Access' ? "DEFAULT 'foo'" : '' ]},
1716                 loader_test10 INTEGER $self->{null},
1717                 FOREIGN KEY (loader_test10) REFERENCES loader_test10 (id10)
1718             ) $self->{innodb}
1719         },
1720         $make_auto_inc->(qw/loader_test11 id11/),
1721
1722         (lc($self->{vendor}) ne 'informix' ?
1723             (q{ ALTER TABLE loader_test10 ADD CONSTRAINT loader_test11_fk } .
1724              q{ FOREIGN KEY (loader_test11) } .
1725              q{ REFERENCES loader_test11 (id11) })
1726         :
1727             (q{ ALTER TABLE loader_test10 ADD CONSTRAINT } .
1728              q{ FOREIGN KEY (loader_test11) } .
1729              q{ REFERENCES loader_test11 (id11) } .
1730              q{ CONSTRAINT loader_test11_fk })
1731         ),
1732     );
1733
1734     @statements_advanced_sqlite = (
1735         qq{
1736             CREATE TABLE loader_test10 (
1737                 id10 $self->{auto_inc_pk},
1738                 subject VARCHAR(8)
1739             ) $self->{innodb}
1740         },
1741         $make_auto_inc->(qw/loader_test10 id10/),
1742
1743         qq{
1744             CREATE TABLE loader_test11 (
1745                 id11 $self->{auto_inc_pk},
1746                 a_message VARCHAR(8) DEFAULT 'foo',
1747                 loader_test10 INTEGER $self->{null},
1748                 FOREIGN KEY (loader_test10) REFERENCES loader_test10 (id10)
1749             ) $self->{innodb}
1750         },
1751         $make_auto_inc->(qw/loader_test11 id11/),
1752
1753         (q{ ALTER TABLE loader_test10 ADD COLUMN } .
1754          q{ loader_test11 INTEGER REFERENCES loader_test11 (id11) }),
1755     );
1756
1757     @statements_inline_rels = (
1758         qq{
1759             CREATE TABLE loader_test12 (
1760                 id INTEGER NOT NULL PRIMARY KEY,
1761                 id2 VARCHAR(8) NOT NULL UNIQUE,
1762                 dat VARCHAR(8) NOT NULL UNIQUE
1763             ) $self->{innodb}
1764         },
1765
1766         q{ INSERT INTO loader_test12 (id,id2,dat) VALUES (1,'aaa','bbb') },
1767
1768         qq{
1769             CREATE TABLE loader_test13 (
1770                 id INTEGER NOT NULL PRIMARY KEY REFERENCES loader_test12,
1771                 loader_test12 VARCHAR(8) NOT NULL REFERENCES loader_test12 (id2),
1772                 dat VARCHAR(8) REFERENCES loader_test12 (dat)
1773             ) $self->{innodb}
1774         },
1775
1776         (q{ INSERT INTO loader_test13 (id,loader_test12,dat) } .
1777          q{ VALUES (1,'aaa','bbb') }),
1778     );
1779
1780
1781     @statements_implicit_rels = (
1782         qq{
1783             CREATE TABLE loader_test14 (
1784                 id INTEGER NOT NULL PRIMARY KEY,
1785                 dat VARCHAR(8)
1786             ) $self->{innodb}
1787         },
1788  
1789         q{ INSERT INTO loader_test14 (id,dat) VALUES (123,'aaa') },
1790
1791         qq{
1792             CREATE TABLE loader_test15 (
1793                 id INTEGER NOT NULL PRIMARY KEY,
1794                 loader_test14 INTEGER NOT NULL,
1795                 FOREIGN KEY (loader_test14) REFERENCES loader_test14
1796             ) $self->{innodb}
1797         },
1798
1799         q{ INSERT INTO loader_test15 (id,loader_test14) VALUES (1,123) },
1800     );
1801
1802     $self->drop_tables;
1803
1804     my $dbh = $self->dbconnect(1);
1805
1806     $dbh->do($_) for @{ $self->{pre_create} || [] };
1807
1808     $dbh->do($_) foreach (@statements);
1809
1810     if (not ($self->{vendor} eq 'mssql' && $dbh->{Driver}{Name} eq 'Sybase')) {
1811         $dbh->do($_) foreach (@{ $self->{data_type_tests}{ddl} || [] });
1812     }
1813
1814     unless($self->{skip_rels}) {
1815         # hack for now, since DB2 doesn't like inline comments, and we need
1816         # to test one for mysql, which works on everyone else...
1817         # this all needs to be refactored anyways.
1818
1819         for my $stmt (@statements_reltests) {
1820             try {
1821                 $dbh->do($stmt);
1822             }
1823             catch {
1824                 die "Error executing '$stmt': $_\n";
1825             };
1826         }
1827         if($self->{vendor} =~ /sqlite/i) {
1828             $dbh->do($_) for (@statements_advanced_sqlite);
1829         }
1830         else {
1831             $dbh->do($_) for (@statements_advanced);
1832         }
1833         unless($self->{no_inline_rels}) {
1834             $dbh->do($_) for (@statements_inline_rels);
1835         }
1836         unless($self->{no_implicit_rels}) {
1837             $dbh->do($_) for (@statements_implicit_rels);
1838         }
1839     }
1840
1841     $dbh->do($_) for @{ $self->{extra}->{create} || [] };
1842     $dbh->disconnect();
1843 }
1844
1845 sub drop_tables {
1846     my $self = shift;
1847
1848     my @tables = qw/
1849         loader_test1
1850         loader_test1s
1851         loader_test2
1852         LOADER_test23
1853         LoAdEr_test24
1854         loader_test35
1855         loader_test36
1856     /;
1857     
1858     my @tables_auto_inc = (
1859         [ qw/loader_test1s id/ ],
1860         [ qw/loader_test2 id/ ],
1861     );
1862
1863     my @tables_reltests = qw/
1864         loader_test4
1865         loader_test3
1866         loader_test6
1867         loader_test5
1868         loader_test8
1869         loader_test7
1870         loader_test9
1871         loader_test17
1872         loader_test16
1873         loader_test20
1874         loader_test19
1875         loader_test18
1876         loader_test22
1877         loader_test21
1878         loader_test26
1879         loader_test25
1880         loader_test28
1881         loader_test29
1882         loader_test27
1883         loader_test32
1884         loader_test31
1885         loader_test34
1886         loader_test33
1887     /;
1888
1889     my @tables_advanced = qw/
1890         loader_test11
1891         loader_test10
1892     /;
1893     
1894     my @tables_advanced_auto_inc = (
1895         [ qw/loader_test10 id10/ ],
1896         [ qw/loader_test11 id11/ ],
1897     );
1898
1899     my @tables_inline_rels = qw/
1900         loader_test13
1901         loader_test12
1902     /;
1903
1904     my @tables_implicit_rels = qw/
1905         loader_test15
1906         loader_test14
1907     /;
1908
1909     my @tables_rescan = qw/ loader_test30 /;
1910
1911     my @tables_preserve_case_tests = qw/ LoaderTest41 LoaderTest40 /;
1912
1913     my %drop_columns = (
1914         loader_test6  => 'loader_test7_id',
1915         loader_test7  => 'lovely_loader_test6',
1916         loader_test8  => 'loader_test16_id',
1917         loader_test16 => 'loader_test8_id',
1918     );
1919
1920     my %drop_constraints = (
1921         loader_test10 => 'loader_test11_fk',
1922         loader_test6  => 'loader_test6_to_7_fk',
1923         loader_test8  => 'loader_test8_to_16_fk',
1924     );
1925
1926     # For some reason some tests do this twice (I guess dependency issues?)
1927     # do it twice for all drops
1928     for (1,2) {
1929         local $^W = 0; # for ADO
1930
1931         my $dbh = $self->dbconnect(0);
1932
1933         $dbh->do($_) for @{ $self->{extra}{pre_drop_ddl} || [] };
1934
1935         $self->drop_table($dbh, $_) for @{ $self->{extra}{drop} || [] };
1936
1937         my $drop_auto_inc = $self->{auto_inc_drop_cb} || sub {};
1938
1939         unless($self->{skip_rels}) {
1940             # drop the circular rel columns if possible, this
1941             # doesn't work on all DBs
1942             foreach my $table (keys %drop_columns) {
1943                 $dbh->do("ALTER TABLE $table DROP $drop_columns{$table}");
1944                 $dbh->do("ALTER TABLE $table DROP COLUMN $drop_columns{$table}");
1945             }
1946
1947             foreach my $table (keys %drop_constraints) {
1948                 # for MSSQL
1949                 $dbh->do("ALTER TABLE $table DROP $drop_constraints{$table}"); 
1950                 # for Sybase and Access
1951                 $dbh->do("ALTER TABLE $table DROP CONSTRAINT $drop_constraints{$table}"); 
1952                 # for MySQL
1953                 $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $drop_constraints{$table}"); 
1954             }
1955
1956             $self->drop_table($dbh, $_) for (@tables_reltests);
1957             $self->drop_table($dbh, $_) for (@tables_reltests);
1958
1959             $dbh->do($_) for map { $drop_auto_inc->(@$_) } @tables_advanced_auto_inc;
1960
1961             $self->drop_table($dbh, $_) for (@tables_advanced);
1962
1963             unless($self->{no_inline_rels}) {
1964                 $self->drop_table($dbh, $_) for (@tables_inline_rels);
1965             }
1966             unless($self->{no_implicit_rels}) {
1967                 $self->drop_table($dbh, $_) for (@tables_implicit_rels);
1968             }
1969         }
1970         $dbh->do($_) for map { $drop_auto_inc->(@$_) } @tables_auto_inc;
1971         $self->drop_table($dbh, $_) for (@tables, @tables_rescan);
1972
1973         if (not ($self->{vendor} eq 'mssql' && $dbh->{Driver}{Name} eq 'Sybase')) {
1974             foreach my $data_type_table (@{ $self->{data_type_tests}{table_names} || [] }) {
1975                 $self->drop_table($dbh, $data_type_table);
1976             }
1977         }
1978
1979         $self->drop_table($dbh, $_) for @tables_preserve_case_tests;
1980
1981         $dbh->disconnect;
1982     }
1983 }
1984
1985 sub drop_table {
1986     my ($self, $dbh, $table) = @_;
1987
1988     local $^W = 0; # for ADO
1989
1990     try { $dbh->do("DROP TABLE $table CASCADE CONSTRAINTS") }; # oracle
1991     try { $dbh->do("DROP TABLE $table CASCADE") }; # postgres and ?
1992     try { $dbh->do("DROP TABLE $table") };
1993
1994     # if table name is case sensitive
1995     my ($oqt, $cqt) = $self->get_oqt_cqt(always => 1);
1996
1997     try { $dbh->do("DROP TABLE ${oqt}${table}${cqt}") };
1998 }
1999
2000 sub _custom_column_info {
2001     my ( $table_name, $column_name, $column_info ) = @_;
2002
2003     $table_name = lc ( $table_name );
2004     $column_name = lc ( $column_name );
2005
2006     if ( $table_name eq 'loader_test35' 
2007         and $column_name eq 'an_int' 
2008     ){
2009         return { is_numeric => 1 }
2010     }
2011     # Set inflate_datetime or  inflate_date to check 
2012     #   datetime_timezone and datetime_locale
2013     if ( $table_name eq 'loader_test36' ){
2014         return { inflate_datetime => 1 } if 
2015             ( $column_name eq 'b_char_as_data' );
2016         return { inflate_date => 1 } if 
2017             ( $column_name eq 'c_char_as_data' );
2018     }
2019
2020     return;
2021 }
2022
2023 my %DATA_TYPE_MULTI_TABLE_OVERRIDES = (
2024     oracle => qr/\blong\b/i,
2025     mssql  => qr/\b(?:timestamp|rowversion)\b/i,
2026     informix => qr/\b(?:bigserial|serial8)\b/i,
2027 );
2028
2029 sub setup_data_type_tests {
2030     my $self = shift;
2031
2032     return unless my $types = $self->{data_types};
2033
2034     my $tests = $self->{data_type_tests} = {};
2035
2036     # split types into tables based on overrides
2037     my (@types, @split_off_types, @first_table_types);
2038     {
2039         my $split_off_re = $DATA_TYPE_MULTI_TABLE_OVERRIDES{lc($self->{vendor})} || qr/(?!)/;
2040
2041         @types = keys %$types;
2042         @split_off_types   = grep  /$split_off_re/, @types;
2043         @first_table_types = grep !/$split_off_re/, @types;
2044     }
2045
2046     @types = +{ map +($_, $types->{$_}), @first_table_types },
2047         map +{ $_, $types->{$_} }, @split_off_types;
2048
2049     my $test_count = 0;
2050     my $table_num  = 10000;
2051
2052     foreach my $types (@types) {
2053         my $table_name    = "loader_test$table_num";
2054         push @{ $tests->{table_names} }, $table_name;
2055
2056         my $table_moniker = "LoaderTest$table_num";
2057         push @{ $tests->{table_monikers} }, $table_moniker;
2058
2059         $table_num++;
2060
2061         my $cols = $tests->{columns}{$table_moniker} = {};
2062
2063         my $ddl = "CREATE TABLE $table_name (\n    id INTEGER NOT NULL PRIMARY KEY,\n";
2064
2065         my %seen_col_names;
2066
2067         while (my ($col_def, $expected_info) = each %$types) {
2068             (my $type_alias = $col_def) =~ s/\( ([^)]+) \)//xg;
2069
2070             my $size = $1;
2071             $size = '' unless defined $size;
2072             $size =~ s/\s+//g;
2073             my @size = split /,/, $size;
2074
2075             # some DBs don't like very long column names
2076             if ($self->{vendor} =~ /^(?:firebird|sqlanywhere|oracle|db2)\z/i) {
2077                 my ($col_def, $default) = $type_alias =~ /^(.*)(default.*)?\z/i;
2078
2079                 $type_alias = substr $col_def, 0, 15;
2080
2081                 $type_alias .= '_with_dflt' if $default;
2082             }
2083
2084             $type_alias =~ s/\s/_/g;
2085             $type_alias =~ s/\W//g;
2086
2087             my $col_name = 'col_' . $type_alias;
2088             
2089             if (@size) {
2090                 my $size_name = join '_', apply { s/\W//g } @size;
2091
2092                 $col_name .= "_sz_$size_name";
2093             }
2094
2095             # XXX would be better to check _loader->preserve_case
2096             $col_name = lc $col_name;
2097
2098             $col_name .= '_' . $seen_col_names{$col_name} if $seen_col_names{$col_name}++;
2099
2100             $ddl .= "    $col_name $col_def,\n";
2101
2102             $cols->{$col_name} = $expected_info;
2103
2104             $test_count++;
2105         }
2106
2107         $ddl =~ s/,\n\z/\n)/;
2108
2109         push @{ $tests->{ddl} }, $ddl;
2110     }
2111
2112     $tests->{test_count} = $test_count;
2113
2114     return $test_count;
2115 }
2116
2117 sub rescan_without_warnings {
2118     my ($self, $conn) = @_;
2119
2120     local $SIG{__WARN__} = sub { warn @_ unless $_[0] =~ RESCAN_WARNINGS };
2121     return $conn->rescan;
2122 }
2123
2124 sub test_col_accessor_map {
2125     my ( $column_name, $default_name, $context ) = @_;
2126     if( lc($column_name) eq 'crumb_crisp_coating' ) {
2127
2128         is( $default_name, 'crumb_crisp_coating', 'col_accessor_map was passed the default name' );
2129         ok( $context->{$_}, "col_accessor_map func was passed the $_" )
2130             for qw( table_name table_class table_moniker schema_class );
2131
2132         return 'trivet';
2133     } else {
2134         return $default_name;
2135     }
2136 }
2137
2138 sub DESTROY {
2139     my $self = shift;
2140     unless ($ENV{SCHEMA_LOADER_TESTS_NOCLEANUP}) {
2141       $self->drop_tables if $self->{_created};
2142       rmtree $DUMP_DIR
2143     }
2144 }
2145
2146 1;
2147 # vim:et sts=4 sw=4 tw=0: