a8ccc741e9d10fea26dda1e22e712f49420aa360
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Base.pm
1 package DBIx::Class::Schema::Loader::Base;
2
3 use strict;
4 use warnings;
5 use base qw/Class::Accessor::Fast Class::C3::Componentised/;
6 use Class::C3;
7 use Carp::Clan qw/^DBIx::Class/;
8 use DBIx::Class::Schema::Loader::RelBuilder;
9 use Data::Dump qw/ dump /;
10 use POSIX qw//;
11 use File::Spec qw//;
12 use Cwd qw//;
13 use Digest::MD5 qw//;
14 use Lingua::EN::Inflect::Number qw//;
15 use File::Temp qw//;
16 use Class::Unload;
17 require DBIx::Class;
18
19 our $VERSION = '0.04999_10';
20
21 __PACKAGE__->mk_ro_accessors(qw/
22                                 schema
23                                 schema_class
24
25                                 exclude
26                                 constraint
27                                 additional_classes
28                                 additional_base_classes
29                                 left_base_classes
30                                 components
31                                 resultset_components
32                                 skip_relationships
33                                 moniker_map
34                                 inflect_singular
35                                 inflect_plural
36                                 debug
37                                 dump_directory
38                                 dump_overwrite
39                                 really_erase_my_files
40                                 use_namespaces
41                                 result_namespace
42                                 resultset_namespace
43                                 default_resultset_class
44                                 schema_base_class
45                                 result_base_class
46
47                                 db_schema
48                                 _tables
49                                 classes
50                                 monikers
51                              /);
52
53 =head1 NAME
54
55 DBIx::Class::Schema::Loader::Base - Base DBIx::Class::Schema::Loader Implementation.
56
57 =head1 SYNOPSIS
58
59 See L<DBIx::Class::Schema::Loader>
60
61 =head1 DESCRIPTION
62
63 This is the base class for the storage-specific C<DBIx::Class::Schema::*>
64 classes, and implements the common functionality between them.
65
66 =head1 CONSTRUCTOR OPTIONS
67
68 These constructor options are the base options for
69 L<DBIx::Class::Schema::Loader/loader_options>.  Available constructor options are:
70
71 =head2 skip_relationships
72
73 Skip setting up relationships.  The default is to attempt the loading
74 of relationships.
75
76 =head2 debug
77
78 If set to true, each constructive L<DBIx::Class> statement the loader
79 decides to execute will be C<warn>-ed before execution.
80
81 =head2 db_schema
82
83 Set the name of the schema to load (schema in the sense that your database
84 vendor means it).  Does not currently support loading more than one schema
85 name.
86
87 =head2 constraint
88
89 Only load tables matching regex.  Best specified as a qr// regex.
90
91 =head2 exclude
92
93 Exclude tables matching regex.  Best specified as a qr// regex.
94
95 =head2 moniker_map
96
97 Overrides the default table name to moniker translation.  Can be either
98 a hashref of table keys and moniker values, or a coderef for a translator
99 function taking a single scalar table name argument and returning
100 a scalar moniker.  If the hash entry does not exist, or the function
101 returns a false value, the code falls back to default behavior
102 for that table name.
103
104 The default behavior is: C<join '', map ucfirst, split /[\W_]+/, lc $table>,
105 which is to say: lowercase everything, split up the table name into chunks
106 anywhere a non-alpha-numeric character occurs, change the case of first letter
107 of each chunk to upper case, and put the chunks back together.  Examples:
108
109     Table Name  | Moniker Name
110     ---------------------------
111     luser       | Luser
112     luser_group | LuserGroup
113     luser-opts  | LuserOpts
114
115 =head2 inflect_plural
116
117 Just like L</moniker_map> above (can be hash/code-ref, falls back to default
118 if hash key does not exist or coderef returns false), but acts as a map
119 for pluralizing relationship names.  The default behavior is to utilize
120 L<Lingua::EN::Inflect::Number/to_PL>.
121
122 =head2 inflect_singular
123
124 As L</inflect_plural> above, but for singularizing relationship names.
125 Default behavior is to utilize L<Lingua::EN::Inflect::Number/to_S>.
126
127 =head2 schema_base_class
128
129 Base class for your schema classes. Defaults to 'DBIx::Class::Schema'.
130
131 =head2 result_base_class
132
133 Base class for your table classes (aka result classes). Defaults to 'DBIx::Class'.
134
135 =head2 additional_base_classes
136
137 List of additional base classes all of your table classes will use.
138
139 =head2 left_base_classes
140
141 List of additional base classes all of your table classes will use
142 that need to be leftmost.
143
144 =head2 additional_classes
145
146 List of additional classes which all of your table classes will use.
147
148 =head2 components
149
150 List of additional components to be loaded into all of your table
151 classes.  A good example would be C<ResultSetManager>.
152
153 =head2 resultset_components
154
155 List of additional ResultSet components to be loaded into your table
156 classes.  A good example would be C<AlwaysRS>.  Component
157 C<ResultSetManager> will be automatically added to the above
158 C<components> list if this option is set.
159
160 =head2 use_namespaces
161
162 Generate result class names suitable for
163 L<DBIx::Class::Schema/load_namespaces> and call that instead of
164 L<DBIx::Class::Schema/load_classes>. When using this option you can also
165 specify any of the options for C<load_namespaces> (i.e. C<result_namespace>,
166 C<resultset_namespace>, C<default_resultset_class>), and they will be added
167 to the call (and the generated result class names adjusted appropriately).
168
169 =head2 dump_directory
170
171 This option is designed to be a tool to help you transition from this
172 loader to a manually-defined schema when you decide it's time to do so.
173
174 The value of this option is a perl libdir pathname.  Within
175 that directory this module will create a baseline manual
176 L<DBIx::Class::Schema> module set, based on what it creates at runtime
177 in memory.
178
179 The created schema class will have the same classname as the one on
180 which you are setting this option (and the ResultSource classes will be
181 based on this name as well).
182
183 Normally you wouldn't hard-code this setting in your schema class, as it
184 is meant for one-time manual usage.
185
186 See L<DBIx::Class::Schema::Loader/dump_to_dir> for examples of the
187 recommended way to access this functionality.
188
189 =head2 dump_overwrite
190
191 Deprecated.  See L</really_erase_my_files> below, which does *not* mean
192 the same thing as the old C<dump_overwrite> setting from previous releases.
193
194 =head2 really_erase_my_files
195
196 Default false.  If true, Loader will unconditionally delete any existing
197 files before creating the new ones from scratch when dumping a schema to disk.
198
199 The default behavior is instead to only replace the top portion of the
200 file, up to and including the final stanza which contains
201 C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!>
202 leaving any customizations you placed after that as they were.
203
204 When C<really_erase_my_files> is not set, if the output file already exists,
205 but the aforementioned final stanza is not found, or the checksum
206 contained there does not match the generated contents, Loader will
207 croak and not touch the file.
208
209 You should really be using version control on your schema classes (and all
210 of the rest of your code for that matter).  Don't blame me if a bug in this
211 code wipes something out when it shouldn't have, you've been warned.
212
213 =head1 METHODS
214
215 None of these methods are intended for direct invocation by regular
216 users of L<DBIx::Class::Schema::Loader>.  Anything you can find here
217 can also be found via standard L<DBIx::Class::Schema> methods somehow.
218
219 =cut
220
221 # ensure that a peice of object data is a valid arrayref, creating
222 # an empty one or encapsulating whatever's there.
223 sub _ensure_arrayref {
224     my $self = shift;
225
226     foreach (@_) {
227         $self->{$_} ||= [];
228         $self->{$_} = [ $self->{$_} ]
229             unless ref $self->{$_} eq 'ARRAY';
230     }
231 }
232
233 =head2 new
234
235 Constructor for L<DBIx::Class::Schema::Loader::Base>, used internally
236 by L<DBIx::Class::Schema::Loader>.
237
238 =cut
239
240 sub new {
241     my ( $class, %args ) = @_;
242
243     my $self = { %args };
244
245     bless $self => $class;
246
247     $self->_ensure_arrayref(qw/additional_classes
248                                additional_base_classes
249                                left_base_classes
250                                components
251                                resultset_components
252                               /);
253
254     push(@{$self->{components}}, 'ResultSetManager')
255         if @{$self->{resultset_components}};
256
257     $self->{monikers} = {};
258     $self->{classes} = {};
259
260     $self->{schema_class} ||= ( ref $self->{schema} || $self->{schema} );
261     $self->{schema} ||= $self->{schema_class};
262
263     croak "dump_overwrite is deprecated.  Please read the"
264         . " DBIx::Class::Schema::Loader::Base documentation"
265             if $self->{dump_overwrite};
266
267     $self->{dynamic} = ! $self->{dump_directory};
268     $self->{temp_directory} ||= File::Temp::tempdir( 'dbicXXXX',
269                                                      TMPDIR  => 1,
270                                                      CLEANUP => 1,
271                                                    );
272
273     $self->{dump_directory} ||= $self->{temp_directory};
274
275     $self->{relbuilder} = DBIx::Class::Schema::Loader::RelBuilder->new(
276         $self->schema, $self->inflect_plural, $self->inflect_singular
277     ) if !$self->{skip_relationships};
278
279     $self->_check_back_compat;
280
281     $self;
282 }
283
284 sub _check_back_compat {
285     my ($self) = @_;
286
287     my $filename = $self->_get_dump_filename($self->schema_class);
288     return unless -e $filename;
289
290     open(my $fh, '<', $filename)
291         or croak "Cannot open '$filename' for reading: $!";
292
293     while (<$fh>) {
294         if (/^# Created by DBIx::Class::Schema::Loader (v\d+)\.(\d+)/) {
295             my $ver = "${1}_${2}";
296             while (1) {
297                 my $compat_class = "DBIx::Class::Schema::Loader::Compat::${ver}";
298                 if ($self->load_optional_class($compat_class)) {
299                     no strict 'refs';
300                     my $class = ref $self || $self;
301                     unshift @{"${class}::ISA"}, $compat_class;
302                     last;
303                 }
304                 $ver =~ s/\d\z// or last;
305             }
306             last;
307         }
308     }
309     close $fh;
310 }
311
312 sub _find_file_in_inc {
313     my ($self, $file) = @_;
314
315     foreach my $prefix (@INC) {
316         my $fullpath = File::Spec->catfile($prefix, $file);
317         return $fullpath if -f $fullpath
318             and Cwd::abs_path($fullpath) ne
319                 Cwd::abs_path(File::Spec->catfile($self->dump_directory, $file)) || '';
320     }
321
322     return;
323 }
324
325 sub _load_external {
326     my ($self, $class) = @_;
327
328     my $class_path = $class;
329     $class_path =~ s{::}{/}g;
330     $class_path .= '.pm';
331
332     my $real_inc_path = $self->_find_file_in_inc($class_path);
333
334     return if !$real_inc_path;
335
336     # If we make it to here, we loaded an external definition
337     warn qq/# Loaded external class definition for '$class'\n/
338         if $self->debug;
339
340     croak 'Failed to locate actual external module file for '
341           . "'$class'"
342               if !$real_inc_path;
343     open(my $fh, '<', $real_inc_path)
344         or croak "Failed to open '$real_inc_path' for reading: $!";
345     $self->_ext_stmt($class,
346          qq|# These lines were loaded from '$real_inc_path' found in \@INC.\n|
347         .qq|# They are now part of the custom portion of this file\n|
348         .qq|# for you to hand-edit.  If you do not either delete\n|
349         .qq|# this section or remove that file from \@INC, this section\n|
350         .qq|# will be repeated redundantly when you re-create this\n|
351         .qq|# file again via Loader!\n|
352     );
353     while(<$fh>) {
354         chomp;
355         $self->_ext_stmt($class, $_);
356     }
357     $self->_ext_stmt($class,
358         qq|# End of lines loaded from '$real_inc_path' |
359     );
360     close($fh)
361         or croak "Failed to close $real_inc_path: $!";
362 }
363
364 =head2 load
365
366 Does the actual schema-construction work.
367
368 =cut
369
370 sub load {
371     my $self = shift;
372
373     $self->_load_tables($self->_tables_list);
374 }
375
376 =head2 rescan
377
378 Arguments: schema
379
380 Rescan the database for newly added tables.  Does
381 not process drops or changes.  Returns a list of
382 the newly added table monikers.
383
384 The schema argument should be the schema class
385 or object to be affected.  It should probably
386 be derived from the original schema_class used
387 during L</load>.
388
389 =cut
390
391 sub rescan {
392     my ($self, $schema) = @_;
393
394     $self->{schema} = $schema;
395     $self->_relbuilder->{schema} = $schema;
396
397     my @created;
398     my @current = $self->_tables_list;
399     foreach my $table ($self->_tables_list) {
400         if(!exists $self->{_tables}->{$table}) {
401             push(@created, $table);
402         }
403     }
404
405     my $loaded = $self->_load_tables(@created);
406
407     return map { $self->monikers->{$_} } @$loaded;
408 }
409
410 sub _relbuilder {
411     my ($self) = @_;
412     $self->{relbuilder} ||= DBIx::Class::Schema::Loader::RelBuilder->new(
413         $self->schema, $self->inflect_plural, $self->inflect_singular
414     );
415 }
416
417 sub _load_tables {
418     my ($self, @tables) = @_;
419
420     # First, use _tables_list with constraint and exclude
421     #  to get a list of tables to operate on
422
423     my $constraint   = $self->constraint;
424     my $exclude      = $self->exclude;
425
426     @tables = grep { /$constraint/ } @tables if $constraint;
427     @tables = grep { ! /$exclude/ } @tables if $exclude;
428
429     # Save the new tables to the tables list
430     foreach (@tables) {
431         $self->{_tables}->{$_} = 1;
432     }
433
434     $self->_make_src_class($_) for @tables;
435     $self->_setup_src_meta($_) for @tables;
436
437     if(!$self->skip_relationships) {
438         # The relationship loader needs a working schema
439         $self->{quiet} = 1;
440         local $self->{dump_directory} = $self->{temp_directory};
441         $self->_reload_classes(@tables);
442         $self->_load_relationships($_) for @tables;
443         $self->{quiet} = 0;
444
445         # Remove that temp dir from INC so it doesn't get reloaded
446         @INC = grep { $_ ne $self->{dump_directory} } @INC;
447     }
448
449     $self->_load_external($_)
450         for map { $self->classes->{$_} } @tables;
451
452     $self->_reload_classes(@tables);
453
454     # Drop temporary cache
455     delete $self->{_cache};
456
457     return \@tables;
458 }
459
460 sub _reload_classes {
461     my ($self, @tables) = @_;
462
463     $self->_dump_to_dir(map { $self->classes->{$_} } @tables);
464
465     unshift @INC, $self->dump_directory;
466     
467     my @to_register;
468     my %have_source = map { $_ => $self->schema->source($_) }
469         $self->schema->sources;
470
471     for my $table (@tables) {
472         my $moniker = $self->monikers->{$table};
473         my $class = $self->classes->{$table};
474         
475         {
476             no warnings 'redefine';
477             local *Class::C3::reinitialize = sub {};
478             use warnings;
479
480             Class::Unload->unload($class);
481             my ($source, $resultset_class);
482             if (
483                 ($source = $have_source{$moniker})
484                 && ($resultset_class = $source->resultset_class)
485                 && ($resultset_class ne 'DBIx::Class::ResultSet')
486             ) {
487                 my $has_file = Class::Inspector->loaded_filename($resultset_class);
488                 Class::Unload->unload($resultset_class);
489                 $self->ensure_class_loaded($resultset_class) if $has_file;
490             }
491             $self->ensure_class_loaded($class);
492         }
493         push @to_register, [$moniker, $class];
494     }
495
496     Class::C3->reinitialize;
497     for (@to_register) {
498         $self->schema->register_class(@$_);
499     }
500 }
501
502 sub _get_dump_filename {
503     my ($self, $class) = (@_);
504
505     $class =~ s{::}{/}g;
506     return $self->dump_directory . q{/} . $class . q{.pm};
507 }
508
509 sub _ensure_dump_subdirs {
510     my ($self, $class) = (@_);
511
512     my @name_parts = split(/::/, $class);
513     pop @name_parts; # we don't care about the very last element,
514                      # which is a filename
515
516     my $dir = $self->dump_directory;
517     while (1) {
518         if(!-d $dir) {
519             mkdir($dir) or croak "mkdir('$dir') failed: $!";
520         }
521         last if !@name_parts;
522         $dir = File::Spec->catdir($dir, shift @name_parts);
523     }
524 }
525
526 sub _dump_to_dir {
527     my ($self, @classes) = @_;
528
529     my $schema_class = $self->schema_class;
530     my $schema_base_class = $self->schema_base_class || 'DBIx::Class::Schema';
531
532     my $target_dir = $self->dump_directory;
533     warn "Dumping manual schema for $schema_class to directory $target_dir ...\n"
534         unless $self->{dynamic} or $self->{quiet};
535
536     my $schema_text =
537           qq|package $schema_class;\n\n|
538         . qq|# Created by DBIx::Class::Schema::Loader\n|
539         . qq|# DO NOT MODIFY THE FIRST PART OF THIS FILE\n\n|
540         . qq|use strict;\nuse warnings;\n\n|
541         . qq|use base '$schema_base_class';\n\n|;
542
543     if ($self->use_namespaces) {
544         $schema_text .= qq|__PACKAGE__->load_namespaces|;
545         my $namespace_options;
546         for my $attr (qw(result_namespace
547                          resultset_namespace
548                          default_resultset_class)) {
549             if ($self->$attr) {
550                 $namespace_options .= qq|    $attr => '| . $self->$attr . qq|',\n|
551             }
552         }
553         $schema_text .= qq|(\n$namespace_options)| if $namespace_options;
554         $schema_text .= qq|;\n|;
555     }
556     else {
557         $schema_text .= qq|__PACKAGE__->load_classes;\n|;
558     }
559
560     $self->_write_classfile($schema_class, $schema_text);
561
562     my $result_base_class = $self->result_base_class || 'DBIx::Class';
563
564     foreach my $src_class (@classes) {
565         my $src_text = 
566               qq|package $src_class;\n\n|
567             . qq|# Created by DBIx::Class::Schema::Loader\n|
568             . qq|# DO NOT MODIFY THE FIRST PART OF THIS FILE\n\n|
569             . qq|use strict;\nuse warnings;\n\n|
570             . qq|use base '$result_base_class';\n\n|;
571
572         $self->_write_classfile($src_class, $src_text);
573     }
574
575     warn "Schema dump completed.\n" unless $self->{dynamic} or $self->{quiet};
576
577 }
578
579 sub _sig_comment {
580     my ($self, $version, $ts) = @_;
581     return qq|\n\n# Created by DBIx::Class::Schema::Loader|
582          . qq| v| . $version
583          . q| @ | . $ts 
584          . qq|\n# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:|;
585 }
586
587 sub _write_classfile {
588     my ($self, $class, $text) = @_;
589
590     my $filename = $self->_get_dump_filename($class);
591     $self->_ensure_dump_subdirs($class);
592
593     if (-f $filename && $self->really_erase_my_files) {
594         warn "Deleting existing file '$filename' due to "
595             . "'really_erase_my_files' setting\n" unless $self->{quiet};
596         unlink($filename);
597     }    
598
599     my ($custom_content, $old_md5, $old_ver, $old_ts) = $self->_get_custom_content($class, $filename);
600
601     $text .= qq|$_\n|
602         for @{$self->{_dump_storage}->{$class} || []};
603
604     # Check and see if the dump is infact differnt
605
606     my $compare_to;
607     if ($old_md5) {
608       $compare_to = $text . $self->_sig_comment($old_ver, $old_ts);
609       
610
611       if (Digest::MD5::md5_base64($compare_to) eq $old_md5) {
612         return;
613       }
614     }
615
616     $text .= $self->_sig_comment(
617       $DBIx::Class::Schema::Loader::VERSION, 
618       POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
619     );
620
621     open(my $fh, '>', $filename)
622         or croak "Cannot open '$filename' for writing: $!";
623
624     # Write the top half and its MD5 sum
625     print $fh $text . Digest::MD5::md5_base64($text) . "\n";
626
627     # Write out anything loaded via external partial class file in @INC
628     print $fh qq|$_\n|
629         for @{$self->{_ext_storage}->{$class} || []};
630
631     # Write out any custom content the user has added
632     print $fh $custom_content;
633
634     close($fh)
635         or croak "Error closing '$filename': $!";
636 }
637
638 sub _default_custom_content {
639     return qq|\n\n# You can replace this text with custom|
640          . qq| content, and it will be preserved on regeneration|
641          . qq|\n1;\n|;
642 }
643
644 sub _get_custom_content {
645     my ($self, $class, $filename) = @_;
646
647     return ($self->_default_custom_content) if ! -f $filename;
648
649     open(my $fh, '<', $filename)
650         or croak "Cannot open '$filename' for reading: $!";
651
652     my $mark_re = 
653         qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\n};
654
655     my $buffer = '';
656     my ($md5, $ts, $ver);
657     while(<$fh>) {
658         if(!$md5 && /$mark_re/) {
659             $md5 = $2;
660             my $line = $1;
661
662             # Pull out the previous version and timestamp
663             ($ver, $ts) = $buffer =~ m/# Created by DBIx::Class::Schema::Loader v(.*?) @ (.*?)$/s;
664
665             $buffer .= $line;
666             croak "Checksum mismatch in '$filename'"
667                 if Digest::MD5::md5_base64($buffer) ne $md5;
668
669             $buffer = '';
670         }
671         else {
672             $buffer .= $_;
673         }
674     }
675
676     croak "Cannot not overwrite '$filename' without 'really_erase_my_files',"
677         . " it does not appear to have been generated by Loader"
678             if !$md5;
679
680     # Default custom content:
681     $buffer ||= $self->_default_custom_content;
682
683     return ($buffer, $md5, $ver, $ts);
684 }
685
686 sub _use {
687     my $self = shift;
688     my $target = shift;
689
690     foreach (@_) {
691         warn "$target: use $_;" if $self->debug;
692         $self->_raw_stmt($target, "use $_;");
693     }
694 }
695
696 sub _inject {
697     my $self = shift;
698     my $target = shift;
699     my $schema_class = $self->schema_class;
700
701     my $blist = join(q{ }, @_);
702     warn "$target: use base qw/ $blist /;" if $self->debug && @_;
703     $self->_raw_stmt($target, "use base qw/ $blist /;") if @_;
704 }
705
706 # Create class with applicable bases, setup monikers, etc
707 sub _make_src_class {
708     my ($self, $table) = @_;
709
710     my $schema       = $self->schema;
711     my $schema_class = $self->schema_class;
712
713     my $table_moniker = $self->_table2moniker($table);
714     my @result_namespace = ($schema_class);
715     if ($self->use_namespaces) {
716         my $result_namespace = $self->result_namespace || 'Result';
717         if ($result_namespace =~ /^\+(.*)/) {
718             # Fully qualified namespace
719             @result_namespace =  ($1)
720         }
721         else {
722             # Relative namespace
723             push @result_namespace, $result_namespace;
724         }
725     }
726     my $table_class = join(q{::}, @result_namespace, $table_moniker);
727
728     my $table_normalized = lc $table;
729     $self->classes->{$table} = $table_class;
730     $self->classes->{$table_normalized} = $table_class;
731     $self->monikers->{$table} = $table_moniker;
732     $self->monikers->{$table_normalized} = $table_moniker;
733
734     $self->_use   ($table_class, @{$self->additional_classes});
735     $self->_inject($table_class, @{$self->left_base_classes});
736
737     $self->_dbic_stmt($table_class, 'load_components', @{$self->components}, 'Core');
738
739     $self->_dbic_stmt($table_class, 'load_resultset_components', @{$self->resultset_components})
740         if @{$self->resultset_components};
741     $self->_inject($table_class, @{$self->additional_base_classes});
742 }
743
744 # Set up metadata (cols, pks, etc)
745 sub _setup_src_meta {
746     my ($self, $table) = @_;
747
748     my $schema       = $self->schema;
749     my $schema_class = $self->schema_class;
750
751     my $table_class = $self->classes->{$table};
752     my $table_moniker = $self->monikers->{$table};
753
754     my $table_name = $table;
755     my $name_sep   = $self->schema->storage->sql_maker->name_sep;
756
757     if ($name_sep && $table_name =~ /\Q$name_sep\E/) {
758         $table_name = \ $self->_quote_table_name($table_name);
759     }
760
761     $self->_dbic_stmt($table_class,'table',$table_name);
762
763     my $cols = $self->_table_columns($table);
764     my $col_info;
765     eval { $col_info = $self->_columns_info_for($table) };
766     if($@) {
767         $self->_dbic_stmt($table_class,'add_columns',@$cols);
768     }
769     else {
770         if ($self->_is_case_sensitive) {
771             for my $col (keys %$col_info) {
772                 $col_info->{$col}{accessor} = lc $col
773                     if $col ne lc($col);
774             }
775         } else {
776             $col_info = { map { lc($_), $col_info->{$_} } keys %$col_info };
777         }
778
779         my $fks = $self->_table_fk_info($table);
780
781         for my $fkdef (@$fks) {
782             for my $col (@{ $fkdef->{local_columns} }) {
783                 $col_info->{$col}{is_foreign_key} = 1;
784             }
785         }
786         $self->_dbic_stmt(
787             $table_class,
788             'add_columns',
789             map { $_, ($col_info->{$_}||{}) } @$cols
790         );
791     }
792
793     my %uniq_tag; # used to eliminate duplicate uniqs
794
795     my $pks = $self->_table_pk_info($table) || [];
796     @$pks ? $self->_dbic_stmt($table_class,'set_primary_key',@$pks)
797           : carp("$table has no primary key");
798     $uniq_tag{ join("\0", @$pks) }++ if @$pks; # pk is a uniq
799
800     my $uniqs = $self->_table_uniq_info($table) || [];
801     for (@$uniqs) {
802         my ($name, $cols) = @$_;
803         next if $uniq_tag{ join("\0", @$cols) }++; # skip duplicates
804         $self->_dbic_stmt($table_class,'add_unique_constraint', $name, $cols);
805     }
806
807 }
808
809 =head2 tables
810
811 Returns a sorted list of loaded tables, using the original database table
812 names.
813
814 =cut
815
816 sub tables {
817     my $self = shift;
818
819     return keys %{$self->_tables};
820 }
821
822 # Make a moniker from a table
823 sub _default_table2moniker {
824     my ($self, $table) = @_;
825
826     return join '', map ucfirst, split /[\W_]+/,
827         Lingua::EN::Inflect::Number::to_S(lc $table);
828 }
829
830 sub _table2moniker {
831     my ( $self, $table ) = @_;
832
833     my $moniker;
834
835     if( ref $self->moniker_map eq 'HASH' ) {
836         $moniker = $self->moniker_map->{$table};
837     }
838     elsif( ref $self->moniker_map eq 'CODE' ) {
839         $moniker = $self->moniker_map->($table);
840     }
841
842     $moniker ||= $self->_default_table2moniker($table);
843
844     return $moniker;
845 }
846
847 sub _load_relationships {
848     my ($self, $table) = @_;
849
850     my $tbl_fk_info = $self->_table_fk_info($table);
851     foreach my $fkdef (@$tbl_fk_info) {
852         $fkdef->{remote_source} =
853             $self->monikers->{delete $fkdef->{remote_table}};
854     }
855     my $tbl_uniq_info = $self->_table_uniq_info($table);
856
857     my $local_moniker = $self->monikers->{$table};
858     my $rel_stmts = $self->_relbuilder->generate_code($local_moniker, $tbl_fk_info, $tbl_uniq_info);
859
860     foreach my $src_class (sort keys %$rel_stmts) {
861         my $src_stmts = $rel_stmts->{$src_class};
862         foreach my $stmt (@$src_stmts) {
863             $self->_dbic_stmt($src_class,$stmt->{method},@{$stmt->{args}});
864         }
865     }
866 }
867
868 # Overload these in driver class:
869
870 # Returns an arrayref of column names
871 sub _table_columns { croak "ABSTRACT METHOD" }
872
873 # Returns arrayref of pk col names
874 sub _table_pk_info { croak "ABSTRACT METHOD" }
875
876 # Returns an arrayref of uniqs [ [ foo => [ col1, col2 ] ], [ bar => [ ... ] ] ]
877 sub _table_uniq_info { croak "ABSTRACT METHOD" }
878
879 # Returns an arrayref of foreign key constraints, each
880 #   being a hashref with 3 keys:
881 #   local_columns (arrayref), remote_columns (arrayref), remote_table
882 sub _table_fk_info { croak "ABSTRACT METHOD" }
883
884 # Returns an array of lower case table names
885 sub _tables_list { croak "ABSTRACT METHOD" }
886
887 # Execute a constructive DBIC class method, with debug/dump_to_dir hooks.
888 sub _dbic_stmt {
889     my $self = shift;
890     my $class = shift;
891     my $method = shift;
892
893     my $args = dump(@_);
894     $args = '(' . $args . ')' if @_ < 2;
895     my $stmt = $method . $args . q{;};
896
897     warn qq|$class\->$stmt\n| if $self->debug;
898     $self->_raw_stmt($class, '__PACKAGE__->' . $stmt);
899 }
900
901 # Store a raw source line for a class (for dumping purposes)
902 sub _raw_stmt {
903     my ($self, $class, $stmt) = @_;
904     push(@{$self->{_dump_storage}->{$class}}, $stmt);
905 }
906
907 # Like above, but separately for the externally loaded stuff
908 sub _ext_stmt {
909     my ($self, $class, $stmt) = @_;
910     push(@{$self->{_ext_storage}->{$class}}, $stmt);
911 }
912
913 sub _quote_table_name {
914     my ($self, $table) = @_;
915
916     my $qt = $self->schema->storage->sql_maker->quote_char;
917
918     return $table unless $qt;
919
920     if (ref $qt) {
921         return $qt->[0] . $table . $qt->[1];
922     }
923
924     return $qt . $table . $qt;
925 }
926
927 sub _is_case_sensitive { 0 }
928
929 =head2 monikers
930
931 Returns a hashref of loaded table to moniker mappings.  There will
932 be two entries for each table, the original name and the "normalized"
933 name, in the case that the two are different (such as databases
934 that like uppercase table names, or preserve your original mixed-case
935 definitions, or what-have-you).
936
937 =head2 classes
938
939 Returns a hashref of table to class mappings.  In some cases it will
940 contain multiple entries per table for the original and normalized table
941 names, as above in L</monikers>.
942
943 =head1 SEE ALSO
944
945 L<DBIx::Class::Schema::Loader>
946
947 =cut
948
949 1;