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