File Coverage

blib/lib/SQL/Translator/Schema.pm
Criterion Covered Total %
statement 196 246 79.6
branch 76 120 63.3
condition 0 5 0.0
subroutine 26 28 92.8
pod 19 19 100.0
total 317 418 75.8


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