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   6 use strict;
  1         3  
  1         26  
36 1     1   4 use warnings;
  1         2  
  1         19  
37 1     1   4 use Config;
  1         2  
  1         34  
38 1     1   5 use SQL::Translator;
  1         2  
  1         17  
39 1     1   4 use File::Temp 'tempfile';
  1         2  
  1         49  
40 1     1   438 use Template;
  1         16854  
  1         30  
41              
42 1     1   7 use Data::Dumper;
  1         2  
  1         424  
43              
44             our $VERSION = '1.6_3';
45              
46             sub produce {
47 1     1 0 2 my $t = shift;
48 1         19 my $args = $t->producer_args;
49 1         16 my $schema = $t->schema;
50 1   50     12 my $add_truncate = $args->{'add_truncate'} || 0;
51 1   50     3 my $skip = $args->{'skip'} || '';
52 1   50     6 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         18 my $parser_name = $t->parser_type;
56 1         4 my %skip = map { $_, 1 } map { s/^\s+|\s+$//; $_ }
  1         4  
  1         4  
  1         3  
57             split (/,/, $skip);
58 1         5 my $sqlt_version = $t->version;
59              
60 1 50       6 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     3 my $dbd = $type_to_dbd{ $parser_name } || 'DBD';
72 1   33     5 my $dsn = $args->{'dsn'} || "dbi:$dbd:";
73 1 50 33     12 if ( $dbd eq 'Pg' && ! $args->{'dsn'} ) {
    50 33        
    50 33        
74 0         0 $dsn .= 'dbname=dbname;host=hostname';
75             }
76             elsif ( $dbd eq 'Oracle' && ! $args->{'dsn'} ) {
77 0         0 $db_user = "$db_user/$db_pass@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)" .
78             "(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=sid)))";
79 0         0 $db_pass = '';
80             }
81             elsif ( $dbd eq 'mysql' && ! $args->{'dsn'} ) {
82 0         0 $dsn .= 'dbname';
83             }
84              
85 1         18 my $template = Template->new;
86 1         19006 my $template_text = template();
87 1         2 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 1 50       107 perl => $Config{'startperl'},
97             skip => \%skip,
98             skiplike => $skiplike,
99             },
100             \$out
101             ) or die $template->error;
102              
103 1         654 return $out;
104             }
105              
106             sub template {
107             #
108             # Returns the template to be processed by Template Toolkit
109             #
110 1     1 0 3 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 Ekclark@cpan.orgE.
284              
285             =cut