package SQL::Translator;
# ----------------------------------------------------------------------
-# $Id: Translator.pm,v 1.17 2003-02-26 13:08:59 dlc Exp $
+# $Id: Translator.pm,v 1.27 2003-05-09 19:51:28 kycl4rk Exp $
# ----------------------------------------------------------------------
# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
# darren chamberlain <darren@cpan.org>,
use base 'Class::Base';
$VERSION = '0.01';
-$REVISION = sprintf "%d.%02d", q$Revision: 1.17 $ =~ /(\d+)\.(\d+)/;
+$REVISION = sprintf "%d.%02d", q$Revision: 1.27 $ =~ /(\d+)\.(\d+)/;
$DEBUG = 0 unless defined $DEBUG;
$ERROR = "";
use File::Spec::Functions qw(catfile);
use File::Basename qw(dirname);
use IO::Dir;
+use SQL::Translator::Schema;
# ----------------------------------------------------------------------
# The default behavior is to "pass through" values (note that the
$self->parser ($config->{'parser'} || $config->{'from'} || $DEFAULT_SUB);
$self->producer($config->{'producer'} || $config->{'to'} || $DEFAULT_SUB);
+ #
+ # Set up callbacks for formatting of pk,fk,table,package names in producer
+ #
+ $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'});
+
#
# Set the parser_args and producer_args
#
#
$self->{'debug'} = defined $config->{'debug'} ? $config->{'debug'} : $DEBUG;
-
$self->add_drop_table( $config->{'add_drop_table'} );
$self->custom_translate( $config->{'xlate'} );
return $self->_args("producer", @_);
}
-
-
# ----------------------------------------------------------------------
# parser([$parser_spec])
# ----------------------------------------------------------------------
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");
local $/;
my $data;
- unless (open FH, $filename) {
- return $self->error("Can't read file '$filename': $!");
- }
+ my @files = ref($filename) eq 'ARRAY' ? @$filename : ($filename);
- $data = <FH>;
- $self->{'data'} = \$data;
+ foreach my $file (@files) {
+ unless (open FH, $file) {
+ return $self->error("Can't read file '$file': $!");
+ }
- unless (close FH) {
- return $self->error("Can't close file '$filename': $!");
- }
+ $data .= <FH>;
+
+ unless (close FH) {
+ return $self->error("Can't close file '$file': $!");
+ }
+ }
+
+ $self->{'data'} = \$data;
}
return $self->{'data'};
}
+# ----------------------------------------------------------------------
+sub schema {
+#
+# Returns the SQL::Translator::Schema object
+#
+ my $self = shift;
+
+ unless ( defined $self->{'schema'} ) {
+ $self->{'schema'} = SQL::Translator::Schema->new;
+ }
+
+ return $self->{'schema'};
+}
+# ----------------------------------------------------------------------
sub trace {
my $self = shift;
my $arg = shift;
$self->filename($filename);
}
- if (my $data = ($self->{'data'} || $self->{'datasource'})) {
+ if (my $data = ($args->{'data'} || $args->{'datasource'})) {
$self->data($data);
}
# Get the data.
# ----------------------------------------------------------------
my $data = $self->data;
- unless (length $$data) {
+ unless (ref($data) eq 'SCALAR' and length $$data) {
return $self->error("Empty data file!");
}
#
# ----------------------------------------------------------------------
sub list_parsers {
- return _list("parsers");
+ return shift->_list("parser");
}
# ----------------------------------------------------------------------
# list_producers as well.
# ----------------------------------------------------------------------
sub list_producers {
- return _list("producers");
+ return shift->_list("producer");
}
# _list($type)
# ----------------------------------------------------------------------
sub _list {
- my $type = ucfirst lc $_[0] || return ();
-
- load("SQL::Translator::$type");
- my $path = catfile(dirname($INC{'SQL/Translator/$type.pm'}), $type);
- my $dh = IO::Dir->new($path);
+ my $self = shift;
+ my $type = shift || return ();
+ my $uctype = ucfirst lc $type;
+ my %found;
+
+ load("SQL::Translator::$uctype") or return ();
+ my $path = catfile "SQL", "Translator", $uctype;
+ for (@INC) {
+ my $dir = catfile $_, $path;
+ $self->debug("_list_${type}s searching $dir");
+ next unless -d $dir;
+
+ my $dh = IO::Dir->new($dir);
+ for (grep /\.pm$/, $dh->read) {
+ s/\.pm$//;
+ $found{ join "::", "SQL::Translator::$uctype", $_ } = 1;
+ }
+ }
- return map { join "::", "SQL::Translator::$type", $_ }
- grep /\.pm$/, $dh->read;
+ return keys %found;
}
# ----------------------------------------------------------------------
sub load {
my $module = do { my $m = shift; $m =~ s[::][/]g; "$m.pm" };
return 1 if $INC{$module};
-
- eval { require $module };
-
- return if ($@);
+
+ eval {
+ require $module;
+ $module->import(@_);
+ };
+
+ return __PACKAGE__->error($@) if ($@);
return 1;
}
+sub format_table_name {
+ my $self = shift;
+ my $sub = shift;
+ $self->{'_format_table_name'} = $sub if ref $sub eq 'CODE';
+ return $self->{'_format_table_name'}->( $sub, @_ )
+ if defined $self->{'_format_table_name'};
+ return $sub;
+}
+
+sub format_package_name {
+ my $self = shift;
+ my $sub = shift;
+ $self->{'_format_package_name'} = $sub if ref $sub eq 'CODE';
+ return $self->{'_format_package_name'}->( $sub, @_ )
+ if defined $self->{'_format_package_name'};
+ return $sub;
+}
+
+sub format_fk_name {
+ my $self = shift;
+ my $sub = shift;
+ $self->{'_format_fk_name'} = $sub if ref $sub eq 'CODE';
+ return $self->{'_format_fk_name'}->( $sub, @_ )
+ if defined $self->{'_format_fk_name'};
+ return $sub;
+}
+
+sub format_pk_name {
+ my $self = shift;
+ my $sub = shift;
+ $self->{'_format_pk_name'} = $sub if ref $sub eq 'CODE';
+ return $self->{'_format_pk_name'}->( $sub, @_ )
+ if defined $self->{'_format_pk_name'};
+ return $sub;
+}
+
# ----------------------------------------------------------------------
# isa($ref, $type)
#
use SQL::Translator;
my $translator = SQL::Translator->new(
- xlate => $xlate || {}, # Overrides for field translation
- debug => $debug, # Print debug info
- trace => $trace, # Print Parse::RecDescent trace
- no_comments => $no_comments, # Don't include comments in output
- show_warnings => $show_warnings, # Print name mutations, conflicts
- add_drop_table => $add_drop_table, # Add "drop table" statements
+ debug => 1, # Print debug info
+ trace => 0, # Print Parse::RecDescent trace
+ no_comments => 0, # Don't include comments in output
+ show_warnings => 0, # Print name mutations, conflicts
+ add_drop_table => 1, # Add "drop table" statements
+
+ # 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
+ format_package_name => sub {return shift},
+ format_fk_name => sub {return shift},
+ format_pk_name => sub {return shift},
);
my $output = $translator->translate(
from => "MySQL",
to => "Oracle",
- filename => $file,
+ # Or an arrayref of filenames, i.e. [ $file1, $file2, $file3 ]
+ filename => $file,
) or die $translator->error;
print $output;
=head1 CONSTRUCTOR
-The constructor is called B<new>, and accepts a optional hash of options.
+The constructor is called C<new>, and accepts a optional hash of options.
Valid options are:
=over 4
=head1 METHODS
-=head2 B<add_drop_table>
+=head2 add_drop_table
Toggles whether or not to add "DROP TABLE" statements just before the
create definitions.
-=head2 B<custom_translate>
+=head2 custom_translate
Allows the user to override default translation of fields. For example,
if a MySQL "text" field would normally be converted to a "long" for Oracle,
keys are the "from" value and values are the "to," returns the current
value of the field.
-=head2 B<no_comments>
+=head2 no_comments
Toggles whether to print comments in the output. Accepts a true or false
value, returns the current value.
-=head2 B<producer>
+=head2 producer
-The B<producer> method is an accessor/mutator, used to retrieve or
+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 2 parameters: its container C<SQL::Translator> instance and a
data structure. It is expected that the function transform the data
structure to a string. The C<SQL::Transformer> instance is provided for
informational purposes; for example, the type of the parser can be
-retrieved using the B<parser_type> method, and the B<error> and
-B<debug> methods can be called when needed.
+retrieved using the C<parser_type> method, and the C<error> and
+C<debug> methods can be called when needed.
When defining a producer, one of several things can be passed in: A
-module name (e.g., C<My::Groovy::Producer>, a module name relative to
-the C<SQL::Translator::Producer> namespace (e.g., MySQL), a module
+module name (e.g., C<My::Groovy::Producer>), a module name relative to
+the C<SQL::Translator::Producer> namespace (e.g., C<MySQL>), a module
name and function combination (C<My::Groovy::Producer::transmogrify>),
or a reference to an anonymous subroutine. If a full module name is
passed in (for the purposes of this method, a string containing "::"
If $modulename cannot be loaded, the final portion is stripped off and
treated as a function. In other words, if there is no file named
F<My/Groovy/Producer/transmogrify.pm>, C<SQL::Translator> will attempt
-to load F<My/Groovy/Producer.pm> and use transmogrify as the name of
-the function, instead of the default "produce".
+to load F<My/Groovy/Producer.pm> and use C<transmogrify> as the name of
+the function, instead of the default C<produce>.
my $tr = SQL::Translator->new;
# $subref->($tr, $data);
$tr->producer(\&my_producer);
-There is also a method named B<producer_type>, which is a string
-containing the classname to which the above B<produce> function
+There is also a method named C<producer_type>, which is a string
+containing the classname to which the above C<produce> function
belongs. In the case of anonymous subroutines, this method returns
the string "CODE".
-Finally, there is a method named B<producer_args>, which is both an
+Finally, there is a method named C<producer_args>, which is both an
accessor and a mutator. Arbitrary data may be stored in name => value
pairs for the producer subroutine to access:
# $pr_args is a hashref.
-Extra data passed to the B<producer> method is passed to
-B<producer_args>:
+Extra data passed to the C<producer> method is passed to
+C<producer_args>:
$tr->producer("xSV", delimiter => ',\s*');
my $args = $tr->producer_args;
my $delimiter = $args->{'delimiter'}; # value is ,\s*
-=head2 B<parser>
+=head2 parser
-The B<parser> method defines or retrieves a subroutine that will be
+The C<parser> method defines or retrieves a subroutine that will be
called to perform the parsing. The basic idea is the same as that of
-B<producer> (see above), except the default subroutine name is
+C<producer> (see above), except the default subroutine name is
"parse", and will be invoked as C<$module_name::parse($tr, $data)>.
Also, the parser subroutine will be passed a string containing the
entirety of the data to be parsed.
return $dumper->Dump;
});
-There is also B<parser_type> and B<parser_args>, which perform
-analogously to B<producer_type> and B<producer_args>
+There is also C<parser_type> and C<parser_args>, which perform
+analogously to C<producer_type> and C<producer_args>
-=head2 B<show_warnings>
+=head2 show_warnings
Toggles whether to print warnings of name conflicts, identifier
mutations, etc. Probably only generated by producers to let the user
fields into Oracle). Accepts a true or false value, returns the
current value.
-=head2 B<translate>
+=head2 translate
-The B<translate> method calls the subroutines referenced by the
-B<parser> and B<producer> data members (described above). It accepts
+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
-B<parser> and B<producer> methods).
+C<parser> and C<producer> methods).
-Here is how the parameter list to B<translate> is parsed:
+Here is how the parameter list to C<translate> is parsed:
=over
=back
-=head2 B<filename>, B<data>
+=head2 filename, data
-Using the B<filename> method, the filename of the data to be parsed
-can be set. This method can be used in conjunction with the B<data>
-method, below. If both the B<filename> and B<data> methods are
-invoked as mutators, the data set in the B<data> method is used.
+Using the C<filename> method, the filename of the data to be parsed
+can be set. This method can be used in conjunction with the C<data>
+method, below. If both the C<filename> and C<data> methods are
+invoked as mutators, the data set in the C<data> method is used.
$tr->filename("/my/data/files/create.sql");
};
$tr->data(\$create_script);
-B<filename> takes a string, which is interpreted as a filename.
-B<data> takes a reference to a string, which is used as the data to be
+C<filename> takes a string, which is interpreted as a filename.
+C<data> takes a reference to a string, which is used as the data to be
parsed. If a filename is set, then that file is opened and read when
-the B<translate> method is called, as long as the data instance
+the C<translate> method is called, as long as the data instance
variable is not set.
=pod
-=head2 B<trace>
+=head2 schema
+
+Returns the SQL::Translator::Schema object.
+
+=head2 trace
Turns on/off the tracing option of Parse::RecDescent.
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
+=head1 BUGS
+
+Please use http://rt.cpan.org/ for reporting bugs.
+
=head1 SEE ALSO
L<perl>,