File Coverage

blib/lib/SQL/Translator/Producer/Dumper.pm
Criterion Covered Total %
statement 47 51 92.1
branch 5 10 50.0
condition 10 24 41.6
subroutine 9 9 100.0
pod 0 2 0.0
total 71 96 73.9


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