Removed check on field name when adding fields, as the fields constructor does it...
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Schema.pm
1 package SQL::Translator::Schema;
2
3 # ----------------------------------------------------------------------
4 # $Id: Schema.pm,v 1.14 2004-03-23 21:12:22 grommit Exp $
5 # ----------------------------------------------------------------------
6 # Copyright (C) 2002-4 SQLFairy Authors
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::Utils 'parse_list_arg';
53
54 use base 'Class::Base';
55 use vars qw[ $VERSION $TABLE_ORDER $VIEW_ORDER $TRIGGER_ORDER $PROC_ORDER ];
56
57 $VERSION = sprintf "%d.%02d", q$Revision: 1.14 $ =~ /(\d+)\.(\d+)/;
58
59 # ----------------------------------------------------------------------
60 sub init {
61
62 =pod
63
64 =head2 new
65
66 Object constructor.
67
68   my $schema   =  SQL::Translator->new(
69       name     => 'Foo',
70       database => 'MySQL',
71   );
72
73 =cut
74
75     my ( $self, $config ) = @_;
76     $self->params( $config, qw[ name database ] ) || return undef;
77     return $self;
78 }
79
80 # ----------------------------------------------------------------------
81 sub add_table {
82
83 =pod
84
85 =head2 add_table
86
87 Add a table object.  Returns the new SQL::Translator::Schema::Table object.
88 The "name" parameter is required.  If you try to create a table with the
89 same name as an existing table, you will get an error and the table will 
90 not be created.
91
92   my $t1 = $schema->add_table( name => 'foo' ) or die $schema->error;
93   my $t2 = SQL::Translator::Schema::Table->new( name => 'bar' );
94   $t2    = $schema->add_table( $table_bar ) or die $schema->error;
95
96 =cut
97
98     my $self        = shift;
99     my $table_class = 'SQL::Translator::Schema::Table';
100     my $table;
101
102     if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
103         $table = shift;
104         $table->schema( $self );
105     }
106     else {
107         my %args = @_;
108         $args{'schema'} = $self;
109         $table = $table_class->new( \%args ) or return 
110             $self->error( $table_class->error );
111     }
112
113     $table->order( ++$TABLE_ORDER );
114     # We know we have a name as the Table->new above errors if none given.
115     my $table_name = $table->name;
116
117     if ( defined $self->{'tables'}{ $table_name } ) {
118         return $self->error(qq[Can't create table: "$table_name" exists]);
119     }
120     else {
121         $self->{'tables'}{ $table_name } = $table;
122     }
123
124     return $table;
125 }
126
127 # ----------------------------------------------------------------------
128 sub add_procedure {
129
130 =pod
131
132 =head2 add_procedure
133
134 Add a procedure object.  Returns the new
135 SQL::Translator::Schema::Procedure object.  The "name" parameter is
136 required.  If you try to create a procedure with the same name as an
137 existing procedure, you will get an error and the procedure will not
138 be created.
139
140   my $p1 = $schema->add_procedure( name => 'foo' );
141   my $p2 = SQL::Translator::Schema::Procedure->new( name => 'bar' );
142   $p2    = $schema->add_procedure( $procedure_bar ) or die $schema->error;
143
144 =cut
145
146     my $self            = shift;
147     my $procedure_class = 'SQL::Translator::Schema::Procedure';
148     my $procedure;
149
150     if ( UNIVERSAL::isa( $_[0], $procedure_class ) ) {
151         $procedure = shift;
152         $procedure->schema( $self );
153     }
154     else {
155         my %args = @_;
156         $args{'schema'} = $self;
157         return $self->error('No procedure name') unless $args{'name'};
158         $procedure = $procedure_class->new( \%args ) or 
159             return $self->error( $procedure_class->error );
160     }
161
162     $procedure->order( ++$PROC_ORDER );
163     my $procedure_name = $procedure->name or return 
164         $self->error('No procedure name');
165
166     if ( defined $self->{'procedures'}{ $procedure_name } ) { 
167         return $self->error(
168             qq[Can't create procedure: "$procedure_name" exists]
169         );
170     }
171     else {
172         $self->{'procedures'}{ $procedure_name } = $procedure;
173     }
174
175     return $procedure;
176 }
177
178 # ----------------------------------------------------------------------
179 sub add_trigger {
180
181 =pod
182
183 =head2 add_trigger
184
185 Add a trigger object.  Returns the new SQL::Translator::Schema::Trigger object.
186 The "name" parameter is required.  If you try to create a trigger with the
187 same name as an existing trigger, you will get an error and the trigger will 
188 not be created.
189
190   my $t1 = $schema->add_trigger( name => 'foo' );
191   my $t2 = SQL::Translator::Schema::Trigger->new( name => 'bar' );
192   $t2    = $schema->add_trigger( $trigger_bar ) or die $schema->error;
193
194 =cut
195
196     my $self          = shift;
197     my $trigger_class = 'SQL::Translator::Schema::Trigger';
198     my $trigger;
199
200     if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
201         $trigger = shift;
202         $trigger->schema( $self );
203     }
204     else {
205         my %args = @_;
206         $args{'schema'} = $self;
207         return $self->error('No trigger name') unless $args{'name'};
208         $trigger = $trigger_class->new( \%args ) or 
209             return $self->error( $trigger_class->error );
210     }
211
212     $trigger->order( ++$TRIGGER_ORDER );
213     my $trigger_name = $trigger->name or return $self->error('No trigger name');
214
215     if ( defined $self->{'triggers'}{ $trigger_name } ) { 
216         return $self->error(qq[Can't create trigger: "$trigger_name" exists]);
217     }
218     else {
219         $self->{'triggers'}{ $trigger_name } = $trigger;
220     }
221
222     return $trigger;
223 }
224
225 # ----------------------------------------------------------------------
226 sub add_view {
227
228 =pod
229
230 =head2 add_view
231
232 Add a view object.  Returns the new SQL::Translator::Schema::View object.
233 The "name" parameter is required.  If you try to create a view with the
234 same name as an existing view, you will get an error and the view will 
235 not be created.
236
237   my $v1 = $schema->add_view( name => 'foo' );
238   my $v2 = SQL::Translator::Schema::View->new( name => 'bar' );
239   $v2    = $schema->add_view( $view_bar ) or die $schema->error;
240
241 =cut
242
243     my $self        = shift;
244     my $view_class = 'SQL::Translator::Schema::View';
245     my $view;
246
247     if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
248         $view = shift;
249         $view->schema( $self );
250     }
251     else {
252         my %args = @_;
253         $args{'schema'} = $self;
254         return $self->error('No view name') unless $args{'name'};
255         $view = $view_class->new( \%args ) or return $view_class->error;
256     }
257
258     $view->order( ++$VIEW_ORDER );
259     my $view_name = $view->name or return $self->error('No view name');
260
261     if ( defined $self->{'views'}{ $view_name } ) { 
262         return $self->error(qq[Can't create view: "$view_name" exists]);
263     }
264     else {
265         $self->{'views'}{ $view_name } = $view;
266     }
267
268     return $view;
269 }
270
271 # ----------------------------------------------------------------------
272 sub database {
273
274 =pod
275
276 =head2 database
277
278 Get or set the schema's database.  (optional)
279
280   my $database = $schema->database('PostgreSQL');
281
282 =cut
283
284     my $self = shift;
285     $self->{'database'} = shift if @_;
286     return $self->{'database'} || '';
287 }
288
289 # ----------------------------------------------------------------------
290 sub is_valid {
291
292 =pod
293
294 =head2 is_valid
295
296 Returns true if all the tables and views are valid.
297
298   my $ok = $schema->is_valid or die $schema->error;
299
300 =cut
301
302     my $self = shift;
303
304     return $self->error('No tables') unless $self->get_tables;
305
306     for my $object ( $self->get_tables, $self->get_views ) {
307         return $object->error unless $object->is_valid;
308     }
309
310     return 1;
311 }
312
313 # ----------------------------------------------------------------------
314 sub get_procedure {
315
316 =pod
317
318 =head2 get_procedure
319
320 Returns a procedure by the name provided.
321
322   my $procedure = $schema->get_procedure('foo');
323
324 =cut
325
326     my $self       = shift;
327     my $procedure_name = shift or return $self->error('No procedure name');
328     return $self->error( qq[Table "$procedure_name" does not exist] ) unless
329         exists $self->{'procedures'}{ $procedure_name };
330     return $self->{'procedures'}{ $procedure_name };
331 }
332
333 # ----------------------------------------------------------------------
334 sub get_procedures {
335
336 =pod
337
338 =head2 get_procedures
339
340 Returns all the procedures as an array or array reference.
341
342   my @procedures = $schema->get_procedures;
343
344 =cut
345
346     my $self   = shift;
347     my @procedures = 
348         map  { $_->[1] } 
349         sort { $a->[0] <=> $b->[0] } 
350         map  { [ $_->order, $_ ] }
351         values %{ $self->{'procedures'} };
352
353     if ( @procedures ) {
354         return wantarray ? @procedures : \@procedures;
355     }
356     else {
357         $self->error('No procedures');
358         return wantarray ? () : undef;
359     }
360 }
361
362 # ----------------------------------------------------------------------
363 sub get_table {
364
365 =pod
366
367 =head2 get_table
368
369 Returns a table by the name provided.
370
371   my $table = $schema->get_table('foo');
372
373 =cut
374
375     my $self       = shift;
376     my $table_name = shift or return $self->error('No table name');
377     return $self->error( qq[Table "$table_name" does not exist] ) unless
378         exists $self->{'tables'}{ $table_name };
379     return $self->{'tables'}{ $table_name };
380 }
381
382 # ----------------------------------------------------------------------
383 sub get_tables {
384
385 =pod
386
387 =head2 get_tables
388
389 Returns all the tables as an array or array reference.
390
391   my @tables = $schema->get_tables;
392
393 =cut
394
395     my $self   = shift;
396     my @tables = 
397         map  { $_->[1] } 
398         sort { $a->[0] <=> $b->[0] } 
399         map  { [ $_->order, $_ ] }
400         values %{ $self->{'tables'} };
401
402     if ( @tables ) {
403         return wantarray ? @tables : \@tables;
404     }
405     else {
406         $self->error('No tables');
407         return wantarray ? () : undef;
408     }
409 }
410
411 # ----------------------------------------------------------------------
412 sub get_trigger {
413
414 =pod
415
416 =head2 get_trigger
417
418 Returns a trigger by the name provided.
419
420   my $trigger = $schema->get_trigger('foo');
421
422 =cut
423
424     my $self       = shift;
425     my $trigger_name = shift or return $self->error('No trigger name');
426     return $self->error( qq[Table "$trigger_name" does not exist] ) unless
427         exists $self->{'triggers'}{ $trigger_name };
428     return $self->{'triggers'}{ $trigger_name };
429 }
430
431 # ----------------------------------------------------------------------
432 sub get_triggers {
433
434 =pod
435
436 =head2 get_triggers
437
438 Returns all the triggers as an array or array reference.
439
440   my @triggers = $schema->get_triggers;
441
442 =cut
443
444     my $self   = shift;
445     my @triggers = 
446         map  { $_->[1] } 
447         sort { $a->[0] <=> $b->[0] } 
448         map  { [ $_->order, $_ ] }
449         values %{ $self->{'triggers'} };
450
451     if ( @triggers ) {
452         return wantarray ? @triggers : \@triggers;
453     }
454     else {
455         $self->error('No triggers');
456         return wantarray ? () : undef;
457     }
458 }
459
460 # ----------------------------------------------------------------------
461 sub get_view {
462
463 =pod
464
465 =head2 get_view
466
467 Returns a view by the name provided.
468
469   my $view = $schema->get_view('foo');
470
471 =cut
472
473     my $self      = shift;
474     my $view_name = shift or return $self->error('No view name');
475     return $self->error('View "$view_name" does not exist') unless
476         exists $self->{'views'}{ $view_name };
477     return $self->{'views'}{ $view_name };
478 }
479
480 # ----------------------------------------------------------------------
481 sub get_views {
482
483 =pod
484
485 =head2 get_views
486
487 Returns all the views as an array or array reference.
488
489   my @views = $schema->get_views;
490
491 =cut
492
493     my $self  = shift;
494     my @views = 
495         map  { $_->[1] } 
496         sort { $a->[0] <=> $b->[0] } 
497         map  { [ $_->order, $_ ] }
498         values %{ $self->{'views'} };
499
500     if ( @views ) {
501         return wantarray ? @views : \@views;
502     }
503     else {
504         $self->error('No views');
505         return wantarray ? () : undef;
506     }
507 }
508
509 # ----------------------------------------------------------------------
510 sub make_natural_joins {
511
512 =pod
513
514 =head2 make_natural_joins
515
516 Creates foriegn key relationships among like-named fields in different
517 tables.  Accepts the following arguments:
518
519 =over 4
520
521 =item * join_pk_only 
522
523 A True or False argument which determins whether or not to perform 
524 the joins from primary keys to fields of the same name in other tables
525
526 =item * skip_fields
527
528 A list of fields to skip in the joins
529
530 =back 4
531
532   $schema->make_natural_joins(
533       join_pk_only => 1,
534       skip_fields  => 'name,department_id',
535   );
536
537 =cut
538
539     my $self         = shift;
540     my %args         = @_;
541     my $join_pk_only = $args{'join_pk_only'} || 0;
542     my %skip_fields  = map { s/^\s+|\s+$//g; $_, 1 } @{ 
543         parse_list_arg( $args{'skip_fields'} ) 
544     };
545
546     my ( %common_keys, %pk );
547     for my $table ( $self->get_tables ) {
548         for my $field ( $table->get_fields ) {
549             my $field_name = $field->name or next;
550             next if $skip_fields{ $field_name };
551             $pk{ $field_name } = 1 if $field->is_primary_key;
552             push @{ $common_keys{ $field_name } }, $table->name;
553         }
554     } 
555    
556     for my $field ( keys %common_keys ) {
557         next if $join_pk_only and !defined $pk{ $field };
558
559         my @table_names = @{ $common_keys{ $field } };
560         next unless scalar @table_names > 1;
561
562         for my $i ( 0 .. $#table_names ) {
563             my $table1 = $self->get_table( $table_names[ $i ] ) or next;
564
565             for my $j ( 1 .. $#table_names ) {
566                 my $table2 = $self->get_table( $table_names[ $j ] ) or next;
567                 next if $table1->name eq $table2->name;
568
569                 $table1->add_constraint(
570                     type             => FOREIGN_KEY,
571                     fields           => $field,
572                     reference_table  => $table2->name,
573                     reference_fields => $field,
574                 );
575             }               
576         }
577     } 
578
579     return 1;
580 }
581
582 # ----------------------------------------------------------------------
583 sub name {
584
585 =pod
586
587 =head2 name
588
589 Get or set the schema's name.  (optional)
590
591   my $schema_name = $schema->name('Foo Database');
592
593 =cut
594
595     my $self = shift;
596     $self->{'name'} = shift if @_;
597     return $self->{'name'} || '';
598 }
599
600 # ----------------------------------------------------------------------
601 sub DESTROY {
602     my $self = shift;
603     undef $_ for values %{ $self->{'tables'} };
604     undef $_ for values %{ $self->{'views'}  };
605 }
606
607 1;
608
609 # ----------------------------------------------------------------------
610
611 =pod
612
613 =head1 AUTHOR
614
615 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>
616
617 =cut