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