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   3594164 use strict;
  24         65  
  24         811  
12 24     24   135 use warnings;
  24         51  
  24         2282  
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   153 use strict;
  24         45  
  24         597  
29              
30 24     24   140 use vars qw( @ISA $VERSION $ATTRIBUTION $drh $err $errstr $sqlstate );
  24         48  
  24         4032  
31              
32             @ISA = qw( DBD::File );
33              
34             $VERSION = "0.58";
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   201 use strict;
  24         63  
  24         743  
50              
51 24     24   986 use Text::CSV_XS ();
  24         16001  
  24         614  
52 24     24   135 use vars qw( @ISA @CSV_TYPES );
  24         46  
  24         8120  
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   665501 my ($drh, $dbname, $user, $auth, $attr) = @_;
79 259 50 33     2019 if ($attr && ref $attr eq "HASH") {
80             # Top-level aliasses
81 259         892 foreach my $key (grep { exists $attr->{$_} } @f_SHORT) {
  2331         5046  
82 3         5 my $f_key = "f_$key";
83 3 50       6 exists $attr->{$f_key} and next;
84 3         7 $attr->{$f_key} = delete $attr->{$key};
85             }
86 259         705 foreach my $key (grep { exists $attr->{$_} } @c_SHORT) {
  5698         9445  
87 3         5 my $c_key = "csv_$key";
88 3 50       8 exists $attr->{$c_key} and next;
89 3         4 $attr->{$c_key} = delete $attr->{$key};
90             }
91             }
92              
93 259         2100 my $dbh = $drh->DBD::File::dr::connect ($dbname, $user, $auth, $attr);
94 259 50       257422 $dbh and $dbh->{Active} = 1;
95 259         2861 $dbh;
96             } # connect
97              
98             # --- DATABASE -----------------------------------------------------------------
99              
100             package DBD::CSV::db;
101              
102 24     24   206 use strict;
  24         90  
  24         14977  
103              
104             our $imp_data_size = 0;
105             our @ISA = qw( DBD::File::db );
106              
107             sub set_versions {
108 259     259   11893 my $this = shift;
109 259         846 $this->{csv_version} = $DBD::CSV::VERSION;
110 259         938 return $this->SUPER::set_versions ();
111             } # set_versions
112              
113             my %csv_xs_attr;
114              
115             sub init_valid_attributes {
116 259     259   32158 my $dbh = shift;
117              
118             # Straight from Text::CSV_XS.pm
119 259         1565 my @xs_attr = @c_SHORT;
120 259         2449 @csv_xs_attr{@xs_attr} = ();
121             # Dynamically add "new" attributes - available in Text::CSV_XS-1.20
122 259 50       635 if (my @ka = eval { Text::CSV_XS->known_attributes }) {
  259         2118  
123 259 100       10319 for (grep { m/^[a-su-z]/ && !exists $csv_xs_attr{$_} } @ka) {
  7511         21914  
124 144         289 push @xs_attr => $_;
125 144         298 $csv_xs_attr{$_} = undef;
126             }
127             };
128              
129 259         1519 $dbh->{csv_xs_valid_attrs} = [ @xs_attr ];
130              
131 259         906 $dbh->{csv_valid_attrs} = { map {("csv_$_" => 1 )} @xs_attr, qw(
  8950         18336  
132              
133             class tables in csv_in out csv_out skip_first_row
134              
135             null sep quote escape bom
136             )};
137              
138 259         1358 $dbh->{csv_readonly_attrs} = { };
139              
140 259         660 $dbh->{csv_meta} = "csv_tables";
141              
142 259         1178 return $dbh->SUPER::init_valid_attributes ();
143             } # init_valid_attributes
144              
145             sub get_csv_versions {
146 1     1   345 my ($dbh, $table) = @_;
147 1   50     9 $table ||= "";
148 1         2 my $class = $dbh->{ImplementorClass};
149 1         6 $class =~ s/::db$/::Table/;
150 1         3 my $meta;
151 1 50       3 $table and (undef, $meta) = $class->get_table_meta ($dbh, $table, 1);
152 1 50       5 unless ($meta) {
153 1         4 $meta = {};
154 1         8 $class->bootstrap_table_meta ($dbh, $meta, $table);
155             }
156 1         106 my $dvsn = eval { $meta->{csv_class}->VERSION (); };
  1         19  
157 1         4 my $dtype = $meta->{csv_class};
158 1 50       6 $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   311906 my ($dbh, $info_type) = @_;
164 209         4222 require DBD::CSV::GetInfo;
165 209         840 my $v = $DBD::CSV::GetInfo::info{int ($info_type)};
166 209 50       812 ref $v eq "CODE" and $v = $v->($dbh);
167 209         592 return $v;
168             } # get_info
169              
170             sub type_info_all {
171             # my $dbh = shift;
172 1     1   4735 require DBD::CSV::TypeInfo;
173 1         6 return [@$DBD::CSV::TypeInfo::type_info_all];
174             } # type_info_all
175              
176             # --- STATEMENT ----------------------------------------------------------------
177              
178             package DBD::CSV::st;
179              
180 24     24   190 use strict;
  24         55  
  24         1628  
181              
182             our $imp_data_size = 0;
183             our @ISA = qw( DBD::File::st );
184              
185             package DBD::CSV::Statement;
186              
187 24     24   169 use strict;
  24         78  
  24         612  
188 24     24   199 use Carp;
  24         46  
  24         2354  
189              
190             our @ISA = qw( DBD::File::Statement );
191              
192             package DBD::CSV::Table;
193              
194 24     24   186 use strict;
  24         70  
  24         929  
195 24     24   148 use Carp;
  24         304  
  24         32426  
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   9393 my $class = shift;
205              
206 6301         8248 my $x = 0;
207 6301 100       12545 if (!%compat_map) {
208 23         375 $compat_map{$_} = "f_$_" for @f_SHORT;
209 23         552 $compat_map{$_} = "csv_$_" for @c_SHORT;
210 23         58 $x++;
211             }
212 6301 100 66     22923 if ($class and !$class_mapped{$class}++ and
      100        
213 25         245 my @ka = eval { $class->known_attributes }) {
214             # exclude types
215 23         1571 $compat_map{$_} = "csv_$_" for grep m/^[a-su-z]/ => @ka;
216 23         63 $x++;
217             }
218 6301 100       12619 if ($x) {
219 23         249 __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   5341849 my ($self, $dbh, $meta, $table) = @_;
232 5990   100     39928 $meta->{csv_class} ||= $dbh->{csv_class} || "Text::CSV_XS";
      66        
233 5990   100     26610 $meta->{csv_eol} ||= $dbh->{csv_eol} || "\r\n";
      66        
234              
235 5990         14499 _register_compat_map ($meta->{csv_class});
236              
237             exists $meta->{csv_skip_first_row} or
238 5990 100       14667 $meta->{csv_skip_first_row} = $dbh->{csv_skip_first_row};
239             exists $meta->{csv_bom} or
240 5990 50       16815 $meta->{csv_bom} = exists $dbh->{bom} ? $dbh->{bom} : $dbh->{csv_bom};
    100          
241 5990         16115 $self->SUPER::bootstrap_table_meta ($dbh, $meta, $table);
242             } # bootstrap_table_meta
243              
244             sub init_table_meta {
245 291     291   250331 my ($self, $dbh, $meta, $table) = @_;
246              
247 291         1126 _register_compat_map ($meta->{csv_class});
248              
249 291         1422 $self->SUPER::init_table_meta ($dbh, $table, $meta);
250              
251 291   66     2211 my $csv_in = $meta->{csv_in} || $dbh->{csv_csv_in};
252 291 100       805 unless ($csv_in) {
253 275         1105 my %opts = ( binary => 1, auto_diag => 1 );
254              
255             # Allow specific Text::CSV_XS options
256 275         524 foreach my $attr (@{$dbh->{csv_xs_valid_attrs}}) {
  275         933  
257 6212 100       9405 $attr eq "eol" and next; # Handles below
258 5937 100       12627 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     1607 $opts{blank_is_undef} = 1;
    100          
263              
264 275         741 my $class = $meta->{csv_class};
265 275         554 my $eol = $meta->{csv_eol};
266 275 50       1492 $eol =~ m/^\A(?:[\r\n]|\r\n)\Z/ or $opts{eol} = $eol;
267 275         1370 for ([ "sep", ',' ],
268             [ "quote", '"' ],
269             [ "escape", '"' ],
270             ) {
271 825         1879 my ($attr, $def) = ($_->[0]."_char", $_->[1]);
272             $opts{$attr} =
273             exists $meta->{$attr} ? $meta->{$attr} :
274 825 100       3166 exists $dbh->{"csv_$attr"} ? $dbh->{"csv_$attr"} : $def;
    50          
275             }
276 275 50       2574 $meta->{csv_in} = $class->new (\%opts) or
277             $class->error_diag;
278 273         57175 $opts{eol} = $eol;
279 273 50       997 $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   4301 my ($class, $meta, $attr, $value) = @_;
286              
287 20         51 _register_compat_map ($meta->{csv_class});
288              
289 20         46 (my $csv_attr = $attr) =~ s/^csv_//;
290 20 100       44 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     35 $meta->{$_}{$csv_attr} = $value;
294             }
295             }
296              
297 20         63 $class->SUPER::table_meta_attr_changed ($meta, $attr, $value);
298             } # table_meta_attr_changed
299              
300             sub open_data {
301 437     437   1369918 my ($self, $meta, $attrs, $flags) = @_;
302 437         1938 $self->SUPER::open_file ($meta, $attrs, $flags);
303              
304 427 50 33     132867 if ($meta && $meta->{fh}) {
305 427         1305 $attrs->{csv_csv_in} = $meta->{csv_in};
306 427         985 $attrs->{csv_csv_out} = $meta->{csv_out};
307 427 100       1378 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         5 my $t = [];
312 2         6 for (@{$types}) {
  2         6  
313 6 100 66     27 $_ = $_
314             ? $DBD::CSV::dr::CSV_TYPES[$_ + 6] || Text::CSV_XS::PV ()
315             : Text::CSV_XS::PV ();
316 6         17 push @$t, $_;
317             }
318 2         5 $meta->{types} = $t;
319             }
320 427 100       1308 if (!$flags->{createMode}) {
321 397         1021 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       1951 : exists $meta->{col_names} ? 0 : 1;
    50          
    100          
327             defined $meta->{skip_rows} or
328 397 100       1201 $meta->{skip_rows} = $skipRows;
329 397 50       1194 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       1157 if ($skipRows--) {
336             $array = $attrs->{csv_csv_in}->getline ($meta->{fh}) or
337 391 50       19587 croak "Missing first row due to ".$attrs->{csv_csv_in}->error_diag;
338 391 50       29288 unless ($meta->{raw_header}) {
339 391         2176 s/\W/_/g for @$array;
340             }
341             defined $meta->{col_names} or
342 391 100       1366 $meta->{col_names} = $array;
343 391         1185 while ($skipRows--) {
344 12         354 $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       1730 $meta->{first_row_pos} = $meta->{fh}->tell ();
351             exists $meta->{col_names} and
352 397 50       1932 $array = $meta->{col_names};
353 397 100 66     1426 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         69 $attrs->{csv_csv_in}->getline ($meta->{fh});
358 1         67 $array = $meta->{col_names};
359 1         5 push @$array, map { "col$_" } 0 .. $#$ar;
  4         14  
360             }
361             }
362             }
363             } # open_file
364              
365 24     24   295 no warnings 'once';
  24         57  
  24         1742  
366             $DBI::VERSION < 1.623 and
367             *open_file = \&open_data;
368 24     24   184 use warnings;
  24         47  
  24         11293  
369              
370             sub _csv_diag {
371 2     2   8 my @diag = $_[0]->error_diag;
372 2         50 for (2, 3) {
373 4 50       14 defined $diag[$_] or $diag[$_] = "?";
374             }
375 2         7 return @diag;
376             } # _csv_diag
377              
378             sub fetch_row {
379 1177     1177   285883 my ($self, $data) = @_;
380              
381 1177         1958 my $tbl = $self->{meta};
382              
383             exists $tbl->{cached_row} and
384 1177 100       2713 return $self->{row} = delete $tbl->{cached_row};
385              
386             my $csv = $self->{csv_csv_in} or
387 1176 50       2473 return do { $data->set_err ($DBI::stderr, "Fetch from undefined handle"); undef };
  0         0  
  0         0  
388              
389 1176         1660 my $fields = eval { $csv->getline ($tbl->{fh}) };
  1176         27852  
390 1176 100       46650 unless ($fields) {
391 285 100       1719 $csv->eof and return;
392              
393 2         20 my @diag = _csv_diag ($csv);
394 2 50       7 $diag[0] == 2012 and return; # Also EOF (broken in Text::CSV_XS-1.10)
395              
396 2         5 my $file = $tbl->{f_fqfn};
397 2         528 croak "Error $diag[0] while reading file $file: $diag[1] \@ line $diag[3] pos $diag[2]";
398             }
399 891         2165 @$fields < @{$tbl->{col_names}} and
400 891 50       1401 push @$fields, (undef) x (@{$tbl->{col_names}} - @$fields);
  0         0  
401 891 50       3524 $self->{row} = (@$fields ? $fields : undef);
402             } # fetch_row
403              
404             sub push_row {
405 189     189   59888 my ($self, $data, $fields) = @_;
406 189         355 my $tbl = $self->{meta};
407 189         299 my $csv = $self->{csv_csv_out};
408 189         282 my $fh = $tbl->{fh};
409              
410 189 50       1722 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   188 no warnings 'once';
  24         87  
  24         1208  
420             *push_names = \&push_row;
421 24     24   146 use warnings;
  24         64  
  24         1457  
422              
423             1;
424              
425             __END__