release 0.11019
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator.pm
index 7980894..13a0551 100644 (file)
 package SQL::Translator;
 
-# ----------------------------------------------------------------------
-# $Id$
-# ----------------------------------------------------------------------
-# Copyright (C) 2002-4 The SQLFairy Authors
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; version 2.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-# 02111-1307  USA
-# -------------------------------------------------------------------
-
-use strict;
-use vars qw( $VERSION $REVISION $DEFAULT_SUB $DEBUG $ERROR );
-use base 'Class::Base';
-
-require 5.004;
+use Moo;
+our ( $DEFAULT_SUB, $DEBUG, $ERROR );
 
-$VERSION  = '0.09002';
-$REVISION = sprintf "%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/;
+our $VERSION  = '0.11019';
 $DEBUG    = 0 unless defined $DEBUG;
 $ERROR    = "";
 
-use Carp qw(carp);
+use Carp qw(carp croak);
 
 use Data::Dumper;
-use Class::Base;
 use File::Find;
 use File::Spec::Functions qw(catfile);
 use File::Basename qw(dirname);
 use IO::Dir;
+use Sub::Quote qw(quote_sub);
 use SQL::Translator::Producer;
 use SQL::Translator::Schema;
+use SQL::Translator::Utils qw(throw ex2err carp_ro);
 
-# ----------------------------------------------------------------------
-# The default behavior is to "pass through" values (note that the
-# SQL::Translator instance is the first value ($_[0]), and the stuff
-# to be parsed is the second value ($_[1])
-# ----------------------------------------------------------------------
 $DEFAULT_SUB = sub { $_[0]->schema } unless defined $DEFAULT_SUB;
 
-# ----------------------------------------------------------------------
-# init([ARGS])
-#   The constructor.
-#
-#   new takes an optional hash of arguments.  These arguments may
-#   include a parser, specified with the keys "parser" or "from",
-#   and a producer, specified with the keys "producer" or "to".
-#
-#   The values that can be passed as the parser or producer are
-#   given directly to the parser or producer methods, respectively.
-#   See the appropriate method description below for details about
-#   what each expects/accepts.
-# ----------------------------------------------------------------------
-sub init {
-    my ( $self, $config ) = @_;
-    #
-    # Set the parser and producer.
-    #
+with qw(
+    SQL::Translator::Role::Debug
+    SQL::Translator::Role::Error
+    SQL::Translator::Role::BuildArgs
+);
+
+around BUILDARGS => sub {
+    my $orig = shift;
+    my $self = shift;
+    my $config = $self->$orig(@_);
+
     # If a 'parser' or 'from' parameter is passed in, use that as the
     # parser; if a 'producer' or 'to' parameter is passed in, use that
     # as the producer; both default to $DEFAULT_SUB.
-    #
-    $self->parser  ($config->{'parser'}   || $config->{'from'} || $DEFAULT_SUB);
-    $self->producer($config->{'producer'} || $config->{'to'}   || $DEFAULT_SUB);
+    $config->{parser} ||= $config->{from} if defined $config->{from};
+    $config->{producer} ||= $config->{to} if defined $config->{to};
 
-    #
-    # Set up callbacks for formatting of pk,fk,table,package names in producer
-    # MOVED TO PRODUCER ARGS
-    #
-    #$self->format_table_name($config->{'format_table_name'});
-    #$self->format_package_name($config->{'format_package_name'});
-    #$self->format_fk_name($config->{'format_fk_name'});
-    #$self->format_pk_name($config->{'format_pk_name'});
+    $config->{filename} ||= $config->{file} if defined $config->{file};
 
-    #
-    # Set the parser_args and producer_args
-    #
-    for my $pargs ( qw[ parser_args producer_args ] ) {
-        $self->$pargs( $config->{$pargs} ) if defined $config->{ $pargs };
-    }
+    my $quote;
+    if (defined $config->{quote_identifiers}) {
+      $quote = $config->{quote_identifiers};
 
-    #
-    # Initialize the filters.
-    #
-    if ( $config->{filters} && ref $config->{filters} eq "ARRAY" ) {
-        $self->filters( @{$config->{filters}} )
-        || return $self->error('Error inititializing filters: '.$self->error);
+      for (qw/quote_table_names quote_field_names/) {
+        carp "Ignoring deprecated parameter '$_', since 'quote_identifiers' is supplied"
+          if defined $config->{$_}
+      }
     }
-
-    #
-    # Set the data source, if 'filename' or 'file' is provided.
-    #
-    $config->{'filename'} ||= $config->{'file'} || "";
-    $self->filename( $config->{'filename'} ) if $config->{'filename'};
-
-    #
-    # Finally, if there is a 'data' parameter, use that in
-    # preference to filename and file
-    #
-    if ( my $data = $config->{'data'} ) {
-        $self->data( $data );
+    # Legacy one set the other is not
+    elsif (
+      defined $config->{'quote_table_names'}
+        xor
+      defined $config->{'quote_field_names'}
+    ) {
+      if (defined $config->{'quote_table_names'}) {
+        carp "Explicitly disabling the deprecated 'quote_table_names' implies disabling 'quote_identifiers' which in turn implies disabling 'quote_field_names'"
+          unless $config->{'quote_table_names'};
+        $quote = $config->{'quote_table_names'} ? 1 : 0;
+      }
+      else {
+        carp "Explicitly disabling the deprecated 'quote_field_names' implies disabling 'quote_identifiers' which in turn implies disabling 'quote_table_names'"
+          unless $config->{'quote_field_names'};
+        $quote = $config->{'quote_field_names'} ? 1 : 0;
+      }
     }
+    # Legacy both are set
+    elsif(defined $config->{'quote_table_names'}) {
+      croak 'Setting quote_table_names and quote_field_names to conflicting values is no longer supported'
+        if ($config->{'quote_table_names'} xor $config->{'quote_field_names'});
 
-    #
-    # Set various other options.
-    #
-    $self->{'debug'} = defined $config->{'debug'} ? $config->{'debug'} : $DEBUG;
-
-    $self->add_drop_table( $config->{'add_drop_table'} );
-
-    $self->no_comments( $config->{'no_comments'} );
-
-    $self->show_warnings( $config->{'show_warnings'} );
-
-    $self->trace( $config->{'trace'} );
+      $quote = $config->{'quote_table_names'} ? 1 : 0;
+    }
 
-    $self->validate( $config->{'validate'} );
-    
-    $self->quote_table_names( (defined $config->{'quote_table_names'} 
-        ? $config->{'quote_table_names'} : 1) );
-    $self->quote_field_names( (defined $config->{'quote_field_names'} 
-        ? $config->{'quote_field_names'} : 1) );
+    $config->{quote_identifiers} = $quote if defined $quote;
 
-    return $self;
-}
+    return $config;
+};
 
-# ----------------------------------------------------------------------
-# add_drop_table([$bool])
-# ----------------------------------------------------------------------
-sub add_drop_table {
-    my $self = shift;
-    if ( defined (my $arg = shift) ) {
-        $self->{'add_drop_table'} = $arg ? 1 : 0;
+sub BUILD {
+    my ($self) = @_;
+    # Make sure all the tool-related stuff is set up
+    foreach my $tool (qw(producer parser)) {
+        $self->$tool($self->$tool);
     }
-    return $self->{'add_drop_table'} || 0;
 }
 
-# ----------------------------------------------------------------------
-# no_comments([$bool])
-# ----------------------------------------------------------------------
-sub no_comments {
-    my $self = shift;
-    my $arg  = shift;
-    if ( defined $arg ) {
-        $self->{'no_comments'} = $arg ? 1 : 0;
-    }
-    return $self->{'no_comments'} || 0;
-}
+has $_ => (
+    is => 'rw',
+    default => quote_sub(q{ 0 }),
+    coerce => quote_sub(q{ $_[0] ? 1 : 0 }),
+) foreach qw(add_drop_table no_comments show_warnings trace validate);
 
+# quote_identifiers is on by default, use a 0-but-true as indicator
+# so we can allow individual producers to change the default
+has quote_identifiers => (
+    is => 'rw',
+    default => quote_sub(q{ '0E0' }),
+    coerce => quote_sub(q{ $_[0] || 0 }),
+);
 
-# ----------------------------------------------------------------------
-# quote_table_names([$bool])
-# ----------------------------------------------------------------------
 sub quote_table_names {
-    my $self = shift;
-    if ( defined (my $arg = shift) ) {
-        $self->{'quote_table_names'} = $arg ? 1 : 0;
-    }
-    return $self->{'quote_table_names'} || 0;
+    (@_ > 1 and ($_[1] xor $_[0]->quote_identifiers) )
+        ? croak 'Using quote_table_names as a setter is no longer supported'
+        : $_[0]->quote_identifiers;
 }
 
-# ----------------------------------------------------------------------
-# quote_field_names([$bool])
-# ----------------------------------------------------------------------
 sub quote_field_names {
-    my $self = shift;
-    if ( defined (my $arg = shift) ) {
-        $self->{'quote_field_names'} = $arg ? 1 : 0;
-    }
-    return $self->{'quote_field_names'} || 0;
+    (@_ > 1 and ($_[1] xor $_[0]->quote_identifiers) )
+        ? croak 'Using quote_field_names as a setter is no longer supported'
+        : $_[0]->quote_identifiers;
 }
 
-# ----------------------------------------------------------------------
-# producer([$producer_spec])
-#
-# Get or set the producer for the current translator.
-# ----------------------------------------------------------------------
-sub producer {
+after quote_identifiers => sub {
+    if (@_ > 1) {
+        # synchronize for old code reaching directly into guts
+        $_[0]->{quote_table_names}
+            = $_[0]->{quote_field_names}
+                = $_[1] ? 1 : 0;
+    }
+};
+
+has producer => ( is => 'rw', default => sub { $DEFAULT_SUB } );
+
+around producer => sub {
+    my $orig = shift;
     shift->_tool({
-            name => 'producer',
-            path => "SQL::Translator::Producer",
-            default_sub => "produce",
+        orig => $orig,
+        name => 'producer',
+        path => "SQL::Translator::Producer",
+        default_sub => "produce",
     }, @_);
-}
+};
 
-# ----------------------------------------------------------------------
-# producer_type()
-#
-# producer_type is an accessor that allows producer subs to get
-# information about their origin.  This is poptentially important;
-# since all producer subs are called as subroutine references, there is
-# no way for a producer to find out which package the sub lives in
-# originally, for example.
-# ----------------------------------------------------------------------
-sub producer_type { $_[0]->{'producer_type'} }
+has producer_type => ( is => 'rwp', init_arg => undef );
 
-# ----------------------------------------------------------------------
-# producer_args([\%args])
-#
-# Arbitrary name => value pairs of paramters can be passed to a
-# producer using this method.
-#
-# If the first argument passed in is undef, then the hash of arguments
-# is cleared; all subsequent elements are added to the hash of name,
-# value pairs stored as producer_args.
-# ----------------------------------------------------------------------
-sub producer_args { shift->_args("producer", @_); }
+around producer_type => carp_ro('producer_type');
 
-# ----------------------------------------------------------------------
-# parser([$parser_spec])
-# ----------------------------------------------------------------------
-sub parser {
+has producer_args => ( is => 'rw', default => quote_sub(q{ +{} }) );
+
+around producer_args => sub {
+    my $orig = shift;
+    shift->_args($orig, @_);
+};
+
+has parser => ( is => 'rw', default => sub { $DEFAULT_SUB }  );
+
+around parser => sub {
+    my $orig = shift;
     shift->_tool({
+        orig => $orig,
         name => 'parser',
         path => "SQL::Translator::Parser",
         default_sub => "parse",
     }, @_);
-}
-
-sub parser_type { $_[0]->{'parser_type'}; }
-
-sub parser_args { shift->_args("parser", @_); }
+};
+
+has parser_type => ( is => 'rwp', init_arg => undef );
+
+around parser_type => carp_ro('parser_type');
+
+has parser_args => ( is => 'rw', default => quote_sub(q{ +{} }) );
+
+around parser_args => sub {
+    my $orig = shift;
+    shift->_args($orig, @_);
+};
+
+has filters => (
+    is => 'rw',
+    default => quote_sub(q{ [] }),
+    coerce => sub {
+        my @filters;
+        # Set. Convert args to list of [\&code,@args]
+        foreach (@{$_[0]||[]}) {
+            my ($filt,@args) = ref($_) eq "ARRAY" ? @$_ : $_;
+            if ( isa($filt,"CODE") ) {
+                push @filters, [$filt,@args];
+                next;
+            }
+            else {
+                __PACKAGE__->debug("Adding $filt filter. Args:".Dumper(\@args)."\n");
+                $filt = _load_sub("$filt\::filter", "SQL::Translator::Filter")
+                    || throw(__PACKAGE__->error);
+                push @filters, [$filt,@args];
+            }
+        }
+        return \@filters;
+    },
+);
 
-# ----------------------------------------------------------------------
-# e.g.
-#   $sqlt->filters => [
-#       sub { },
-#       [ "NormalizeNames", field => "lc", tabel => "ucfirst" ],
-#       [
-#           "DataTypeMap",
-#           "TEXT" => "BIGTEXT",
-#       ],
-#   ],
-# ----------------------------------------------------------------------
-sub filters {
+around filters => sub {
+    my $orig = shift;
     my $self = shift;
-    my $filters = $self->{filters} ||= [];
-    return @$filters unless @_;
-
-    # Set. Convert args to list of [\&code,@args]
-    foreach (@_) {
-        my ($filt,@args) = ref($_) eq "ARRAY" ? @$_ : $_;
-        if ( isa($filt,"CODE") ) {
-            push @$filters, [$filt,@args];
-            next;
+    return @{$self->$orig([@{$self->$orig}, @_])} if @_;
+    return @{$self->$orig};
+};
+
+has filename => (
+    is => 'rw',
+    isa => sub {
+        foreach my $filename (ref($_[0]) eq 'ARRAY' ? @{$_[0]} : $_[0]) {
+            if (-d $filename) {
+                throw("Cannot use directory '$filename' as input source");
+            }
+            elsif (not -f _ && -r _) {
+                throw("Cannot use '$filename' as input source: ".
+                      "file does not exist or is not readable.");
+            }
         }
-        else {
-            $self->debug("Adding $filt filter. Args:".Dumper(\@args)."\n");
-            $filt = _load_sub("$filt\::filter", "SQL::Translator::Filter")
-            || return $self->error(__PACKAGE__->error);
-            push @$filters, [$filt,@args];
+    },
+);
+
+around filename => \&ex2err;
+
+has data => (
+    is => 'rw',
+    builder => 1,
+    lazy => 1,
+    coerce => sub {
+        # Set $self->data based on what was passed in.  We will
+        # accept a number of things; do our best to get it right.
+        my $data = shift;
+        if (isa($data, 'ARRAY')) {
+            $data = join '', @$data;
         }
-    }
-    return @$filters;
-}
-
-# ----------------------------------------------------------------------
-sub show_warnings {
-    my $self = shift;
-    my $arg  = shift;
-    if ( defined $arg ) {
-        $self->{'show_warnings'} = $arg ? 1 : 0;
-    }
-    return $self->{'show_warnings'} || 0;
-}
-
-
-# filename - get or set the filename
-sub filename {
-    my $self = shift;
-    if (@_) {
-        my $filename = shift;
-        if (-d $filename) {
-            my $msg = "Cannot use directory '$filename' as input source";
-            return $self->error($msg);
-        } elsif (ref($filename) eq 'ARRAY') {
-            $self->{'filename'} = $filename;
-            $self->debug("Got array of files: ".join(', ',@$filename)."\n");
-        } elsif (-f _ && -r _) {
-            $self->{'filename'} = $filename;
-            $self->debug("Got filename: '$self->{'filename'}'\n");
-        } else {
-            my $msg = "Cannot use '$filename' as input source: ".
-                      "file does not exist or is not readable.";
-            return $self->error($msg);
+        elsif (isa($data, 'GLOB')) {
+            seek ($data, 0, 0) if eof ($data);
+            local $/;
+            $data = <$data>;
         }
-    }
+        return isa($data, 'SCALAR') ? $data : \$data;
+    },
+);
 
-    $self->{'filename'};
-}
-
-# ----------------------------------------------------------------------
-# data([$data])
-#
-# if $self->{'data'} is not set, but $self->{'filename'} is, then
-# $self->{'filename'} is opened and read, with the results put into
-# $self->{'data'}.
-# ----------------------------------------------------------------------
-sub data {
+around data => sub {
+    my $orig = shift;
     my $self = shift;
 
-    # Set $self->{'data'} based on what was passed in.  We will
-    # accept a number of things; do our best to get it right.
-    if (@_) {
-        my $data = shift;
-        if (isa($data, "SCALAR")) {
-            $self->{'data'} =  $data;
-        }
-        else {
-            if (isa($data, 'ARRAY')) {
-                $data = join '', @$data;
-            }
-            elsif (isa($data, 'GLOB')) {
-                local $/;
-                $data = <$data>;
-            }
-            elsif (! ref $data && @_) {
-                $data = join '', $data, @_;
-            }
-            $self->{'data'} = \$data;
-        }
+    if (@_ > 1 && !ref $_[0]) {
+        return $self->$orig(\join('', @_));
+    }
+    elsif (@_) {
+        return $self->$orig(@_);
     }
+    return ex2err($orig, $self);
+};
 
+sub _build_data {
+    my $self = shift;
     # If we have a filename but no data yet, populate.
-    if (not $self->{'data'} and my $filename = $self->filename) {
+    if (my $filename = $self->filename) {
         $self->debug("Opening '$filename' to get contents.\n");
-        local *FH;
         local $/;
         my $data;
 
         my @files = ref($filename) eq 'ARRAY' ? @$filename : ($filename);
 
         foreach my $file (@files) {
-            unless (open FH, $file) {
-                return $self->error("Can't read file '$file': $!");
-            }
+            open my $fh, '<', $file
+               or throw("Can't read file '$file': $!");
 
-            $data .= <FH>;
+            $data .= <$fh>;
 
-            unless (close FH) {
-                return $self->error("Can't close file '$file': $!");
-            }
+            close $fh or throw("Can't close file '$file': $!");
         }
 
-        $self->{'data'} = \$data;
+        return \$data;
     }
-
-    return $self->{'data'};
 }
 
-# ----------------------------------------------------------------------
-sub reset {
-#
-# Deletes the existing Schema object so that future calls to translate
-# don't append to the existing.
-#
-    my $self = shift;
-    $self->{'schema'} = undef;
-    return 1;
-}
-
-# ----------------------------------------------------------------------
-sub schema {
-#
-# Returns the SQL::Translator::Schema object
-#
-    my $self = shift;
-
-    unless ( defined $self->{'schema'} ) {
-        $self->{'schema'} = SQL::Translator::Schema->new(
-            translator      => $self,
-        );
-    }
+has schema => (
+    is => 'lazy',
+    init_arg => undef,
+    clearer => 'reset',
+    predicate => '_has_schema',
+);
 
-    return $self->{'schema'};
-}
+around schema => carp_ro('schema');
 
-# ----------------------------------------------------------------------
-sub trace {
+around reset => sub {
+    my $orig = shift;
     my $self = shift;
-    my $arg  = shift;
-    if ( defined $arg ) {
-        $self->{'trace'} = $arg ? 1 : 0;
-    }
-    return $self->{'trace'} || 0;
-}
+    $self->$orig(@_);
+    return 1
+};
+
+sub _build_schema { SQL::Translator::Schema->new(translator => shift) }
 
-# ----------------------------------------------------------------------
-# translate([source], [\%args])
-#
-# translate does the actual translation.  The main argument is the
-# source of the data to be translated, which can be a filename, scalar
-# reference, or glob reference.
-#
-# Alternatively, translate takes optional arguements, which are passed
-# to the appropriate places.  Most notable of these arguments are
-# parser and producer, which can be used to set the parser and
-# producer, respectively.  This is the applications last chance to set
-# these.
-#
-# translate returns a string.
-# ----------------------------------------------------------------------
 sub translate {
     my $self = shift;
     my ($args, $parser, $parser_type, $producer, $producer_type);
@@ -508,7 +386,7 @@ sub translate {
     # ----------------------------------------------------------------
 
     # Run parser
-    unless ( defined $self->{'schema'} ) {
+    unless ( $self->_has_schema ) {
         eval { $parser_output = $parser->($self, $$data) };
         if ($@ || ! $parser_output) {
             my $msg = sprintf "translate: Error with parser '%s': %s",
@@ -553,36 +431,10 @@ sub translate {
     return wantarray ? @producer_output : $producer_output;
 }
 
-# ----------------------------------------------------------------------
-# list_parsers()
-#
-# Hacky sort of method to list all available parsers.  This has
-# several problems:
-#
-#   - Only finds things in the SQL::Translator::Parser namespace
-#
-#   - Only finds things that are located in the same directory
-#     as SQL::Translator::Parser.  Yeck.
-#
-# This method will fail in several very likely cases:
-#
-#   - Parser modules in different namespaces
-#
-#   - Parser modules in the SQL::Translator::Parser namespace that
-#     have any XS componenets will be installed in
-#     arch_lib/SQL/Translator.
-#
-# ----------------------------------------------------------------------
 sub list_parsers {
     return shift->_list("parser");
 }
 
-# ----------------------------------------------------------------------
-# list_producers()
-#
-# See notes for list_parsers(), above; all the problems apply to
-# list_producers as well.
-# ----------------------------------------------------------------------
 sub list_producers {
     return shift->_list("producer");
 }
@@ -599,12 +451,7 @@ sub list_producers {
 # ----------------------------------------------------------------------
 sub _args {
     my $self = shift;
-    my $type = shift;
-    $type = "${type}_args" unless $type =~ /_args$/;
-
-    unless (defined $self->{$type} && isa($self->{$type}, 'HASH')) {
-        $self->{$type} = { };
-    }
+    my $orig = shift;
 
     if (@_) {
         # If the first argument is an explicit undef (remember, we
@@ -612,20 +459,20 @@ sub _args {
         # out the producer_args hash.
         if (! defined $_[0]) {
             shift @_;
-            %{$self->{$type}} = ();
+            $self->$orig({});
         }
 
         my $args = isa($_[0], 'HASH') ? shift : { @_ };
-        %{$self->{$type}} = (%{$self->{$type}}, %$args);
+        return $self->$orig({ %{$self->$orig}, %$args });
     }
 
-    $self->{$type};
+    return $self->$orig;
 }
 
 # ----------------------------------------------------------------------
 # Does the get/set work for parser and producer. e.g.
-# return $self->_tool({ 
-#   name => 'producer', 
+# return $self->_tool({
+#   name => 'producer',
 #   path => "SQL::Translator::Producer",
 #   default_sub => "produce",
 # }, @_);
@@ -633,16 +480,17 @@ sub _args {
 sub _tool {
     my ($self,$args) = (shift, shift);
     my $name = $args->{name};
+    my $orig = $args->{orig};
     return $self->{$name} unless @_; # get accessor
 
     my $path = $args->{path};
     my $default_sub = $args->{default_sub};
     my $tool = shift;
-   
+
     # passed an anonymous subroutine reference
     if (isa($tool, 'CODE')) {
-        $self->{$name} = $tool;
-        $self->{"$name\_type"} = "CODE";
+        $self->$orig($tool);
+        $self->${\"_set_${name}_type"}("CODE");
         $self->debug("Got $name: code ref\n");
     }
 
@@ -668,8 +516,8 @@ sub _tool {
 
         # get code reference and assign
         my (undef,$module,undef) = $sub =~ m/((.*)::)?(\w+)$/;
-        $self->{$name} = $code;
-        $self->{"$name\_type"} = $sub eq "CODE" ? "CODE" : $module;
+        $self->$orig($code);
+        $self->${\"_set_$name\_type"}($sub eq "CODE" ? "CODE" : $module);
         $self->debug("Got $name: $sub\n");
     }
 
@@ -692,7 +540,7 @@ sub _list {
     my $uctype = ucfirst lc $type;
 
     #
-    # First find all the directories where SQL::Translator 
+    # First find all the directories where SQL::Translator
     # parsers or producers (the "type") appear to live.
     #
     load("SQL::Translator::$uctype") or return ();
@@ -706,13 +554,13 @@ sub _list {
     }
 
     #
-    # Now use File::File::find to look recursively in those 
+    # Now use File::File::find to look recursively in those
     # directories for all the *.pm files, then present them
     # with the slashes turned into dashes.
     #
     my %found;
-    find( 
-        sub { 
+    find(
+        sub {
             if ( -f && m/\.pm$/ ) {
                 my $mod      =  $_;
                    $mod      =~ s/\.pm$//;
@@ -769,7 +617,7 @@ sub load {
         return $module if $INC{$file}; # Already loaded
 
         eval { require $file };
-        next if $@ =~ /Can't locate $file in \@INC/; 
+        next if $@ =~ /Can't locate $file in \@INC/;
         eval { $module->import() } unless $@;
         return __PACKAGE__->error("Error loading $name as $module : $@")
         if $@ && $@ !~ /"SQL::Translator::Producer" is not exported/;
@@ -797,22 +645,18 @@ sub _load_sub {
     return undef;
 }
 
-# ----------------------------------------------------------------------
 sub format_table_name {
     return shift->_format_name('_format_table_name', @_);
 }
 
-# ----------------------------------------------------------------------
 sub format_package_name {
     return shift->_format_name('_format_package_name', @_);
 }
 
-# ----------------------------------------------------------------------
 sub format_fk_name {
     return shift->_format_name('_format_fk_name', @_);
 }
 
-# ----------------------------------------------------------------------
 sub format_pk_name {
     return shift->_format_name('_format_pk_name', @_);
 }
@@ -838,35 +682,18 @@ sub _format_name {
     return @args ? $self->{$field}->(@args) : $self->{$field};
 }
 
-# ----------------------------------------------------------------------
-# isa($ref, $type)
-#
-# Calls UNIVERSAL::isa($ref, $type).  I think UNIVERSAL::isa is ugly,
-# but I like function overhead.
-# ----------------------------------------------------------------------
 sub isa($$) {
     my ($ref, $type) = @_;
     return UNIVERSAL::isa($ref, $type);
 }
 
-# ----------------------------------------------------------------------
-# version
-#
-# Returns the $VERSION of the main SQL::Translator package.
-# ----------------------------------------------------------------------
 sub version {
     my $self = shift;
     return $VERSION;
 }
 
-# ----------------------------------------------------------------------
-sub validate {
-    my ( $self, $arg ) = @_;
-    if ( defined $arg ) {
-        $self->{'validate'} = $arg ? 1 : 0;
-    }
-    return $self->{'validate'} || 0;
-}
+# Must come after all 'has' declarations
+around new => \&ex2err;
 
 1;
 
@@ -899,8 +726,7 @@ SQL::Translator - manipulate structured data definitions (SQL and more)
       # Add "drop table" statements
       add_drop_table      => 1,
       # to quote or not to quote, thats the question
-      quote_table_names     => 1,
-      quote_field_names     => 1,
+      quote_identifiers     => 1,
       # Validate schema object
       validate            => 1,
       # Make all table names CAPS in producers which support this option
@@ -984,11 +810,15 @@ add_drop_table
 
 =item *
 
-quote_table_names
+quote_identifiers
 
 =item *
 
-quote_field_names
+quote_table_names (DEPRECATED)
+
+=item *
+
+quote_field_names (DEPRECATED)
 
 =item *
 
@@ -1012,18 +842,22 @@ advantage is gained by passing options to the constructor.
 
 =head2 add_drop_table
 
-Toggles whether or not to add "DROP TABLE" statements just before the 
+Toggles whether or not to add "DROP TABLE" statements just before the
 create definitions.
 
+=head2 quote_identifiers
+
+Toggles whether or not to quote identifiers (table, column, constraint, etc.)
+with a quoting mechanism suitable for the chosen Producer. The default (true)
+is to quote them.
+
 =head2 quote_table_names
 
-Toggles whether or not to quote table names with " in DROP and CREATE
-statements. The default (true) is to quote them.
+DEPRECATED - A legacy proxy to L</quote_identifiers>
 
 =head2 quote_field_names
 
-Toggles whether or not to quote field names with " in most
-statements. The default (true), is to quote them.
+DEPRECATED - A legacy proxy to L</quote_identifiers>
 
 =head2 no_comments
 
@@ -1036,9 +870,9 @@ The C<producer> method is an accessor/mutator, used to retrieve or
 define what subroutine is called to produce the output.  A subroutine
 defined as a producer will be invoked as a function (I<not a method>)
 and passed its container C<SQL::Translator> instance, which it should
-call the C<schema> method on, to get the C<SQL::Translator::Schema> 
+call the C<schema> method on, to get the C<SQL::Translator::Schema>
 generated by the parser.  It is expected that the function transform the
-schema structure to a string.  The C<SQL::Translator> instance is also useful 
+schema structure to a string.  The C<SQL::Translator> instance is also useful
 for informational purposes; for example, the type of the parser can be
 retrieved using the C<parser_type> method, and the C<error> and
 C<debug> methods can be called when needed.
@@ -1125,7 +959,7 @@ analogously to C<producer_type> and C<producer_args>
 
 =head2 filters
 
-Set or retreive the filters to run over the schema during the
+Set or retrieve the filters to run over the schema during the
 translation, before the producer creates its output. Filters are sub
 routines called, in order, with the schema object to filter as the 1st
 arg and a hash of options (passed as a list) for the rest of the args.
@@ -1271,45 +1105,8 @@ Returns the version of the SQL::Translator release.
 
 =head1 AUTHORS
 
-The following people have contributed to the SQLFairy project:
-
-=over 4
-
-=item * Mark Addison <grommit@users.sourceforge.net>
-
-=item * Sam Angiuoli <angiuoli@users.sourceforge.net>
-
-=item * Anders Nor Berle <berle@cpan.org>
-
-=item * Dave Cash <dave@gnofn.org>
-
-=item * Darren Chamberlain <dlc@users.sourceforge.net>
-
-=item * Ken Y. Clark <kclark@cpan.org>
-
-=item * Allen Day <allenday@users.sourceforge.net>
-
-=item * Paul Harrington <phrrngtn@users.sourceforge.net>
-
-=item * Mikey Melillo <mmelillo@users.sourceforge.net>
-
-=item * Chris Mungall <cjm@fruitfly.org>
-
-=item * Ross Smith II <rossta@users.sf.net>
-
-=item * Gudmundur A. Thorisson <mummi@cshl.org>
-
-=item * Chris To <christot@users.sourceforge.net>
-
-=item * Jason Williams <smdwilliams@users.sourceforge.net>
-
-=item * Ying Zhang <zyolive@yahoo.com>
-
-=item * Daniel Ruoso <daniel@ruoso.com>
-
-=item * Ryan D Johnson <ryan@innerfence.com>
-
-=back
+See the included AUTHORS file:
+L<http://search.cpan.org/dist/SQL-Translator/AUTHORS>
 
 If you would like to contribute to the project, you can send patches
 to the developers mailing list:
@@ -1322,19 +1119,12 @@ added to the project and what you'd like to contribute.
 
 =head1 COPYRIGHT
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; version 2.
+Copyright 2012 the SQL::Translator authors, as listed in L</AUTHORS>.
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+=head1 LICENSE
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-USA
+This library is free software and may be distributed under the same terms as
+Perl 5 itself.
 
 =head1 BUGS
 
@@ -1342,7 +1132,7 @@ Please use L<http://rt.cpan.org/> for reporting bugs.
 
 =head1 PRAISE
 
-If you find this module useful, please use 
+If you find this module useful, please use
 L<http://cpanratings.perl.org/rate/?distribution=SQL-Translator> to rate it.
 
 =head1 SEE ALSO