Here's what I'm thinking of with respect to $sth->more_results:
Principles:
1: At any point in time the state of a statement handle corresponds
with having executed one 'sub-statement' within the 'compound-statement'.
2: The $sth->more_results() method appears to have the effect of
preparing and executing the next sub-statement within the
compound-statement;
The first principle means that $sth->{NUM_OF_FIELDS} attribute can
be used to tell if the current sub-statement was a SELECT.
$sth->rows can be used to get the row count if it wasn't.
Another way of looking at it is to imagine a DBI subclass that
did something like this:
sub prepare {
my ($dbh, $Statement) = @_;
my @statements = split /;/, $Statement; # split statements
my $sth = $dbh->SUPER::prepare(shift @statements); # prepare the first
$sth->{statements} = [EMAIL PROTECTED]; # store the rest
return $sth;
}
sub more_results {
my ($sth) = @_;
my $statements = $sth->{statements} or return; # got multiple?
my $Statement = shift @$statements or return; # got more?
my $next_sth = $sth->{Database}->prepare($Statement) or return;
$next_sth->execute or return;
$sth->replace_guts($next_sth); # in effect
return 1;
}
Taking it a little further, following the lead of DBD::ODBC, it would be
convienient to be able to automatically skip pass non-SELECT statements
if an attribute is set. Something like:
sub more_results {
my ($sth) = @_;
my $statements = $sth->{statements} or return;
+ more_results:
my $Statement = shift @$statements or return;
my $next_sth = $sth->{Database}->prepare($Statement)
or return; # XXX
$next_sth->execute
or return; # XXX
+ goto more_results
+ if $sth->{NUM_OF_FIELDS} == 0
+ && $sth->{ignore_non_select_result_sets}; # eg
$sth->replace_guts($next_sth); # in effect
return 1;
}
Note that SELECT statements which return no rows are never skipped,
only non-SELECT statements. We can discuss the name and default setting
of that attribute later (not now, please).
[Someone with more time than they know what to do with is very welcome to
create such a subclass to experiment with. split /;\n/ should suffice.
And more_results() would need to return the $new_sth so the application
would do { ... } while ($sth = $sth->more_results) ]
Application Examples:
So the generic loop to fetch multiple result sets would be like this:
do {
if ($sth->{NUM_OF_FIELDS}) {
while (@data = $sth->fetchrow_array) {
...
}
} else {
printf "Non-SELECT: %d rows\n", $sth->rows;
}
} while ($sth->more_results};
and if $sth->{ignore_non_select_result_sets} (whatever it gets called)
is set then that simplifies down to:
do {
while (@data = $sth->fetchrow_array) {
...
}
} while ($sth->more_results};
or even, for the do-loop-phobic, just:
while ($data = $sth->fetchrow_arrayref
or $sth->more_results && $data = $sth->fetchrow_arrayref
) {
...
}
Open issues:
Active: the principles above mean that $sth->{Active} goes false
at the end of each set of results. That goes against the definition
of what Active means but I think that's a small issue compared with
the greater consistency elsewhere. A new attribute could be added
to fill the gap if need be.
finish: $sth->finish will discard all pending result sets
by default, but take an option to only apply to the current set.
row errors: row fetch errors (where a row has an error, like a
truncation, but more rows can still be fetched) could be distinguished
previously by the fetch*() method returning false but Active still
being true. That will still be true.
err: Obviously some errors will be detected and reported by the
original prepare() and execute() but others will not be detected
till later. As far as possible more_results() is the place to
report any errors that would have been detected and reported by a
corresponding prepare() and execute() of the individual sub-statement.
Sounds good, but...
more err: can more_results() return true after returning false
due to an error? In other words can later sub-statements still
execute after an error in a preceeding sub-statement?
Or, to put it another way: should more_results() always return
true the same number of times as there are sub-statements
(assuming no network errors etc). Then the generic loop becomes:
do {
if ($sth->err) {
...deal with error in this sub-statement...
}
...
} while ($sth->more_results};
In this case the two returns marked XXX in the second sub more_results
above should return true.
get_info: Martin mentioned SQL_BATCH_ROW_COUNT, SQL_BATCH_SUPPORT
and SQL_MULT_RESULT_SETS. I need to look into those (I don't have
my book handy) but certainly there will be some get_info() items
designated as mandatory for drivers which support more_results().
selectall_* methods: could perhaps be extended to automatically
call more_results() and loop.
Comments?
Tim.
p.s. Many thanks to Jeff Urlwin and Martin Evans for their original
exploratory work with DBD::ODBC which raised and resolved many issues.