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   2170 use Moo;
  70         29522  
  70         463  
30 70     70   34322 use SQL::Translator::Schema::Constants;
  70         167  
  70         4247  
31 70     70   25620 use SQL::Translator::Schema::Procedure;
  70         219  
  70         2081  
32 70     70   34338 use SQL::Translator::Schema::Table;
  70         236  
  70         2281  
33 70     70   33377 use SQL::Translator::Schema::Trigger;
  70         231  
  70         2164  
34 70     70   29685 use SQL::Translator::Schema::View;
  70         204  
  70         2270  
35 70     70   482 use Sub::Quote qw(quote_sub);
  70         132  
  70         3203  
36              
37 70     70   390 use SQL::Translator::Utils 'parse_list_arg';
  70         129  
  70         2269  
38 70     70   365 use Carp;
  70         125  
  70         175243  
39              
40             extends 'SQL::Translator::Schema::Object';
41              
42             our $VERSION = '1.6_3';
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 333     333 1 3332 my $self = shift;
104 333         849 my $table_class = 'SQL::Translator::Schema::Table';
105 333         527 my $table;
106              
107 333 100       2251 if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
108 18         41 $table = shift;
109 18         367 $table->schema($self);
110             }
111             else {
112 315 50       1549 my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0         0  
113 315         1280 $args{'schema'} = $self;
114 315 100       7018 $table = $table_class->new( \%args )
115             or return $self->error( $table_class->error );
116             }
117              
118 332         12560 $table->order( ++$self->_order->{table} );
119              
120             # We know we have a name as the Table->new above errors if none given.
121 332         5436 my $table_name = $table->name;
122              
123 332 100       6396 if ( defined $self->_tables->{$table_name} ) {
124 1         20 return $self->error(qq[Can't use table name "$table_name": table exists]);
125             }
126             else {
127 331         1172 $self->_tables->{$table_name} = $table;
128             }
129              
130 331         1344 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 102 my $self = shift;
150 5         10 my $table_class = 'SQL::Translator::Schema::Table';
151 5         11 my $table_name;
152              
153 5 100       152 if ( UNIVERSAL::isa( $_[0], $table_class ) ) {
154 2         36 $table_name = shift->name;
155             }
156             else {
157 3         22 $table_name = shift;
158             }
159 5         45 my %args = @_;
160 5         12 my $cascade = $args{'cascade'};
161              
162 5 100       35 if ( !exists $self->_tables->{$table_name} ) {
163 1         22 return $self->error(qq[Can't drop table: "$table_name" doesn't exist]);
164             }
165              
166 4         18 my $table = delete $self->_tables->{$table_name};
167              
168 4 100       14 if ($cascade) {
169              
170             # Drop all triggers on this table
171             $self->drop_trigger()
172 2         3 for ( grep { $_->on_table eq $table_name } values %{ $self->_triggers } );
  0         0  
  2         8  
173             }
174 4         65 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 578 my $self = shift;
197 34         65 my $procedure_class = 'SQL::Translator::Schema::Procedure';
198 34         54 my $procedure;
199              
200 34 100       323 if ( UNIVERSAL::isa( $_[0], $procedure_class ) ) {
201 2         4 $procedure = shift;
202 2         42 $procedure->schema($self);
203             }
204             else {
205 32 50       238 my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0         0  
206 32         100 $args{'schema'} = $self;
207 32 50       101 return $self->error('No procedure name') unless $args{'name'};
208 32 50       750 $procedure = $procedure_class->new( \%args )
209             or return $self->error( $procedure_class->error );
210             }
211              
212 34         1109 $procedure->order( ++$self->_order->{proc} );
213 34 50       169 my $procedure_name = $procedure->name
214             or return $self->error('No procedure name');
215              
216 34 50       155 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         138 $self->_procedures->{$procedure_name} = $procedure;
222             }
223              
224 34         334 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 2052 my $self = shift;
243 3         6 my $proc_class = 'SQL::Translator::Schema::Procedure';
244 3         3 my $proc_name;
245              
246 3 100       15 if ( UNIVERSAL::isa( $_[0], $proc_class ) ) {
247 1         3 $proc_name = shift->name;
248             }
249             else {
250 2         3 $proc_name = shift;
251             }
252              
253 3 100       11 if ( !exists $self->_procedures->{$proc_name} ) {
254 1         27 return $self->error(
255             qq[Can't drop procedure: "$proc_name" doesn't exist]);
256             }
257              
258 2         6 my $proc = delete $self->_procedures->{$proc_name};
259              
260 2         5 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 72     72 1 2005 my $self = shift;
283 72         139 my $trigger_class = 'SQL::Translator::Schema::Trigger';
284 72         107 my $trigger;
285              
286 72 100       445 if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
287 2         4 $trigger = shift;
288 2         43 $trigger->schema($self);
289             }
290             else {
291 70 50       492 my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0         0  
292 70         209 $args{'schema'} = $self;
293 70 50       222 return $self->error('No trigger name') unless $args{'name'};
294 70 50       1524 $trigger = $trigger_class->new( \%args )
295             or return $self->error( $trigger_class->error );
296             }
297              
298 72         3515 $trigger->order( ++$self->_order->{trigger} );
299              
300 72 50       299 my $trigger_name = $trigger->name or return $self->error('No trigger name');
301 72 50       371 if ( defined $self->_triggers->{$trigger_name} ) {
302 0         0 return $self->error(qq[Can't create trigger: "$trigger_name" exists]);
303             }
304             else {
305 72         308 $self->_triggers->{$trigger_name} = $trigger;
306             }
307              
308 72         377 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 1080 my $self = shift;
326 3         4 my $trigger_class = 'SQL::Translator::Schema::Trigger';
327 3         5 my $trigger_name;
328              
329 3 100       12 if ( UNIVERSAL::isa( $_[0], $trigger_class ) ) {
330 1         4 $trigger_name = shift->name;
331             }
332             else {
333 2         5 $trigger_name = shift;
334             }
335              
336 3 100       12 if ( !exists $self->_triggers->{$trigger_name} ) {
337 1         23 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         5 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 43     43 1 1249 my $self = shift;
366 43         102 my $view_class = 'SQL::Translator::Schema::View';
367 43         76 my $view;
368              
369 43 100       343 if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
370 3         5 $view = shift;
371 3         60 $view->schema($self);
372             }
373             else {
374 40 50       272 my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0         0  
375 40         104 $args{'schema'} = $self;
376 40 50       214 return $self->error('No view name') unless $args{'name'};
377 40 50       991 $view = $view_class->new( \%args ) or return $view_class->error;
378             }
379              
380 43         2041 $view->order( ++$self->_order->{view} );
381 43 50       319 my $view_name = $view->name or return $self->error('No view name');
382              
383 43 100       225 if ( defined $self->_views->{$view_name} ) {
384 1         19 return $self->error(qq[Can't create view: "$view_name" exists]);
385             }
386             else {
387 42         172 $self->_views->{$view_name} = $view;
388             }
389              
390 42         227 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 1030 my $self = shift;
408 3         5 my $view_class = 'SQL::Translator::Schema::View';
409 3         3 my $view_name;
410              
411 3 100       13 if ( UNIVERSAL::isa( $_[0], $view_class ) ) {
412 1         3 $view_name = shift->name;
413             }
414             else {
415 2         5 $view_name = shift;
416             }
417              
418 3 100       8 if ( !exists $self->_views->{$view_name} ) {
419 1         25 return $self->error(qq[Can't drop view: "$view_name" doesn't exist]);
420             }
421              
422 2         6 my $view = delete $self->_views->{$view_name};
423              
424 2         4 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 4245 my $self = shift;
450              
451 17 100       209 return $self->error('No tables') unless $self->get_tables;
452              
453 16         55 for my $object ( $self->get_tables, $self->get_views ) {
454 62 50       228 return $object->error unless $object->is_valid;
455             }
456              
457 16         169 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 616 my $self = shift;
473 1 50       4 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       6 unless exists $self->_procedures->{$procedure_name};
476 1         4 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 4948 my $self = shift;
492             my @procedures =
493 23         54 map { $_->[1] }
494 24         35 sort { $a->[0] <=> $b->[0] }
495 23         50 map { [ $_->order, $_ ] } values %{ $self->_procedures };
  23         107  
  23         432  
496              
497 23 100       90 if (@procedures) {
498 13 50       59 return wantarray ? @procedures : \@procedures;
499             }
500             else {
501 10         196 $self->error('No procedures');
502 10         111 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 435     435 1 7790 my $self = shift;
519 435 100       1846 my $table_name = shift or return $self->error('No table name');
520 434         1980 my $case_insensitive = shift;
521 434 50       967 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 434 100       2584 unless exists $self->_tables->{$table_name};
530 388         4086 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 272     272 1 100151 my $self = shift;
546             my @tables =
547 794         1474 map { $_->[1] }
548 953         1855 sort { $a->[0] <=> $b->[0] }
549 272         531 map { [ $_->order, $_ ] } values %{ $self->_tables };
  794         13190  
  272         1507  
550              
551 272 100       969 if (@tables) {
552 268 100       1411 return wantarray ? @tables : \@tables;
553             }
554             else {
555 4         96 $self->error('No tables');
556 4         26 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 613 my $self = shift;
573 2 50       8 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       14 unless exists $self->_triggers->{$trigger_name};
576 2         8 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 43     43 1 1626 my $self = shift;
592             my @triggers =
593 61         125 map { $_->[1] }
594 41         114 sort { $a->[0] <=> $b->[0] }
595 43         98 map { [ $_->order, $_ ] } values %{ $self->_triggers };
  61         1076  
  43         232  
596              
597 43 100       159 if (@triggers) {
598 26 50       119 return wantarray ? @triggers : \@triggers;
599             }
600             else {
601 17         360 $self->error('No triggers');
602 17         61 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 1015 my $self = shift;
619 4 100       32 my $view_name = shift or return $self->error('No view name');
620             return $self->error('View "$view_name" does not exist')
621 3 100       34 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 74     74 1 2305 my $self = shift;
638             my @views =
639 44         154 map { $_->[1] }
640 7         56 sort { $a->[0] <=> $b->[0] }
641 74         175 map { [ $_->order, $_ ] } values %{ $self->_views };
  44         963  
  74         427  
642              
643 74 100       286 if (@views) {
644 39 50       194 return wantarray ? @views : \@views;
645             }
646             else {
647 35         846 $self->error('No views');
648 35         172 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