[racket] database query throws exception in servlet, works stand alone
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