Here's how this has all panned out...

Changes says:

  Changed set_err() to append to errstr, with a leading "\n" if it's
    not empty, so that multiple error/warning messages are recorded.

  Added way for drivers to indicate 'success with info' or 'warning'
    by setting err to "0" for warning and "" for information.
    Both values are false and so don't trigger RaiseError etc.
    Warnings (err="0") are automatically printed if PrintError is set.
    Thanks to Steffen Goeldner for the original idea.

  Added $h->{HandleSetError} = sub { ... } to be called at the
    point that an error, warn, or info state is recorded.
    The code can alter the err, errstr, and state values
    (e.g., to promote an error to a warning, or the reverse).

The set_err() docs now read:

  =item C<set_err>

    $rv = $h->set_err($err, $errstr);
    $rv = $h->set_err($err, $errstr, $state);
    $rv = $h->set_err($err, $errstr, $state, $method);
    $rv = $h->set_err($err, $errstr, $state, $method, $rv);

  Set the C<err>, C<errstr>, and C<state> values for the handle.
  This method is typically only used by DBI drivers and DBI subclasses.

  If the L</HandleSetError> attribute holds a reference to a subroutine
  it is called first. The subroutine can alter the $err, $errstr, $state,
  and $method values. See L</HandleSetError> for full details.
  If the subroutine returns a true value then the handle C<err>,
  C<errstr>, and C<state> values are not altered and set_err() returns
  an empty list (it normally returns $rv which defaults to undef, see below).

  Setting C<err> to a I<true> value indicates an error and will trigger
  the normal DBI error handling mechanisms, such as C<RaiseError> and
  C<HandleError>, if they are enabled, when execution returns from
  the DBI back to the application.

  Setting C<err> to C<""> indicates an 'information' state, and setting
  it to C<"0"> indicates a 'warning' state.

  The $method parameter provides an alternate method name for the
  C<RaiseError>/C<PrintError> error string instead of the fairly
  unhelpful 'C<set_err>'.

  The C<set_err> method normally returns undef.  The $rv parameter
  provides an alternate return value.

  Some special rules apply if the C<err> or C<errstr>
  values for the handle are already set...

  If C<errstr> is true then: "C< [err was %s now %s]>" is appended if
  $err is true and C<err> is already true; "C< [state was %s now %s]>"
  is appended if $state is true and C<state> is already true; then
  "C<\n>" and the new $errstr are appended. Obviously the C<%s>'s
  above are replaced by the corresponding values.

  The handle C<err> value is set to $err if: $err is true; or handle
  C<err> value is undef; or $err is defined and the length is greater
  than the handle C<err> length. The effect is that an 'information'
  state only overides undef; a 'warning' overrides undef or 'information',
  and an 'error' state overrides anything.

  The handle C<state> value is set to $state if $state is true and
  the handle C<err> value was set (by the rules above).

And HandleSetError docs are:

  =item C<HandleSetError> (code ref, inherited)

  The C<HandleSetError> attribute can be used to intercept
  the setting of handle C<err>, C<errstr>, and C<state> values.
  If set to a reference to a subroutine then that subroutine is called
  whenever set_err() is called, typically by the driver or a subclass.

  The subroutine is called with five arguments, the first five that
  were passed to set_err(): the handle, the C<err>, C<errstr>, and
  C<state> values being set, and the method name. These can be altered
  by changing the values in the @_ array. The return value affects
  set_err() behaviour, see L</set_err> for details.

  It is possible to 'stack' multiple HandleSetError handlers by using
  closures. See L</HandleError> for an example.

  The C<HandleSetError> and C<HandleError> subroutines differ in subtle
  but significant ways. HandleError is only invoked at the point where
  the DBI is about to return to the application with C<err> set true.
  It's not invoked by the failure of a method that's been caled by
  another DBI method.  HandleSetError, on the other hand, is called
  whenever set_err() is called with a defined C<err> value, even if false.
  So it's not just for errors, despite the name, but also warn and info states.
  The set_err method, and thus HandleSetError, may be called multiple
  times within a method and is usually invoked from deep within driver code.

  In theory a driver can use the return value from HandleSetError via
  set_err() to decide whether to continue or not. If set_err() returns
  an empty list, indicating that the HandleSetError code has 'handled'
  the 'error', the driver could then continue instead of failing (if
  that's a reasonable thing to do).  This isn't excepted to be
  common and any such cases should be clearly marked in the driver
  documentation.

Comments most welcome.

Pure-perl drivers should already be using $h->set_err to record errors.
Compiled drivers are probably not, just setting err and errstr directly.
I'll provide a couple of simple hooks into set_err for compiled drivers.
One to take char* values and one for SV*.

I'm interested to know if people (especially driver authors) think this
mechanism is viable for recording/returning whatever kinds of 'warnings'
and 'success_with_info' your database supports. Including things like
text messages sent from stored procedures, which could be recorded as 'info'.

Tim.

p.s. I'm thinking of renaming HandleSetError to HandleSetState since it's
not just for 'errors'. Also $h->state() will become more widely used in time.
Or perhaps HandleSetErr to match set_err would suffice.

p.p.s Thanks to Steffen Goeldner for the original elegant idea.

Reply via email to