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