Can DBI infer or be informed about numeric column types when fetching rows?
Asked Answered
A

2

7

Suppose I am querying a table with something like the following:

$dbh->selectrow_hashref('SELECT id, name FROM foos WHERE name = "bar"');

Naturally, id would be an integer, but the resulting hashref will have a value internally stored as a Perl PV instead of an IV. This, in turn, will yield an undesirable result when serializing the data to, e.g., JSON.

Of course, one can manually call 0+ on the value, but is there a way to have DBI automatically store it as an actual integer and not merely as a string that looks like a number? Granted also that DBIx::Class and friends have a solution to this problem, but what about DBI by its lonesome?

Anaesthetize answered 27/3, 2014 at 19:14 Comment(0)
D
7

Depending on your database driver, you may* be able to use type hints in bind_col:

use DBI qw(:sql_types);

...

my $sth = $dbh->prepare('SELECT id, name FROM foos WHERE name = "bar"');
$sth->execute;

$sth->bind_col(1, undef, {
    TYPE => SQL_INTEGER,
    StrictlyTyped => 1,
    DiscardString => 1
});

while (my $hr = $sth->fetchrow_hashref) {
    say to_json $hr;
}

This attempts to bind the first column (indexed from one) to the SQL_INTEGER type and throws an error if the cast fails for any value. As bohica notes, the DiscardString attribute is necessary because it "throws away the string portion of your data (the pv)."


* According to the DBI documentation:

Few drivers support specifying a data type via a bind_col call (most will simply ignore the data type).

DBD::Oracle and DBD::ODBC support it, and DBD::Pg may support it, according to this thread (although I can't verify it), while DBD::mysql doesn't. I'm not sure about other drivers.

Dysphagia answered 27/3, 2014 at 21:17 Comment(0)
H
6

Although ThisSuitlsBlackNot is nearly correct there are some important omissions from his answer. As the author of most of the StrictlyTyped and DiscardString attributes I can tell you that in fact, the DiscardString attribute is far more important in this case.

DBI will attempt to cast your data to the TYPE specified and if that fails your data will be left alone and no error is generated. If the cast fails and StrictlyTyped is specified then an error is generated.

The DiscardString attribute throws away the string portion of your data (the pv) is discarded. This is particularly important when using JSON::XS and perhaps other JSON modules as JSON::XS specifically looks at the pv.

So really you should be doing at least:

$sth->bind_col(1, undef, {TYPE => SQL_INTEGER, DiscardString => 1});

NOTE few DBDs actually pay any attention to a bind type in bind_col. DBD::ODBC does because I maintain it. DBD::Oracle does not pay attention to the TYPE normally, except when used with StrictlyTyped and/or DiscardString (because I added that functionality for exactly the same problem you have).

Whether a driver supports those attributes or not can only truly be ascertained (unless documentation says so) by searching the code for use of sql_type_cast and their xs equivalents sql_type_cast_svpv etc. I don't believe DBD::Pg supports StrictlyTyped or DiscardString; in fact I believe at this time only DBD::ODBC and DBD::Oracle do (as I added them).

You might also find this interesting.

Helles answered 28/3, 2014 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.