The argument seems to have got confused here between what DBI does, what it says it does and DBD::Oracle (which does neither). I don't want (and don't think) any change (other than in pod) in DBI is necessary. The thread started with my observations of differences between DBI and DBD::Oracle with the summary:
a) even though RaiseError was set, no error was raised although a warning was. b) execute_array returned undef (correct) c) errstr is set but err is not (0) d) the HandleError routine was not called - due to (a)? e) the count of rows affected is -1 for all rows which worked - I believe this is permissible I later noticed: f) DBD::Oracle was never returning the total rows affected in list context g) DBI was documenting 2 fields in ArrayTupleStatus for an error put 3 in On 02/02/11 12:15, Tim Bunce wrote: > On Tue, Feb 01, 2011 at 09:02:26PM +0000, Martin J. Evans wrote: >> On 01/02/2011 20:50, Tim Bunce wrote: >>> On Tue, Feb 01, 2011 at 10:58:14AM -0500, John Scoles wrote: >> >>>> My only concern is when it does error (no matter what the setting of >>>> AutoCommit) you always get unef; >>> Umm, yes. Returning undef (or an empty list) if there was any kind of >>> error causes useful information (the total row count) to be discarded. >> >> I don't see why when called in list context. Just because >> execute_array partially succeeded (or failured) only means undef >> needs to be returned for the first scalar and does not affect the >> rows affected. > > True for the total row count. I was thinking of the RaiseError case, > I think. Also, by that definition, this wouldn't work: Yes, sorry, me being an idiot - of course, when RaiseError is set whether scalar/list context nothing it returned as $tuples/$rows. > unless (($tuples, $rows) = $sth->execute_array(...)) { > ...handle error... > } > That's not a big deal though. (Returning undef for the tuple count > isn't a big problem because you can determine that from the length of > ArrayTupleStatus.) agreed. I only added list context to DBI ages ago because I noticed the affected rows was available for the batch but not individually and at the time I wanted it. Some time since then this has stopped working and I probably ignored it since I have a broken Oracle DB which fails the 26exe_array test anyway and I don't use execute_array in this project now. DBD::Oracle no longer (in 1.27 at least) works to the spec as it is now because even against a working Oracle 10 database (working in that it commits working tuples) rows_affected is always undef even when RaiseError is NOT set. See the script I posted which output: DB Version: 10.2.0.1.0 AutoCommit = 1 RaiseError = 0 # raise error NOT set # warning output by PrintWarn: DBD::Oracle::st execute_array warning: ORA-24381: error(s) in array DML (DBD SUCCESS_WITH_INFO: OCIStmtExecute) [for Statement "insert into mytest values (?,?)"] at /home/martin/svn/dbd-odbc/trunk/rt_data/execute_array/execute_array_oracle.pl line 62. execute_array did not raise error: '' # which you'd expect execute_array = undef # which is to the spec as it is now total affected rows = undef # OOPS - not set even though some rows were affected Error from execute_array - errstr=ORA-24381: error(s) in array DML (DBD SUCCESS_WITH_INFO: OCIStmtExecute), err=0, state='' $tuple_status = [ -1, # because Oracle cannot give you these per tuple [ 1, 'ORA-00001: unique constraint (SYSTEM.SYS_C005676) violated (DBD SUCCESS_WITH_INFO)' ], -1, -1 ]; Error captured in handler: undef # as you'd expect as RaiseError not set # warning captured in SIGWARN handler: Warning captured in SIGWARN handler: 'DBD::Oracle::st execute_array warning: ORA-24381: error(s) in array DML (DBD SUCCESS_WITH_INFO: OCIStmtExecute) [for Statement "insert into mytest values (?,?)"] at /home/martin/svn/dbd-odbc/trunk/rt_data/execute_array/execute_array_oracle.pl line 62. ' # good rows successfully committed: $select * from mytest = [ [ '1', 'onetwothree ' ], [ '51', 'fiftyone ' ], [ '52', 'fiftythree ' ], [ '53', 'one ' ] ]; >>> This brings us back to the question of whether a failure of a single >>> tuple should cause execute_array() itself to return an error. >>> >>> ODBC doesn't do that. It treats that situation as SUCCESS_WITH_INFO >>> and that does seem very reasonable. >> >> The funny thing is being an big user of ODBC, I don't (and others in >> this thread expressed the same belief). > > I'm (wobbling) on the fence on that one. I can see it both ways, > though I'm more sympathetic to your viewpoint :) I wasn't wobbling and am only wobbling a little now since the point about $rows_affected being lost when RaiseError is set was made. I looked again at ODBC as to why it might do that and it appears it is because it states when an error occurs the various bound buffers are in an indeterminate state and yet an app may have bound buffers for row status array, rows processed array etc (like ArrayTupleStatus). So they return SQL_SUCCESS_WITH_INFO which a) avoid the problem of not allowing you to look at bound buffers b) allows you to find out what went wrong and where. DBI already has this covered with ArrayTupleStatus. Years of experience with writing, maintaining and debugging database code tells me that if something does not work completely then the programmer should have to go out of his way to accept that situation and continue i.e., I still think the default action for RaiseError in execute_array should be to raise an error. >> For me, if I use execute_array (with AutoCommit on or not) my >> principle issue is I want to know if anything failed so I do: >> >> $dbh->{RaiseError} = 1; >> my $ret = eval { >> execute_array(...) >> }; >> do_something_on_error() if ($@ || !$ret); # amended for DBD::Oracle behavior > > Ok. How would you get the total row count when using RaiseError? If you mean rows_affected, then you cannot as it stands but bear in mind we only have 2 usage cases at present - DBI and DBD::Oracle so read on, and in any case if I want it I just disable RaiseError temporarily. Clearly we cannot guarantee to be able to return the affected rows when RaiseError is on because Oracle cannot do it on a tuple basis and when an error is raised the return from execute_array is undef. The main point is if I want it I can get it. If you mean rows executed it is scalar(@status). >> Why put the onus on people who cannot accept some tuples to fail to >> check the ArrayTupleStatus instead of put it on those who can accept >> some to fail to examine ArrayTupleStatus? My feeling is that it is >> very serious to ignore a partial failure and so the default should >> be to ensure you know about it with RaiseError. For those people who >> can accept a tuple to fail, they can look at ArrayTupleStatus and >> still commit the changes. > > I agree. good, some progress ;-) >>>> but I can live with that. >>> Perhaps we could default to the SUCCESS_WITH_INFO model and add an >>> attribute for people to use if they want tutple errors to cause >>> execute_array() itself to return an error. but that is a serious change in the default behaviour which could cause a lot of people a lot of work. >>> We can debate the name later, but for now let's call it: >>> >>> ArrayPromoteTupleError => 1 >> >> which defaults to 1, I hope. > > Probably ;-) > >> Incidentally, I just blogged about this a few hours ago as I think >> it is a serious problem to ignore potential failures. Add that to >> the fact that some versions of Oracle database (broken) return >> success with info but don't then commit the ok tuples. > > Yes, that's clearly a serious bug. > > Tim. Let me see if I can sum this all up so we can move on: Ok, revision of some facts: 1 there are currently only 2 implementations of execute_array, DBI and DBD::Oracle 2 DBD::Oracle at least in 1.27 does not conform to the written spec today in that it does not raise an error if RaiseError is set but DBI does. fixed by John raising an error instead of a warning and I believe he has already done this. 3 DBI and the written spec disagree in that one says an error field in ArrayTupleStatus contains 2 fields and the other puts in 3 fields. fixed in DBI trunk already 4 DBD::Oracle at least in 1.27 does not return the rows_affected in list context even if RaiseError is disabled so no one could be using it right now. John has already shown me a branch which fixes this 5 DBD::Oracle puts 2 fields in an ArrayTupleStatus error field but DBI puts 3 in. can easily be fixed in DBD::Oracle and I believe John has addressed this now Now some differences we would like to cope with: 6 Some drivers may be able to return rows affected per tuple and some like Oracle cannot We already do this because drivers which can return the rows affected per tuple already do so in the ArrayTupleStatus 7 Some drivers may be able to return the total number of rows affected (in the batch, like DBD::Oracle) and some may not. We already the handle the tuples executed and rows_affected with array context on execute_array if RaiseError is off. Now if RaiseError is on you'll not get these so if you need them you just disable it locally. Even if you don't disable RaiseError you can still get tuples executed but not rows affected. { local $sth->{RaiseError} = 0; my ($executed, $affected) = $sth->execute_array(ArrayTupleStatus = \my @status); if (!$executed) { # some/all failed # $affected in Oracle's case is the right number once fixed # if you want to find the failured tuples look at @status # it you want to know the affected per tuple look at @status # to find the number of executed tuples scalar(@status) } } 8 Some drivers may abort on the first error and some may continue and do all tuples Now some coding situations we would like to allow: 9 for those people (me most of the time) who expect all the tuples to succeed (and I suspect that covers most people) we need an easy way to indicate an error. The convention in DBI if you don't want to check every return status is to RaiseError => 1. People expect RaiseError => 1 will raise an error if something failed and would be surprised to find it did in all cases except one. Just search for RaiseError on perl monks - it is probably the most mentioned attribute wrt DBI. I'm willing to bet if you asked most people who use DBI whether execute_array raised an error when some of the rows failed most would say yes. Now, I'm assuming these people have not been looking at the rows_affected in list context since it is not available via DBI when RaiseError is set and it is not available via DBD::Oracle (in 1.27) at all whether RaiseError is set or not. # this is what most people do and in addition DBIx::Class does the stuff in # comments too { my @status; $sth->{RaiseError} = 1; eval { $sth->execute_array({ArrayTupleStatus = \@status); }; if ($@) { # to find tuple in error look in @status # to find rows executed scalar(@status) and hence know if batch was aborted # you don't know affected rows unless the driver does it per tuple # if you need then disable RaiseError die $sth->errstr if ($@); } } 10 some more "sophisticated" people (we have not found yet) might expect some tuples to fail and instead of setting RaiseError (or by clearing it) they check execute_array status and may be even look at rows_affected in list context. case already covered in 7 above So the benefits: o only a small doc change to DBI o no ones code using DBI and not DBD::Oracle is broken o DBD::Oracle is fixed to work to the spec the disadvantages: o you cannot know the total rows affected when RaiseError is on but a) you never could in DBI and even when it was off, you only could in DBD::Oracle for a few versions a long time ago. You can work around this easily. So, I think DBI works fine right now (given slight pod change) and don't want to complicate it or worse, break peoples existing code. DBD:: Oracle on the other hand does not but I believe John has already demonstrated it can be made to work. Apologies for length but I could not work out how the simple clarification I was seeking so we could fix DBD::Oracle ended up in changing DBI. I might be at fault for that - sorry. Martin -- Martin J. Evans Easysoft Limited http://www.easysoft.com