remove commented copyright
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / Dumper.pm
CommitLineData
fa44993c 1package SQL::Translator::Producer::Dumper;
2
fa44993c 3=head1 NAME
4
5SQL::Translator::Producer::Dumper - SQL Dumper producer for SQL::Translator
6
7=head1 SYNOPSIS
8
9 use SQL::Translator::Producer::Dumper;
10
11 Options:
12
fa44993c 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
22This producer creates a Perl script that can connect to a database and
23dump the data as INSERT statements (a la mysqldump) or as a file
24suitable for MySQL's LOAD DATA command. If you enable "add-truncate"
25or specify tables to "skip" (also using the "skiplike" regular
26expression) then the generated dumper script will leave out those
27tables. However, these will also be options in the generated dumper,
28so you can wait to specify these options when you dump your database.
29The database username, password, and DSN can be hardcoded into the
30generated script, or part of the DSN can be intuited from the
31"database" argument.
32
33=cut
34
35use strict;
36use Config;
37use SQL::Translator;
38use File::Temp 'tempfile';
39use Template;
da06ac74 40use vars qw($VERSION);
fa44993c 41
42use Data::Dumper;
43
11ad2df9 44$VERSION = '1.59';
da06ac74 45
fa44993c 46sub produce {
47 my $t = shift;
48 my $args = $t->producer_args;
49 my $schema = $t->schema;
ea93df61 50 my $add_truncate = $args->{'add_truncate'} || 0;
fa44993c 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;
ea93df61 56 my %skip = map { $_, 1 } map { s/^\s+|\s+$//; $_ }
fa44993c 57 split (/,/, $skip);
58 my $sqlt_version = $t->version;
59
ea93df61 60 if ( $parser_name =~ /Parser::(\w+)$/ ) {
61 $parser_name = $1
fa44993c 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;
ea93df61 88 $template->process(
89 \$template_text,
90 {
fa44993c 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,
ea93df61 99 },
100 \$out
fa44993c 101 ) or die $template->error;
102
103 return $out;
104}
105
fa44993c 106sub 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
119use strict;
120use Cwd;
121use DBI;
122use Getopt::Long;
123use File::Spec::Functions 'catfile';
124
ea93df61 125my ( $help, $add_truncate, $skip, $skiplike, $no_comments,
b7df9b7e 126 $takelike, $mysql_loadfile );
fa44993c 127GetOptions(
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,
b7df9b7e 134 'takelike:s' => \$takelike,
fa44993c 135);
136
137if ( $help ) {
138 print <<"USAGE";
139Usage:
25afc779 140 $0 [options] > dump.sql
fa44993c 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
f4086310 148 --skiplike=regex Regular expression of table names to skip
149 --takelike=regex Regular expression of table names to take
fa44993c 150
151USAGE
152 exit(0);
153}
154
155$no_comments = 1 if $mysql_loadfile;
156
157[%-
158SET table_defs = [];
159SET max_field = 0;
160
161FOREACH 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;
43cf00a1 172 types.$field_name = field.data_type.match( '(char|str|long|text|enum|date)' )
fa44993c 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 });
ea93df61 182END
fa44993c 183-%]
184
e96fe4d3 185my $db = DBI->connect(
ea93df61 186 '[% dsn %]',
187 '[% db_user %]',
188 '[% db_pass %]',
e96fe4d3 189 { RaiseError => 1 }
190);
fa44993c 191my %skip = map { $_, 1 } map { s/^\s+|\s+$//; $_ } split (/,/, $skip);
192my @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
206for my $table ( @tables ) {
207 my $table_name = $table->{'table_name'};
208 next if $skip{ $table_name };
209 next if $skiplike && $table_name =~ qr/$skiplike/;
b7df9b7e 210 next if $takelike && $table_name !~ qr/$takelike/;
fa44993c 211
212 my ( $out_fh, $outfile );
213 if ( $mysql_loadfile ) {
214 $outfile = catfile( cwd(), "$table_name.txt" );
ea93df61 215 open $out_fh, ">$outfile" or
fa44993c 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
ea378e76 225 my $sql =
226 'select ' . join(', ', @{ $table->{'fields'} } ) . " from $table_name"
227 ;
228 my $sth = $db->prepare( $sql );
229 $sth->execute;
fa44993c 230
ea378e76 231 while ( my $rec = $sth->fetchrow_hashref ) {
fa44993c 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;
ea93df61 248 }
fa44993c 249
250 if ( $mysql_loadfile ) {
251 print $out_fh join("\t", @vals), "\n";
252 }
253 else {
254 print "INSERT INTO $table_name (".
ea93df61 255 join(', ', @{ $table->{'fields'} }) .
afff8ac6 256 ') VALUES (', join(', ', @vals), ");\n";
fa44993c 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}
269EOF
270}
271
2721;
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
ea378e76 283Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
fa44993c 284
285=cut