commenting shortcoming
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / ClassDBI.pm
1 package SQL::Translator::Producer::ClassDBI;
2
3 # -------------------------------------------------------------------
4 # $Id: ClassDBI.pm,v 1.27 2003-07-09 06:09:56 allenday Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2003 Allen Day <allenday@ucla.edu>,
7 #                    Ying Zhang <zyolive@yahoo.com>
8 #
9 # This program is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU General Public License as
11 # published by the Free Software Foundation; version 2.
12 #
13 # This program is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 # 02111-1307  USA
22 # -------------------------------------------------------------------
23
24 use strict;
25 use vars qw[ $VERSION $DEBUG ];
26 $VERSION = sprintf "%d.%02d", q$Revision: 1.27 $ =~ /(\d+)\.(\d+)/;
27 $DEBUG   = 1 unless defined $DEBUG;
28
29 use SQL::Translator::Schema::Constants;
30 use SQL::Translator::Utils qw(header_comment);
31 use Data::Dumper;
32
33 my %CDBI_auto_pkgs = (
34     MySQL      => 'mysql',
35     PostgreSQL => 'Pg',
36     Oracle     => 'Oracle',
37 );
38
39 # -------------------------------------------------------------------
40 sub produce {
41     my $t             = shift;
42         my $create        = undef;
43     local $DEBUG      = $t->debug;
44     my $no_comments   = $t->no_comments;
45     my $schema        = $t->schema;
46     my $args          = $t->producer_args;
47     my $db_user       = $args->{'db_user'} || '';
48     my $db_pass       = $args->{'db_pass'} || '';
49     my $main_pkg_name = $t->format_package_name('DBI');
50     my $header        = header_comment(__PACKAGE__, "# ");
51     my $parser_type   = ( split /::/, $t->parser_type )[-1];
52     my $from          = $CDBI_auto_pkgs{ $parser_type } || '';
53     my $dsn           = $args->{'dsn'} || sprintf( 'dbi:%s:_',
54                             $CDBI_auto_pkgs{ $parser_type }
55                             ? $CDBI_auto_pkgs{ $parser_type } : $parser_type
56                         );
57     my $sep           = '# ' . '-' x 67;
58
59     #
60     # Identify "link tables" (have only PK and FK fields).
61     #
62     my %linkable;
63     my %linktable;
64     foreach my $table ( $schema->get_tables ) {
65         my $is_link = 1;
66         foreach my $field ( $table->get_fields ) {
67             unless ( $field->is_primary_key or $field->is_foreign_key ) {
68                 $is_link = 0; 
69                 last;
70             }
71         }
72
73         next unless $is_link;
74       
75         foreach my $left ( $table->get_fields ) {
76             next unless $left->is_foreign_key;
77             my $lfk           = $left->foreign_key_reference or next;
78             my $lr_table      = $schema->get_table( $lfk->reference_table )
79                                  or next;
80             my $lr_field_name = ($lfk->reference_fields)[0];
81             my $lr_field      = $lr_table->get_field($lr_field_name);
82             next unless $lr_field->is_primary_key;
83
84             foreach my $right ( $table->get_fields ) {
85                 next if $left->name eq $right->name;
86         
87                 my $rfk      = $right->foreign_key_reference or next;
88                 my $rr_table = $schema->get_table( $rfk->reference_table )
89                                or next;
90                 my $rr_field_name = ($rfk->reference_fields)[0];
91                 my $rr_field      = $rr_table->get_field($rr_field_name);
92                 next unless $rr_field->is_primary_key;
93         
94                 $linkable{ $lr_table->name }{ $rr_table->name } = $table;
95                 $linkable{ $rr_table->name }{ $lr_table->name } = $table;
96                 $linktable{ $table->name } = $table;
97             }
98         }
99     }
100
101     #
102     # Iterate over all tables
103     #
104     my ( %packages, $order );
105     for my $table ( $schema->get_tables ) {
106         my $table_name = $table->name or next;
107
108         my $table_pkg_name = $t->format_package_name($table_name);
109         $packages{ $table_pkg_name } = {
110             order     => ++$order,
111             pkg_name  => $table_pkg_name,
112             base      => $main_pkg_name,
113             table     => $table_name,
114         };
115
116         #
117         # Primary key may have a differenct accessor method name
118         #
119         if ( my $pk_xform = $t->format_pk_name ) {
120             if ( my $constraint = $table->primary_key ) {
121                 my $field          = ($constraint->fields)[0];
122                 my $pk_name        = $pk_xform->($table_pkg_name, $field);
123                 
124                 $packages{ $table_pkg_name }{'pk_accessor'} = 
125                     "#\n# Primary key accessor\n#\n".
126                     "sub $pk_name {\n    shift->$field\n}\n\n"
127                 ;
128             }
129         }
130         
131         #
132         # Use foreign keys to set up "has_a/has_many" relationships.
133         #
134         my $is_data = 0;
135         foreach my $field ( $table->get_fields ) {
136             $is_data++ if !$field->is_foreign_key and !$field->is_primary_key;
137             if ( $field->is_foreign_key ) {
138                 my $table_name = $table->name;
139                 my $field_name = $field->name;
140                 my $fk_method  = $t->format_fk_name($table_name, $field_name);
141                 my $fk         = $field->foreign_key_reference;
142                 my $ref_table  = $fk->reference_table;
143                 my $ref_pkg    = $t->format_package_name($ref_table);
144                 my $ref_field  = ($fk->reference_fields)[0];
145
146                 push @{ $packages{ $table_pkg_name }{'has_a'} },
147                     "$table_pkg_name->has_a(\n".
148                     "    $field_name => '$ref_pkg'\n);\n\n".
149                     "sub $fk_method {\n".
150                     "    return shift->$field_name\n}\n\n"
151                 ;
152
153                 #
154                 # If this table "has a" to the other, then it follows 
155                 # that the other table "has many" of this one, right?
156                 #
157                                 # No... there is the possibility of 1-1 cardinality
158                 push @{ $packages{ $ref_pkg }{'has_many'} },
159                     "$ref_pkg->has_many(\n    '${table_name}_${field_name}', ".
160                     "'$table_pkg_name' => '$field_name'\n);\n\n"
161                 ;
162             }
163                 }
164
165          my %linked;
166          if ( $is_data ) {
167              foreach my $link ( keys %{ $linkable{ $table_name } } ) {
168                            my $linkmethodname;
169
170                            # ADD CALLBACK FOR PLURALIZATION MANGLING HERE
171                            if ( my $fk_xform = $t->format_fk_name ){
172                                  $linkmethodname = $fk_xform->($linkable{$table_name}{$link}->name,
173                                    ($schema->get_table($link)->primary_key->fields)[0]).'s';
174                            } else {
175                                  $linkmethodname = $linkable{$table_name}{$link}->name.'_'.
176                                    ($schema->get_table($link)->primary_key->fields)[0].'s';
177                            }
178
179 #$create .= $field->name. "\n";
180 #$create .= $field->foreign_key_reference->reference_table. "\n";
181 #$create .= $linkable{ $table_name }{ $link }->name. "\n";
182 #$create .= $table_name. "\n";
183 #$create .= $link. "\n";
184 #$create .= "***\n\n";
185
186                            my @rk_fields = ();
187                            my @lk_fields = ();
188                            foreach my $field ($linkable{$table_name}{$link}->get_fields){
189                                  next unless $field->is_foreign_key;
190
191                                  next unless(
192                                                          $field->foreign_key_reference->reference_table eq $table_name
193                                                          ||
194                                                          $field->foreign_key_reference->reference_table eq $link
195                                                         );
196                                  push @lk_fields, ($field->foreign_key_reference->reference_fields)[0]
197                                    if $field->foreign_key_reference->reference_table eq $link;
198                                  push @rk_fields, $field->name
199                                    if $field->foreign_key_reference->reference_table eq $table_name;
200                            }
201
202                            #if one possible traversal via link table
203                            if(scalar(@rk_fields) == 1 and scalar(@lk_fields) == 1){
204                                  foreach my $rk_field (@rk_fields){
205                                    push @{ $packages{ $table_pkg_name }{'has_many'} },
206                                          "sub ".$linkmethodname." { my \$self = shift; ".
207                                            "return map \$_->".
208                                                  ($schema->get_table($link)->primary_key->fields)[0].
209                                                    ", \$self->".$linkable{$table_name}{$link}->name.
210                                                          "_".$rk_field." }\n\n";
211                                  }
212                            #else there is more than one way to traverse it.  ack!
213                            #let's treat these types of link tables as a many-to-one (easier)
214                            #
215                            #NOTE: we need to rethink the link method name, as the cardinality
216                            #has shifted on us.
217                            } elsif(scalar(@rk_fields) == 1){
218                                  foreach my $rk_field (@rk_fields){
219                                    push @{ $packages{ $table_pkg_name }{'has_many'} },
220                                          "sub " . $linkable{$table_name}{$link}->name .
221                                            "s { my \$self = shift; return \$self->" .
222                                                  $linkable{$table_name}{$link}->name . "_" .
223                                                    $rk_field . "(\@_) }\n\n";
224                                  }
225                            } elsif(scalar(@lk_fields) == 1){
226                                  #these will be taken care of on the other end...
227                            } else {
228                                  #many many many.  need multiple iterations here, data structure revision
229                                  #to handle N FK sources
230                                  foreach my $rk_field (@rk_fields){
231                                    push @{ $packages{ $table_pkg_name }{'has_many'} },
232                                          "sub " . $linkable{$table_name}{$link}->name . "_" . $rk_field .
233                                            "s { my \$self = shift; return \$self->" .
234                                                  $linkable{$table_name}{$link}->name . "_" .
235                                                    $rk_field . "(\@_) }\n\n";
236                                  }
237                            }
238             }
239         }
240     }
241
242     #
243     # Now build up text of package.
244     #
245     my $base_pkg = sprintf( 'Class::DBI%s', $from ? "::$from" : '' );
246     $create .= join("\n",
247       "package $main_pkg_name;\n",
248       $header,
249       "use strict;",
250       "use base '$base_pkg';\n",
251       "$main_pkg_name->set_db('Main', '$dsn', '$db_user', '$db_pass');\n\n",
252     ); 
253
254     for my $pkg_name ( 
255         sort { $packages{ $a }{'order'} <=> $packages{ $b }{'order'} }
256         keys %packages
257     ) {
258         my $pkg = $packages{ $pkg_name };
259
260         $create .= join("\n",
261             $sep,
262             "package ".$pkg->{'pkg_name'}.";",
263             "use base '".$pkg->{'base'}."';",
264             "use Class::DBI::Pager;\n\n",
265         );    
266
267         if ( $from ) {
268             $create .= 
269                 $pkg->{'pkg_name'}."->set_up_table('".$pkg->{'table'}."');\n\n";
270         }
271         else {
272             my $table       = $schema->get_table( $pkg->{'table'} );
273             my @field_names = map { $_->name } $table->get_fields;
274
275             $create .= join("\n",
276                 $pkg_name."->table('".$pkg->{'table'}."');\n",
277                 $pkg_name."->columns(All => qw/".
278                 join(' ', @field_names)."/);\n\n",
279             );
280         }
281
282         if ( my $pk = $pkg->{'pk_accessor'} ) {
283             $create .= $pk;
284         }
285
286         if ( my @has_a = @{ $pkg->{'has_a'} || [] } ) {
287             $create .= $_ for @has_a;
288         }
289
290         if ( my @has_many = @{ $pkg->{'has_many'} || [] } ) {
291             $create .= $_ for @has_many;
292         }
293     }
294
295     $create .= "1;\n";
296
297     return $create;
298 }
299
300 1;
301
302 # -------------------------------------------------------------------
303
304 =pod
305
306 =head1 NAME
307
308 SQL::Translator::Producer::ClassDBI - create Class::DBI classes from schema
309
310 =head1 SYNOPSIS
311
312 Use this producer as you would any other from SQL::Translator.  See
313 L<SQL::Translator> for details.
314
315 This package utilizes SQL::Translator's formatting methods
316 format_package_name(), format_pk_name(), format_fk_name(), and
317 format_table_name() as it creates classes, one per table in the schema
318 provided.  An additional base class is also created for database connectivity
319 configuration.  See L<Class::DBI> for details on how this works.
320
321 =head1 AUTHORS
322
323 Allen Day E<lt>allenday@ucla.eduE<gt>
324 Ying Zhang E<lt>zyolive@yahoo.comE<gt>,
325 Ken Y. Clark E<lt>kclark@cpan.org<gt>.