[racket] racket/db exception handling and unmanaged transactions with postgres

From: George Neuner (gneuner2 at comcast.net)
Date: Tue Jan 20 10:46:52 EST 2015

Hi Thomas,

On 1/20/2015 6:53 AM, Thomas Lynch wrote:
> For purposes of illustration, I run the following query here in the 
> psql shell to show the the error message that it correctly issues:
>
>     => insert into x_unique_counters values ('table_author','1');
>     ERROR:  duplicate key value violates unique constraint
>     "x_unique_counters_pkey"
>     DETAIL:  Key (name)=(table_author) already exists.
>
>
> Now the racket program is ready for this kind of problem, here 
> 'the-query' is the same query just shown above, but this time issued 
> from this code
>
>
>            (begin
>                 (semaphore-wait (current-db-semaphore))
>                 (query-exec pgc  "begin")
>                 (begin0
>                  (with-handlers
>                    (
>                      [(lambda (v) #t) ; this handler catches anything
>                        (lambda (v)
>                          (query-exec pgc "rollback")
>                          (semaphore-post (current-db-semaphore))
>                          (raise v)
>                          )
>                        ]
>                      )
>
>                    (query-exec pgc the-query) ; this throws an exception
>
>                    )
>                   (db-exec "commit")
>                   (semaphore-post (current-db-semaphore))
>                 ))
>             ]
>           ))
>
>
> So when query-exec throws an exception, the handler grabs it.  Then 
> the handler executes an SQL rollback to cancel the transaction.  All 
> appears to be good, but then bang!  we get an exception in the handler 
> while doing the rollback:
>
>     >(db-exec* '("rollback"))
>     query-exec: current transaction is invalid
>
>
> All attempts to do any further queries on the connection fail with the 
> same error.   It appears to be foo-bar and a new connection must be 
> made before any further SQL can be issued against the database.


Your code is fine but I believe you've bumped into a quirk of 
transaction error handling in Postgresql:  when an SQL exception is 
raised, Postgresql aborts the current (sub-)transaction automatically - 
so in your example the transaction already has been rolled back and no 
longer exists when your code goes to do the explicit rollback.

If you want to keep the transaction open in the event of an error - 
e.g., to preserve things that *did* work - you need to establish a 
savepoint prior to executing the statement that might fail.  The 
savepoint opens an implicit sub-transaction that can fail independently. 
If an error occurs, automatic rollback affects only the subtransaction 
and returns to the last savepoint rather than aborting the whole [outer] 
transaction.   It is subtly different from opening an explicit 
sub-transaction, however.
see  http://www.postgresql.org/docs/9.3/static/sql-savepoint.html

Incidentally, Oracle and SQL Server do this also.

Hope this helps.
George

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20150120/0aca1783/attachment-0001.html>

Posted on the users mailing list.