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