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