Author: timbo
Date: Wed Feb 21 17:44:25 2007
New Revision: 9152
Modified:
dbi/trunk/Changes
dbi/trunk/DBI.pm
dbi/trunk/DBI.xs
dbi/trunk/lib/DBD/Gofer.pm
dbi/trunk/lib/DBD/Gofer/Policy/rush.pm
dbi/trunk/lib/DBI/DBD.pm
dbi/trunk/lib/DBI/Gofer/Execute.pm
dbi/trunk/lib/DBI/Gofer/Transport/mod_perl.pm
dbi/trunk/t/12quote.t
Log:
Make dbi_dumpcom more robust, also show Name or Statement if possible.
Change dbih_setup_fbav to be able to adjust the row buffer size properly.
Change STORE of NUM_OF_FIELDS to call dbih_setup_fbav if required.
No longer auto adjust DBIc_NUM_FIELDS if inconsistent when a row is fetched.
Note more caveats in DBD:Gofer docs.
Add hack mechanism in Gofer/Execute.pm to return updated dbh attr after an sth
method call
(specifically for $dbh->{mysql_insertid})
Add more_results method docs to DBI::DBD.
Updated column_info docs to note that if a table doesn't exist you get an sth
for an empty result set and not an error
Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes (original)
+++ dbi/trunk/Changes Wed Feb 21 17:44:25 2007
@@ -51,8 +51,13 @@
Changed handle factory methods, like connect, prepare, and table_info,
to copy any error/warn/info state of the handle being returned
up into the handle the method was called on.
+ Changed row buffer handling to not alter NUM_OF_FIELDS if it's
+ inconsistent with number of elements in row buffer array.
+ Updated DBI::DBD docs re handling multiple result sets.
Updated DBI::DBD docs for driver authors thanks to Ammon Riley
and Dean Arnold.
+ Updated column_info docs to note that if a table doesn't exist
+ you get an sth for an empty result set and not an error.
Added new DBD::Gofer 'stateless proxy' driver and framework,
and the DBI test suite is now also executed via DBD::Gofer,
Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm (original)
+++ dbi/trunk/DBI.pm Wed Feb 21 17:44:25 2007
@@ -4439,6 +4439,8 @@
$sth = $dbh->table_info( $catalog, $schema, $table, $type );
$sth = $dbh->table_info( $catalog, $schema, $table, $type, \%attr );
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
Returns an active statement handle that can be used to fetch
information about tables and views that exist in the database.
@@ -4515,6 +4517,8 @@
$sth = $dbh->column_info( $catalog, $schema, $table, $column );
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
Returns an active statement handle that can be used to fetch
information about columns in specified tables.
@@ -4525,6 +4529,9 @@
driver doesn't support one or more of them then you may get back more
than you asked for and can do the filtering yourself.
+If the arguments don't match any tables then you'll still get a statement
+handle, it'll just return no rows.
+
The statement handle returned has at least the following fields in the
order shown below. Other fields, after these, may also be present.
@@ -4627,15 +4634,12 @@
$sth = $dbh->primary_key_info( $catalog, $schema, $table );
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
Returns an active statement handle that can be used to fetch information
about columns that make up the primary key for a table.
The arguments don't accept search patterns (unlike table_info()).
-For example:
-
- $sth = $dbh->primary_key_info( undef, $user, 'foo' );
- $data = $sth->fetchall_arrayref;
-
The statement handle will return one row per column, ordered by
TABLE_CAT, TABLE_SCHEM, TABLE_NAME, and KEY_SEQ.
If there is no primary key then the statement handle will fetch no rows.
@@ -4686,6 +4690,8 @@
, $fk_catalog, $fk_schema, $fk_table
, \%attr );
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
Returns an active statement handle that can be used to fetch information
about foreign keys in and/or referencing the specified table(s).
The arguments don't accept search patterns (unlike table_info()).
@@ -4713,6 +4719,8 @@
$sth = $dbh->foreign_key_info( undef, undef, undef , undef, $user,
'detail');
$sth = $dbh->foreign_key_info( undef, $user, 'master', undef, $user,
'detail');
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
Note: The support for the selection criteria, such as C<$catalog>, is
driver specific. If the driver doesn't support catalogs and/or
schemas, it may ignore these criteria.
@@ -4801,6 +4809,8 @@
$sth = $dbh->statistics_info( $catalog, $schema, $table, $unique_only,
$quick );
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
Returns an active statement handle that can be used to fetch statistical
information about a table and its indexes.
@@ -4814,11 +4824,6 @@
available from the server, and might not be current. Some databases may
return stale statistics or no statistics at all with this flag set.
-For example:
-
- $sth = $dbh->statistics_info( undef, $user, 'foo', 1, 1 );
- $data = $sth->fetchall_arrayref;
-
The statement handle will return at most one row per column name per index,
plus at most one row for the entire table itself, ordered by NON_UNIQUE, TYPE,
INDEX_QUALIFIER, INDEX_NAME, and ORDINAL_POSITION.
Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs (original)
+++ dbi/trunk/DBI.xs Wed Feb 21 17:44:25 2007
@@ -1197,6 +1197,7 @@
dTHX;
dPERINTERP;
SV *flags = sv_2mortal(newSVpv("",0));
+ SV *inner;
STRLEN lna;
static const char pad[] = " ";
if (!msg)
@@ -1248,17 +1249,28 @@
PerlIO_printf(DBILOGFP,"%s NUM_OF_FIELDS %d\n", pad,
DBIc_NUM_FIELDS(imp_sth));
PerlIO_printf(DBILOGFP,"%s NUM_OF_PARAMS %d\n", pad,
DBIc_NUM_PARAMS(imp_sth));
}
+ inner = dbih_inner((SV*)DBIc_MY_H(imp_xxh), msg);
+ if (!inner || !SvROK(inner))
+ return 1;
if (level > 0) {
SV* value;
char *key;
I32 keylen;
- SV *inner;
PerlIO_printf(DBILOGFP,"%s cached attributes:\n", pad);
- inner = dbih_inner((SV*)DBIc_MY_H(imp_xxh), msg);
while ( (value = hv_iternextsv((HV*)SvRV(inner), &key, &keylen)) ) {
PerlIO_printf(DBILOGFP,"%s '%s' => %s\n", pad, key,
neatsvpv(value,0));
}
}
+ else if (DBIc_TYPE(imp_xxh) == DBIt_DB) {
+ SV **svp = hv_fetch((HV*)SvRV(inner), "Name", 4, 0);
+ if (svp && SvOK(*svp))
+ PerlIO_printf(DBILOGFP,"%s Name %s\n", pad, neatsvpv(*svp,0));
+ }
+ else if (DBIc_TYPE(imp_xxh) == DBIt_ST) {
+ SV **svp = hv_fetch((HV*)SvRV(inner), "Statement", 9, 0);
+ if (svp && SvOK(*svp))
+ PerlIO_printf(DBILOGFP,"%s Statement %s\n", pad, neatsvpv(*svp,0));
+ }
return 1;
}
@@ -1274,7 +1286,6 @@
int debug = DBIS_TRACE_LEVEL;
int auto_dump = (debug >= 6);
imp_xxh_t * const parent_xxh = DBIc_PARENT_COM(imp_xxh);
-
/* Note that we're very much on our own here. DBIc_MY_H(imp_xxh) almost
*/
/* certainly points to memory which has been freed. Don't use it!
*/
@@ -1382,31 +1393,43 @@
static AV *
dbih_setup_fbav(imp_sth_t *imp_sth)
{
+ /* Usually called to setup the row buffer for new sth.
+ * Also called if the value of NUM_OF_FIELDS is altered,
+ * in which case it adjusts the row buffer to match NUM_OF_FIELDS.
+ */
dTHX;
dPERINTERP;
- int i;
- AV *av;
+ int i = DBIc_NUM_FIELDS(imp_sth);
+ AV *av = DBIc_FIELDS_AV(imp_sth);
- if (DBIc_FIELDS_AV(imp_sth))
- return DBIc_FIELDS_AV(imp_sth);
+ if (i < 0)
+ i = 0;
+
+ if (av) {
+ if (av_len(av)+1 == i) /* is existing array the right size? */
+ return av;
+ /* we need to adjust the size of the array */
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 3)
+ PerlIO_printf(DBILOGFP," dbih_setup_fbav realloc from %d to %d
fields\n", av_len(av)+1, i);
+ SvREADONLY_off(av);
+ }
+ else {
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 3)
+ PerlIO_printf(DBILOGFP," dbih_setup_fbav alloc for %d
fields\n", i);
+ av = newAV();
+ DBIc_FIELDS_AV(imp_sth) = av;
+ }
- i = DBIc_NUM_FIELDS(imp_sth);
- if (i <= 0 || i > 32000) /* trap obvious mistakes */
- croak("dbih_setup_fbav: invalid number of fields: %d%s",
- i, ", NUM_OF_FIELDS attribute probably not set right");
- av = newAV();
- if (DBIS_TRACE_LEVEL >= 3)
- PerlIO_printf(DBILOGFP," dbih_setup_fbav for %d fields => 0x%lx\n",
- i, (long)av);
/* load array with writeable SV's. Do this backwards so */
/* the array only gets extended once. */
while(i--) /* field 1 stored at index 0 */
av_store(av, i, newSV(0));
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 3)
+ PerlIO_printf(DBILOGFP," dbih_setup_fbav now %d fields\n",
av_len(av)+1);
SvREADONLY_on(av); /* protect against shift @$row etc */
/* row_count will need to be manually reset by the driver if the */
/* sth is re-executed (since this code won't get rerun) */
DBIc_ROW_COUNT(imp_sth) = 0;
- DBIc_FIELDS_AV(imp_sth) = av;
return av;
}
@@ -1423,12 +1446,14 @@
dTHX;
int i = av_len(av) + 1;
if (i != DBIc_NUM_FIELDS(imp_sth)) {
- SV *sth = dbih_inner((SV*)DBIc_MY_H(imp_sth), "_get_fbav");
+ /*SV *sth = dbih_inner((SV*)DBIc_MY_H(imp_sth), "_get_fbav");*/
/* warn via PrintWarn */
set_err_char(SvRV(DBIc_MY_H(imp_sth)), (imp_xxh_t*)imp_sth,
- "0", 0, "Number of row fields inconsistent with
NUM_OF_FIELDS, NUM_OF_FIELDS updated", "", "_get_fbav");
+ "0", 0, "Number of row fields inconsistent with
NUM_OF_FIELDS (driver bug)", "", "_get_fbav");
+ /*
DBIc_NUM_FIELDS(imp_sth) = i;
hv_delete((HV*)SvRV(sth), "NUM_OF_FIELDS", 13, G_DISCARD);
+ */
}
/* don't let SvUTF8 flag persist from one row to the next */
/* (only affects drivers that use sv_setpv, but most XS do) */
@@ -1719,20 +1744,16 @@
}
else if (htype==DBIt_ST && strEQ(key, "NUM_OF_FIELDS")) {
D_imp_sth(h);
+ int new_num_fields = (SvOK(valuesv)) ? SvIV(valuesv) : -1;
+
if (DBIc_NUM_FIELDS(imp_sth) > 0 /* don't change NUM_FIELDS! */
&& DBIc_ACTIVE(imp_sth) /* if sth is Active */
) {
croak("Can't change NUM_OF_FIELDS of Active handle (already set to
%d)", DBIc_NUM_FIELDS(imp_sth));
}
- if (DBIc_NUM_FIELDS(imp_sth) > 0
- && SvIV(valuesv) != DBIc_NUM_FIELDS(imp_sth)
- && DBIc_TRACE_LEVEL(imp_sth)
- && DBIc_FIELDS_AV(imp_sth)
- ) {
- PerlIO_printf(DBILOGFP,"Changing NUM_OF_FIELDS (from %d to %d)
after row buffer already allocated\n",
- DBIc_NUM_FIELDS(imp_sth), (int)SvIV(valuesv));
- }
- DBIc_NUM_FIELDS(imp_sth) = (SvOK(valuesv)) ? SvIV(valuesv) : -1;
+ DBIc_NUM_FIELDS(imp_sth) = new_num_fields;
+ if (DBIc_FIELDS_AV(imp_sth)) /* modify existing fbav */
+ dbih_setup_fbav(imp_sth);
cacheit = 1;
}
else if (htype==DBIt_ST && strEQ(key, "NUM_OF_PARAMS")) {
Modified: dbi/trunk/lib/DBD/Gofer.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer.pm (original)
+++ dbi/trunk/lib/DBD/Gofer.pm Wed Feb 21 17:44:25 2007
@@ -266,7 +266,6 @@
# Methods that should be forwarded if policy says so
for my $method (qw(
quote
- quote_identifier
)) {
no strict 'refs';
# XXX add policy checks
@@ -440,6 +439,7 @@
$sth->more_results;
}
else {
+ $sth->STORE(Active => 0);
$sth->{go_rows} = $response->rv;
}
# set error/warn/info (after more_results as that'll clear err)
@@ -483,6 +483,7 @@
my $meta = shift @$resultset_list
or return undef; # no more result sets
+ #warn "more_results: ".Data::Dumper::Dumper($meta);
# pull out the special non-atributes first
my ($rowset, $err, $errstr, $state)
@@ -713,11 +714,11 @@
Some driver-private dbh attributes may not be available if the driver has not
implemented the private_attribute_info() method (added in DBI 1.54).
-=head1 Multiple Resultsets
+=head2 Multiple Resultsets
Multiple resultsets are supported if the driver supports the more_results()
method.
-=head1 Use of last_insert_id requires a minor code change
+=head2 Use of last_insert_id requires a minor code change
To enable use of last_insert_id you need to indicate to DBD::Gofer that you'd
like to use it. You do that my adding a C<go_last_insert_id_args> attribute to
@@ -736,6 +737,18 @@
XXX allow $dbh->{go_last_insert_id_args} = [] to enable it by default?
+=head2 Statement activity that also updates dbh attributes
+
+Some drivers may update one or more dbh attributes after performing activity on
+a child sth. For example, DBD::mysql provides $dbh->{mysql_insertid} in
addition to
+$sth->{mysql_insertid}. Currently this isn't supported, but probably needs to
be.
+
+=head2 Methods that report an error always return undef
+
+With DBD::Gofer a method that sets an error always return an undef or empty
list.
+That shouldn't be a problem in practice because the DBI doesn't define any
+methods that do return meaningful values while also reporting an error.
+
=head1 TRANSPORTS
DBD::Gofer doesn't concern itself with transporting requests and responses to
and fro.
Modified: dbi/trunk/lib/DBD/Gofer/Policy/rush.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer/Policy/rush.pm (original)
+++ dbi/trunk/lib/DBD/Gofer/Policy/rush.pm Wed Feb 21 17:44:25 2007
@@ -17,8 +17,8 @@
__PACKAGE__->create_default_policy_subs({
# Skipping the connect check is fast, but it also skips
- # setting dbh attributes! Make sure that your application doesn't
- # need changing dbh attributes between requests
+ # fetching the remote dbh attributes!
+ # Make sure that your application doesn't need access to dbh attributes.
skip_connect_check => 1,
# most code doesn't rely on sth attributes being set after prepare
Modified: dbi/trunk/lib/DBI/DBD.pm
==============================================================================
--- dbi/trunk/lib/DBI/DBD.pm (original)
+++ dbi/trunk/lib/DBI/DBD.pm Wed Feb 21 17:44:25 2007
@@ -1237,6 +1237,53 @@
to the B<DBI>'s own method which does the right thing based
on the number of calls to C<_set_fbav()>.
+=head4 The more_results method
+
+If your driver doesn't support multiple result sets, then don't even implement
this method.
+
+Otherwise, this method needs to get the statement handle ready to fetch results
+from the next result set, if there is one. Typically you'd start with:
+
+ $sth->finish;
+
+then you should delete all the attributes from the attribute cache that may no
+longer be relevant for the new result set:
+
+ delete $sth->{$_}
+ for qw(NAME TYPE PRECISION SCALE ...);
+
+for drivers written in C use:
+
+ hv_delete((HV*)SvRV(sth), "NAME", 4, G_DISCARD);
+ hv_delete((HV*)SvRV(sth), "NULLABLE", 8, G_DISCARD);
+ hv_delete((HV*)SvRV(sth), "NUM_OF_FIELDS", 13, G_DISCARD);
+ hv_delete((HV*)SvRV(sth), "PRECISION", 9, G_DISCARD);
+ hv_delete((HV*)SvRV(sth), "SCALE", 5, G_DISCARD);
+ hv_delete((HV*)SvRV(sth), "TYPE", 4, G_DISCARD);
+
+Don't forget to also delete, or update, any driver-private attributes that may
+not be correct for the next resultset.
+
+The NUM_OF_FIELDS attribute is a special case. It should be set using STORE:
+
+ $sth->STORE(NUM_OF_FIELDS => 0); /* for DBI <= 1.53 */
+ $sth->STORE(NUM_OF_FIELDS => $new_value);
+
+for drivers written in C use this incantation:
+
+ /* Adjust NUM_OF_FIELDS - which also adjusts the row buffer size */
+ DBIc_NUM_FIELDS(imp_sth) = 0; /* for DBI <= 1.53 */
+ DBIS->set_attr_k(sth, sv_2mortal(newSVpvn("NUM_OF_FIELDS",13)), 0,
+ sv_2mortal(newSViv(mysql_num_fields(imp_sth->result)))
+ );
+
+For DBI versions prior to 1.54 you'll also need to explicitly adjust the
+number of elements in the row buffer array (C<DBIc_FIELDS_AV(imp_sth)>)
+to match the new result set. Fill any new values with newSV(0) not &sv_undef.
+Alternatively you could free DBIc_FIELDS_AV(imp_sth) and set it to null,
+but that would mean bind_columns() woudn't work across result sets.
+
+
=head4 Statement attributes
The main difference between I<dbh> and I<sth> attributes is, that you
@@ -1245,7 +1292,7 @@
L<DBI/Statement Handle Attributes> for a complete list.
Pay attention to attributes which are marked as read only, such as
-I<NUM_OF_FIELDS>. These attributes can only be set the first time
+I<NUM_OF_PARAMS>. These attributes can only be set the first time
a statement is executed. If a statement is prepared, then executed
multiple times, warnings may be generated.
@@ -1253,9 +1300,9 @@
of attributes which might be expensive to calculate (such as the
I<NAME> and I<NAME_*> attributes):
- my $storedNumFields = $sth->FETCH('NUM_OF_FIELDS');
- if (!defined $storedNumFields or $storedNumFields < 0) {
- $sth->STORE('NUM_OF_FIELDS') = $numFields;
+ my $storedNumParams = $sth->FETCH('NUM_OF_PARAMS');
+ if (!defined $storedNumParams or $storedNumFields < 0) {
+ $sth->STORE('NUM_OF_PARAMS') = $numParams;
# Set other useful attributes that only need to be set once
# for a statement, like $sth->{NAME} and $sth->{TYPE}
@@ -2117,9 +2164,9 @@
DBIc_NUM_PARAMS(imp_sth) = ...
-If you can, you should also setup attributes like I<NUM_OF_FIELDS>,
-I<NAME>, ... here, but B<DBI> doesn't require that. However, if you do,
-document it.
+If you can, you should also setup attributes like I<NUM_OF_FIELDS>, I<NAME>,
+etc. here, but B<DBI> doesn't require that - they can be deferred until
+execute() is called. However, if you do, document it.
In any case you should set the I<IMPSET> flag, as you did in
C<dbd_db_connect()> above:
@@ -2175,8 +2222,8 @@
}
It is important that the I<ACTIVE> flag only be set for C<SELECT>
-statements (or any other statements that can return multiple sets
-of values from the database using a cursor-like mechanism). See
+statements (or any other statements that can return many
+values from the database using a cursor-like mechanism). See
C<dbd_db_connect()> above for more explanations.
There plans for a preparse function to be provided by B<DBI>, but this has
Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm Wed Feb 21 17:44:25 2007
@@ -56,10 +56,18 @@
# which would reduce processing/traffic for non-select statements
mysql => {
dbh => [qw(
+ mysql_errno mysql_error mysql_hostinfo mysql_info mysql_insertid
+ mysql_protoinfo mysql_serverinfo mysql_stat mysql_thread_id
)],
sth => [qw(
mysql_is_blob mysql_is_key mysql_is_num mysql_is_pri_key
mysql_is_auto_increment
- mysql_length mysql_max_length mysql_table mysql_type
mysql_type_name
+ mysql_length mysql_max_length mysql_table mysql_type
mysql_type_name mysql_insertid
+ )],
+ # XXX this dbh_after_sth stuff is a temporary, but important, hack.
+ # should be done via hash instead of arrays where the hash value
contains
+ # flags that can indicate which attributes need to be handled in this
way
+ dbh_after_sth => [qw(
+ mysql_insertid
)],
},
Pg => {
@@ -311,15 +319,24 @@
};
my $response = $self->new_response_with_err($rv, $@);
- $response->last_insert_id( $last_insert_id ) if defined $last_insert_id;
+ $response->last_insert_id( $last_insert_id )
+ if defined $last_insert_id;
+ # XXX would be nice to be able to support streaming of results
# even if the eval failed we still want to try to gather attribute values
- $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request) )
if $sth;
+ $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request) )
+ if $sth;
+
+ if (my $dbh_attr = $extra_attr{$dbh->{Driver}{Name}}{dbh_after_sth}) {
+ my %dbh_attr_values;
+ $dbh_attr_values{$_} = $dbh->FETCH($_) for @$dbh_attr;
+ $response->dbh_attributes(\%dbh_attr_values);
+ }
- # XXX would be nice to be able to support streaming of results
# which would reduce memory usage and latency for large results
- $self->reset_dbh($dbh) if $dbh;
+ $self->reset_dbh($dbh)
+ if $dbh;
return $response;
}
Modified: dbi/trunk/lib/DBI/Gofer/Transport/mod_perl.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Transport/mod_perl.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Transport/mod_perl.pm Wed Feb 21 17:44:25 2007
@@ -43,7 +43,7 @@
my $frozen_response = $transport->freeze_data($response);
print $frozen_response;
- return MP2 ? Apache2::Const::OK : Apache::Constants::OK;
+ return OK;
}
Modified: dbi/trunk/t/12quote.t
==============================================================================
--- dbi/trunk/t/12quote.t (original)
+++ dbi/trunk/t/12quote.t Wed Feb 21 17:44:25 2007
@@ -45,3 +45,5 @@
}
check_quote_identifier();
+
+1;