- Removed use of $Revision$ SVN keyword to generate VERSION variables; now sub-module...
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator.pm
index 82855be..0a0808a 100644 (file)
@@ -1,9 +1,9 @@
 package SQL::Translator;
 
 # ----------------------------------------------------------------------
-# $Id: Translator.pm,v 1.61 2004-11-09 05:27:45 grommit Exp $
+# $Id$
 # ----------------------------------------------------------------------
-# Copyright (C) 2002-4 The SQLFairy Authors
+# Copyright (C) 2002-2009 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
@@ -21,13 +21,12 @@ package SQL::Translator;
 # -------------------------------------------------------------------
 
 use strict;
-use vars qw( $VERSION $REVISION $DEFAULT_SUB $DEBUG $ERROR );
+use vars qw( $VERSION $DEFAULT_SUB $DEBUG $ERROR );
 use base 'Class::Base';
 
 require 5.004;
 
-$VERSION  = '0.06';
-$REVISION = sprintf "%d.%02d", q$Revision: 1.61 $ =~ /(\d+)\.(\d+)/;
+$VERSION  = '0.09002';
 $DEBUG    = 0 unless defined $DEBUG;
 $ERROR    = "";
 
@@ -39,6 +38,7 @@ use File::Find;
 use File::Spec::Functions qw(catfile);
 use File::Basename qw(dirname);
 use IO::Dir;
+use SQL::Translator::Producer;
 use SQL::Translator::Schema;
 
 # ----------------------------------------------------------------------
@@ -90,13 +90,21 @@ sub init {
     }
 
     #
+    # Initialize the filters.
+    #
+    if ( $config->{filters} && ref $config->{filters} eq "ARRAY" ) {
+        $self->filters( @{$config->{filters}} )
+        || return $self->error('Error inititializing filters: '.$self->error);
+    }
+
+    #
     # 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 
+    # Finally, if there is a 'data' parameter, use that in
     # preference to filename and file
     #
     if ( my $data = $config->{'data'} ) {
@@ -109,7 +117,7 @@ sub init {
     $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'} );
@@ -117,6 +125,11 @@ sub init {
     $self->trace( $config->{'trace'} );
 
     $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) );
 
     return $self;
 }
@@ -146,15 +159,37 @@ sub no_comments {
 
 
 # ----------------------------------------------------------------------
+# 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;
+}
+
+# ----------------------------------------------------------------------
+# 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;
+}
+
+# ----------------------------------------------------------------------
 # producer([$producer_spec])
 #
 # Get or set the producer for the current translator.
 # ----------------------------------------------------------------------
 sub producer {
     shift->_tool({
-            name => 'producer', 
+            name => 'producer',
             path => "SQL::Translator::Producer",
-            default_sub => "produce" 
+            default_sub => "produce",
     }, @_);
 }
 
@@ -186,9 +221,9 @@ sub producer_args { shift->_args("producer", @_); }
 # ----------------------------------------------------------------------
 sub parser {
     shift->_tool({
-        name => 'parser', 
+        name => 'parser',
         path => "SQL::Translator::Parser",
-        default_sub => "parse" 
+        default_sub => "parse",
     }, @_);
 }
 
@@ -197,6 +232,39 @@ sub parser_type { $_[0]->{'parser_type'}; }
 sub parser_args { shift->_args("parser", @_); }
 
 # ----------------------------------------------------------------------
+# e.g.
+#   $sqlt->filters => [
+#       sub { },
+#       [ "NormalizeNames", field => "lc", tabel => "ucfirst" ],
+#       [
+#           "DataTypeMap",
+#           "TEXT" => "BIGTEXT",
+#       ],
+#   ],
+# ----------------------------------------------------------------------
+sub filters {
+    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;
+        }
+        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];
+        }
+    }
+    return @$filters;
+}
+
+# ----------------------------------------------------------------------
 sub show_warnings {
     my $self = shift;
     my $arg  = shift;
@@ -345,10 +413,10 @@ sub trace {
 sub translate {
     my $self = shift;
     my ($args, $parser, $parser_type, $producer, $producer_type);
-    my ($parser_output, $producer_output);
+    my ($parser_output, $producer_output, @producer_output);
 
     # Parse arguments
-    if (@_ == 1) { 
+    if (@_ == 1) {
         # Passed a reference to a hash?
         if (isa($_[0], 'HASH')) {
             # yep, a hashref
@@ -431,12 +499,14 @@ sub translate {
     $producer_type = $self->producer_type;
 
     # ----------------------------------------------------------------
-    # Execute the parser, then execute the producer with that output.
+    # Execute the parser, the filters and then execute the producer.
     # Allowances are made for each piece to die, or fail to compile,
     # since the referenced subroutines could be almost anything.  In
     # the future, each of these might happen in a Safe environment,
     # depending on how paranoid we want to be.
     # ----------------------------------------------------------------
+
+    # Run parser
     unless ( defined $self->{'schema'} ) {
         eval { $parser_output = $parser->($self, $$data) };
         if ($@ || ! $parser_output) {
@@ -445,22 +515,41 @@ sub translate {
             return $self->error($msg);
         }
     }
-
     $self->debug("Schema =\n", Dumper($self->schema), "\n");
 
+    # Validate the schema if asked to.
     if ($self->validate) {
         my $schema = $self->schema;
         return $self->error('Invalid schema') unless $schema->is_valid;
     }
 
-    eval { $producer_output = $producer->($self) };
-    if ($@ || ! $producer_output) {
+    # Run filters
+    my $filt_num = 0;
+    foreach ($self->filters) {
+        $filt_num++;
+        my ($code,@args) = @$_;
+        eval { $code->($self->schema, @args) };
+        my $err = $@ || $self->error || 0;
+        return $self->error("Error with filter $filt_num : $err") if $err;
+    }
+
+    # Run producer
+    # Calling wantarray in the eval no work, wrong scope.
+    my $wantarray = wantarray ? 1 : 0;
+    eval {
+        if ($wantarray) {
+            @producer_output = $producer->($self);
+        } else {
+            $producer_output = $producer->($self);
+        }
+    };
+    if ($@ || !( $producer_output || @producer_output)) {
         my $err = $@ || $self->error || "no results";
         my $msg = "translate: Error with producer '$producer_type': $err";
         return $self->error($msg);
     }
 
-    return $producer_output;
+    return wantarray ? @producer_output : $producer_output;
 }
 
 # ----------------------------------------------------------------------
@@ -564,8 +653,18 @@ sub _tool {
         $tool =~ s/-/::/g if $tool !~ /::/;
         my ($code,$sub);
         ($code,$sub) = _load_sub("$tool\::$default_sub", $path);
-        ($code,$sub) = _load_sub("$tool", $path) unless $code;
-        
+        unless ($code) {
+            if ( __PACKAGE__->error =~ m/Can't find module/ ) {
+                # Mod not found so try sub
+                ($code,$sub) = _load_sub("$tool", $path) unless $code;
+                die "Can't load $name subroutine '$tool' : ".__PACKAGE__->error
+                unless $code;
+            }
+            else {
+                die "Can't load $name '$tool' : ".__PACKAGE__->error;
+            }
+        }
+
         # get code reference and assign
         my (undef,$module,undef) = $sub =~ m/((.*)::)?(\w+)$/;
         $self->{$name} = $code;
@@ -648,12 +747,12 @@ sub _list {
 # MODULE - is the name of the module to load.
 #
 # PATH - optional list of 'package paths' to look for the module in. e.g
-# If you called load(Bar => 'Foo', 'My::Modules') it will try to load the mod
-# Bar then Foo::Bar then My::Modules::Bar.
+# If you called load('Super::Foo' => 'My', 'Other') it will
+# try to load the mod Super::Foo then My::Super::Foo then Other::Super::Foo.
 #
 # Returns package name of the module actually loaded or false and sets error.
 #
-# Note, you can't load a name from the root namespace (ie one without '::' in 
+# Note, you can't load a name from the root namespace (ie one without '::' in
 # it), therefore a single word name without a path fails.
 # ----------------------------------------------------------------------
 sub load {
@@ -667,16 +766,17 @@ sub load {
         my $file = $module; $file =~ s[::][/]g; $file .= ".pm";
         __PACKAGE__->debug("Loading $name as $file\n");
         return $module if $INC{$file}; # Already loaded
-        
+
         eval { require $file };
         next if $@ =~ /Can't locate $file in \@INC/; 
-        eval { $file->import(@_) } unless $@;
-        return __PACKAGE__->error("Error loading $name as $module : $@") if $@;
+        eval { $module->import() } unless $@;
+        return __PACKAGE__->error("Error loading $name as $module : $@")
+        if $@ && $@ !~ /"SQL::Translator::Producer" is not exported/;
 
         return $module; # Module loaded ok
     }
 
-    return __PACKAGE__->error("Can't find $name. Path:".join(",",@path));
+    return __PACKAGE__->error("Can't find module $name. Path:".join(",",@path));
 }
 
 # ----------------------------------------------------------------------
@@ -687,13 +787,12 @@ sub load {
 # ----------------------------------------------------------------------
 sub _load_sub {
     my ($tool, @path) = @_;
-    
-    # Passed a module name or module and sub name 
+
     my (undef,$module,$func_name) = $tool =~ m/((.*)::)?(\w+)$/;
     if ( my $module = load($module => @path) ) {
         my $sub = "$module\::$func_name";
-        return ( \&{ $sub }, $sub );
-    } 
+        return wantarray ? ( \&{ $sub }, $sub ) : \&$sub;
+    }
     return undef;
 }
 
@@ -718,7 +817,7 @@ sub format_pk_name {
 }
 
 # ----------------------------------------------------------------------
-# The other format_*_name methods rely on this one.  It optionally 
+# The other format_*_name methods rely on this one.  It optionally
 # accepts a subroutine ref as the first argument (or uses an identity
 # sub if one isn't provided or it doesn't already exist), and applies
 # it to the rest of the arguments (if any).
@@ -791,15 +890,18 @@ SQL::Translator - manipulate structured data definitions (SQL and more)
       # Print debug info
       debug               => 1,
       # Print Parse::RecDescent trace
-      trace               => 0, 
+      trace               => 0,
       # Don't include comments in output
-      no_comments         => 0, 
+      no_comments         => 0,
       # Print name mutations, conflicts
-      show_warnings       => 0, 
+      show_warnings       => 0,
       # Add "drop table" statements
-      add_drop_table      => 1, 
+      add_drop_table      => 1,
+      # to quote or not to quote, thats the question
+      quote_table_names     => 1,
+      quote_field_names     => 1,
       # Validate schema object
-      validate            => 1, 
+      validate            => 1,
       # Make all table names CAPS in producers which support this option
       format_table_name   => sub {my $tablename = shift; return uc($tablename)},
       # Null-op formatting, only here for documentation's sake
@@ -812,7 +914,7 @@ SQL::Translator - manipulate structured data definitions (SQL and more)
       from       => 'MySQL',
       to         => 'Oracle',
       # Or an arrayref of filenames, i.e. [ $file1, $file2, $file3 ]
-      filename   => $file, 
+      filename   => $file,
   ) or die $translator->error;
 
   print $output;
@@ -861,6 +963,10 @@ producer_args
 
 =item *
 
+filters
+
+=item *
+
 filename / file
 
 =item *
@@ -877,6 +983,14 @@ add_drop_table
 
 =item *
 
+quote_table_names
+
+=item *
+
+quote_field_names
+
+=item *
+
 no_comments
 
 =item *
@@ -900,6 +1014,16 @@ advantage is gained by passing options to the constructor.
 Toggles whether or not to add "DROP TABLE" statements just before the 
 create definitions.
 
+=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.
+
+=head2 quote_field_names
+
+Toggles whether or not to quote field names with " in most
+statements. The default (true), is to quote them.
+
 =head2 no_comments
 
 Toggles whether to print comments in the output.  Accepts a true or false
@@ -998,6 +1122,56 @@ entirety of the data to be parsed.
 There is also C<parser_type> and C<parser_args>, which perform
 analogously to C<producer_type> and C<producer_args>
 
+=head2 filters
+
+Set or retreive 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.
+They are free to do whatever they want to the schema object, which will be
+handed to any following filters, then used by the producer.
+
+Filters are set as an array, which gives the order they run in.
+Like parsers and producers, they can be defined by a module name, a
+module name relative to the SQL::Translator::Filter namespace, a module
+name and function name together or a reference to an anonymous subroutine.
+When using a module name a function called C<filter> will be invoked in
+that package to do the work.
+
+To pass args to the filter set it as an array ref with the 1st value giving
+the filter (name or sub) and the rest its args. e.g.
+
+ $tr->filters(
+     sub {
+        my $schema = shift;
+        # Do stuff to schema here!
+     },
+     DropFKeys,
+     [ "Names", table => 'lc' ],
+     [ "Foo",   foo => "bar", hello => "world" ],
+     [ "Filter5" ],
+ );
+
+Although you normally set them in the constructor, which calls
+through to filters. i.e.
+
+  my $translator  = SQL::Translator->new(
+      ...
+      filters => [
+          sub { ... },
+          [ "Names", table => 'lc' ],
+      ],
+      ...
+  );
+
+See F<t/36-filters.t> for more examples.
+
+Multiple set calls to filters are cumulative with new filters added to
+the end of the current list.
+
+Returns the filters as a list of array refs, the 1st value being a
+reference to the filter sub and the rest its args.
+
 =head2 show_warnings
 
 Toggles whether to print warnings of name conflicts, identifier
@@ -1008,11 +1182,12 @@ current value.
 
 =head2 translate
 
-The C<translate> method calls the subroutines referenced by the
-C<parser> and C<producer> data members (described above).  It accepts
-as arguments a number of things, in key => value format, including
-(potentially) a parser and a producer (they are passed directly to the
-C<parser> and C<producer> methods).
+The C<translate> method calls the subroutine referenced by the
+C<parser> data member, then calls any C<filters> and finally calls
+the C<producer> sub routine (these members are described above).
+It accepts as arguments a number of things, in key => value format,
+including (potentially) a parser and a producer (they are passed
+directly to the C<parser> and C<producer> methods).
 
 Here is how the parameter list to C<translate> is parsed:
 
@@ -1103,6 +1278,8 @@ The following people have contributed to the SQLFairy project:
 
 =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>
@@ -1127,6 +1304,12 @@ The following people have contributed to the SQLFairy project:
 
 =item * Ying Zhang <zyolive@yahoo.com>
 
+=item * Daniel Ruoso <daniel@ruoso.com>
+
+=item * Ryan D Johnson <ryan@innerfence.com>
+
+=item * Jonathan Yu <frequency@cpan.org>
+
 =back
 
 If you would like to contribute to the project, you can send patches