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