Only redump the files when something has actually changed
[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_08';
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;
280 }
281
282 sub _find_file_in_inc {
283     my ($self, $file) = @_;
284
285     foreach my $prefix (@INC) {
286         my $fullpath = File::Spec->catfile($prefix, $file);
287         return $fullpath if -f $fullpath
288             and Cwd::abs_path($fullpath) ne
289                 Cwd::abs_path(File::Spec->catfile($self->dump_directory, $file)) || '';
290     }
291
292     return;
293 }
294
295 sub _load_external {
296     my ($self, $class) = @_;
297
298     my $class_path = $class;
299     $class_path =~ s{::}{/}g;
300     $class_path .= '.pm';
301
302     my $real_inc_path = $self->_find_file_in_inc($class_path);
303
304     return if !$real_inc_path;
305
306     # If we make it to here, we loaded an external definition
307     warn qq/# Loaded external class definition for '$class'\n/
308         if $self->debug;
309
310     croak 'Failed to locate actual external module file for '
311           . "'$class'"
312               if !$real_inc_path;
313     open(my $fh, '<', $real_inc_path)
314         or croak "Failed to open '$real_inc_path' for reading: $!";
315     $self->_ext_stmt($class,
316          qq|# These lines were loaded from '$real_inc_path' found in \@INC.\n|
317         .qq|# They are now part of the custom portion of this file\n|
318         .qq|# for you to hand-edit.  If you do not either delete\n|
319         .qq|# this section or remove that file from \@INC, this section\n|
320         .qq|# will be repeated redundantly when you re-create this\n|
321         .qq|# file again via Loader!\n|
322     );
323     while(<$fh>) {
324         chomp;
325         $self->_ext_stmt($class, $_);
326     }
327     $self->_ext_stmt($class,
328         qq|# End of lines loaded from '$real_inc_path' |
329     );
330     close($fh)
331         or croak "Failed to close $real_inc_path: $!";
332 }
333
334 =head2 load
335
336 Does the actual schema-construction work.
337
338 =cut
339
340 sub load {
341     my $self = shift;
342
343     $self->_load_tables($self->_tables_list);
344 }
345
346 =head2 rescan
347
348 Arguments: schema
349
350 Rescan the database for newly added tables.  Does
351 not process drops or changes.  Returns a list of
352 the newly added table monikers.
353
354 The schema argument should be the schema class
355 or object to be affected.  It should probably
356 be derived from the original schema_class used
357 during L</load>.
358
359 =cut
360
361 sub rescan {
362     my ($self, $schema) = @_;
363
364     $self->{schema} = $schema;
365     $self->{relbuilder}{schema} = $schema;
366
367     my @created;
368     my @current = $self->_tables_list;
369     foreach my $table ($self->_tables_list) {
370         if(!exists $self->{_tables}->{$table}) {
371             push(@created, $table);
372         }
373     }
374
375     my $loaded = $self->_load_tables(@created);
376
377     return map { $self->monikers->{$_} } @$loaded;
378 }
379
380 sub _load_tables {
381     my ($self, @tables) = @_;
382
383     # First, use _tables_list with constraint and exclude
384     #  to get a list of tables to operate on
385
386     my $constraint   = $self->constraint;
387     my $exclude      = $self->exclude;
388
389     @tables = grep { /$constraint/ } @tables if $constraint;
390     @tables = grep { ! /$exclude/ } @tables if $exclude;
391
392     # Save the new tables to the tables list
393     foreach (@tables) {
394         $self->{_tables}->{$_} = 1;
395     }
396
397     $self->_make_src_class($_) for @tables;
398     $self->_setup_src_meta($_) for @tables;
399
400     if(!$self->skip_relationships) {
401         # The relationship loader needs a working schema
402         $self->{quiet} = 1;
403         local $self->{dump_directory} = $self->{temp_directory};
404         $self->_reload_classes(@tables);
405         $self->_load_relationships($_) for @tables;
406         $self->{quiet} = 0;
407
408         # Remove that temp dir from INC so it doesn't get reloaded
409         @INC = grep { $_ ne $self->{dump_directory} } @INC;
410     }
411
412     $self->_load_external($_)
413         for map { $self->classes->{$_} } @tables;
414
415     $self->_reload_classes(@tables);
416
417     # Drop temporary cache
418     delete $self->{_cache};
419
420     return \@tables;
421 }
422
423 sub _reload_classes {
424     my ($self, @tables) = @_;
425
426     $self->_dump_to_dir(map { $self->classes->{$_} } @tables);
427
428     unshift @INC, $self->dump_directory;
429     
430     my @to_register;
431     my %have_source = map { $_ => $self->schema->source($_) }
432         $self->schema->sources;
433
434     for my $table (@tables) {
435         my $moniker = $self->monikers->{$table};
436         my $class = $self->classes->{$table};
437         
438         {
439             no warnings 'redefine';
440             local *Class::C3::reinitialize = sub {};
441             use warnings;
442
443             Class::Unload->unload($class);
444             my ($source, $resultset_class);
445             if (
446                 ($source = $have_source{$moniker})
447                 && ($resultset_class = $source->resultset_class)
448                 && ($resultset_class ne 'DBIx::Class::ResultSet')
449             ) {
450                 my $has_file = Class::Inspector->loaded_filename($resultset_class);
451                 Class::Unload->unload($resultset_class);
452                 $self->ensure_class_loaded($resultset_class) if $has_file;
453             }
454             $self->ensure_class_loaded($class);
455         }
456         push @to_register, [$moniker, $class];
457     }
458
459     Class::C3->reinitialize;
460     for (@to_register) {
461         $self->schema->register_class(@$_);
462     }
463 }
464
465 sub _get_dump_filename {
466     my ($self, $class) = (@_);
467
468     $class =~ s{::}{/}g;
469     return $self->dump_directory . q{/} . $class . q{.pm};
470 }
471
472 sub _ensure_dump_subdirs {
473     my ($self, $class) = (@_);
474
475     my @name_parts = split(/::/, $class);
476     pop @name_parts; # we don't care about the very last element,
477                      # which is a filename
478
479     my $dir = $self->dump_directory;
480     while (1) {
481         if(!-d $dir) {
482             mkdir($dir) or croak "mkdir('$dir') failed: $!";
483         }
484         last if !@name_parts;
485         $dir = File::Spec->catdir($dir, shift @name_parts);
486     }
487 }
488
489 sub _dump_to_dir {
490     my ($self, @classes) = @_;
491
492     my $schema_class = $self->schema_class;
493     my $schema_base_class = $self->schema_base_class || 'DBIx::Class::Schema';
494
495     my $target_dir = $self->dump_directory;
496     warn "Dumping manual schema for $schema_class to directory $target_dir ...\n"
497         unless $self->{dynamic} or $self->{quiet};
498
499     my $schema_text =
500           qq|package $schema_class;\n\n|
501         . qq|use strict;\nuse warnings;\n\n|
502         . qq|use base '$schema_base_class';\n\n|;
503
504     if ($self->use_namespaces) {
505         $schema_text .= qq|__PACKAGE__->load_namespaces|;
506         my $namespace_options;
507         for my $attr (qw(result_namespace
508                          resultset_namespace
509                          default_resultset_class)) {
510             if ($self->$attr) {
511                 $namespace_options .= qq|    $attr => '| . $self->$attr . qq|',\n|
512             }
513         }
514         $schema_text .= qq|(\n$namespace_options)| if $namespace_options;
515         $schema_text .= qq|;\n|;
516     }
517     else {
518         $schema_text .= qq|__PACKAGE__->load_classes;\n|;
519     }
520
521     $self->_write_classfile($schema_class, $schema_text);
522
523     my $result_base_class = $self->result_base_class || 'DBIx::Class';
524
525     foreach my $src_class (@classes) {
526         my $src_text = 
527               qq|package $src_class;\n\n|
528             . qq|use strict;\nuse warnings;\n\n|
529             . qq|use base '$result_base_class';\n\n|;
530
531         $self->_write_classfile($src_class, $src_text);
532     }
533
534     warn "Schema dump completed.\n" unless $self->{dynamic} or $self->{quiet};
535
536 }
537
538 sub _sig_comment {
539     my ($self, $version, $ts) = @_;
540     return qq|\n\n# Created by DBIx::Class::Schema::Loader|
541          . qq| v| . $version
542          . q| @ | . $ts 
543          . qq|\n# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:|;
544 }
545
546 sub _write_classfile {
547     my ($self, $class, $text) = @_;
548
549     my $filename = $self->_get_dump_filename($class);
550     $self->_ensure_dump_subdirs($class);
551
552     if (-f $filename && $self->really_erase_my_files) {
553         warn "Deleting existing file '$filename' due to "
554             . "'really_erase_my_files' setting\n" unless $self->{quiet};
555         unlink($filename);
556     }    
557
558     my ($custom_content, $old_md5, $old_ver, $old_ts) = $self->_get_custom_content($class, $filename);
559
560     $text .= qq|$_\n|
561         for @{$self->{_dump_storage}->{$class} || []};
562
563     # Check and see if the dump is infact differnt
564
565     my $compare_to;
566     if ($old_md5) {
567       $compare_to = $text . $self->_sig_comment($old_ver, $old_ts);
568       
569
570       if (Digest::MD5::md5_base64($compare_to) eq $old_md5) {
571         return;
572       }
573     }
574
575     $text .= $self->_sig_comment(
576       $DBIx::Class::Schema::Loader::VERSION, 
577       POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
578     );
579
580     open(my $fh, '>', $filename)
581         or croak "Cannot open '$filename' for writing: $!";
582
583     # Write the top half and its MD5 sum
584     print $fh $text . Digest::MD5::md5_base64($text) . "\n";
585
586     # Write out anything loaded via external partial class file in @INC
587     print $fh qq|$_\n|
588         for @{$self->{_ext_storage}->{$class} || []};
589
590     # Write out any custom content the user has added
591     print $fh $custom_content;
592
593     close($fh)
594         or croak "Error closing '$filename': $!";
595 }
596
597 sub _default_custom_content {
598     return qq|\n\n# You can replace this text with custom|
599          . qq| content, and it will be preserved on regeneration|
600          . qq|\n1;\n|;
601 }
602
603 sub _get_custom_content {
604     my ($self, $class, $filename) = @_;
605
606     return ($self->_default_custom_content) if ! -f $filename;
607
608     open(my $fh, '<', $filename)
609         or croak "Cannot open '$filename' for reading: $!";
610
611     my $mark_re = 
612         qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\n};
613
614     my $buffer = '';
615     my ($md5, $ts, $ver);
616     while(<$fh>) {
617         if(!$md5 && /$mark_re/) {
618             $md5 = $2;
619             my $line = $1;
620
621             # Pull out the previous version and timestamp
622             ($ver, $ts) = $buffer =~ m/# Created by DBIx::Class::Schema::Loader v(.*?) @ (.*?)$/s;
623
624             $buffer .= $line;
625             croak "Checksum mismatch in '$filename'"
626                 if Digest::MD5::md5_base64($buffer) ne $md5;
627
628             $buffer = '';
629         }
630         else {
631             $buffer .= $_;
632         }
633     }
634
635     croak "Cannot not overwrite '$filename' without 'really_erase_my_files',"
636         . " it does not appear to have been generated by Loader"
637             if !$md5;
638
639     # Default custom content:
640     $buffer ||= $self->_default_custom_content;
641
642     return ($buffer, $md5, $ver, $ts);
643 }
644
645 sub _use {
646     my $self = shift;
647     my $target = shift;
648
649     foreach (@_) {
650         warn "$target: use $_;" if $self->debug;
651         $self->_raw_stmt($target, "use $_;");
652     }
653 }
654
655 sub _inject {
656     my $self = shift;
657     my $target = shift;
658     my $schema_class = $self->schema_class;
659
660     my $blist = join(q{ }, @_);
661     warn "$target: use base qw/ $blist /;" if $self->debug && @_;
662     $self->_raw_stmt($target, "use base qw/ $blist /;") if @_;
663 }
664
665 # Create class with applicable bases, setup monikers, etc
666 sub _make_src_class {
667     my ($self, $table) = @_;
668
669     my $schema       = $self->schema;
670     my $schema_class = $self->schema_class;
671
672     my $table_moniker = $self->_table2moniker($table);
673     my @result_namespace = ($schema_class);
674     if ($self->use_namespaces) {
675         my $result_namespace = $self->result_namespace || 'Result';
676         if ($result_namespace =~ /^\+(.*)/) {
677             # Fully qualified namespace
678             @result_namespace =  ($1)
679         }
680         else {
681             # Relative namespace
682             push @result_namespace, $result_namespace;
683         }
684     }
685     my $table_class = join(q{::}, @result_namespace, $table_moniker);
686
687     my $table_normalized = lc $table;
688     $self->classes->{$table} = $table_class;
689     $self->classes->{$table_normalized} = $table_class;
690     $self->monikers->{$table} = $table_moniker;
691     $self->monikers->{$table_normalized} = $table_moniker;
692
693     $self->_use   ($table_class, @{$self->additional_classes});
694     $self->_inject($table_class, @{$self->left_base_classes});
695
696     $self->_dbic_stmt($table_class, 'load_components', @{$self->components}, 'Core');
697
698     $self->_dbic_stmt($table_class, 'load_resultset_components', @{$self->resultset_components})
699         if @{$self->resultset_components};
700     $self->_inject($table_class, @{$self->additional_base_classes});
701 }
702
703 # Set up metadata (cols, pks, etc)
704 sub _setup_src_meta {
705     my ($self, $table) = @_;
706
707     my $schema       = $self->schema;
708     my $schema_class = $self->schema_class;
709
710     my $table_class = $self->classes->{$table};
711     my $table_moniker = $self->monikers->{$table};
712
713     my $table_name = $table;
714     my $name_sep   = $self->schema->storage->sql_maker->name_sep;
715
716     if ($name_sep && $table_name =~ /\Q$name_sep\E/) {
717         $table_name = \ $self->_quote_table_name($table_name);
718     }
719
720     $self->_dbic_stmt($table_class,'table',$table_name);
721
722     my $cols = $self->_table_columns($table);
723     my $col_info;
724     eval { $col_info = $self->_columns_info_for($table) };
725     if($@) {
726         $self->_dbic_stmt($table_class,'add_columns',@$cols);
727     }
728     else {
729         if ($self->_is_case_sensitive) {
730             for my $col (keys %$col_info) {
731                 $col_info->{$col}{accessor} = lc $col
732                     if $col ne lc($col);
733             }
734         } else {
735             $col_info = { map { lc($_), $col_info->{$_} } keys %$col_info };
736         }
737
738         my $fks = $self->_table_fk_info($table);
739
740         for my $fkdef (@$fks) {
741             for my $col (@{ $fkdef->{local_columns} }) {
742                 $col_info->{$col}{is_foreign_key} = 1;
743             }
744         }
745         $self->_dbic_stmt(
746             $table_class,
747             'add_columns',
748             map { $_, ($col_info->{$_}||{}) } @$cols
749         );
750     }
751
752     my %uniq_tag; # used to eliminate duplicate uniqs
753
754     my $pks = $self->_table_pk_info($table) || [];
755     @$pks ? $self->_dbic_stmt($table_class,'set_primary_key',@$pks)
756           : carp("$table has no primary key");
757     $uniq_tag{ join("\0", @$pks) }++ if @$pks; # pk is a uniq
758
759     my $uniqs = $self->_table_uniq_info($table) || [];
760     for (@$uniqs) {
761         my ($name, $cols) = @$_;
762         next if $uniq_tag{ join("\0", @$cols) }++; # skip duplicates
763         $self->_dbic_stmt($table_class,'add_unique_constraint', $name, $cols);
764     }
765
766 }
767
768 =head2 tables
769
770 Returns a sorted list of loaded tables, using the original database table
771 names.
772
773 =cut
774
775 sub tables {
776     my $self = shift;
777
778     return keys %{$self->_tables};
779 }
780
781 # Make a moniker from a table
782 sub _default_table2moniker {
783     my ($self, $table) = @_;
784
785     return join '', map ucfirst, split /[\W_]+/,
786         Lingua::EN::Inflect::Number::to_S(lc $table);
787 }
788
789 sub _table2moniker {
790     my ( $self, $table ) = @_;
791
792     my $moniker;
793
794     if( ref $self->moniker_map eq 'HASH' ) {
795         $moniker = $self->moniker_map->{$table};
796     }
797     elsif( ref $self->moniker_map eq 'CODE' ) {
798         $moniker = $self->moniker_map->($table);
799     }
800
801     $moniker ||= $self->_default_table2moniker($table);
802
803     return $moniker;
804 }
805
806 sub _load_relationships {
807     my ($self, $table) = @_;
808
809     my $tbl_fk_info = $self->_table_fk_info($table);
810     foreach my $fkdef (@$tbl_fk_info) {
811         $fkdef->{remote_source} =
812             $self->monikers->{delete $fkdef->{remote_table}};
813     }
814     my $tbl_uniq_info = $self->_table_uniq_info($table);
815
816     my $local_moniker = $self->monikers->{$table};
817     my $rel_stmts = $self->{relbuilder}->generate_code($local_moniker, $tbl_fk_info, $tbl_uniq_info);
818
819     foreach my $src_class (sort keys %$rel_stmts) {
820         my $src_stmts = $rel_stmts->{$src_class};
821         foreach my $stmt (@$src_stmts) {
822             $self->_dbic_stmt($src_class,$stmt->{method},@{$stmt->{args}});
823         }
824     }
825 }
826
827 # Overload these in driver class:
828
829 # Returns an arrayref of column names
830 sub _table_columns { croak "ABSTRACT METHOD" }
831
832 # Returns arrayref of pk col names
833 sub _table_pk_info { croak "ABSTRACT METHOD" }
834
835 # Returns an arrayref of uniqs [ [ foo => [ col1, col2 ] ], [ bar => [ ... ] ] ]
836 sub _table_uniq_info { croak "ABSTRACT METHOD" }
837
838 # Returns an arrayref of foreign key constraints, each
839 #   being a hashref with 3 keys:
840 #   local_columns (arrayref), remote_columns (arrayref), remote_table
841 sub _table_fk_info { croak "ABSTRACT METHOD" }
842
843 # Returns an array of lower case table names
844 sub _tables_list { croak "ABSTRACT METHOD" }
845
846 # Execute a constructive DBIC class method, with debug/dump_to_dir hooks.
847 sub _dbic_stmt {
848     my $self = shift;
849     my $class = shift;
850     my $method = shift;
851
852     my $args = dump(@_);
853     $args = '(' . $args . ')' if @_ < 2;
854     my $stmt = $method . $args . q{;};
855
856     warn qq|$class\->$stmt\n| if $self->debug;
857     $self->_raw_stmt($class, '__PACKAGE__->' . $stmt);
858 }
859
860 # Store a raw source line for a class (for dumping purposes)
861 sub _raw_stmt {
862     my ($self, $class, $stmt) = @_;
863     push(@{$self->{_dump_storage}->{$class}}, $stmt);
864 }
865
866 # Like above, but separately for the externally loaded stuff
867 sub _ext_stmt {
868     my ($self, $class, $stmt) = @_;
869     push(@{$self->{_ext_storage}->{$class}}, $stmt);
870 }
871
872 sub _quote_table_name {
873     my ($self, $table) = @_;
874
875     my $qt = $self->schema->storage->sql_maker->quote_char;
876
877     return $table unless $qt;
878
879     if (ref $qt) {
880         return $qt->[0] . $table . $qt->[1];
881     }
882
883     return $qt . $table . $qt;
884 }
885
886 sub _is_case_sensitive { 0 }
887
888 =head2 monikers
889
890 Returns a hashref of loaded table to moniker mappings.  There will
891 be two entries for each table, the original name and the "normalized"
892 name, in the case that the two are different (such as databases
893 that like uppercase table names, or preserve your original mixed-case
894 definitions, or what-have-you).
895
896 =head2 classes
897
898 Returns a hashref of table to class mappings.  In some cases it will
899 contain multiple entries per table for the original and normalized table
900 names, as above in L</monikers>.
901
902 =head1 SEE ALSO
903
904 L<DBIx::Class::Schema::Loader>
905
906 =cut
907
908 1;