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