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