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