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