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