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