[racket] database query throws exception in servlet, works stand alone

From: George Neuner (gneuner2 at comcast.net)
Date: Sat Jan 3 05:06:07 EST 2015

Hi all,

Using 6.0.1,  webserver and postgresql.

Got a weird problem:

I have a servlet that uses Postgresql's  tsquery function to do a text 
search.  tsquery requires a particular syntax in its search string, but 
I can't guarantee that the provided string will be valid (too many 
variations), so the only thing I can do is try it and see what happens.  
When the search string is valid, everything is fine.

However, when the search string syntax is invalid I get the following 
exception:

length: contract violation
   expected: list?
   given: #f

Stack trace:

activity/search at:
   line 645, column 0, in file <redacted>\lookup.ss
<unknown procedure> at:
   line 255, column 18, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-val-first.rkt
<unknown procedure> at:
   line 255, column 18, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-val-first.rkt
<unknown procedure> at:
   line 58, column 2, in file C:\Program Files\Racket\share\pkgs\web-server-lib\web-server\dispatchers\dispatch-servlets.rkt
select-handler/no-breaks at:
   line 162, column 2, in file C:\Program Files\Racket\collects\racket\private\more-scheme.rkt
<unknown procedure> at:
   line 112, column 8, in file C:\Program Files\Racket\share\pkgs\web-server-lib\web-server\private\dispatch-server-unit.rkt

Yes, this is on Windows, but the same happens on Linux.

Some exploration has determined that the servlet exception is coming 
from  query-rows.    The with-database macro shown below in the 2nd 
listing is in (exported from) a separate file which included into 
#:servlet-namespace  in serve/servlet  because it contains a large 
number of utility functions used by all my servlets.


  =====  lookup.ss  =====

(define (activity/search request)
   (let* [
          (tid     (make-log-id))
          (params  (request-bindings request))
          (inquiry (exists-binding? 'query params))

          (result  #f)
          (success #f)
          (dberror #f)
         ]

     ; validate request
     (unless inquiry
       (log-write tid 'activity/search "parameters" params )
       (send/back (response/parameters "parameters: query string")))

     ; get request parameters
     (set! inquiry (extract-binding/single 'query params))

     ; log query
     (log-write tid 'activity/search "query" inquiry)

     ; database
     (with-database db errors to dberror
        (let [
              (sql-cmd (string-join '(
                              "select * from activities"
                              "   where to_tsvector(activity)"
                              "         @@ to_tsquery($1)"
                              )))
             ]

          (set! result (query-rows db sql-cmd inquiry))

          ; valid result?
          (when (and result
                     (not (empty? result))
                     (valid-data? result))
            (set! success #t))
          ))

     ; log result
     (log-write tid 'activity/search (if success "success" "failure")
                inquiry (length result) dberror)

    ; send response
     (let [
           (retval (make-hash `((success . ,success)
                                (data    . #f)
                                (error   . ,dberror))
                              ))
          ]
       (when success
          (hash-set! retval 'data
                     (for/list [(row result)]
                       (activity->json row)
                       ))
          )

       (send/back (response/json retval))
       )

     ))



  =====  utility.ss  =====

(define-syntax with-database
   (syntax-rules (errors to)
     ((with-database dbc errors to errmsg body ...)
      ; -- start template
      (let/ec fail-network
        (with-handlers [
                        (exn:fail:network?
                         (lambda (e)
                           (set! errmsg "database connection error")
                           (fail-network)))
                       ]
          (let [
                (dbc (connect-database))
               ]
            (let/ec fail-sql
              (with-handlers [
                              (exn:fail:sql?
                               (lambda (e)
                                 (let [(info (exn:fail:sql-info e))]
                                   (set! errmsg (cdr (assoc 'message info)))
                                   (fail-sql))))
                             ]
                ;----------
                  body ...
                ;----------

                )
              (disconnect dbc))
            )))
      ; -- end template
      )))


  =================================


If I run the exact same database query code stand alone in a test app - 
i.e. without the surrounding servlet baggage - and give it an invalid 
search string, it works as expected and returns a database error:  
"syntax error in tsquery: \"<the invalid string>\"".

I don't understand why the query code is throwing a LIST? contract error 
and why it happens only when run in a servlet.  I also don't know what 
to do about it.   Any enlightenment and/or suggestions would be greatly 
appreciated.

Thanks,
George



Posted on the users mailing list.