remove commented copyright
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Dumper.pm
1 package SQL::Translator::Producer::Dumper;
2
3 =head1 NAME
4
5 SQL::Translator::Producer::Dumper - SQL Dumper producer for SQL::Translator
6
7 =head1 SYNOPSIS
8
9   use SQL::Translator::Producer::Dumper;
10
11   Options:
12
13     db_user         Database username
14     db_password     Database password
15     dsn             DSN for DBI
16     mysql_loadfile  Create MySQL's LOAD FILE syntax instead of INSERTs
17     skip=t1[,t2]    Skip tables in comma-separated list
18     skiplike=regex  Skip tables in comma-separated list
19
20 =head1 DESCRIPTION
21
22 This producer creates a Perl script that can connect to a database and
23 dump the data as INSERT statements (a la mysqldump) or as a file
24 suitable for MySQL's LOAD DATA command.  If you enable "add-truncate"
25 or specify tables to "skip" (also using the "skiplike" regular
26 expression) then the generated dumper script will leave out those
27 tables.  However, these will also be options in the generated dumper,
28 so you can wait to specify these options when you dump your database.
29 The database username, password, and DSN can be hardcoded into the
30 generated script, or part of the DSN can be intuited from the
31 "database" argument.
32
33 =cut
34
35 use strict;
36 use Config;
37 use SQL::Translator;
38 use File::Temp 'tempfile';
39 use Template;
40 use vars qw($VERSION);
41
42 use Data::Dumper;
43
44 $VERSION = '1.59';
45
46 sub produce {
47     my $t              = shift;
48     my $args           = $t->producer_args;
49     my $schema         = $t->schema;
50     my $add_truncate   = $args->{'add_truncate'}   || 0;
51     my $skip           = $args->{'skip'}           || '';
52     my $skiplike       = $args->{'skiplike'}       || '';
53     my $db_user        = $args->{'db_user'}        || 'db_user';
54     my $db_pass        = $args->{'db_password'}    || 'db_pass';
55     my $parser_name    = $t->parser_type;
56     my %skip           = map { $_, 1 } map { s/^\s+|\s+$//; $_ }
57                          split (/,/, $skip);
58     my $sqlt_version   = $t->version;
59
60     if ( $parser_name  =~ /Parser::(\w+)$/ ) {
61         $parser_name = $1
62     }
63
64     my %type_to_dbd    = (
65         MySQL          => 'mysql',
66         Oracle         => 'Oracle',
67         PostgreSQL     => 'Pg',
68         SQLite         => 'SQLite',
69         Sybase         => 'Sybase',
70     );
71     my $dbd            = $type_to_dbd{ $parser_name } || 'DBD';
72     my $dsn            = $args->{'dsn'} || "dbi:$dbd:";
73     if ( $dbd eq 'Pg' && ! $args->{'dsn'} ) {
74         $dsn .= 'dbname=dbname;host=hostname';
75     }
76     elsif ( $dbd eq 'Oracle' && ! $args->{'dsn'} ) {
77         $db_user = "$db_user/$db_pass@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)" .
78             "(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=sid)))";
79         $db_pass = '';
80     }
81     elsif ( $dbd eq 'mysql' && ! $args->{'dsn'} ) {
82         $dsn .= 'dbname';
83     }
84
85     my $template      = Template->new;
86     my $template_text = template();
87     my $out;
88     $template->process(
89         \$template_text,
90         {
91             translator     => $t,
92             schema         => $schema,
93             db_user        => $db_user,
94             db_pass        => $db_pass,
95             dsn            => $dsn,
96             perl           => $Config{'startperl'},
97             skip           => \%skip,
98             skiplike       => $skiplike,
99         },
100         \$out
101     ) or die $template->error;
102
103     return $out;
104 }
105
106 sub template {
107 #
108 # Returns the template to be processed by Template Toolkit
109 #
110     return <<'EOF';
111 [% perl || '#!/usr/bin/perl' %]
112 [% USE date %]
113 #
114 # Generated by SQL::Translator [% translator.version %]
115 # [% date.format( date.now, "%Y-%m-%d" ) %]
116 # For more info, see http://sqlfairy.sourceforge.net/
117 #
118
119 use strict;
120 use Cwd;
121 use DBI;
122 use Getopt::Long;
123 use File::Spec::Functions 'catfile';
124
125 my ( $help, $add_truncate, $skip, $skiplike, $no_comments,
126     $takelike, $mysql_loadfile );
127 GetOptions(
128     'add-truncate'   => \$add_truncate,
129     'h|help'         => \$help,
130     'no-comments'    => \$no_comments,
131     'mysql-loadfile' => \$mysql_loadfile,
132     'skip:s'         => \$skip,
133     'skiplike:s'     => \$skiplike,
134     'takelike:s'     => \$takelike,
135 );
136
137 if ( $help ) {
138     print <<"USAGE";
139 Usage:
140   $0 [options] > dump.sql
141
142   Options:
143     -h|--help          Show help and exit
144     --add-truncate     Add "TRUNCATE TABLE" statements
145     --mysql-loadfile   Create MySQL's LOAD FILE syntax, not INSERTs
146     --no-comments      Suppress comments
147     --skip=t1[,t2]     Comma-separated list of tables to skip
148     --skiplike=regex   Regular expression of table names to skip
149     --takelike=regex   Regular expression of table names to take
150
151 USAGE
152     exit(0);
153 }
154
155 $no_comments = 1 if $mysql_loadfile;
156
157 [%-
158 SET table_defs = [];
159 SET max_field  = 0;
160
161 FOREACH table IN schema.get_tables;
162     SET table_name = table.name;
163     NEXT IF skip.$table_name;
164     NEXT IF skiplike AND table_name.match("(?:$skiplike)");
165
166     SET field_names = [];
167     SET types       = {};
168     FOR field IN table.get_fields;
169         field_name = field.name;
170         fname_len  = field.name.length;
171         max_field  = fname_len > max_field ? fname_len : max_field;
172         types.$field_name = field.data_type.match( '(char|str|long|text|enum|date)' )
173             ? 'string' : 'number';
174         field_names.push( field_name );
175     END;
176
177     table_defs.push({
178         name   => table_name,
179         types  => types,
180         fields => field_names,
181     });
182 END
183 -%]
184
185 my $db     = DBI->connect(
186     '[% dsn %]',
187     '[% db_user %]',
188     '[% db_pass %]',
189     { RaiseError => 1 }
190 );
191 my %skip   = map { $_, 1 } map { s/^\s+|\s+$//; $_ } split (/,/, $skip);
192 my @tables = (
193 [%- FOREACH t IN table_defs %]
194     {
195         table_name  => '[% t.name %]',
196         fields      => [ qw/ [% t.fields.join(' ') %] / ],
197         types       => {
198             [%- FOREACH fname IN t.types.keys %]
199             '[% fname %]' => '[% t.types.$fname %]',
200             [%- END %]
201         },
202     },
203 [%- END %]
204 );
205
206 for my $table ( @tables ) {
207     my $table_name = $table->{'table_name'};
208     next if $skip{ $table_name };
209     next if $skiplike && $table_name =~ qr/$skiplike/;
210     next if $takelike && $table_name !~ qr/$takelike/;
211
212     my ( $out_fh, $outfile );
213     if ( $mysql_loadfile ) {
214         $outfile = catfile( cwd(), "$table_name.txt" );
215         open $out_fh, ">$outfile" or
216             die "Can't write LOAD FILE to '$table_name': $!\n";
217     }
218
219     print "--\n-- Data for table '$table_name'\n--\n" unless $no_comments;
220
221     if ( $add_truncate ) {
222         print "TRUNCATE TABLE $table_name;\n";
223     }
224
225     my $sql =
226         'select ' . join(', ', @{ $table->{'fields'} } ) . " from $table_name"
227     ;
228     my $sth = $db->prepare( $sql );
229     $sth->execute;
230
231     while ( my $rec = $sth->fetchrow_hashref ) {
232         my @vals;
233         for my $fld ( @{ $table->{'fields'} } ) {
234             my $val = $rec->{ $fld };
235             if ( $table->{'types'}{ $fld } eq 'string' ) {
236                 if ( defined $val ) {
237                     $val =~ s/'/\\'/g;
238                     $val = qq['$val']
239                 }
240                 else {
241                     $val = qq[''];
242                 }
243             }
244             else {
245                 $val = defined $val ? $val : $mysql_loadfile ? '\N' : 'NULL';
246             }
247             push @vals, $val;
248         }
249
250         if ( $mysql_loadfile ) {
251             print $out_fh join("\t", @vals), "\n";
252         }
253         else {
254             print "INSERT INTO $table_name (".
255                 join(', ', @{ $table->{'fields'} }) .
256                 ') VALUES (', join(', ', @vals), ");\n";
257         }
258     }
259
260     if ( $out_fh ) {
261         print "LOAD DATA INFILE '$outfile' INTO TABLE $table_name ",
262             "FIELDS OPTIONALLY ENCLOSED BY '\\'';\n";
263         close $out_fh or die "Can't close filehandle: $!\n";
264     }
265     else {
266         print "\n";
267     }
268 }
269 EOF
270 }
271
272 1;
273
274 # -------------------------------------------------------------------
275 # To create a little flower is the labour of ages.
276 # William Blake
277 # -------------------------------------------------------------------
278
279 =pod
280
281 =head1 AUTHOR
282
283 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
284
285 =cut