File Coverage

blib/lib/DBD/CSV.pm
Criterion Covered Total %
statement 207 218 94.9
branch 78 110 70.9
condition 25 41 60.9
subroutine 31 32 96.8
pod n/a
total 341 401 85.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #
3             # DBD::CSV - A DBI driver for CSV and similar structured files
4             #
5             # This module is currently maintained by
6             #
7             # H.Merijn Brand
8             #
9             # See for full acknowledgements the last two pod sections in this file
10              
11 24     24   3789484 use strict;
  24         64  
  24         937  
12 24     24   158 use warnings;
  24         59  
  24         2373  
13              
14             require DynaLoader;
15             require DBD::File;
16             require IO::File;
17              
18             our @f_SHORT = qw( class file dir dir_search ext lock lockfile schema encoding );
19             our @c_SHORT = qw( eof
20             eol sep_char quote_char escape_char binary decode_utf8 auto_diag
21             diag_verbose blank_is_undef empty_is_undef allow_whitespace
22             allow_loose_quotes allow_loose_escapes allow_unquoted_escape
23             always_quote quote_empty quote_space escape_null quote_binary
24             keep_meta_info callbacks );
25              
26             package DBD::CSV;
27              
28 24     24   185 use strict;
  24         60  
  24         643  
29              
30 24     24   127 use vars qw( @ISA $VERSION $ATTRIBUTION $drh $err $errstr $sqlstate );
  24         51  
  24         4445  
31              
32             @ISA = qw( DBD::File );
33              
34             $VERSION = "0.59";
35             $ATTRIBUTION = "DBD::CSV $DBD::CSV::VERSION by H.Merijn Brand";
36              
37             $err = 0; # holds error code for DBI::err
38             $errstr = ""; # holds error string for DBI::errstr
39             $sqlstate = ""; # holds error state for DBI::state
40             $drh = undef; # holds driver handle once initialized
41              
42       0     sub CLONE { # empty method: prevent warnings when threads are cloned
43             } # CLONE
44              
45             # --- DRIVER -------------------------------------------------------------------
46              
47             package DBD::CSV::dr;
48              
49 24     24   229 use strict;
  24         70  
  24         697  
50              
51 24     24   1142 use Text::CSV_XS ();
  24         20398  
  24         595  
52 24     24   202 use vars qw( @ISA @CSV_TYPES );
  24         50  
  24         8793  
53              
54             @CSV_TYPES = (
55             Text::CSV_XS::IV (), # SQL_TINYINT
56             Text::CSV_XS::IV (), # SQL_BIGINT
57             Text::CSV_XS::PV (), # SQL_LONGVARBINARY
58             Text::CSV_XS::PV (), # SQL_VARBINARY
59             Text::CSV_XS::PV (), # SQL_BINARY
60             Text::CSV_XS::PV (), # SQL_LONGVARCHAR
61             Text::CSV_XS::PV (), # SQL_ALL_TYPES
62             Text::CSV_XS::PV (), # SQL_CHAR
63             Text::CSV_XS::NV (), # SQL_NUMERIC
64             Text::CSV_XS::NV (), # SQL_DECIMAL
65             Text::CSV_XS::IV (), # SQL_INTEGER
66             Text::CSV_XS::IV (), # SQL_SMALLINT
67             Text::CSV_XS::NV (), # SQL_FLOAT
68             Text::CSV_XS::NV (), # SQL_REAL
69             Text::CSV_XS::NV (), # SQL_DOUBLE
70             );
71              
72             our @ISA = qw( DBD::File::dr );
73              
74             our $imp_data_size = 0;
75             our $data_sources_attr = undef;
76              
77             sub connect {
78 259     259   699051 my ($drh, $dbname, $user, $auth, $attr) = @_;
79 259 50 33     1813 if ($attr && ref $attr eq "HASH") {
80             # Top-level aliasses
81 259         771 foreach my $key (grep { exists $attr->{$_} } @f_SHORT) {
  2331         4786  
82 3         7 my $f_key = "f_$key";
83 3 50       8 exists $attr->{$f_key} and next;
84 3         8 $attr->{$f_key} = delete $attr->{$key};
85             }
86 259         589 foreach my $key (grep { exists $attr->{$_} } @c_SHORT) {
  5698         9718  
87 3         17 my $c_key = "csv_$key";
88 3 50       11 exists $attr->{$c_key} and next;
89 3         7 $attr->{$c_key} = delete $attr->{$key};
90             }
91             }
92              
93 259         1861 my $dbh = $drh->DBD::File::dr::connect ($dbname, $user, $auth, $attr);
94 259 50       257458 $dbh and $dbh->{Active} = 1;
95 259         3003 $dbh;
96             } # connect
97              
98             # --- DATABASE -----------------------------------------------------------------
99              
100             package DBD::CSV::db;
101              
102 24     24   201 use strict;
  24         65  
  24         16414  
103              
104             our $imp_data_size = 0;
105             our @ISA = qw( DBD::File::db );
106              
107             sub set_versions {
108 259     259   10788 my $this = shift;
109 259         636 $this->{csv_version} = $DBD::CSV::VERSION;
110 259         801 return $this->SUPER::set_versions ();
111             } # set_versions
112              
113             my %csv_xs_attr;
114              
115             sub init_valid_attributes {
116 259     259   28943 my $dbh = shift;
117              
118             # Straight from Text::CSV_XS.pm
119 259         1501 my @xs_attr = @c_SHORT;
120 259         2030 @csv_xs_attr{@xs_attr} = ();
121             # Dynamically add "new" attributes - available in Text::CSV_XS-1.20
122 259 50       612 if (my @ka = eval { Text::CSV_XS->known_attributes }) {
  259         1687  
123 259 100       10188 for (grep { m/^[a-su-z]/ && !exists $csv_xs_attr{$_} } @ka) {
  8029         24485  
124 192         397 push @xs_attr => $_;
125 192         407 $csv_xs_attr{$_} = undef;
126             }
127             };
128              
129 259         1541 $dbh->{csv_xs_valid_attrs} = [ @xs_attr ];
130              
131 259         816 $dbh->{csv_valid_attrs} = { map {("csv_$_" => 1 )} @xs_attr, qw(
  8998         19123  
132              
133             class tables in csv_in out csv_out skip_first_row
134              
135             null sep quote escape bom
136             )};
137              
138 259         1248 $dbh->{csv_readonly_attrs} = { };
139              
140 259         592 $dbh->{csv_meta} = "csv_tables";
141              
142 259         1143 return $dbh->SUPER::init_valid_attributes ();
143             } # init_valid_attributes
144              
145             sub get_csv_versions {
146 1     1   303 my ($dbh, $table) = @_;
147 1   50     7 $table ||= "";
148 1         2 my $class = $dbh->{ImplementorClass};
149 1         4 $class =~ s/::db$/::Table/;
150 1         2 my $meta;
151 1 50       4 $table and (undef, $meta) = $class->get_table_meta ($dbh, $table, 1);
152 1 50       5 unless ($meta) {
153 1         2 $meta = {};
154 1         7 $class->bootstrap_table_meta ($dbh, $meta, $table);
155             }
156 1         65 my $dvsn = eval { $meta->{csv_class}->VERSION (); };
  1         12  
157 1         12 my $dtype = $meta->{csv_class};
158 1 50       7 $dvsn and $dtype .= " ($dvsn)";
159 1         9 return sprintf "%s using %s", $dbh->{csv_version}, $dtype;
160             } # get_csv_versions
161              
162             sub get_info {
163 209     209   309506 my ($dbh, $info_type) = @_;
164 209         4047 require DBD::CSV::GetInfo;
165 209         754 my $v = $DBD::CSV::GetInfo::info{int ($info_type)};
166 209 50       710 ref $v eq "CODE" and $v = $v->($dbh);
167 209         541 return $v;
168             } # get_info
169              
170             sub type_info_all {
171             # my $dbh = shift;
172 1     1   6247 require DBD::CSV::TypeInfo;
173 1         7 return [@$DBD::CSV::TypeInfo::type_info_all];
174             } # type_info_all
175              
176             # --- STATEMENT ----------------------------------------------------------------
177              
178             package DBD::CSV::st;
179              
180 24     24   224 use strict;
  24         62  
  24         1657  
181              
182             our $imp_data_size = 0;
183             our @ISA = qw( DBD::File::st );
184              
185             package DBD::CSV::Statement;
186              
187 24     24   161 use strict;
  24         46  
  24         647  
188 24     24   146 use Carp;
  24         53  
  24         2384  
189              
190             our @ISA = qw( DBD::File::Statement );
191              
192             package DBD::CSV::Table;
193              
194 24     24   192 use strict;
  24         89  
  24         1039  
195 24     24   153 use Carp;
  24         332  
  24         34797  
196              
197             our @ISA = qw( DBD::File::Table );
198              
199             my %compat_map;
200              
201             { my %class_mapped;
202              
203             sub _register_compat_map {
204 6301     6301   9883 my $class = shift;
205              
206 6301         9104 my $x = 0;
207 6301 100       12491 if (!%compat_map) {
208 23         413 $compat_map{$_} = "f_$_" for @f_SHORT;
209 23         613 $compat_map{$_} = "csv_$_" for @c_SHORT;
210 23         69 $x++;
211             }
212 6301 100 66     24273 if ($class and !$class_mapped{$class}++ and
      100        
213 25         259 my @ka = eval { $class->known_attributes }) {
214             # exclude types
215 23         1776 $compat_map{$_} = "csv_$_" for grep m/^[a-su-z]/ => @ka;
216 23         71 $x++;
217             }
218 6301 100       13466 if ($x) {
219 23         284 __PACKAGE__->register_compat_map (\%compat_map);
220             }
221             } # _register_compat_map
222             }
223              
224             #sub DESTROY {
225             # my $self = shift or return;
226             #
227             # $self->{meta} and delete $self->{meta}{csv_in};
228             # } # DESTROY
229              
230             sub bootstrap_table_meta {
231 5990     5990   5547324 my ($self, $dbh, $meta, $table) = @_;
232 5990   100     41045 $meta->{csv_class} ||= $dbh->{csv_class} || "Text::CSV_XS";
      66        
233 5990   100     26790 $meta->{csv_eol} ||= $dbh->{csv_eol} || "\r\n";
      66        
234              
235 5990         13567 _register_compat_map ($meta->{csv_class});
236              
237             exists $meta->{csv_skip_first_row} or
238 5990 100       14963 $meta->{csv_skip_first_row} = $dbh->{csv_skip_first_row};
239             exists $meta->{csv_bom} or
240 5990 50       17452 $meta->{csv_bom} = exists $dbh->{bom} ? $dbh->{bom} : $dbh->{csv_bom};
    100          
241 5990         16156 $self->SUPER::bootstrap_table_meta ($dbh, $meta, $table);
242             } # bootstrap_table_meta
243              
244             sub init_table_meta {
245 291     291   260979 my ($self, $dbh, $meta, $table) = @_;
246              
247 291         1012 _register_compat_map ($meta->{csv_class});
248              
249 291         1403 $self->SUPER::init_table_meta ($dbh, $table, $meta);
250              
251 291   66     2226 my $csv_in = $meta->{csv_in} || $dbh->{csv_csv_in};
252 291 100       722 unless ($csv_in) {
253 275         1064 my %opts = ( binary => 1, auto_diag => 1 );
254              
255             # Allow specific Text::CSV_XS options
256 275         412 foreach my $attr (@{$dbh->{csv_xs_valid_attrs}}) {
  275         946  
257 6266 100       9957 $attr eq "eol" and next; # Handles below
258 5991 100       12559 exists $dbh->{"csv_$attr"} and $opts{$attr} = $dbh->{"csv_$attr"};
259             }
260             $dbh->{csv_null} || $meta->{csv_null} and
261             $opts{Text::CSV_XS->version < 1.18 ? "always_quote" : "quote_empty"} =
262 275 50 66     1457 $opts{blank_is_undef} = 1;
    100          
263              
264 275         681 my $class = $meta->{csv_class};
265 275         524 my $eol = $meta->{csv_eol};
266 275 50       1277 $eol =~ m/^\A(?:[\r\n]|\r\n)\Z/ or $opts{eol} = $eol;
267 275         1197 for ([ "sep", ',' ],
268             [ "quote", '"' ],
269             [ "escape", '"' ],
270             ) {
271 825         1948 my ($attr, $def) = ($_->[0]."_char", $_->[1]);
272             $opts{$attr} =
273             exists $meta->{$attr} ? $meta->{$attr} :
274 825 100       3181 exists $dbh->{"csv_$attr"} ? $dbh->{"csv_$attr"} : $def;
    50          
275             }
276 275 50       2263 $meta->{csv_in} = $class->new (\%opts) or
277             $class->error_diag;
278 273         53118 $opts{eol} = $eol;
279 273 50       1038 $meta->{csv_out} = $class->new (\%opts) or
280             $class->error_diag;
281             }
282             } # init_table_meta
283              
284             sub table_meta_attr_changed {
285 20     20   4890 my ($class, $meta, $attr, $value) = @_;
286              
287 20         63 _register_compat_map ($meta->{csv_class});
288              
289 20         57 (my $csv_attr = $attr) =~ s/^csv_//;
290 20 100       51 if (exists $csv_xs_attr{$csv_attr}) {
291 3         11 for ("csv_in", "csv_out") {
292             exists $meta->{$_} && exists $meta->{$_}{$csv_attr} and
293 6 50 33     37 $meta->{$_}{$csv_attr} = $value;
294             }
295             }
296              
297 20         80 $class->SUPER::table_meta_attr_changed ($meta, $attr, $value);
298             } # table_meta_attr_changed
299              
300             sub open_data {
301 437     437   1383341 my ($self, $meta, $attrs, $flags) = @_;
302 437         1965 $self->SUPER::open_file ($meta, $attrs, $flags);
303              
304 427 50 33     113970 if ($meta && $meta->{fh}) {
305 427         1221 $attrs->{csv_csv_in} = $meta->{csv_in};
306 427         976 $attrs->{csv_csv_out} = $meta->{csv_out};
307 427 100       1296 if (my $types = $meta->{types}) {
308             # XXX $meta->{types} is nowhere assigned and should better $meta->{csv_types}
309             # The 'types' array contains DBI types, but we need types
310             # suitable for Text::CSV_XS.
311 2         6 my $t = [];
312 2         5 for (@{$types}) {
  2         9  
313 6 100 66     35 $_ = $_
314             ? $DBD::CSV::dr::CSV_TYPES[$_ + 6] || Text::CSV_XS::PV ()
315             : Text::CSV_XS::PV ();
316 6         20 push @$t, $_;
317             }
318 2         8 $meta->{types} = $t;
319             }
320 427 100       1476 if (!$flags->{createMode}) {
321 397         695 my $array;
322             my $skipRows = defined $meta->{skip_rows}
323             ? $meta->{skip_rows}
324             : defined $meta->{csv_skip_first_row}
325             ? 1
326 397 100       1773 : exists $meta->{col_names} ? 0 : 1;
    50          
    100          
327             defined $meta->{skip_rows} or
328 397 100       1151 $meta->{skip_rows} = $skipRows;
329 397 50       1078 if ($meta->{csv_bom}) {
330             my @hdr = $attrs->{csv_csv_in}->header ($meta->{fh}) or
331 0 0       0 croak "Failed using the header row: ".$attrs->{csv_csv_in}->error_diag;
332 0   0     0 $meta->{col_names} ||= \@hdr;
333 0 0       0 $skipRows and $skipRows = 0;
334             }
335 397 100       1073 if ($skipRows--) {
336             $array = $attrs->{csv_csv_in}->getline ($meta->{fh}) or
337 391 50       18567 croak "Missing first row due to ".$attrs->{csv_csv_in}->error_diag;
338 391 50       30927 unless ($meta->{raw_header}) {
339 391         2173 s/\W/_/g for @$array;
340             }
341             defined $meta->{col_names} or
342 391 100       1340 $meta->{col_names} = $array;
343 391         1328 while ($skipRows--) {
344 12         443 $attrs->{csv_csv_in}->getline ($meta->{fh});
345             }
346             }
347             # lockMode is set 1 for DELETE, INSERT or UPDATE
348             # no other case need seeking
349             $flags->{lockMode} and # $meta->{fh}->can ("tell") and
350 397 100       1680 $meta->{first_row_pos} = $meta->{fh}->tell ();
351             exists $meta->{col_names} and
352 397 50       1787 $array = $meta->{col_names};
353 397 100 66     1146 if (!$meta->{col_names} || !@{$meta->{col_names}}) {
354             # No column names given; fetch first row and create default
355             # names.
356             my $ar = $meta->{cached_row} =
357 1         76 $attrs->{csv_csv_in}->getline ($meta->{fh});
358 1         87 $array = $meta->{col_names};
359 1         6 push @$array, map { "col$_" } 0 .. $#$ar;
  4         14  
360             }
361             }
362             }
363             } # open_file
364              
365 24     24   228 no warnings 'once';
  24         69  
  24         1679  
366             $DBI::VERSION < 1.623 and
367             *open_file = \&open_data;
368 24     24   171 use warnings;
  24         58  
  24         11927  
369              
370             sub _csv_diag {
371 2     2   9 my @diag = $_[0]->error_diag;
372 2         64 for (2, 3) {
373 4 50       24 defined $diag[$_] or $diag[$_] = "?";
374             }
375 2         7 return @diag;
376             } # _csv_diag
377              
378             sub fetch_row {
379 1177     1177   279899 my ($self, $data) = @_;
380              
381 1177         2031 my $tbl = $self->{meta};
382              
383             exists $tbl->{cached_row} and
384 1177 100       2792 return $self->{row} = delete $tbl->{cached_row};
385              
386             my $csv = $self->{csv_csv_in} or
387 1176 50       2482 return do { $data->set_err ($DBI::stderr, "Fetch from undefined handle"); undef };
  0         0  
  0         0  
388              
389 1176         1670 my $fields = eval { $csv->getline ($tbl->{fh}) };
  1176         28968  
390 1176 100       49163 unless ($fields) {
391 285 100       1267 $csv->eof and return;
392              
393 2         26 my @diag = _csv_diag ($csv);
394 2 50       10 $diag[0] == 2012 and return; # Also EOF (broken in Text::CSV_XS-1.10)
395              
396 2         6 my $file = $tbl->{f_fqfn};
397 2         553 croak "Error $diag[0] while reading file $file: $diag[1] \@ line $diag[3] pos $diag[2]";
398             }
399 891         2132 @$fields < @{$tbl->{col_names}} and
400 891 50       1411 push @$fields, (undef) x (@{$tbl->{col_names}} - @$fields);
  0         0  
401 891 50       3377 $self->{row} = (@$fields ? $fields : undef);
402             } # fetch_row
403              
404             sub push_row {
405 189     189   61994 my ($self, $data, $fields) = @_;
406 189         359 my $tbl = $self->{meta};
407 189         288 my $csv = $self->{csv_csv_out};
408 189         289 my $fh = $tbl->{fh};
409              
410 189 50       1833 unless ($csv->print ($fh, $fields)) {
411 0         0 my @diag = _csv_diag ($csv);
412 0         0 my $file = $tbl->{f_fqfn};
413 0         0 return do { $data->set_err ($DBI::stderr,
  0         0  
414 0         0 "Error $diag[0] while writing file $file: $diag[1] \@ line $diag[3] pos $diag[2]"); undef };
415             }
416 189         2832 1;
417             } # push_row
418              
419 24     24   194 no warnings 'once';
  24         58  
  24         1443  
420             *push_names = \&push_row;
421 24     24   164 use warnings;
  24         56  
  24         1690  
422              
423             1;
424              
425             __END__