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