Dont Shoot Yourself in the Foot: Check for Errors
Apparent ly, t he principle t hat you should check for errors is not so obvious or widely appreciat ed as one m ight hope. Many m essages post ed on MySQL- relat ed m ailing
list s are request s for help wit h program s t hat fail for reasons unknown t o t he people t hat wrot e t hem . I n a surprising num ber of cases, t he reason t hese developers are
m yst ified by t heir program s is t hat t hey put in no error checking, and t hus gave t hem selves no way t o know t hat t here was a problem or t o find out what it was You
cannot help yourself t his way. Plan for failure by checking for errors so t hat you can t ake appropriat e act ion if t hey occur.
Now were ready t o see how t o issue queries in each API . Not e t hat alt hough t he script s check for errors as necessary, for brevit y t hey j ust print a generic m essage t hat an error occurred.
You can display m ore specific error m essages using t he t echniques illust rat ed in Recipe 2.3
.
2.5.5 Perl
The Perl DBI m odule provides t wo basic approaches t o query execut ion, depending on whet her or not you expect t o get back a result set . To issue a query such as
INSERT
or
UPDATE
t hat ret urns no result set , use t he
do
m et hod. I t execut es t he query and ret urns t he num ber of rows affect ed by t he query, or
undef
if an error occurs. For exam ple, if Fred get s a new kit t y, t he following query can be used t o increm ent his
cats
count by one: my count = dbh-do UPDATE profile SET cats = cats+1 WHERE name =
Fred; if count print row count if no error occurred
{ count += 0;
print count rows were updated\n; }
I f t he query execut es successfully but affect s no rows,
do
ret urns a special value, t he st r ing
0E0
t hat is, t he value zero in scient ific not at ion .
0E0
can be used for t est ing t he execut ion st at us of a query because it is t rue in Boolean cont ext s unlike
undef
. For successful queries, it can also be used when count ing how m any rows were affect ed, because
it is t reat ed as t he num ber zero in num eric cont ext s. Of course, if you print t hat value as is, youll print
0E0
, which m ight look kind of weird t o people who use your program . The preceding exam ple shows one way t o m ake sure t his doesnt happen: adding zero t o t he value
explicit ly coerces it t o num eric form so t hat it displays as . You can also use
printf
w it h a
d
form at specifier t o cause an im plicit num eric conversion: my count = dbh-do UPDATE profile SET color = color WHERE name =
Fred; if count print row count if no error occurred
{ printf d rows were updated\n, count;
}
I f
RaiseError
is enabled, your script will t erm inat e aut om at ically if a DBI - relat ed error occurs and you dont need t o bot her checking
count
t o see if
do
failed: my count = dbh-do UPDATE profile SET color = color WHERE name =
Fred; printf d rows were updated\n, count;
To process queries such as
SELECT
t hat do ret urn a result set , use a different approach t hat involves four st eps:
•
Specify t he query by calling
prepare
using t he dat abase handle.
prepare
ret urns a st at em ent handle t o use wit h all subsequent operat ions on t he query. I f an error occurs, t he script t erm inat es if
RaiseError
is enabled; ot herwise,
prepare
ret urns
undef
.
•
Call
execute
t o execut e t he query and generat e t he result set .
•
Perform a loop t o fet ch t he rows ret urned by t he query. DBI provides several m et hods you can use in t his loop, which well describe short ly.
•
Release resources associat ed wit h t he result set by calling
finish
. The following exam ple illust rat es t hese st eps, using
fetchrow_array
as t he row - fet ching m et hod and assum ing
RaiseError
is enabled: my sth = dbh-prepare SELECT id, name, cats FROM profile;
sth-execute ; my count = 0;
while my val = sth-fetchrow_array {
print id: val[0], name: val[1], cats: val[2]\n; ++count;
} sth-finish ;
print count rows were returned\n;
The row - fet ching loop j ust shown is followed by a call t o
finish
, w hich closes t he result set and t ells t he server t hat it can free any resources associat ed wit h it . You dont act ually
need t o call
finish
if you fet ch every row in t he set , because DBI not ices when youve reached t he last row and releases t he set for it self. Thus, t he exam ple could have om it t ed t he
finish
call wit hout ill effect . I t s m ore im port ant t o invoke
finish
explicit ly if you fet ch only part of a result set .
The exam ple illust rat es t hat if you want t o know how m any rows a result set cont ains, you should count t hem yourself while youre fet ching t hem . Do not use t he DBI
rows
m et hod for t his purpose; t he DBI docum ent at ion discourages t his pract ice. The reason is t hat it is not
necessarily reliable for
SELECT
st at em ent s—not because of som e deficiency in DBI , but because of differences in t he behavior of various dat abase engines.
DBI has several funct ions t hat can be used t o obt ain a row at a t im e in a row- fet ching loop. The one used in t he previous exam ple,
fetchrow_array
, r et urns an array cont aining t he next row, or an em pt y list when t here are no m ore rows. Elem ent s of t he array are accessed
as
val[0]
,
val[1]
, ..., and are present in t he array in t he sam e order t hey are nam ed in t he
SELECT
st at em ent . This funct ion is m ost useful for queries t hat explicit ly nam e colum ns t o select ed. I f you ret rieve colum ns wit h
SELECT
, t here are no guarant ees about t he posit ions of colum ns wit hin t he array.
fetchrow_arrayref
is like
fetchrow_array
, except t hat it ret urns a reference t o t he array, or
undef
when t here are no m ore rows. Elem ent s of t he array are accessed as
ref-[0]
,
ref-[1]
, and so fort h. As wit h
fetchrow_array
, t he values are present in t he order nam ed in t he query:
my sth = dbh-prepare SELECT id, name, cats FROM profile; sth-execute ;
my count = 0; while my ref = sth-fetchrow_arrayref
{ print id: ref-[0], name: ref-[1], cats: ref-[2]\n;
++count; }
print count rows were returned\n;
fetchrow_hashref
ret urns a reference t o a hash st ruct ure, or
undef
when t here are no m ore rows:
my sth = dbh-prepare SELECT id, name, cats FROM profile; sth-execute ;
my count = 0; while my ref = sth-fetchrow_hashref
{ print id: ref-{id}, name: ref-{name}, cats: ref-{cats}\n;
++count; }
print count rows were returned\n;
The elem ent s of t he hash are accessed using t he nam es of t he colum ns t hat are select ed by t he query
ref-{id}
,
ref-{name}
, and so fort h .
fetchrow_hashref
is part icularly useful for
SELECT
queries, because you can access elem ent s of rows wit hout knowing anyt hing about t he order in which colum ns are ret urned. You j ust need t o know t heir
nam es. On t he ot her hand, it s m ore expensive t o set up a hash t han an array, so
fetchrow_hashref
is slow er t han
fetchrow_array
or
fetchrow_arrayref
. I t s also possible t o lose row elem ent s if t hey have t he sam e nam e, because colum n nam es
m ust be unique. The following query select s t wo values, but
fetchrow_hashref
would r et ur n a hash st ruct ure cont aining a single elem ent nam ed
id
: SELECT id, id FROM profile
To avoid t his problem , you can use colum n aliases t o ensure t hat like- nam ed colum ns have dist inct nam es in t he result set . The following query ret rieves t he sam e colum ns as t he
previous query, but gives t hem t he dist inct nam es
id
and
id2
: SELECT id, id AS id2 FROM profile
Adm it t edly, t his query is pret t y silly, but if youre ret rieving colum ns from m ult iple t ables, you m ay very easily run int o t he problem of having colum ns in t he result set t hat have t he sam e
nam e. An exam ple where t his occurs m ay be seen in Recipe 12.4
. I n addit ion t o t he m et hods for perform ing t he query execut ion process j ust described, DBI
provides several high- level ret rieval m et hods t hat issue a query and ret urn t he result set in a single operat ion. These all are dat abase handle m et hods t hat t ake care of creat ing and
disposing of t he st at em ent handle int ernally before ret urning t he result set . Where t he m et hods differ is t he form in which t hey ret urn t he result . Som e ret urn t he ent ire result set ,
ot hers ret urn a single row or colum n of t he set , as sum m arized in t he following t able:
[ 5] [ 5]
selectrow_arrayref
and
selectall_hashref
require DBI 1.15 or newer.
selectrow_hashref
requires DBI 1.20 or newer it was present a few versions before t hat , but wit h a different behavior t han it uses
now .
M e t h od Re t u r n va lu e
selectrow_array
First row of result set as an array
selectrow_arrayref
First row of result set as a reference t o an array
selectrow_hashref
First row of result set as a reference t o a hash
selectcol_arrayref
First colum n of result set as a reference t o an array
selectall_arrayref
Ent ire result set as a reference t o an array of array references
selectall_hashref
Ent ire result set as a reference t o a hash of hash references Most of t hese m et hods ret urn a reference. The except ion is
selectrow_array
, w hich select s t he first row of t he result set and ret urns an array or a scalar, depending on how you
call it . I n array cont ext ,
selectrow_array
ret urns t he ent ire row as an array or t he em pt y list if no row was select ed . This is useful for queries from which you expect t o obt ain
only a single row: my val = dbh-selectrow_array
SELECT name, birth, foods FROM profile WHERE id = 3; When
selectrow_array
is called in array cont ext , t he ret urn value can be used t o det erm ine t he size of t he result set . The colum n count is t he num ber of elem ent s in t he array,
and t he row count is 1 or 0: my ncols = val;
my nrows = ncols ? 1 : 0;
You can also invoke
selectrow_array
in scalar cont ext , in which case it ret urns only t he first colum n from t he row. This is especially convenient for queries t hat ret urn a single value:
my buddy_count = dbh-selectrow_array SELECT COUNT FROM profile; I f a query ret urns no result ,
selectrow_array
ret urns an em pt y array or
undef
, depending on whet her you call it in array or scalar cont ext .
selectrow_arrayref
and
selectrow_hashref
select t he first row of t he result set and ret urn a reference t o it , or
undef
if no row was select ed. To access t he colum n values, t reat t he reference t he sam e way you t reat t he ret urn value from
fetchrow_arrayref
or
fetchrow_hashref
. You can also use t he reference t o get t he row and colum n count s:
my ref = dbh-selectrow_arrayref query; my ncols = defined ref ? {ref} : 0;
my nrows = ncols ? 1 : 0; my ref = dbh-selectrow_hashref query;
my ncols = defined ref ? keys {ref} : 0; my nrows = ncols ? 1 : 0;
Wit h
selectcol_arrayref
, a reference t o a single- colum n array is ret urned, represent ing t he first colum n of t he result set . Assum ing a non-
undef
ret urn value, elem ent s of t he array are accessed as
ref-[ i
]
for t he value from row
i
. The num ber of rows is t he num ber of elem ent s in t he array, and t he colum n count is 1 or 0:
my ref = dbh-selectcol_arrayref query; my nrows = defined ref ? {ref} : 0;
my ncols = nrows ? 1 : 0;
selectall_arrayref
ret urns a reference t o an array, where t he array cont ains an elem ent for each row of t he result . Each of t hese elem ent s is a reference t o an array. To
access row
i
of t he result set , use
ref-[ i
]
t o get a reference t o t he row. Then t reat t he row reference t he sam e way as a ret urn value from
fetchrow_arrayref
t o access individual colum n values in t he row. The result set row and colum n count s are available as
follows: my ref = dbh-selectall_arrayref query;
my nrows = defined ref ? {ref} : 0; my ncols = nrows ? {ref-[0]} : 0;
selectall_hashref
is som ewhat sim ilar t o
selectall_arrayref
, but ret urns a reference t o a hash, each elem ent of which is a hash reference t o a row of t he result . To call
it , specify an argum ent t hat indicat es which colum n t o use for hash keys. For exam ple, if youre ret rieving rows from t he
profile
t able, t he
PRIMARY KEY
is t he
id
colum n: my ref = dbh-selectall_hashref SELECT FROM profile, id;
Then access rows using t he keys of t he hash. For exam ple, if one of t he rows has a key colum n value of 12, t he hash reference for t he row is accessed as
ref-{12}
. That value is keyed on colum n nam es, which you can use t o access individual colum n elem ent s for
exam ple,
ref-{12}-{name}
. The result set row and colum n count s are available as follows:
my keys = defined ref ? keys {ref} : ; my nrows = scalar keys;
my ncols = nrows ? keys {ref-{keys[0]}} : 0;
The
selectall_ XXX
m et hods are useful when you need t o process a result set m ore t han once, because DBI provides no way t o rewind a result set . By assigning t he ent ire
result set t o a variable, you can it erat e t hrough it s elem ent s as oft en as you please. Take care when using t he high- level m et hods if you have
RaiseError
disabled. I n t hat case, a m et hods ret urn value m ay not always allow you t o dist inguish an error from an em pt y result
set . For exam ple, if you call
selectrow_array
in scalar cont ext t o ret rieve a single value, an
undef
ret urn value is part icularly am biguous because it m ay indicat e any of t hree t hings: an error, an em pt y result set , or a result set consist ing of a single
NULL
value. I f you need t o t est for an error, you can check t he value of
DBI::errstr
or
DBI::err
. 2.5.6 PHP
PHP doesnt have separat e funct ions for issuing queries t hat ret urn result set s and t hose t hat do not . I nst ead, t here is a single funct ion
mysql_query
for all queries.
mysql_query
t akes a query st ring and an opt ional connect ion ident ifier as argum ent s, and ret urns a result ident ifier. I f you leave out t he connect ion ident ifier argum ent ,
mysql_query
uses t he m ost recent ly opened connect ion by default . The first st at em ent below uses an explicit
ident ifier; t he second uses t he default connect ion: result_id = mysql_query query, conn_id;
result_id = mysql_query query; I f t he query fails,
result_id
w ill be
FALSE
. This m eans t hat an error occurred because your query was bad: it was synt act ically invalid, you didnt have perm ission t o access a t able
nam ed in t he query, or som e ot her problem prevent ed t he query from execut ing. A
FALSE
ret urn value does not m ean t hat t he query affect ed 0 rows for a
DELETE
,
INSERT
, or
UPDATE
or ret urned rows for a
SELECT
. I f
result_id
is not
FALSE
, t he query execut ed properly. What you do at t hat point depends on t he t ype of query. For queries t hat dont ret urn rows,
result_id
w ill be
TRUE
, and t he query has com plet ed. I f you want , you can call
mysql_affected_rows
t o find out how m any rows were changed:
result_id = mysql_query DELETE FROM profile WHERE cats = 0, conn_id; if result_id
die Oops, the query failed; print mysql_affected_rows conn_id . rows were deleted\n;
mysql_affected_rows
t akes t he connect ion ident ifier as it s argum ent . I f you om it t he argum ent , t he current connect ion is assum ed.
For queries t hat ret urn a result set ,
mysql_query
ret urns a nonzero result ident ifier. Generally, you use t his ident ifier t o call a row- fet ching funct ion in a loop, t hen call
mysql_free_result
t o release t he result set . The result ident ifier is really not hing m ore t han a num ber t hat t ells PHP which result set youre using. This ident ifier is not a count of t he
num ber of rows select ed, nor does it cont ain t he cont ent s of any of t hose rows. Many beginning PHP program m ers m ake t he m ist ake of t hinking
mysql_query
ret urns a row count or a result set , but it doesnt . Make sure youre clear on t his point and youll save
yourself a lot of t rouble. Heres an exam ple t hat show s how t o run a
SELECT
query and use t he result ident ifier t o fet ch t he rows:
result_id = mysql_query SELECT id, name, cats FROM profile, conn_id; if result_id
die Oops, the query failed; while row = mysql_fetch_row result_id
print id: row[0], name: row[1], cats: row[2]\n; print mysql_num_rows result_id . rows were returned\n;
mysql_free_result result_id;
The exam ple dem onst rat es t hat you obt ain t he rows in t he result set by execut ing a loop in w hich you pass t he result ident ifier t o one of PHPs row- fet ching funct ions. To obt ain a count
of t he num ber of rows in a result set , pass t he result ident ifier t o
mysql_num_rows
. When t here are no m ore rows, pass t he ident ifier t o
mysql_free_result
t o close t he r esult set . Aft er you call
mysql_free_result
, dont t ry t o fet ch a row or get t he row count , because at t hat point
result_id
is no longer valid. Each PHP row- fet ching funct ion ret urns t he next row of t he result set indicat ed by
result_id
, or
FALSE
when t here are no m ore rows. Where t hey differ is in t he dat a t ype of t he ret urn value. The funct ion shown in t he preceding exam ple,
mysql_fetch_row
, ret urns an array whose elem ent s correspond t o t he colum ns select ed by t he query and are
accessed using num eric subscript s.
mysql_fetch_array
is like
mysql_fetch_row
, but t he array it ret urns also cont ains elem ent s t hat can be accessed using t he nam es of t he
select ed colum ns. I n ot her words, you can access each colum n using eit her it s num eric posit ion or it s nam e:
result_id = mysql_query SELECT id, name, cats FROM profile, conn_id; if result_id
die Oops, the query failed; while row = mysql_fetch_array result_id
{ print id: row[0], name: row[1], cats: row[2]\n;
print id: row[id], name: row[name], cats: row[cats]\n; }
print mysql_num_rows result_id . rows were returned\n; mysql_free_result result_id;
Despit e what you m ight expect ,
mysql_fetch_array
is not appreciably slower t han
mysql_fetch_row
, even t hough t he array it ret urns cont ains m ore inform at ion. The previous exam ple does not quot e t he non- num eric elem ent nam es because t hey appear
inside a quot ed st ring. Should you refer t o t he elem ent s out side of a st ring, t he elem ent nam es should be quot ed:
printf id: s, name: s, cats: s\n, row[id], row[name], row[cats];
mysql_fetch_object
ret urns an obj ect having m em bers t hat correspond t o t he colum ns select ed by t he query and t hat are accessed using t he colum n nam es:
result_id = mysql_query SELECT id, name, cats FROM profile, conn_id; if result_id
die Oops, the query failed; while row = mysql_fetch_object result_id
print id: row-id, name: row-name, cats: row-cats\n; print mysql_num_rows result_id . rows were returned\n;
mysql_free_result result_id;
PHP 4.0.3 adds a fourt h row- fet ching funct ion,
mysql_fetch_assoc
, t hat ret urns an array cont aining elem ent s t hat are accessed by nam e. I n ot her words, it is like
mysql_fetch_array
, except t hat t he row does not cont ain t he values accessed by num eric index.
Dont Use count to Get a Column Count in PHP 3
PHP program m ers som et im es fet ch a result set row and t hen use
countrow
t o det erm ine how m any values t he row cont ains. I t s preferable t o use
mysql_num_fields
inst ead, as you can see for yourself by execut ing t he following fragm ent of PHP code:
if result_id = mysql_query SELECT 1, 0, NULL, conn_id die Cannot issue query\n;
count = mysql_num_fields result_id; print The row contains count columns\n;
if row = mysql_fetch_row result_id die Cannot fetch row\n;
count = count row; print The row contains count columns\n;
I f you run t he code under PHP 3, youll find t hat
count
ret urns 2. Wit h PHP 4,
count
ret urns 3. These differing result s occur because
count
count s array values t hat correspond t o
NULL
values in PHP 4, but not in PHP 3. By cont rast ,
mysql_field_count
uniform ly ret urns 3 for bot h versions of PHP. The m oral is t hat
count
wont necessarily give you an accurat e value. Use
mysql_field_count
if you want t o know t he t rue colum n count .
2.5.7 Python