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