table/col case fixes, Changes updated, release 0.02006
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Generic.pm
1 package DBIx::Class::Schema::Loader::Generic;
2
3 use strict;
4 use warnings;
5 use base qw/Class::Accessor::Fast/;
6 use Class::C3;
7 use Carp;
8 use Lingua::EN::Inflect;
9 use UNIVERSAL::require;
10 require DBIx::Class;
11
12 # The first group are all arguments which are may be defaulted within,
13 # The last two (classes, monikers) are generated locally:
14
15 __PACKAGE__->mk_ro_accessors(qw/
16                                 schema
17                                 connect_info
18                                 exclude
19                                 constraint
20                                 additional_classes
21                                 additional_base_classes
22                                 left_base_classes
23                                 components
24                                 resultset_components
25                                 relationships
26                                 inflect_map
27                                 moniker_map
28                                 db_schema
29                                 drop_db_schema
30                                 debug
31
32                                 _tables
33                                 classes
34                                 monikers
35                              /);
36
37 =head1 NAME
38
39 DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation.
40
41 =head1 SYNOPSIS
42
43 See L<DBIx::Class::Schema::Loader>
44
45 =head1 DESCRIPTION
46
47 This is the base class for the vendor-specific C<DBIx::Class::Schema::*>
48 classes, and implements the common functionality between them.
49
50 =head1 OPTIONS
51
52 Available constructor options are:
53
54 =head2 connect_info
55
56 Identical to the connect_info arguments to C<connect> and C<connection>
57 that are mentioned in L<DBIx::Class::Schema>.
58
59 An arrayref of connection information.  For DBI-based Schemas,
60 this takes the form:
61
62   connect_info => [ $dsn, $user, $pass, { AutoCommit => 1 } ],
63
64 =head2 additional_base_classes
65
66 List of additional base classes your table classes will use.
67
68 =head2 left_base_classes
69
70 List of additional base classes, that need to be leftmost.
71
72 =head2 additional_classes
73
74 List of additional classes which your table classes will use.
75
76 =head2 components
77
78 List of additional components to be loaded into your table classes.
79 A good example would be C<ResultSetManager>.
80
81 =head2 resultset_components
82
83 List of additional resultset components to be loaded into your table
84 classes.  A good example would be C<AlwaysRS>.  Component
85 C<ResultSetManager> will be automatically added to the above
86 C<components> list if this option is set.
87
88 =head2 constraint
89
90 Only load tables matching regex.
91
92 =head2 exclude
93
94 Exclude tables matching regex.
95
96 =head2 debug
97
98 Enable debug messages.
99
100 =head2 relationships
101
102 Try to automatically detect/setup has_a and has_many relationships.
103
104 =head2 moniker_map
105
106 Overrides the default tablename -> moniker translation.  Can be either
107 a hashref of table => moniker names, or a coderef for a translator
108 function taking a single scalar table name argument and returning
109 a scalar moniker.  If the hash entry does not exist, or the function
110 returns a false/undef value, the code falls back to default behavior
111 for that table name.
112
113 =head2 inflect_map
114
115 Just like L</moniker_map> above, but for inflecting (pluralizing)
116 relationship names.
117
118 =head2 inflect
119
120 Deprecated.  Equivalent to L</inflect_map>, but previously only took
121 a hashref argument, not a coderef.  If you set C<inflect> to anything,
122 that setting will be copied to L</inflect_map>.
123
124 =head2 dsn
125
126 DEPRECATED, use L</connect_info> instead.
127
128 DBI Data Source Name.
129
130 =head2 user
131
132 DEPRECATED, use L</connect_info> instead.
133
134 Username.
135
136 =head2 password
137
138 DEPRECATED, use L</connect_info> instead.
139
140 Password.
141
142 =head2 options
143
144 DEPRECATED, use L</connect_info> instead.
145
146 DBI connection options hashref, like:
147
148   { AutoCommit => 1 }
149
150 =head1 METHODS
151
152 =cut
153
154 # ensure that a peice of object data is a valid arrayref, creating
155 # an empty one or encapsulating whatever's there.
156 sub _ensure_arrayref {
157     my $self = shift;
158
159     foreach (@_) {
160         $self->{$_} ||= [];
161         $self->{$_} = [ $self->{$_} ]
162             unless ref $self->{$_} eq 'ARRAY';
163     }
164 }
165
166 =head2 new
167
168 Constructor for L<DBIx::Class::Schema::Loader::Generic>, used internally
169 by L<DBIx::Class::Schema::Loader>.
170
171 =cut
172
173 sub new {
174     my ( $class, %args ) = @_;
175
176     my $self = { %args };
177
178     bless $self => $class;
179
180     $self->{db_schema}  ||= '';
181     $self->{constraint} ||= '.*';
182     $self->_ensure_arrayref(qw/additional_classes
183                                additional_base_classes
184                                left_base_classes
185                                components
186                                resultset_components
187                                connect_info/);
188
189     push(@{$self->{components}}, 'ResultSetManager')
190         if @{$self->{resultset_components}};
191
192     $self->{monikers} = {};
193     $self->{classes} = {};
194
195     # Support deprecated argument name
196     $self->{inflect_map} ||= $self->{inflect};
197
198     # Support deprecated connect_info args, even mixed
199     #  with a valid partially-filled connect_info
200     $self->{connect_info}->[0] ||= $self->{dsn};
201     $self->{connect_info}->[1] ||= $self->{user};
202     $self->{connect_info}->[2] ||= $self->{password};
203     $self->{connect_info}->[3] ||= $self->{options};
204
205     $self;
206 }
207
208 =head2 load
209
210 Does the actual schema-construction work, used internally by
211 L<DBIx::Class::Schema::Loader> right after object construction.
212
213 =cut
214
215 sub load {
216     my $self = shift;
217
218     $self->schema->connection(@{$self->connect_info});
219
220     warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/
221         if $self->debug;
222
223     $self->_load_classes;
224     $self->_load_relationships if $self->relationships;
225     $self->_load_external;
226
227     warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/
228         if $self->debug;
229     $self->schema->storage->disconnect;
230
231     $self;
232 }
233
234 sub _load_external {
235     my $self = shift;
236
237     foreach my $table_class (map { $self->classes->{$_} } $self->tables) {
238         $table_class->require;
239         if($@ && $@ !~ /^Can't locate /) {
240             croak "Failed to load external class definition"
241                   . "for '$table_class': $@";
242         }
243         elsif(!$@) {
244             warn qq/# Loaded external class definition for '$table_class'\n/
245                 if $self->debug;
246         }
247     }
248 }
249
250 # Overload in your driver class
251 sub _db_classes { croak "ABSTRACT METHOD" }
252
253 # Inflect a relationship name
254 sub _inflect_relname {
255     my ($self, $relname) = @_;
256
257     if( ref $self->{inflect_map} eq 'HASH' ) {
258         return $self->inflect_map->{$relname}
259             if exists $self->inflect_map->{$relname};
260     }
261     elsif( ref $self->{inflect_map} eq 'CODE' ) {
262         my $inflected = $self->inflect_map->($relname);
263         return $inflected if $inflected;
264     }
265
266     return Lingua::EN::Inflect::PL($relname);
267 }
268
269 # Set up a simple relation with just a local col and foreign table
270 sub _make_simple_rel {
271     my ($self, $table, $other, $col) = @_;
272
273     my $table_class = $self->classes->{$table};
274     my $other_class = $self->classes->{$other};
275     my $table_relname = $self->_inflect_relname(lc $table);
276
277     warn qq/\# Belongs_to relationship\n/ if $self->debug;
278     warn qq/$table_class->belongs_to( '$col' => '$other_class' );\n\n/
279       if $self->debug;
280     $table_class->belongs_to( $col => $other_class );
281
282     warn qq/\# Has_many relationship\n/ if $self->debug;
283     warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
284       .  qq/$col);\n\n/
285       if $self->debug;
286
287     $other_class->has_many( $table_relname => $table_class, $col);
288 }
289
290 # not a class method, just a helper for cond_rel XXX
291 sub _stringify_hash {
292     my $href = shift;
293
294     return '{ ' .
295            join(q{, }, map("$_ => $href->{$_}", keys %$href))
296            . ' }';
297 }
298
299 # Set up a complex relation based on a hashref condition
300 sub _make_cond_rel {
301     my ( $self, $table, $other, $cond ) = @_;
302
303     my $table_class = $self->classes->{$table};
304     my $other_class = $self->classes->{$other};
305     my $table_relname = $self->_inflect_relname(lc $table);
306     my $other_relname = lc $other;
307
308     # for single-column case, set the relname to the column name,
309     # to make filter accessors work
310     if(scalar keys %$cond == 1) {
311         my ($col) = keys %$cond;
312         $other_relname = $cond->{$col};
313     }
314
315     my $rev_cond = { reverse %$cond };
316
317     for (keys %$rev_cond) {
318         $rev_cond->{"foreign.$_"} = "self.".$rev_cond->{$_};
319         delete $rev_cond->{$_};
320     }
321
322     my $cond_printable = _stringify_hash($cond)
323         if $self->debug;
324     my $rev_cond_printable = _stringify_hash($rev_cond)
325         if $self->debug;
326
327     warn qq/\# Belongs_to relationship\n/ if $self->debug;
328
329     warn qq/$table_class->belongs_to( '$other_relname' => '$other_class',/
330       .  qq/$cond_printable);\n\n/
331       if $self->debug;
332
333     $table_class->belongs_to( $other_relname => $other_class, $cond);
334
335     warn qq/\# Has_many relationship\n/ if $self->debug;
336
337     warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
338       .  qq/$rev_cond_printable);\n\n/
339       .  qq/);\n\n/
340       if $self->debug;
341
342     $other_class->has_many( $table_relname => $table_class, $rev_cond);
343 }
344
345 sub _use {
346     my $self = shift;
347     my $target = shift;
348
349     foreach (@_) {
350         $_->require or croak ($_ . "->require: $@");
351         eval "package $target; use $_;";
352         croak "use $_: $@" if $@;
353     }
354 }
355
356 sub _inject {
357     my $self = shift;
358     my $target = shift;
359     my $schema = $self->schema;
360
361     foreach (@_) {
362         $_->require or croak ($_ . "->require: $@");
363         $schema->inject_base($target, $_);
364     }
365 }
366
367 # Load and setup classes
368 sub _load_classes {
369     my $self = shift;
370
371     my @db_classes = $self->_db_classes();
372     my $schema     = $self->schema;
373
374     my $constraint = $self->constraint;
375     my $exclude = $self->exclude;
376     my @tables = sort grep
377         { /$constraint/ && (!$exclude || ! /$exclude/) }
378             $self->_tables_list;
379
380     $self->{_tables} = \@tables;
381
382     foreach my $table (@tables) {
383
384         my ($db_schema, $tbl) = split /\./, $table;
385         if($tbl) {
386             $table = $self->drop_db_schema ? $tbl : $table;
387         }
388         my $lc_table = lc $table;
389
390         my $table_moniker = $self->_table2moniker($db_schema, $tbl);
391         my $table_class = $schema . q{::} . $table_moniker;
392
393         { no strict 'refs';
394           @{"${table_class}::ISA"} = qw/DBIx::Class/;
395         }
396         $self->_use   ($table_class, @{$self->additional_classes});
397         $self->_inject($table_class, @{$self->additional_base_classes});
398         $table_class->load_components(@{$self->components}, @db_classes, 'Core');
399         $table_class->load_resultset_components(@{$self->resultset_components})
400             if @{$self->resultset_components};
401         $self->_inject($table_class, @{$self->left_base_classes});
402
403         warn qq/\# Initializing table "$table" as "$table_class"\n/
404             if $self->debug;
405         $table_class->table($table);
406
407         my ( $cols, $pks ) = $self->_table_info($table);
408         carp("$table has no primary key") unless @$pks;
409         $table_class->add_columns(@$cols);
410         $table_class->set_primary_key(@$pks) if @$pks;
411
412         warn qq/$table_class->table('$table');\n/ if $self->debug;
413         my $columns = join "', '", @$cols;
414         warn qq/$table_class->add_columns('$columns')\n/ if $self->debug;
415         my $primaries = join "', '", @$pks;
416         warn qq/$table_class->set_primary_key('$primaries')\n/
417             if $self->debug && @$pks;
418
419         $schema->register_class($table_moniker, $table_class);
420         $self->classes->{$lc_table} = $table_class;
421         $self->monikers->{$lc_table} = $table_moniker;
422         $self->classes->{$table} = $table_class;
423         $self->monikers->{$table} = $table_moniker;
424     }
425 }
426
427 =head2 tables
428
429 Returns a sorted list of loaded tables, using the original database table
430 names.
431
432   my @tables = $schema->loader->tables;
433
434 =cut
435
436 sub tables {
437     my $self = shift;
438
439     return @{$self->_tables};
440 }
441
442 # Find and setup relationships
443 sub _load_relationships {
444     my $self = shift;
445
446     my $dbh = $self->schema->storage->dbh;
447     my $quoter = $dbh->get_info(29) || q{"};
448     foreach my $table ( $self->tables ) {
449         my $rels = {};
450         my $sth = $dbh->foreign_key_info( '',
451             $self->db_schema, '', '', '', $table );
452         next if !$sth;
453         while(my $raw_rel = $sth->fetchrow_hashref) {
454             my $uk_tbl  = $raw_rel->{UK_TABLE_NAME};
455             my $uk_col  = lc $raw_rel->{UK_COLUMN_NAME};
456             my $fk_col  = lc $raw_rel->{FK_COLUMN_NAME};
457             my $relid   = $raw_rel->{UK_NAME};
458             $uk_tbl =~ s/$quoter//g;
459             $uk_col =~ s/$quoter//g;
460             $fk_col =~ s/$quoter//g;
461             $relid  =~ s/$quoter//g;
462             $rels->{$relid}->{tbl} = $uk_tbl;
463             $rels->{$relid}->{cols}->{$uk_col} = $fk_col;
464         }
465
466         foreach my $relid (keys %$rels) {
467             my $reltbl = $rels->{$relid}->{tbl};
468             my $cond   = $rels->{$relid}->{cols};
469             eval { $self->_make_cond_rel( $table, $reltbl, $cond ) };
470               warn qq/\# belongs_to_many failed "$@"\n\n/
471                 if $@ && $self->debug;
472         }
473     }
474 }
475
476 # Make a moniker from a table
477 sub _table2moniker {
478     my ( $self, $db_schema, $table ) = @_;
479
480     my $db_schema_ns;
481
482     if($table) {
483         $db_schema = ucfirst lc $db_schema;
484         $db_schema_ns = $db_schema if(!$self->drop_db_schema);
485     } else {
486         $table = $db_schema;
487     }
488
489     my $moniker;
490
491     if( ref $self->moniker_map eq 'HASH' ) {
492         $moniker = $self->moniker_map->{$table};
493     }
494     elsif( ref $self->moniker_map eq 'CODE' ) {
495         $moniker = $self->moniker_map->($table);
496     }
497
498     $moniker ||= join '', map ucfirst, split /[\W_]+/, lc $table;
499
500     $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker;
501
502     return $moniker;
503 }
504
505 # Overload in driver class
506 sub _tables_list { croak "ABSTRACT METHOD" }
507
508 sub _table_info { croak "ABSTRACT METHOD" }
509
510 =head2 monikers
511
512 Returns a hashref of loaded table-to-moniker mappings for the original
513 database table names.  In cases where the database driver returns table
514 names as uppercase or mixed case, there will also be a duplicate entry
515 here in all lowercase.  Best practice would be to use lower-case table
516 names when accessing this.
517
518   my $monikers = $schema->loader->monikers;
519   my $foo_tbl_moniker = $monikers->{foo_tbl};
520   # -or-
521   my $foo_tbl_moniker = $schema->loader->monikers->{foo_tbl};
522   # $foo_tbl_moniker would look like "FooTbl"
523
524 =head2 classes
525
526 Returns a hashref of table-to-classname mappings for the original database
527 table names.  Same lowercase stuff as above applies here. 
528
529 You probably shouldn't be using this for any normal or simple
530 usage of your Schema.  The usual way to run queries on your tables is via
531 C<$schema-E<gt>resultset('FooTbl')>, where C<FooTbl> is a moniker as
532 returned by C<monikers> above.
533
534   my $classes = $schema->loader->classes;
535   my $foo_tbl_class = $classes->{foo_tbl};
536   # -or-
537   my $foo_tbl_class = $schema->loader->classes->{foo_tbl};
538   # $foo_tbl_class would look like "My::Schema::FooTbl",
539   #   assuming the schema class is "My::Schema"
540
541 =head1 SEE ALSO
542
543 L<DBIx::Class::Schema::Loader>
544
545 =cut
546
547 1;