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