Initial checkin.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator.pm
1 package SQL::Translator;
2
3 #-----------------------------------------------------
4 # $Id: Translator.pm,v 1.1.1.1 2002-03-01 02:26:25 kycl4rk Exp $
5 #
6 # File       : SQL/Translator.pm
7 # Programmer : Ken Y. Clark, kclark@logsoft.com
8 # Created    : 2002/02/27
9 # Purpose    : convert schema from one database to another
10 #-----------------------------------------------------
11
12 use strict;
13 use vars qw( $VERSION );
14 $VERSION = (qw$Revision: 1.1.1.1 $)[-1];
15
16 use Data::Dumper;
17
18 use SQL::Translator::Parser::MySQL;
19 use SQL::Translator::Parser::Sybase;
20 use SQL::Translator::Producer::Oracle;
21 use SQL::Translator::Producer::XML;
22
23 #
24 # These are the inputs we can parse.
25 #
26 my %parsers = (
27     mysql    => 'MySQL',
28     sybase   => 'Sybase',
29 );
30
31 #
32 # These are the formats we can produce.
33 #
34 my %producers = (
35     oracle    => 'Oracle',
36     xml       => 'XML',
37 );
38
39 #-----------------------------------------------------
40 sub new {
41 #
42 # Makes a new object.  Intentionally made very bare as 
43 # it is used by all subclasses (unless they override, 
44 # of course).
45 #
46     my $class = shift;
47     my %args  = @_;
48     my $self  = { %args };
49     return bless $self, $class;
50 }
51
52 #-----------------------------------------------------
53 sub error {
54 #
55 # Return the last error.
56 #
57     return shift()->{'error'} || '';
58 }
59
60 #-----------------------------------------------------
61 sub error_out {
62 #
63 # Record the error and return undef.
64 #
65     my $self = shift;
66     if ( my $error = shift ) {
67         $self->{'error'} = $error;
68     }
69     return;
70 }
71
72 #-----------------------------------------------------
73 sub translate {
74 #
75 # Translates any number of given files.
76 #
77     my ( $self, %args ) = @_;
78     my $from            = $args{'from'}        || '';
79     my $to              = $args{'to'}          || '';
80     my $input           = $args{'input'}       || [];
81     my $verbose         = $args{'verbose'}     ||  0;
82     my $no_comments     = $args{'no_comments'} ||  0;
83
84     if ( exists $parsers{ $from } ) {
85         $self->{'from'} = $from;
86         warn "Using parser '$from.'\n" if $verbose;
87     }
88     else {
89         my $msg = "The parsers '$from' is not valid.\n" .
90                   "Please choose from the following list:\n";
91         $msg .= "  $_\n" for sort keys %parsers;
92         return $self->error_out( $msg );
93     }
94
95     if ( exists $producers{ $to } ) {
96         $self->{'to'} = $to;
97         warn "Using producer '$to.'\n" if $verbose;
98     }
99     else {
100         my $msg = "The producer '$to' is not valid.\n" .
101                   "Please choose from the following list:\n";
102         $msg .= "  $_\n" for sort keys %producers;
103         return $self->error_out( $msg );
104     }
105
106     #
107     # Slurp the entire text file we're parsing.
108     #
109     my $parser   = $self->parser;
110     my $producer = $self->producer;
111     my $data;
112     for my $file ( @$input ) {
113         warn "Parsing file '$file.'\n" if $verbose;
114         open my $fh, $file or return $self->error_out( "Can't read $file: $!" );
115         local $/;
116         $data = $parser->parse( <$fh> );
117     }
118
119     warn "Data =\n", Dumper( $data ) if $verbose;
120     my $output = $producer->translate( $data );
121 }
122
123 #-----------------------------------------------------
124 sub parser {
125 #
126 # Figures out which module to load based on the "from" argument
127 #
128     my $self = shift;
129     unless ( $self->{'parser'} ) {
130         my $parser_module = 
131             'SQL::Translator::Parser::'.$parsers{ $self->{'from'} };
132         $self->{'parser'} = $parser_module->new;
133     }
134     return $self->{'parser'};
135 }
136
137 #-----------------------------------------------------
138 sub producer {
139 #
140 # Figures out which module to load based on the "to" argument
141 #
142     my $self = shift;
143     unless ( $self->{'producer'} ) {
144         my $from            = $parsers{ $self->{'from'} };
145         my $producer_module = 
146             'SQL::Translator::Producer::'.$producers{ $self->{'to'} };
147         $self->{'producer'} = $producer_module->new( from => $from );
148     }
149     return $self->{'producer'};
150 }
151
152 1;
153
154 #-----------------------------------------------------
155 # Rescue the drowning and tie your shoestrings.
156 # Henry David Thoreau 
157 #-----------------------------------------------------
158
159 =head1 NAME
160
161 SQL::Translator - convert schema from one database to another
162
163 =head1 SYNOPSIS
164
165   use SQL::Translator;
166   my $translator = SQL::Translator->new;
167   my $output     =  $translator->translate(
168       from => 'mysql',
169       to   => 'oracle',
170       file => $file,
171   ) or die $translator->error;
172   print $output;
173
174 =head1 DESCRIPTION
175
176 This module attempts to simplify the task of converting one database
177 create syntax to another through the use of Parsers and Producers.
178 The idea is that any Parser can be used with any Producer in the
179 conversion process.  So, if you wanted PostgreSQL-to-Oracle, you could
180 just write the PostgreSQL parser and use an existing Oracle producer.
181
182 Currently, the existing parsers use Parse::RecDescent, and the
183 producers are just printing formatted output of the parsed data
184 structure.  New parsers don't necessarily have to use
185 Parse::RecDescent, however, as long as the data structure conforms to
186 what the producers are expecting.  With this separation of code, it is
187 hoped that developers will find it easy to add more database dialects
188 by using what's written, writing only what they need, and then
189 contributing their parsers or producers back to the project.
190
191 =head1 AUTHOR
192
193 Ken Y. Clark, kclark@logsoft.com
194
195 =head1 SEE ALSO
196
197 perl(1).
198
199 =cut