2f489ecc3cb550601fb52ebcde2d21d4b7c4d483
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Schema.pm
1 package SQL::Translator::Schema;
2
3 =pod
4
5 =head1 NAME
6
7 SQL::Translator::Schema - SQL::Translator schema object
8
9 =head1 SYNOPSIS
10
11   use SQL::Translator::Schema;
12   my $schema   =  SQL::Translator::Schema->new(
13       name     => 'Foo',
14       database => 'MySQL',
15   );
16   my $table    = $schema->add_table( name => 'foo' );
17   my $view     = $schema->add_view( name => 'bar', sql => '...' );
18
19
20 =head1 DESCSIPTION
21
22 C<SQL::Translator::Schema> is the object that accepts, validates, and
23 returns the database structure.
24
25 =head1 METHODS
26
27 =cut
28
29 use strict;
30 use SQL::Translator::Schema::Constants;
31 use SQL::Translator::Schema::Procedure;
32 use SQL::Translator::Schema::Table;
33 use SQL::Translator::Schema::Trigger;
34 use SQL::Translator::Schema::View;
35
36 use SQL::Translator::Utils 'parse_list_arg';
37
38 use base 'SQL::Translator::Schema::Object';
39 use vars qw[ $VERSION ];
40
41 $VERSION = '1.59';
42
43 __PACKAGE__->_attributes(qw/name database translator/);
44
45 sub new {
46   my $class = shift;
47   my $self = $class->SUPER::new (@_)
48     or return;
49
50   $self->{_order} = { map { $_ => 0 } qw/
51     table
52     view
53     trigger
54     proc
55   /};
56
57   return $self;
58 }
59
60 sub as_graph {
61
62 =pod
63
64 =head2 as_graph
65
66 Returns the schema as an L<SQL::Translator::Schema::Graph> object.
67
68 =cut
69     require  SQL::Translator::Schema::Graph;
70
71     my $self = shift;
72
73     return SQL::Translator::Schema::Graph->new(
74         translator => $self->translator );
75 }
76
77 sub as_graph_pm {
78
79 =pod
80
81 =head2 as_graph_pm
82
83 Returns a Graph::Directed object with the table names for nodes.
84
85 =cut
86
87     require Graph::Directed;
88
89     my $self = shift;
90     my $g    = Graph::Directed->new;
91
92     for my $table ( $self->get_tables ) {
93         my $tname  = $table->name;
94         $g->add_vertex( $tname );
95
96         for my $field ( $table->get_fields ) {
97             if ( $field->is_foreign_key ) {
98                 my $fktable = $field->foreign_key_reference->reference_table;
99
100                 $g->add_edge( $fktable, $tname );
101             }
102         }
103     }
104
105     return $g;
106 }
107
108 sub add_table {
109
110 =pod
111
112 =head2 add_table
113
114 Add a table object.  Returns the new SQL::Translator::Schema::Table object.
115 The "name" parameter is required.  If you try to create a table with the
116 same name as an existing table, you will get an error and the table will
117 not be created.
118
119   my $t1 = $schema->add_table( name => 'foo' ) or die $schema->error;
120   my $t2 = SQL::Translator::Schema::Table->new( name => 'bar' );
121   $t2    = $schema->add_table( $table_bar ) or die $schema->error;
122
123 =cut
124
125     my $self        = shift;
126     my $table_class = 'SQL::Translator::Schema::Table';
127     my $table;
128
129     if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
130         $table = shift;
131         $table->schema($self);
132     }
133     else {
134         my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
135         $args{'schema'} = $self;
136         $table = $table_class->new( \%args )
137           or return $self->error( $table_class->error );
138     }
139
140     $table->order( ++$self->{_order}{table} );
141
142     # We know we have a name as the Table->new above errors if none given.
143     my $table_name = $table->name;
144
145     if ( defined $self->{'tables'}{$table_name} ) {
146         return $self->error(qq[Can't create table: "$table_name" exists]);
147     }
148     else {
149         $self->{'tables'}{$table_name} = $table;
150     }
151
152     return $table;
153 }
154
155 sub drop_table {
156
157 =pod
158
159 =head2 drop_table
160
161 Remove a table from the schema. Returns the table object if the table was found
162 and removed, an error otherwise. The single parameter can be either a table
163 name or an C<SQL::Translator::Schema::Table> object. The "cascade" parameter
164 can be set to 1 to also drop all triggers on the table, default is 0.
165
166   $schema->drop_table('mytable');
167   $schema->drop_table('mytable', cascade => 1);
168
169 =cut
170
171     my $self        = shift;
172     my $table_class = 'SQL::Translator::Schema::Table';
173     my $table_name;
174
175     if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
176         $table_name = shift->name;
177     }
178     else {
179         $table_name = shift;
180     }
181     my %args    = @_;
182     my $cascade = $args{'cascade'};
183
184     if ( !exists $self->{'tables'}{$table_name} ) {
185         return $self->error(qq[Can't drop table: $table_name" doesn't exist]);
186     }
187
188     my $table = delete $self->{'tables'}{$table_name};
189
190     if ($cascade) {
191
192         # Drop all triggers on this table
193         $self->drop_trigger()
194           for ( grep { $_->on_table eq $table_name } @{ $self->{'triggers'} } );
195     }
196     return $table;
197 }
198
199 sub add_procedure {
200
201 =pod
202
203 =head2 add_procedure
204
205 Add a procedure object.  Returns the new SQL::Translator::Schema::Procedure
206 object.  The "name" parameter is required.  If you try to create a procedure
207 with the same name as an existing procedure, you will get an error and the
208 procedure will not be created.
209
210   my $p1 = $schema->add_procedure( name => 'foo' );
211   my $p2 = SQL::Translator::Schema::Procedure->new( name => 'bar' );
212   $p2    = $schema->add_procedure( $procedure_bar ) or die $schema->error;
213
214 =cut
215
216     my $self            = shift;
217     my $procedure_class = 'SQL::Translator::Schema::Procedure';
218     my $procedure;
219
220     if ( UNIVERSAL::isa( $_[0], $procedure_class ) ) {
221         $procedure = shift;
222         $procedure->schema($self);
223     }
224     else {
225         my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
226         $args{'schema'} = $self;
227         return $self->error('No procedure name') unless $args{'name'};
228         $procedure = $procedure_class->new( \%args )
229           or return $self->error( $procedure_class->error );
230     }
231
232     $procedure->order( ++$self->{_order}{proc} );
233     my $procedure_name = $procedure->name
234       or return $self->error('No procedure name');
235
236     if ( defined $self->{'procedures'}{$procedure_name} ) {
237         return $self->error(
238             qq[Can't create procedure: "$procedure_name" exists] );
239     }
240     else {
241         $self->{'procedures'}{$procedure_name} = $procedure;
242     }
243
244     return $procedure;
245 }
246
247 sub drop_procedure {
248
249 =pod
250
251 =head2 drop_procedure
252
253 Remove a procedure from the schema. Returns the procedure object if the
254 procedure was found and removed, an error otherwise. The single parameter
255 can be either a procedure name or an C<SQL::Translator::Schema::Procedure>
256 object.
257
258   $schema->drop_procedure('myprocedure');
259
260 =cut
261
262     my $self       = shift;
263     my $proc_class = 'SQL::Translator::Schema::Procedure';
264     my $proc_name;
265
266     if ( UNIVERSAL::isa( $_[0], $proc_class ) ) {
267         $proc_name = shift->name;
268     }
269     else {
270         $proc_name = shift;
271     }
272
273     if ( !exists $self->{'procedures'}{$proc_name} ) {
274         return $self->error(
275             qq[Can't drop procedure: $proc_name" doesn't exist]);
276     }
277
278     my $proc = delete $self->{'procedures'}{$proc_name};
279
280     return $proc;
281 }
282
283 sub add_trigger {
284
285 =pod
286
287 =head2 add_trigger
288
289 Add a trigger object.  Returns the new SQL::Translator::Schema::Trigger object.
290 The "name" parameter is required.  If you try to create a trigger with the
291 same name as an existing trigger, you will get an error and the trigger will
292 not be created.
293
294   my $t1 = $schema->add_trigger( name => 'foo' );
295   my $t2 = SQL::Translator::Schema::Trigger->new( name => 'bar' );
296   $t2    = $schema->add_trigger( $trigger_bar ) or die $schema->error;
297
298 =cut
299
300     my $self          = shift;
301     my $trigger_class = 'SQL::Translator::Schema::Trigger';
302     my $trigger;
303
304     if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
305         $trigger = shift;
306         $trigger->schema($self);
307     }
308     else {
309         my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
310         $args{'schema'} = $self;
311         return $self->error('No trigger name') unless $args{'name'};
312         $trigger = $trigger_class->new( \%args )
313           or return $self->error( $trigger_class->error );
314     }
315
316     $trigger->order( ++$self->{_order}{trigger} );
317
318     my $trigger_name = $trigger->name or return $self->error('No trigger name');
319     if ( defined $self->{'triggers'}{$trigger_name} ) {
320         return $self->error(qq[Can't create trigger: "$trigger_name" exists]);
321     }
322     else {
323         $self->{'triggers'}{$trigger_name} = $trigger;
324     }
325
326     return $trigger;
327 }
328
329 sub drop_trigger {
330
331 =pod
332
333 =head2 drop_trigger
334
335 Remove a trigger from the schema. Returns the trigger object if the trigger was
336 found and removed, an error otherwise. The single parameter can be either a
337 trigger name or an C<SQL::Translator::Schema::Trigger> object.
338
339   $schema->drop_trigger('mytrigger');
340
341 =cut
342
343     my $self          = shift;
344     my $trigger_class = 'SQL::Translator::Schema::Trigger';
345     my $trigger_name;
346
347     if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
348         $trigger_name = shift->name;
349     }
350     else {
351         $trigger_name = shift;
352     }
353
354     if ( !exists $self->{'triggers'}{$trigger_name} ) {
355         return $self->error(
356             qq[Can't drop trigger: $trigger_name" doesn't exist]);
357     }
358
359     my $trigger = delete $self->{'triggers'}{$trigger_name};
360
361     return $trigger;
362 }
363
364 sub add_view {
365
366 =pod
367
368 =head2 add_view
369
370 Add a view object.  Returns the new SQL::Translator::Schema::View object.
371 The "name" parameter is required.  If you try to create a view with the
372 same name as an existing view, you will get an error and the view will
373 not be created.
374
375   my $v1 = $schema->add_view( name => 'foo' );
376   my $v2 = SQL::Translator::Schema::View->new( name => 'bar' );
377   $v2    = $schema->add_view( $view_bar ) or die $schema->error;
378
379 =cut
380
381     my $self       = shift;
382     my $view_class = 'SQL::Translator::Schema::View';
383     my $view;
384
385     if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
386         $view = shift;
387         $view->schema($self);
388     }
389     else {
390         my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
391         $args{'schema'} = $self;
392         return $self->error('No view name') unless $args{'name'};
393         $view = $view_class->new( \%args ) or return $view_class->error;
394     }
395
396     $view->order( ++$self->{_order}{view} );
397     my $view_name = $view->name or return $self->error('No view name');
398
399     if ( defined $self->{'views'}{$view_name} ) {
400         return $self->error(qq[Can't create view: "$view_name" exists]);
401     }
402     else {
403         $self->{'views'}{$view_name} = $view;
404     }
405
406     return $view;
407 }
408
409 sub drop_view {
410
411 =pod
412
413 =head2 drop_view
414
415 Remove a view from the schema. Returns the view object if the view was found
416 and removed, an error otherwise. The single parameter can be either a view
417 name or an C<SQL::Translator::Schema::View> object.
418
419   $schema->drop_view('myview');
420
421 =cut
422
423     my $self       = shift;
424     my $view_class = 'SQL::Translator::Schema::View';
425     my $view_name;
426
427     if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
428         $view_name = shift->name;
429     }
430     else {
431         $view_name = shift;
432     }
433
434     if ( !exists $self->{'views'}{$view_name} ) {
435         return $self->error(qq[Can't drop view: $view_name" doesn't exist]);
436     }
437
438     my $view = delete $self->{'views'}{$view_name};
439
440     return $view;
441 }
442
443 sub database {
444
445 =pod
446
447 =head2 database
448
449 Get or set the schema's database.  (optional)
450
451   my $database = $schema->database('PostgreSQL');
452
453 =cut
454
455     my $self = shift;
456     $self->{'database'} = shift if @_;
457     return $self->{'database'} || '';
458 }
459
460 sub is_valid {
461
462 =pod
463
464 =head2 is_valid
465
466 Returns true if all the tables and views are valid.
467
468   my $ok = $schema->is_valid or die $schema->error;
469
470 =cut
471
472     my $self = shift;
473
474     return $self->error('No tables') unless $self->get_tables;
475
476     for my $object ( $self->get_tables, $self->get_views ) {
477         return $object->error unless $object->is_valid;
478     }
479
480     return 1;
481 }
482
483 sub get_procedure {
484
485 =pod
486
487 =head2 get_procedure
488
489 Returns a procedure by the name provided.
490
491   my $procedure = $schema->get_procedure('foo');
492
493 =cut
494
495     my $self = shift;
496     my $procedure_name = shift or return $self->error('No procedure name');
497     return $self->error(qq[Table "$procedure_name" does not exist])
498       unless exists $self->{'procedures'}{$procedure_name};
499     return $self->{'procedures'}{$procedure_name};
500 }
501
502 sub get_procedures {
503
504 =pod
505
506 =head2 get_procedures
507
508 Returns all the procedures as an array or array reference.
509
510   my @procedures = $schema->get_procedures;
511
512 =cut
513
514     my $self       = shift;
515     my @procedures =
516       map  { $_->[1] }
517       sort { $a->[0] <=> $b->[0] }
518       map  { [ $_->order, $_ ] } values %{ $self->{'procedures'} };
519
520     if (@procedures) {
521         return wantarray ? @procedures : \@procedures;
522     }
523     else {
524         $self->error('No procedures');
525         return wantarray ? () : undef;
526     }
527 }
528
529 sub get_table {
530
531 =pod
532
533 =head2 get_table
534
535 Returns a table by the name provided.
536
537   my $table = $schema->get_table('foo');
538
539 =cut
540
541     my $self = shift;
542     my $table_name = shift or return $self->error('No table name');
543     my $case_insensitive = shift;
544     if ( $case_insensitive ) {
545       $table_name = uc($table_name);
546       foreach my $table ( keys %{$self->{tables}} ) {
547          return $self->{tables}{$table} if $table_name eq uc($table);
548       }
549       return $self->error(qq[Table "$table_name" does not exist]);
550     }
551     return $self->error(qq[Table "$table_name" does not exist])
552       unless exists $self->{'tables'}{$table_name};
553     return $self->{'tables'}{$table_name};
554 }
555
556 sub get_tables {
557
558 =pod
559
560 =head2 get_tables
561
562 Returns all the tables as an array or array reference.
563
564   my @tables = $schema->get_tables;
565
566 =cut
567
568     my $self   = shift;
569     my @tables =
570       map  { $_->[1] }
571       sort { $a->[0] <=> $b->[0] }
572       map  { [ $_->order, $_ ] } values %{ $self->{'tables'} };
573
574     if (@tables) {
575         return wantarray ? @tables : \@tables;
576     }
577     else {
578         $self->error('No tables');
579         return wantarray ? () : undef;
580     }
581 }
582
583 sub get_trigger {
584
585 =pod
586
587 =head2 get_trigger
588
589 Returns a trigger by the name provided.
590
591   my $trigger = $schema->get_trigger('foo');
592
593 =cut
594
595     my $self = shift;
596     my $trigger_name = shift or return $self->error('No trigger name');
597     return $self->error(qq[Table "$trigger_name" does not exist])
598       unless exists $self->{'triggers'}{$trigger_name};
599     return $self->{'triggers'}{$trigger_name};
600 }
601
602 sub get_triggers {
603
604 =pod
605
606 =head2 get_triggers
607
608 Returns all the triggers as an array or array reference.
609
610   my @triggers = $schema->get_triggers;
611
612 =cut
613
614     my $self     = shift;
615     my @triggers =
616       map  { $_->[1] }
617       sort { $a->[0] <=> $b->[0] }
618       map  { [ $_->order, $_ ] } values %{ $self->{'triggers'} };
619
620     if (@triggers) {
621         return wantarray ? @triggers : \@triggers;
622     }
623     else {
624         $self->error('No triggers');
625         return wantarray ? () : undef;
626     }
627 }
628
629 sub get_view {
630
631 =pod
632
633 =head2 get_view
634
635 Returns a view by the name provided.
636
637   my $view = $schema->get_view('foo');
638
639 =cut
640
641     my $self = shift;
642     my $view_name = shift or return $self->error('No view name');
643     return $self->error('View "$view_name" does not exist')
644       unless exists $self->{'views'}{$view_name};
645     return $self->{'views'}{$view_name};
646 }
647
648 sub get_views {
649
650 =pod
651
652 =head2 get_views
653
654 Returns all the views as an array or array reference.
655
656   my @views = $schema->get_views;
657
658 =cut
659
660     my $self  = shift;
661     my @views =
662       map  { $_->[1] }
663       sort { $a->[0] <=> $b->[0] }
664       map  { [ $_->order, $_ ] } values %{ $self->{'views'} };
665
666     if (@views) {
667         return wantarray ? @views : \@views;
668     }
669     else {
670         $self->error('No views');
671         return wantarray ? () : undef;
672     }
673 }
674
675 sub make_natural_joins {
676
677 =pod
678
679 =head2 make_natural_joins
680
681 Creates foriegn key relationships among like-named fields in different
682 tables.  Accepts the following arguments:
683
684 =over 4
685
686 =item * join_pk_only
687
688 A True or False argument which determins whether or not to perform
689 the joins from primary keys to fields of the same name in other tables
690
691 =item * skip_fields
692
693 A list of fields to skip in the joins
694
695 =back
696
697   $schema->make_natural_joins(
698       join_pk_only => 1,
699       skip_fields  => 'name,department_id',
700   );
701
702 =cut
703
704     my $self         = shift;
705     my %args         = @_;
706     my $join_pk_only = $args{'join_pk_only'} || 0;
707     my %skip_fields  =
708       map { s/^\s+|\s+$//g; $_, 1 } @{ parse_list_arg( $args{'skip_fields'} ) };
709
710     my ( %common_keys, %pk );
711     for my $table ( $self->get_tables ) {
712         for my $field ( $table->get_fields ) {
713             my $field_name = $field->name or next;
714             next if $skip_fields{$field_name};
715             $pk{$field_name} = 1 if $field->is_primary_key;
716             push @{ $common_keys{$field_name} }, $table->name;
717         }
718     }
719
720     for my $field ( keys %common_keys ) {
721         next if $join_pk_only and !defined $pk{$field};
722
723         my @table_names = @{ $common_keys{$field} };
724         next unless scalar @table_names > 1;
725
726         for my $i ( 0 .. $#table_names ) {
727             my $table1 = $self->get_table( $table_names[$i] ) or next;
728
729             for my $j ( 1 .. $#table_names ) {
730                 my $table2 = $self->get_table( $table_names[$j] ) or next;
731                 next if $table1->name eq $table2->name;
732
733                 $table1->add_constraint(
734                     type             => FOREIGN_KEY,
735                     fields           => $field,
736                     reference_table  => $table2->name,
737                     reference_fields => $field,
738                 );
739             }
740         }
741     }
742
743     return 1;
744 }
745
746 sub name {
747
748 =pod
749
750 =head2 name
751
752 Get or set the schema's name.  (optional)
753
754   my $schema_name = $schema->name('Foo Database');
755
756 =cut
757
758     my $self = shift;
759     $self->{'name'} = shift if @_;
760     return $self->{'name'} || '';
761 }
762
763 sub translator {
764
765 =pod
766
767 =head2 translator
768
769 Get the SQL::Translator instance that instantiated the parser.
770
771 =cut
772
773     my $self = shift;
774     $self->{'translator'} = shift if @_;
775     return $self->{'translator'};
776 }
777
778 sub DESTROY {
779     my $self = shift;
780     undef $_ for values %{ $self->{'tables'} };
781     undef $_ for values %{ $self->{'views'} };
782 }
783
784 1;
785
786 =pod
787
788 =head1 AUTHOR
789
790 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
791
792 =cut
793