Skip to content

Commit 1fe1043

Browse files
authored
Merge pull request #12 from pilcrow/sponge-PRECISION
DBD::Sponge corrected and lazy default PRECISION
2 parents 680da7e + 5372c94 commit 1fe1043

2 files changed

Lines changed: 115 additions & 13 deletions

File tree

lib/DBD/Sponge.pm

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,15 @@ use warnings;
9393
|| [ map { "col$_" } 1..$numFields ];
9494
$sth->{TYPE} = $attribs->{TYPE}
9595
|| [ (DBI::SQL_VARCHAR()) x $numFields ];
96-
$sth->{PRECISION} = $attribs->{PRECISION}
97-
|| [ map { length($sth->{NAME}->[$_]) } 0..$numFields -1 ];
9896
$sth->{SCALE} = $attribs->{SCALE}
9997
|| [ (0) x $numFields ];
10098
$sth->{NULLABLE} = $attribs->{NULLABLE}
10199
|| [ (2) x $numFields ];
100+
# Allow user to specify precision, otherwise
101+
# FETCH will lazily compute if needed
102+
if ($attribs->{PRECISION}) {
103+
$sth->{PRECISION} = $attribs->{PRECISION};
104+
}
102105
}
103106

104107
$outer;
@@ -154,6 +157,7 @@ use warnings;
154157
return $dbh->set_err(42, "not enough parameters") unless @args >= 2;
155158
return \@args;
156159
}
160+
157161
}
158162

159163

@@ -202,6 +206,11 @@ use warnings;
202206
my ($sth, $attrib) = @_;
203207
# would normally validate and only fetch known attributes
204208
# else pass up to DBI to handle
209+
210+
if ($attrib eq 'PRECISION') {
211+
# prepare() did _not_ specify PRECISION, so lazily compute it now
212+
return $sth->{PRECISION} = _max_col_lengths(@{$sth}{'NUM_OF_FIELDS', 'rows'});
213+
}
205214
return $sth->SUPER::FETCH($attrib);
206215
}
207216

@@ -211,6 +220,22 @@ use warnings;
211220
# else pass up to DBI to handle
212221
return $sth->SUPER::STORE($attrib, $value);
213222
}
223+
224+
sub _max_col_lengths {
225+
# compute our columns' PRECISION (data length) by looking for the
226+
# max lengths of each column's data, row by row
227+
my ($num_of_fields, $rows) = @_;
228+
my @precision = (0,) x $num_of_fields;
229+
my $n = $num_of_fields - 1;
230+
my $len;
231+
for my $row (@$rows) {
232+
for my $i (0 .. $n) {
233+
next unless defined($len = length($row->[$i]));
234+
$precision[$i] = $len if $len > $precision[$i];
235+
}
236+
}
237+
return \@precision;
238+
}
214239
}
215240

216241
1;
@@ -261,33 +286,38 @@ No username and password are needed.
261286
262287
=item *
263288
264-
The C<$statement> here is an arbitrary statement or name you want
265-
to provide as identity of your data. If you're using DBI::Profile
266-
it will appear in the profile data.
289+
The C<$statement> here is an arbitrary statement or name you want to
290+
provide as identity of your data. If you're using DBI::Profile it will
291+
appear in the profile data.
267292
268-
Generally it's expected that you are preparing a statement handle
269-
as if a C<select> statement happened.
293+
Generally it's expected that you are preparing a statement handle as if
294+
a C<select> statement happened.
270295
271296
=item *
272297
273-
C<$data> is a reference to the data you are providing, given as an array of arrays.
298+
C<$data> is a reference to the data you are providing, given as an array
299+
of arrays.
274300
275301
=item *
276302
277-
C<$names> is a reference an array of column names for the C<$data> you are providing.
278-
The number and order should match the number and ordering of the C<$data> columns.
303+
C<$names> is a reference an array of column names for the C<$data> you
304+
are providing. The number and order should match the number and
305+
ordering of the C<$data> columns.
279306
280307
=item *
281308
282-
C<%attr> is a hash of other standard DBI attributes that you might pass to a prepare statement.
309+
C<%attr> is a hash of other standard DBI attributes that you might pass
310+
to a prepare statement.
283311
284-
Currently only NAME, TYPE, and PRECISION are supported.
312+
Currently only NAME, TYPE, and PRECISION are supported. TYPE defaults
313+
to SQL_VARCHAR. PRECISION will be lazily computed if not supplied.
285314
286315
=back
287316
288317
=head1 BUGS
289318
290-
Using this module to prepare INSERT-like statements is not currently documented.
319+
Using this module to prepare INSERT-like statements is not currently
320+
documented.
291321
292322
=head1 AUTHOR AND COPYRIGHT
293323

t/xx_sponge.t

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#! /usr/bin/env perl
2+
3+
# vim: noet ts=2 sw=2:
4+
5+
use strict;
6+
use warnings;
7+
use Test::More tests => 17;
8+
9+
use Storable qw(dclone);
10+
use DBI qw(:sql_types);
11+
12+
# our reference table:
13+
#
14+
# A0 B1 C2
15+
# ------- --------- -------
16+
# foo NULL bazooka
17+
# foolery bar NULL
18+
# NULL barrowman baz
19+
#
20+
21+
# Historically, DBD::Sponge defaulted an sth's PRECISION to the length
22+
# of its column names, meaning that some DBI shells could truncate row
23+
# display. For example, formatting a row ('fo', NULL, 'ba') from our
24+
# reference table above.
25+
26+
our @NAMES = ( 'A0', 'B1', 'C2' );
27+
our @ROWS = (['foo', undef, 'bazooka'],
28+
['foolery', 'bar', undef ],
29+
[undef, 'barrowman', 'baz' ]);
30+
31+
my $dbh = DBI->connect("dbi:Sponge:", '', '');
32+
ok($dbh, "connect(dbi:Sponge:) succeeds");
33+
34+
my $sth = $dbh->prepare("simple, correct sponge", {
35+
rows => dclone( \@ROWS ),
36+
NAME => [ @NAMES ],
37+
});
38+
39+
ok($sth, "prepare() of 3x3 result succeeded");
40+
is_deeply($sth->{NAME}, ['A0', 'B1', 'C2'], "column NAMEs as expected");
41+
is_deeply($sth->{TYPE}, [SQL_VARCHAR, SQL_VARCHAR, SQL_VARCHAR],
42+
"column TYPEs default to SQL_VARCHAR");
43+
#
44+
# Old versions of DBD-Sponge defaulted PRECISION (data "length") to
45+
# length of the field _names_ rather than the length of the _data_.
46+
#
47+
is_deeply($sth->{PRECISION}, [7, 9, 7],
48+
"column PRECISION matches lengths of longest field data");
49+
is_deeply($sth->fetch(), $ROWS[0], "first row fetch as expected");
50+
is_deeply($sth->fetch(), $ROWS[1], "second row fetch as expected");
51+
is_deeply($sth->fetch(), $ROWS[2], "third row fetch as expected");
52+
ok(!defined($sth->fetch()), "fourth fetch returns undef");
53+
54+
# Test that DBD-Sponge preserves bogus user-supplied attributes but
55+
# ignores them when returning rows
56+
$sth = $dbh->prepare('user-supplied silly TYPE and PRECISION', {
57+
rows => dclone( \@ROWS ),
58+
NAME => [qw( first_col second_col third_col )],
59+
TYPE => [SQL_INTEGER, SQL_DATETIME, SQL_CHAR],
60+
PRECISION => [1, 100_000, 0],
61+
});
62+
ok($sth, "prepare() 3x3 result with TYPE and PRECISION succeeded");
63+
is_deeply($sth->{NAME}, ['first_col','second_col','third_col'],
64+
"column NAMEs again as expected");
65+
is_deeply($sth->{TYPE}, [SQL_INTEGER, SQL_DATETIME, SQL_CHAR],
66+
"column TYPEs not overwritten");
67+
is_deeply($sth->{PRECISION}, [1, 100_000, 0],
68+
"column PRECISION not overwritten");
69+
is_deeply($sth->fetch(), $ROWS[0], "first row fetch as expected, despite bogus attributes");
70+
is_deeply($sth->fetch(), $ROWS[1], "second row fetch as expected, despite bogus attributes");
71+
is_deeply($sth->fetch(), $ROWS[2], "third row fetch as expected, despite bogus attributes");
72+
ok(!defined($sth->fetch()), "fourth fetch returns undef, despite bogus attributes");

0 commit comments

Comments
 (0)