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