From: Joe Corneli
Subject: totally ignoring database error?
Date: 
Message-ID: <dc4ba03e-517a-4ba2-91d2-67623cc1d34b@q77g2000hsh.googlegroups.com>
I am not a pro on the exception handling system.  I think my question
is easy.

I wrote the following code that handles errors that arise when
duplicate strings are added to my database via CLSQL.

(defun add-string (text)
 "Add a new string to the strings table."
 (handler-bind ((sql-database-data-error
                 #'(lambda (condition)
                     (declare (ignore condition))
                     (warn "\"~A\" already exists." text)
                     (abort))))
   (insert-records :into [strings]
                   :attributes '(text)
                   :values `(,text))
   (add-record (string-present-p text) 0)))

Just as expected, when a duplicate string is provided, the code aborts
evaluation, e.g.:

(add-string "Introduction")
WARNING: "Introduction" already exists.
; Evaluation aborted.

My problem is, if I run the code synchronously through SLIME, I get
the message "Synchronous Lisp Evaluation aborted", and SLIME itself
aborts.  This can derail a much larger process.

So, I'd like to handle the error without aborting, or anyway, without
bothering SLIME.

Any advice is appreciated!

From: Cor
Subject: Re: totally ignoring database error?
Date: 
Message-ID: <87prw6jspv.fsf@atthis.clsnet.nl>
So, packaging matters .... ?

I realy don't give a rattus rattus negris
postdigestitiveremainderexhaustpoint  about opinions anyway.

Cor

-- 
SPAM DELENDA EST                         http://www.clsnet.nl/mail.php
    (defvar My-Computer '((OS . "GNU/Emacs") (IPL . "GNU/Linux")))
Alle schraifvauden zijn opsettelick, teneynde ieder lafaart de cans te 
           gevuh over spelingk te mekkuh instede de inhaut
From: Maciej Katafiasz
Subject: Re: totally ignoring database error?
Date: 
Message-ID: <43b91e5a-307d-4718-ad50-e7afb2eb6ffe@d21g2000prf.googlegroups.com>
On Jan 13, 4:16 am, Joe Corneli <·············@gmail.com> wrote:
> I wrote the following code that handles errors that arise when
> duplicate strings are added to my database via CLSQL.
>
> (defun add-string (text)
>  "Add a new string to the strings table."
>  (handler-bind ((sql-database-data-error
>                  #'(lambda (condition)
>                      (declare (ignore condition))
>                      (warn "\"~A\" already exists." text)
>                      (abort))))
>    (insert-records :into [strings]
>                    :attributes '(text)
>                    :values `(,text))
>    (add-record (string-present-p text) 0)))
>
> Just as expected, when a duplicate string is provided, the code aborts
> evaluation, e.g.:
>
> (add-string "Introduction")
> WARNING: "Introduction" already exists.
> ; Evaluation aborted.
>
> My problem is, if I run the code synchronously through SLIME, I get
> the message "Synchronous Lisp Evaluation aborted", and SLIME itself
> aborts.  This can derail a much larger process.

What exactly do you mean by "run the code synchronously through
SLIME"?

> So, I'd like to handle the error without aborting, or anyway, without
> bothering SLIME.

Reading CLHS:

http://www.lisp.org/HyperSpec/Body/fun_abortcm_c_cm_use-value.html#abort

reveals that ABORT invokes the same-named restart. So apparently
you're invoking a restart established by SLIME to guard against
unexpected termination. The obvious solution is to establish an ABORT
restart of your own, to have a well-defined point to jump to. Which,
btw, hints at another dubious point: you abort the computation without
ever specifying what is your unit. That's bound to end up badly, as
you just found out. And from the topic of your post, I assume that you
actually want to just skip over the error and continue processing (ie.
"abort" the function). For that ABORT is absolutely the wrong tool, it
only worked for you in REPL because if you call ABORT one-level deep,
it has the effect of just jumping back to the REPL, which incidentally
gave the same result as what you wanted, but it'll work completely
differently in any other context.

The proper solution requires you to define the actual result you
desire, and code for that. So, I'm gonna assume the following:

1) You want duplicates to be a non-fatal occurence (which really,
really should've warned you that ABORT is not the right tool)
2) Upon discovery of a duplicate, you want to warn, and by default
just skip over the entry
3) It's often a good form[1] to leave the policy decisions to the
higher-ups, yourself only implementing mechanisms (which is exactly
what drove the design of the CL condition system). Thus you should
provide several ways of handling the condition, and let the caller
choose.

The following is one possible solution:

(define-condition sql-duplicate-string (warning)
  ((text :initarg :text :reader text-of))
  (:report (lambda (cond stream)
             (format stream "\"~A\" already exists." (text-of
cond)))))

(defmacro with-retry ((&key (restart-name 'retry) (report "Try
executing the code again.")) &body forms)
  "Provides a restart called RETRY (or REPORT-NAME, if provided),
invoking which causes BODY to be executed again. Optional REPORT
parameter (which should be a string or function) can be used to
provide a custom restart description."
  (with-gensyms (ok retry)
    `(restart-bind ((,restart-name
                     (lambda () (throw ',retry nil))
                      :report-function
                      ,(if (stringp report)
                           `(lambda (stream)
                                               (write ,report :stream
stream))
                           report)))
      (catch ',ok
        (loop do
             (catch ',retry
               (throw ',ok
                 (progn
                   ,@forms))))))))

(defun add-string (text)
 "Add a new string to the strings table."
 (handler-bind ((sql-database-data-error
                 #'(lambda (condition)
                     (declare (ignore condition))
                     (warn 'sql-duplicate-string :text text)
                     (invoke-restart 'ignore))))
   (restart-case
       (with-retry ()
         (insert-records :into [strings]
                         :attributes '(text)
                         :values `(,text)))
     (ignore () :report "Ignore the warning and continue."
       (return-from add-string)))
   (add-record (string-present-p text) 0)))

It might seem overly long and complicated on its own, but notice how
SQL-DUPLICATE-STRING and WITH-RETRY are nicely packaged for future
reuse, so you (and I, I actually wrote WITH-RETRY because I noticed my
private utility package didn't have it yet, and the actual code to
implement it is far from lucid :) won't have to implement it again. So
the actual code addition to ADD-STRING is very slight. And your
callers will be able to handle the duplicate strings in some special
way (like, say, logging them somewhere) if they choose to, so they
won't have to go always with whatever choice you made.

Cheers,
Maciej

[1] "Good form"'s definition depending on the context, obviously. If
your function is not intended to be generic at all, it makes no sense
to try and add genericity. But if it is, then you should consider
adding the plumbing for others to use.
From: Alex Mizrahi
Subject: Re: totally ignoring database error?
Date: 
Message-ID: <4789d1c3$0$90264$14726298@news.sunsite.dk>
 JC> So, I'd like to handle the error without aborting, or anyway, without
 JC> bothering SLIME.

why are you calling abort then?
use handler-case instead of handler-bind -- it automatically unwinds stack 
to the point it's called.
and then you can signal warning:

CL-USER> (defun chocho ()
    (handler-case (error "bu-goga")
      (error (c) (warn "Shit happens. shit:~a" c))))
CHOCHO
CL-USER> (chocho)
WARNING: Shit happens. shit:bu-goga
NIL

the idea of warning is that we can just log it can continue execution.
if something critical happens, you should signal an error.

handler-bind and restarts are instruments for quite different situation, you 
shouldn't use them in "normal" functions that you have.