Handle on_delete => 'restrict' in Producer::Oracle
[dbsrgits/SQL-Translator.git] / lib / Test / SQL / Translator.pm
CommitLineData
9bbbf403 1package Test::SQL::Translator;
2
44659089 3# ----------------------------------------------------------------------
4# Copyright (C) 2003 The SQLFairy Authors
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18# 02111-1307 USA
19# -------------------------------------------------------------------
20
9bbbf403 21=pod
22
23=head1 NAME
24
25Test::SQL::Translator - Test::More test functions for the Schema objects.
26
27=cut
28
29use strict;
30use warnings;
11fee3e0 31use Test::More;
32use SQL::Translator::Schema::Constants;
9bbbf403 33
34use base qw(Exporter);
9bbbf403 35use vars qw($VERSION @EXPORT @EXPORT_OK);
11ad2df9 36$VERSION = '1.59';
e2a489c2 37@EXPORT = qw(
a0eb602d 38 schema_ok
9bbbf403 39 table_ok
40 field_ok
41 constraint_ok
42 index_ok
43 view_ok
44 trigger_ok
45 procedure_ok
49133ae7 46 maybe_plan
9bbbf403 47);
9bbbf403 48
9bbbf403 49# $ATTRIBUTES{ <schema_object_name> } = { <attribname> => <default>, ... }
11fee3e0 50my %ATTRIBUTES = (
51 field => {
52 name => undef,
53 data_type => '',
54 default_value => undef,
55 size => '0',
56 is_primary_key => 0,
57 is_unique => 0,
58 is_nullable => 1,
59 is_foreign_key => 0,
60 is_auto_increment => 0,
61 comments => '',
62 extra => {},
63 # foreign_key_reference,
64 is_valid => 1,
65 # order
66 },
67 constraint => {
68 name => '',
69 type => '',
70 deferrable => 1,
71 expression => '',
72 is_valid => 1,
73 fields => [],
74 match_type => '',
75 options => [],
76 on_delete => '',
77 on_update => '',
78 reference_fields => [],
79 reference_table => '',
80 extra => {},
81 },
82 index => {
83 fields => [],
84 is_valid => 1,
85 name => "",
86 options => [],
87 type => NORMAL,
88 extra => {},
89 },
90 view => {
91 name => "",
92 sql => "",
93 fields => [],
94 is_valid => 1,
95 extra => {},
96 },
97 trigger => {
98 name => '',
99 perform_action_when => undef,
100 database_events => undef,
101 on_table => undef,
102 action => undef,
103 is_valid => 1,
104 extra => {},
105 },
106 procedure => {
107 name => '',
108 sql => '',
109 parameters => [],
110 owner => '',
111 comments => '',
112 extra => {},
113 },
114 table => {
115 comments => undef,
116 name => '',
117 #primary_key => undef, # pkey constraint
118 options => [],
119 #order => 0,
120 fields => undef,
121 constraints => undef,
122 indices => undef,
123 is_valid => 1,
124 extra => {},
125 },
126 schema => {
127 name => '',
128 database => '',
129 procedures => undef, # [] when set
130 tables => undef, # [] when set
131 triggers => undef, # [] when set
132 views => undef, # [] when set
133 is_valid => 1,
134 extra => {},
135 }
136);
9bbbf403 137
138# Given a test hash and schema object name set any attribute keys not present in
139# the test hash to their default value for that schema object type.
140# e.g. default_attribs( $test, "field" );
141sub default_attribs {
11fee3e0 142 my ($hashref, $object_type) = @_;
143
144 if ( !exists $ATTRIBUTES{ $object_type } ) {
145 die "Can't add default attribs for unknown Schema "
146 . "object type '$object_type'.";
147 }
148
149 for my $attr (
150 grep { !exists $hashref->{ $_ } }
151 keys %{ $ATTRIBUTES{ $object_type } }
152 ) {
153 $hashref->{ $attr } = $ATTRIBUTES{ $object_type }{ $attr }
154 }
155
156 return $hashref;
9bbbf403 157}
158
159# Format test name so it will prepend the test names used below.
160sub t_name {
161 my $name = shift;
162 $name ||= "";
163 $name = "$name - " if $name;
164 return $name;
165}
166
167sub field_ok {
168 my ($f1,$test,$name) = @_;
169 my $t_name = t_name($name);
170 default_attribs($test,"field");
171
172 unless ($f1) {
173 fail " Field '$test->{name}' doesn't exist!";
a0eb602d 174 # TODO Do a skip on the following tests. Currently the test counts wont
175 # match at the end. So at least it fails.
9bbbf403 176 return;
177 }
178
a0eb602d 179 my $full_name = $f1->table->name.".".$test->{name};
180
181 is( $f1->name, $test->{name}, "${t_name}Field '$full_name'" );
9bbbf403 182
183 is( $f1->is_valid, $test->{is_valid},
a0eb602d 184 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
9bbbf403 185
186 is( $f1->data_type, $test->{data_type},
187 "$t_name type is '$test->{data_type}'" );
188
189 is( $f1->size, $test->{size}, "$t_name size is '$test->{size}'" );
190
191 is( $f1->default_value, $test->{default_value},
192 "$t_name default value is "
193 .(defined($test->{default_value}) ? "'$test->{default_value}'" : "UNDEF" )
194 );
195
196 is( $f1->is_nullable, $test->{is_nullable},
197 "$t_name ".($test->{is_nullable} ? 'can' : 'cannot').' be null' );
198
199 is( $f1->is_unique, $test->{is_unique},
200 "$t_name ".($test->{is_unique} ? 'can' : 'cannot').' be unique' );
201
202 is( $f1->is_primary_key, $test->{is_primary_key},
203 "$t_name is ".($test->{is_primary_key} ? '' : 'not ').'a primary_key' );
204
205 is( $f1->is_foreign_key, $test->{is_foreign_key},
206 "$t_name is ".($test->{is_foreign_key} ? '' : 'not').' a foreign_key' );
207
208 is( $f1->is_auto_increment, $test->{is_auto_increment},
209 "$t_name is "
210 .($test->{is_auto_increment} ? '' : 'not ').'an auto_increment' );
211
212 is( $f1->comments, $test->{comments}, "$t_name comments" );
213
214 is_deeply( { $f1->extra }, $test->{extra}, "$t_name extra" );
215}
216
217sub constraint_ok {
218 my ($obj,$test,$name) = @_;
219 my $t_name = t_name($name);
220 default_attribs($test,"constraint");
221
222 is( $obj->name, $test->{name}, "${t_name}Constraint '$test->{name}'" );
223
224 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
225
226 is( $obj->deferrable, $test->{deferrable},
227 "$t_name ".($test->{deferrable} ? 'can' : 'cannot').' be deferred' );
228
229 is( $obj->is_valid, $test->{is_valid},
230 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
231
232 is($obj->table->name,$test->{table},"$t_name table is '$test->{table}'" );
233
234 is( $obj->expression, $test->{expression},
235 "$t_name expression is '$test->{expression}'" );
236
237 is_deeply( [$obj->fields], $test->{fields},
238 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
239
240 is( $obj->reference_table, $test->{reference_table},
241 "$t_name reference_table is '$test->{reference_table}'" );
242
243 is_deeply( [$obj->reference_fields], $test->{reference_fields},
244 "$t_name reference_fields are '".join(",",@{$test->{reference_fields}})."'" );
245
246 is( $obj->match_type, $test->{match_type},
247 "$t_name match_type is '$test->{match_type}'" );
248
249 is( $obj->on_delete, $test->{on_delete},
250 "$t_name on_delete is '$test->{on_delete}'" );
251
252 is( $obj->on_update, $test->{on_update},
253 "$t_name on_update is '$test->{on_update}'" );
254
255 is_deeply( [$obj->options], $test->{options},
256 "$t_name options are '".join(",",@{$test->{options}})."'" );
b1789409 257
258 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 259}
260
261sub index_ok {
262 my ($obj,$test,$name) = @_;
263 my $t_name = t_name($name);
264 default_attribs($test,"index");
265
266 is( $obj->name, $test->{name}, "${t_name}Index '$test->{name}'" );
267
268 is( $obj->is_valid, $test->{is_valid},
269 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
270
271 is( $obj->type, $test->{type}, "$t_name type is '$test->{type}'" );
272
273 is_deeply( [$obj->fields], $test->{fields},
274 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
275
276 is_deeply( [$obj->options], $test->{options},
277 "$t_name options are '".join(",",@{$test->{options}})."'" );
b1789409 278
279 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 280}
281
282sub trigger_ok {
283 my ($obj,$test,$name) = @_;
284 my $t_name = t_name($name);
285 default_attribs($test,"index");
286
14ad260a 287 is( $obj->name, $test->{name}, "${t_name}Trigger '$test->{name}'" );
9bbbf403 288
289 is( $obj->is_valid, $test->{is_valid},
290 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
291
292 is( $obj->perform_action_when, $test->{perform_action_when},
293 "$t_name perform_action_when is '$test->{perform_action_when}'" );
294
11fee3e0 295 is( join(',', $obj->database_events), $test->{database_events},
296 sprintf("%s database_events is '%s'",
297 $t_name,
298 $test->{'database_events'},
299 )
300 );
9bbbf403 301
302 is( $obj->on_table, $test->{on_table},
303 "$t_name on_table is '$test->{on_table}'" );
304
305 is( $obj->action, $test->{action}, "$t_name action is '$test->{action}'" );
b1789409 306
307 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 308}
309
310sub view_ok {
311 my ($obj,$test,$name) = @_;
312 my $t_name = t_name($name);
313 default_attribs($test,"index");
314
315 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
316
317 is( $obj->name, $test->{name}, "${t_name}View '$test->{name}'" );
318
319 is( $obj->is_valid, $test->{is_valid},
320 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
321
322 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
323
324 is_deeply( [$obj->fields], $test->{fields},
325 "$t_name fields are '".join(",",@{$test->{fields}})."'" );
b1789409 326
327 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 328}
329
330sub procedure_ok {
331 my ($obj,$test,$name) = @_;
332 my $t_name = t_name($name);
333 default_attribs($test,"index");
334
335 #isa_ok( $v, 'SQL::Translator::Schema::View', 'View' );
336
337 is( $obj->name, $test->{name}, "${t_name}Procedure '$test->{name}'" );
338
339 is( $obj->sql, $test->{sql}, "$t_name sql is '$test->{sql}'" );
340
341 is_deeply( [$obj->parameters], $test->{parameters},
342 "$t_name parameters are '".join(",",@{$test->{parameters}})."'" );
343
344 is( $obj->comments, $test->{comments},
345 "$t_name comments is '$test->{comments}'" );
346
347 is( $obj->owner, $test->{owner}, "$t_name owner is '$test->{owner}'" );
14ad260a 348
b1789409 349 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
9bbbf403 350}
351
352sub table_ok {
353 my ($obj,$test,$name) = @_;
354 my $t_name = t_name($name);
355 default_attribs($test,"table");
356 my %arg = %$test;
357
358 my $tbl_name = $arg{name} || die "Need a table name to test.";
a0eb602d 359 is( $obj->{name}, $arg{name}, "${t_name}Table '$arg{name}'" );
9bbbf403 360
361 is_deeply( [$obj->options], $test->{options},
362 "$t_name options are '".join(",",@{$test->{options}})."'" );
363
b1789409 364 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
365
9bbbf403 366 # Fields
367 if ( $arg{fields} ) {
a0eb602d 368 my @fldnames = map {$_->{name}} @{$arg{fields}};
369 is_deeply(
370 [ map {$_->name} $obj->get_fields ],
371 [ @fldnames ],
372 "${t_name} field names are ".join(", ",@fldnames)
373 );
9bbbf403 374 foreach ( @{$arg{fields}} ) {
375 my $f_name = $_->{name} || die "Need a field name to test.";
a0eb602d 376 next unless my $fld = $obj->get_field($f_name);
377 field_ok( $fld, $_, $name );
9bbbf403 378 }
379 }
380 else {
381 is(scalar($obj->get_fields), undef,
a0eb602d 382 "${t_name} has no fields.");
9bbbf403 383 }
384
a0eb602d 385 # Constraints and Indices
386 _test_kids($obj, $test, $name, {
11fee3e0 387 constraint => 'constraints',
388 index => 'indices',
a0eb602d 389 });
390}
391
392sub _test_kids {
11fee3e0 393 my ( $obj, $test, $name, $kids ) = @_;
394 my $t_name = t_name($name);
a0eb602d 395 my $obj_name = ref $obj;
396 ($obj_name) = $obj_name =~ m/^.*::(.*)$/;
397
11fee3e0 398 while ( my ( $object_type, $plural ) = each %$kids ) {
399 next unless defined $test->{ $plural };
400
401 if ( my @tests = @{ $test->{ $plural } } ) {
9bbbf403 402 my $meth = "get_$plural";
11fee3e0 403 my @objects = $obj->$meth;
404 is( scalar(@objects), scalar(@tests),
405 "${t_name}$obj_name has " . scalar(@tests) . " $plural"
406 );
407
408 for my $object (@objects) {
409 my $ans = { lc($obj_name) => $obj->name, %{ shift @tests } };
410
411 my $meth = "${object_type}_ok";
412 {
413 no strict 'refs';
414 $meth->( $object, $ans, $name );
9bbbf403 415 }
416 }
417 }
418 }
419}
420
421sub schema_ok {
422 my ($obj,$test,$name) = @_;
423 my $t_name = t_name($name);
424 default_attribs($test,"schema");
a0eb602d 425
426 is( $obj->name, $test->{name}, "${t_name}Schema name is '$test->{name}'" );
427
428 is( $obj->database, $test->{database},
429 "$t_name database is '$test->{database}'" );
b1789409 430
431 is_deeply( { $obj->extra }, $test->{extra}, "$t_name extra" );
a0eb602d 432
433 is( $obj->is_valid, $test->{is_valid},
434 "$t_name is ".($test->{is_valid} ? '' : 'not ').'valid' );
435
436 # Tables
437 if ( $test->{tables} ) {
438 is_deeply( [ map {$_->name} $obj->get_tables ],
439 [ map {$_->{name}} @{$test->{tables}} ],
440 "${t_name} table names match" );
441 foreach ( @{$test->{tables}} ) {
442 my $t_name = $_->{name} || die "Need a table name to test.";
443 table_ok( $obj->get_table($t_name), $_, $name );
444 }
445 }
446 else {
447 is(scalar($obj->get_tables), undef,
448 "${t_name} has no tables.");
449 }
450
451 # Procedures, Triggers, Views
452 _test_kids($obj, $test, $name, {
11fee3e0 453 procedure => 'procedures',
454 trigger => 'triggers',
455 view => 'views',
a0eb602d 456 });
9bbbf403 457}
458
49133ae7 459# maybe_plan($ntests, @modules)
460#
461# Calls plan $ntests if @modules can all be loaded; otherwise,
462# calls skip_all with an explanation of why the tests were skipped.
463sub maybe_plan {
464 my ($ntests, @modules) = @_;
465 my @errors;
466
467 for my $module (@modules) {
468 eval "use $module;";
559aa8a9 469 next if !$@;
470
471 if ($@ =~ /Can't locate (\S+)/) {
49133ae7 472 my $mod = $1;
473 $mod =~ s/\.pm$//;
474 $mod =~ s#/#::#g;
475 push @errors, $mod;
476 }
559aa8a9 477 elsif ($@ =~ /([\w\:]+ version [\d\.]+) required.+?this is only version/) {
478 push @errors, $1;
479 }
49133ae7 480 }
481
482 if (@errors) {
483 my $msg = sprintf "Missing dependenc%s: %s",
484 @errors == 1 ? 'y' : 'ies',
485 join ", ", @errors;
486 plan skip_all => $msg;
487 }
dc34f950 488 elsif ($ntests and $ntests ne 'no_plan') {
49133ae7 489 plan tests => $ntests;
490 }
dc34f950 491 else {
492 plan 'no_plan';
493 }
49133ae7 494}
495
9bbbf403 4961; # compile please ===========================================================
497__END__
498
499=pod
500
501=head1 SYNOPSIS
502
503 # t/magic.t
504
505 use FindBin '$Bin';
506 use Test::More;
507 use Test::SQL::Translator;
508
509 # Run parse
510 my $sqlt = SQL::Translator->new(
511 parser => "Magic",
512 filename => "$Bin/data/magic/test.magic",
e2a489c2 513 ...
9bbbf403 514 );
515 ...
516 my $schema = $sqlt->schema;
e2a489c2 517
9bbbf403 518 # Test the table it produced.
519 table_ok( $schema->get_table("Customer"), {
520 name => "Customer",
521 fields => [
522 {
523 name => "CustomerID",
524 data_type => "INT",
525 size => 12,
526 default_value => undef,
527 is_nullable => 0,
528 is_primary_key => 1,
529 },
530 {
531 name => "bar",
532 data_type => "VARCHAR",
533 size => 255,
534 is_nullable => 0,
535 },
536 ],
537 constraints => [
538 {
539 type => "PRIMARY KEY",
540 fields => "CustomerID",
541 },
542 ],
543 indices => [
544 {
545 name => "barindex",
546 fields => ["bar"],
547 },
548 ],
549 });
550
551=head1 DESCSIPTION
552
e2a489c2 553Provides a set of Test::More tests for Schema objects. Testing a parsed
9bbbf403 554schema is then as easy as writing a perl data structure describing how you
e2a489c2 555expect the schema to look. Also provides maybe_plan for conditionally running
556tests based on their dependencies.
9bbbf403 557
e2a489c2 558The data structures given to the test subs don't have to include all the
9bbbf403 559possible values, only the ones you expect to have changed. Any left out will be
10f70490 560tested to make sure they are still at their default value. This is a useful
9bbbf403 561check that you your parser hasn't accidentally set schema values you didn't
e2a489c2 562expect it to.
9bbbf403 563
564For an example of the output run the t/16xml-parser.t test.
565
566=head1 Tests
567
e2a489c2 568All the tests take a first arg of the schema object to test, followed by a
9bbbf403 569hash ref describing how you expect that object to look (you only need give the
570attributes you expect to have changed from the default).
e2a489c2 571The 3rd arg is an optional test name to pre-pend to all the generated test
9bbbf403 572names.
573
574=head2 table_ok
575
576=head2 field_ok
577
578=head2 constraint_ok
579
580=head2 index_ok
581
582=head2 view_ok
583
584=head2 trigger_ok
585
586=head2 procedure_ok
587
49133ae7 588=head1 CONDITIONAL TESTS
589
590The C<maybe_plan> function handles conditionally running an individual
591test. It is here to enable running the test suite even when dependencies
592are missing; not having (for example) GraphViz installed should not keep
593the test suite from passing.
594
595C<maybe_plan> takes the number of tests to (maybe) run, and a list of
596modules on which test execution depends:
597
598 maybe_plan(180, 'SQL::Translator::Parser::MySQL');
599
600If one of C<SQL::Translator::Parser::MySQL>'s dependencies does not exist,
601then the test will be skipped.
602
9bbbf403 603=head1 EXPORTS
604
49133ae7 605table_ok, field_ok, constraint_ok, index_ok, view_ok, trigger_ok, procedure_ok,
606maybe_plan
9bbbf403 607
608=head1 TODO
609
610=over 4
611
612=item Test the tests!
613
19458c2f 614=item Test Count Constants
615
e2a489c2 616Constants to give the number of tests each *_ok sub uses. e.g. How many tests
19458c2f 617does field_ok run? Can then use these to set up the test plan easily.
618
9bbbf403 619=item Test skipping
620
621As the test subs wrap up lots of tests in one call you can't skip idividual
622tests only whole sets e.g. a whole table or field.
623We could add skip_* items to the test hashes to allow per test skips. e.g.
624
625 skip_is_primary_key => "Need to fix primary key parsing.",
626
627=item yaml test specs
628
629Maybe have the test subs also accept yaml for the test hash ref as its a much
630nicer for writing big data structures. We can then define tests as in input
631schema file and test yaml file to compare it against.
632
633=back
634
635=head1 BUGS
636
637=head1 AUTHOR
638
11fee3e0 639Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>,
640Darren Chamberlain <darren@cpan.org>.
9bbbf403 641
11ad2df9 642Thanks to Ken Y. Clark for the original table and field test code taken from
643his mysql test.
9bbbf403 644
645=head1 SEE ALSO
646
647perl(1), SQL::Translator, SQL::Translator::Schema, Test::More.
648
649=cut