File Coverage

blib/lib/SQL/Translator/Parser/DBIx/Class.pm
Criterion Covered Total %
statement 196 207 94.6
branch 88 116 75.8
condition 33 46 71.7
subroutine 14 14 100.0
pod 0 1 0.0
total 331 384 86.2


line stmt bran cond sub pod time code
1             package SQL::Translator::Parser::DBIx::Class;
2              
3             # AUTHOR: Jess Robinson
4              
5             # Some mistakes the fault of Matt S Trout
6              
7             # Others the fault of Ash Berlin
8              
9 6     6   64579 use strict;
  6         16  
  6         190  
10 6     6   34 use warnings;
  6         16  
  6         468  
11             our ($DEBUG, $VERSION, @EXPORT_OK);
12             $VERSION = '1.10';
13             $DEBUG = 0 unless defined $DEBUG;
14              
15 6     6   42 use Exporter;
  6         13  
  6         286  
16 6     6   994 use SQL::Translator::Utils qw(debug normalize_name);
  6         11976  
  6         402  
17 6     6   52 use DBIx::Class::Carp qw/^SQL::Translator|^DBIx::Class|^Try::Tiny/;
  6         63  
  6         62  
18 6     6   45 use DBIx::Class::Exception;
  6         13  
  6         160  
19 6     6   34 use Scalar::Util 'blessed';
  6         23  
  6         275  
20 6     6   40 use Try::Tiny;
  6         13  
  6         299  
21 6     6   39 use namespace::clean;
  6         15  
  6         61  
22              
23 6     6   2046 use base qw(Exporter);
  6         14  
  6         14797  
24              
25             @EXPORT_OK = qw(parse);
26              
27             # -------------------------------------------------------------------
28             # parse($tr, $data)
29             #
30             # setting parser_args => { add_fk_index => 0 } will prevent
31             # the auto-generation of an index for each FK.
32             #
33             # Note that $data, in the case of this parser, is not useful.
34             # We're working with DBIx::Class Schemas, not data streams.
35             # -------------------------------------------------------------------
36             sub parse {
37 23     23 0 47711 my ($tr, $data) = @_;
38 23         462 my $args = $tr->parser_args;
39              
40 23   100     943 my $dbicschema = $data || $args->{dbic_schema};
41              
42 23         78 for (qw(DBIx::Class::Schema DBIx::Schema package)) {
43 69 100       219 if (defined (my $s = delete $args->{$_} )) {
44 3         30 carp_unique("Supplying a schema via ... parser_args => { '$_' => \$schema } is deprecated. Please use parser_args => { dbic_schema => \$schema } instead");
45              
46             # move it from the deprecated to the proper $args slot
47 3 50       334 unless ($dbicschema) {
48 3         15 $args->{dbic_schema} = $dbicschema = $s;
49             }
50             }
51             }
52              
53 23 50       84 DBIx::Class::Exception->throw('No DBIx::Class::Schema') unless ($dbicschema);
54              
55 23 100       99 if (!ref $dbicschema) {
56 1 50       372 eval "require $dbicschema"
57             or DBIx::Class::Exception->throw("Can't load $dbicschema: $@");
58             }
59              
60 23 100 100     241 if (
61             ref $args->{dbic_schema}
62             and
63             $args->{dbic_schema}->storage
64             ) {
65             # we have a storage-holding $schema instance in $args
66             # we need to dissociate it from that $storage
67             # otherwise SQLT insanity may ensue due to how some
68             # serializing producers treat $args (crazy crazy shit)
69 2         39 local $args->{dbic_schema}{storage};
70 2         19 $args->{dbic_schema} = $args->{dbic_schema}->clone;
71             }
72              
73 23         577 my $schema = $tr->schema;
74 23         18111 my $table_no = 0;
75              
76 23 50 50     301 $schema->name( ref($dbicschema) . " v" . ($dbicschema->schema_version || '1.x'))
77             unless ($schema->name);
78              
79 23         129 my @monikers = sort $dbicschema->sources;
80 23 100       1188 if (my $limit_sources = $args->{'sources'}) {
81 1   50     7 my $ref = ref $limit_sources || '';
82 1 50 33     7 $dbicschema->throw_exception ("'sources' parameter must be an array or hash ref")
83             unless( $ref eq 'ARRAY' || ref eq 'HASH' );
84              
85             # limit monikers to those specified in
86 1         3 my $sources;
87 1 50       4 if ($ref eq 'ARRAY') {
88 1         6 $sources->{$_} = 1 for (@$limit_sources);
89             } else {
90 0         0 $sources = $limit_sources;
91             }
92 1         3 @monikers = grep { $sources->{$_} } @monikers;
  46         69  
93             }
94              
95              
96 23         59 my(%table_monikers, %view_monikers);
97 23         71 for my $moniker (@monikers){
98 972         1942 my $source = $dbicschema->source($moniker);
99 972 100       3093 if ( $source->isa('DBIx::Class::ResultSource::Table') ) {
    50          
100 929         2162 $table_monikers{$moniker}++;
101             } elsif( $source->isa('DBIx::Class::ResultSource::View') ){
102 43 100       199 next if $source->is_virtual;
103 22         89 $view_monikers{$moniker}++;
104             }
105             }
106              
107 23         53 my %tables;
108 23         456 foreach my $moniker (sort keys %table_monikers)
109             {
110 929         170537 my $source = $dbicschema->source($moniker);
111 929         3956 my $table_name = $source->name;
112              
113             # FIXME - this isn't the right way to do it, but sqlt does not
114             # support quoting properly to be signaled about this
115 929 100       2616 $table_name = $$table_name if ref $table_name eq 'SCALAR';
116              
117             # It's possible to have multiple DBIC sources using the same table
118 929 100       3059 next if $tables{$table_name};
119              
120 866         3234 $tables{$table_name}{source} = $source;
121 866         18716 my $table = $tables{$table_name}{object} = SQL::Translator::Schema::Table->new(
122             name => $table_name,
123             type => 'TABLE',
124             );
125 866         289607 foreach my $col ($source->columns)
126             {
127             # assuming column_info in dbic is the same as DBI (?)
128             # data_type is a number, column_type is text?
129             my %colinfo = (
130             name => $col,
131             size => 0,
132             is_auto_increment => 0,
133             is_foreign_key => 0,
134             is_nullable => 0,
135 2814         3125974 %{$source->column_info($col)}
  2814         9944  
136             );
137 2814 100       8621 if ($colinfo{is_nullable}) {
138 717 50       2595 $colinfo{default} = '' unless exists $colinfo{default};
139             }
140 2814   33     11262 my $f = $table->add_field(%colinfo)
141             || $dbicschema->throw_exception ($table->error);
142             }
143              
144 866         1562805 my @primary = $source->primary_columns;
145              
146 866 100       4822 $table->primary_key(@primary) if @primary;
147              
148 866         2290758 my %unique_constraints = $source->unique_constraints;
149 866         3735 foreach my $uniq (sort keys %unique_constraints) {
150 1274 100       259649 if (!$source->_compare_relationship_keys($unique_constraints{$uniq}, \@primary)) {
151             $table->add_constraint(
152             type => 'unique',
153             name => $uniq,
154 387         1267 fields => $unique_constraints{$uniq}
155             );
156             }
157             }
158              
159 866         159017 my @rels = $source->relationships();
160              
161 866         1957 my %created_FK_rels;
162              
163             # global add_fk_index set in parser_args
164 866 100 100     4133 my $add_fk_index = (exists $args->{add_fk_index} && ! $args->{add_fk_index}) ? 0 : 1;
165              
166             REL:
167 866         2954 foreach my $rel (sort @rels) {
168              
169 2059         152600 my $rel_info = $source->relationship_info($rel);
170              
171             # Ignore any rel cond that isn't a straight hash
172 2059 100       8162 next unless ref $rel_info->{cond} eq 'HASH';
173              
174 1611     1611   8521 my $relsource = try { $source->related_source($rel) };
  1611         73711  
175 1611 100       19464 unless ($relsource) {
176 11         92 carp "Ignoring relationship '$rel' on '$moniker' - related resultsource '$rel_info->{class}' is not registered with this schema\n";
177 11         57 next;
178             };
179              
180             # related sources might be excluded via a {sources} filter or might be views
181 1600 100       7523 next unless exists $table_monikers{$relsource->source_name};
182              
183 1545         5775 my $rel_table = $relsource->name;
184              
185             # FIXME - this isn't the right way to do it, but sqlt does not
186             # support quoting properly to be signaled about this
187 1545 100       4099 $rel_table = $$rel_table if ref $rel_table eq 'SCALAR';
188              
189             # Force the order of @cond to match the order of ->add_columns
190 1545         2414 my $idx;
191 1545         4807 my %other_columns_idx = map {'foreign.'.$_ => ++$idx } $relsource->columns;
  6023         18524  
192              
193 1545         3777 for ( keys %{$rel_info->{cond}} ) {
  1545         6048  
194 1734 50       4828 unless (exists $other_columns_idx{$_}) {
195 0         0 carp "Ignoring relationship '$rel' on '$moniker' - related resultsource '@{[ $relsource->source_name ]}' does not contain one of the specified columns: '$_'\n";
  0         0  
196 0         0 next REL;
197             }
198             }
199              
200 1545         3027 my @cond = sort { $other_columns_idx{$a} <=> $other_columns_idx{$b} } keys(%{$rel_info->{cond}});
  264         738  
  1545         5166  
201              
202             # Get the key information, mapping off the foreign/self markers
203 1545         3364 my @refkeys = map {/^\w+\.(\w+)$/} @cond;
  1734         9490  
204 1545         3341 my @keys = map {$rel_info->{cond}->{$_} =~ /^\w+\.(\w+)$/} @cond;
  1734         7849  
205              
206             # determine if this relationship is a self.fk => foreign.pk (i.e. belongs_to)
207 1545         2732 my $fk_constraint;
208              
209             #first it can be specified explicitly
210 1545 100 66     7680 if ( exists $rel_info->{attrs}{is_foreign_key_constraint} ) {
    100          
211 763         1997 $fk_constraint = $rel_info->{attrs}{is_foreign_key_constraint};
212             }
213             # it can not be multi
214             elsif ( $rel_info->{attrs}{accessor}
215             && $rel_info->{attrs}{accessor} eq 'multi' ) {
216 655         1407 $fk_constraint = 0;
217             }
218             # if indeed single, check if all self.columns are our primary keys.
219             # this is supposed to indicate a has_one/might_have...
220             # where's the introspection!!?? :)
221             else {
222 127         542 $fk_constraint = not $source->_compare_relationship_keys(\@keys, \@primary);
223             }
224              
225 1545         2441 my ($otherrelname, $otherrelationship) = %{ $source->reverse_relationship_info($rel) };
  1545         4931  
226              
227 1545         3640 my $cascade;
228 1545         3403 for my $c (qw/delete update/) {
229 3090 100 100     17917 if (exists $rel_info->{attrs}{"on_$c"}) {
    100          
    100          
230 277 100       722 if ($fk_constraint) {
    50          
231 275         970 $cascade->{$c} = $rel_info->{attrs}{"on_$c"};
232             }
233             elsif ( $rel_info->{attrs}{"on_$c"} ) {
234 2         25 carp "SQLT attribute 'on_$c' was supplied for relationship '$moniker/$rel', which does not appear to be a foreign constraint. "
235             . "If you are sure that SQLT must generate a constraint for this relationship, add 'is_foreign_key_constraint => 1' to the attributes.\n";
236             }
237             }
238             elsif (defined $otherrelationship and $otherrelationship->{attrs}{$c eq 'update' ? 'cascade_copy' : 'cascade_delete'}) {
239 934         2877 $cascade->{$c} = 'CASCADE';
240             }
241             }
242              
243 1545 50       3711 if($rel_table) {
244             # Constraints are added only if applicable
245 1545 100       6350 next unless $fk_constraint;
246              
247             # Make sure we don't create the same foreign key constraint twice
248 721         2725 my $key_test = join("\x00", sort @keys);
249 721 100       3298 next if $created_FK_rels{$rel_table}->{$key_test};
250              
251 635 50       1670 if (scalar(@keys)) {
252 635         1683 $created_FK_rels{$rel_table}->{$key_test} = 1;
253              
254 635         1414 my $is_deferrable = $rel_info->{attrs}{is_deferrable};
255              
256             # calculate dependencies: do not consider deferrable constraints and
257             # self-references for dependency calculations
258 635 100 100     3187 if (! $is_deferrable and $rel_table ne $table_name) {
259 572         2159 $tables{$table_name}{foreign_table_deps}{$rel_table}++;
260             }
261              
262             # trim schema before generating constraint/index names
263 635         1952 (my $table_abbrev = $table_name) =~ s/ ^ [^\.]+ \. //x;
264              
265             $table->add_constraint(
266             type => 'foreign_key',
267             name => join('_', $table_abbrev, 'fk', @keys),
268             fields => \@keys,
269             reference_fields => \@refkeys,
270             reference_table => $rel_table,
271             on_delete => uc ($cascade->{delete} || ''),
272 635 100 100     7693 on_update => uc ($cascade->{update} || ''),
      100        
273             (defined $is_deferrable ? ( deferrable => $is_deferrable ) : ()),
274             );
275              
276             # global parser_args add_fk_index param can be overridden on the rel def
277 635 100       742597 my $add_fk_index_rel = (exists $rel_info->{attrs}{add_fk_index}) ? $rel_info->{attrs}{add_fk_index} : $add_fk_index;
278              
279             # Check that we do not create an index identical to the PK index
280             # (some RDBMS croak on this, and it generally doesn't make much sense)
281             # NOTE: we do not sort the key columns because the order of
282             # columns is important for indexes and two indexes with the
283             # same cols but different order are allowed and sometimes
284             # needed
285 635 100       3488 next if join("\x00", @keys) eq join("\x00", @primary);
286              
287 572 100       1795 if ($add_fk_index_rel) {
288 525         1435 (my $idx_name = $table_name) =~ s/ ^ [^\.]+ \. //x;
289 525         3011 my $index = $table->add_index(
290             name => join('_', $table_abbrev, 'idx', @keys),
291             fields => \@keys,
292             type => 'NORMAL',
293             );
294             }
295             }
296             }
297             }
298              
299             }
300              
301             # attach the tables to the schema in dependency order
302             my $dependencies = {
303 23         565 map { $_ => _resolve_deps ($_, \%tables) } (keys %tables)
  866         1606  
304             };
305              
306 23         314 for my $table (sort
307             {
308 3641 50       4558 keys %{$dependencies->{$a} || {} } <=> keys %{ $dependencies->{$b} || {} }
  3641 50       6485  
  3641 50       7878  
309             ||
310             $a cmp $b
311             }
312             (keys %tables)
313             ) {
314 866         270329 $schema->add_table ($tables{$table}{object});
315 866         344408 $tables{$table}{source} -> _invoke_sqlt_deploy_hook( $tables{$table}{object} );
316              
317             # the hook might have already removed the table
318 866 50 66     20850 if ($schema->get_table($table) && $table =~ /^ \s* \( \s* SELECT \s+/ix) {
319 0         0 carp <<'EOW';
320              
321             Custom SQL through ->name(\'( SELECT ...') is DEPRECATED, for more details see
322             "Arbitrary SQL through a custom ResultSource" in DBIx::Class::Manual::Cookbook
323             or http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class/Manual/Cookbook.pod
324              
325             EOW
326              
327             # remove the table as there is no way someone might want to
328             # actually deploy this
329 0         0 $schema->drop_table ($table);
330             }
331             }
332              
333 23         7603 my %views;
334 23         107 my @views = map { $dbicschema->source($_) } keys %view_monikers;
  22         199  
335              
336             my $view_dependencies = {
337             map {
338 23         85 $_ => _resolve_deps( $dbicschema->source($_), \%view_monikers )
  22         90  
339             } ( keys %view_monikers )
340             };
341              
342             my @view_sources =
343             sort {
344 1 50       9 keys %{ $view_dependencies->{ $a->source_name } || {} } <=>
345 1 50       3 keys %{ $view_dependencies->{ $b->source_name } || {} }
  1 0       17  
346             || $a->source_name cmp $b->source_name
347             }
348 23         88 map { $dbicschema->source($_) }
  22         212  
349             keys %view_monikers;
350              
351 23         95 foreach my $source (@view_sources)
352             {
353 21         105 my $view_name = $source->name;
354              
355             # FIXME - this isn't the right way to do it, but sqlt does not
356             # support quoting properly to be signaled about this
357 21 50       86 $view_name = $$view_name if ref $view_name eq 'SCALAR';
358              
359             # Skip custom query sources
360 21 50       76 next if ref $view_name;
361              
362             # Its possible to have multiple DBIC source using same table
363 21 50       107 next if $views{$view_name}++;
364              
365 21 100       126 $dbicschema->throw_exception ("view $view_name is missing a view_definition")
366             unless $source->view_definition;
367              
368 20   33     141 my $view = $schema->add_view (
369             name => $view_name,
370             fields => [ $source->columns ],
371             $source->view_definition ? ( 'sql' => $source->view_definition ) : ()
372             ) || $dbicschema->throw_exception ($schema->error);
373              
374 20         13330 $source->_invoke_sqlt_deploy_hook($view);
375             }
376              
377              
378 22 100       198 if ($dbicschema->can('sqlt_deploy_hook')) {
379 21         122 $dbicschema->sqlt_deploy_hook($schema);
380             }
381              
382 22         3933 return 1;
383             }
384              
385             #
386             # Quick and dirty dependency graph calculator
387             #
388             sub _resolve_deps {
389 1942     1942   3221 my ( $question, $answers, $seen ) = @_;
390 1942         2958 my $ret = {};
391 1942   100     5056 $seen ||= {};
392 1942         2585 my @deps;
393              
394             # copy and bump all deps by one (so we can reconstruct the chain)
395 1942         3561 my %seen = map { $_ => $seen->{$_} + 1 } ( keys %$seen );
  1764         3737  
396 1942 100 66     4863 if ( blessed($question)
397             && $question->isa('DBIx::Class::ResultSource::View') )
398             {
399 22         500 $seen{ $question->result_class } = 1;
400 22         64 @deps = keys %{ $question->{deploy_depends_on} };
  22         157  
401             }
402             else {
403 1920         3118 $seen{$question} = 1;
404 1920         2466 @deps = keys %{ $answers->{$question}{foreign_table_deps} };
  1920         5202  
405             }
406              
407 1942         3246 for my $dep (@deps) {
408 1310 100       2369 if ( $seen->{$dep} ) {
409 256         731 return {};
410             }
411 1054         1409 my $next_dep;
412              
413 1054 50 33     2325 if ( blessed($question)
414             && $question->isa('DBIx::Class::ResultSource::View') )
415             {
416 6     6   54 no warnings 'uninitialized';
  6         15  
  6         1532  
417             my ($next_dep_source_name) =
418             grep {
419 0 0       0 $question->schema->source($_)->result_class eq $dep
420             && !( $question->schema->source($_)
421             ->isa('DBIx::Class::ResultSource::Table') )
422 0         0 } @{ [ $question->schema->sources ] };
  0         0  
423 0 0       0 return {} unless $next_dep_source_name;
424 0         0 $next_dep = $question->schema->source($next_dep_source_name);
425             }
426             else {
427 1054         1457 $next_dep = $dep;
428             }
429 1054         1879 my $subdeps = _resolve_deps( $next_dep, $answers, \%seen );
430 1054         2530 $ret->{$_} += $subdeps->{$_} for ( keys %$subdeps );
431 1054         2328 ++$ret->{$dep};
432             }
433 1686         4173 return $ret;
434             }
435              
436             1;
437              
438             =head1 NAME
439              
440             SQL::Translator::Parser::DBIx::Class - Create a SQL::Translator schema
441             from a DBIx::Class::Schema instance
442              
443             =head1 SYNOPSIS
444              
445             ## Via DBIx::Class
446             use MyApp::Schema;
447             my $schema = MyApp::Schema->connect("dbi:SQLite:something.db");
448             $schema->create_ddl_dir();
449             ## or
450             $schema->deploy();
451              
452             ## Standalone
453             use MyApp::Schema;
454             use SQL::Translator;
455              
456             my $schema = MyApp::Schema->connect;
457             my $trans = SQL::Translator->new (
458             parser => 'SQL::Translator::Parser::DBIx::Class',
459             parser_args => {
460             dbic_schema => $schema,
461             add_fk_index => 0,
462             sources => [qw/
463             Artist
464             CD
465             /],
466             },
467             producer => 'SQLite',
468             ) or die SQL::Translator->error;
469             my $out = $trans->translate() or die $trans->error;
470              
471             =head1 DESCRIPTION
472              
473             This class requires L<SQL::Translator> installed to work.
474              
475             C<SQL::Translator::Parser::DBIx::Class> reads a DBIx::Class schema,
476             interrogates the columns, and stuffs it all in an $sqlt_schema object.
477              
478             Its primary use is in deploying database layouts described as a set
479             of L<DBIx::Class> classes, to a database. To do this, see
480             L<DBIx::Class::Schema/deploy>.
481              
482             This can also be achieved by having DBIx::Class export the schema as a
483             set of SQL files ready for import into your database, or passed to
484             other machines that need to have your application installed but don't
485             have SQL::Translator installed. To do this see
486             L<DBIx::Class::Schema/create_ddl_dir>.
487              
488             =head1 PARSER OPTIONS
489              
490             =head2 dbic_schema
491              
492             The DBIx::Class schema (either an instance or a class name) to be parsed.
493             This argument is in fact optional - instead one can supply it later at
494             translation time as an argument to L<SQL::Translator/translate>. In
495             other words both of the following invocations are valid and will produce
496             conceptually identical output:
497              
498             my $yaml = SQL::Translator->new(
499             parser => 'SQL::Translator::Parser::DBIx::Class',
500             parser_args => {
501             dbic_schema => $schema,
502             },
503             producer => 'SQL::Translator::Producer::YAML',
504             )->translate;
505              
506             my $yaml = SQL::Translator->new(
507             parser => 'SQL::Translator::Parser::DBIx::Class',
508             producer => 'SQL::Translator::Producer::YAML',
509             )->translate(data => $schema);
510              
511             =head2 add_fk_index
512              
513             Create an index for each foreign key.
514             Enabled by default, as having indexed foreign key columns is normally the
515             sensible thing to do.
516              
517             =head2 sources
518              
519             =over 4
520              
521             =item Arguments: \@class_names
522              
523             =back
524              
525             Limit the amount of parsed sources by supplying an explicit list of source names.
526              
527             =head1 SEE ALSO
528              
529             L<SQL::Translator>, L<DBIx::Class::Schema>
530              
531             =head1 FURTHER QUESTIONS?
532              
533             Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
534              
535             =head1 COPYRIGHT AND LICENSE
536              
537             This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
538             by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
539             redistribute it and/or modify it under the same terms as the
540             L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.