Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix detection of XQuery errors thrown by XQueryServlet #284

Merged
merged 2 commits into from
Dec 21, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 49 additions & 39 deletions modules/session.xql
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
xquery version "3.0";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update to 3.1?


(:~
Post-processes query results for the sandbox application. The
controller first sends the user-supplied query to XQueryServlet
for evaluation. The result is then passed to this script, which
stores the result set into the HTTP session and returns the number
of hits and time elapsed.
Post-processes query results for the sandbox application. The
controller first sends the user-supplied query to XQueryServlet
for evaluation. The result is then passed to this script, which
stores the result set into the HTTP session and returns the number
of hits and time elapsed.

Subsequent requests from the sandbox application may retrieve single
items from the result set stored in the session (see controller).
Subsequent requests from the sandbox application may retrieve single
items from the result set stored in the session (see controller).
:)

declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
Expand Down Expand Up @@ -77,48 +77,58 @@ declare function local:retrieve($num as xs:integer) as element() {

(:~ Take the query results and store them into the HTTP session. :)
declare function local:store-in-session($results as item()*) as element(result) {
let $null := session:set-attribute('cached', $results)
let $null := session:set-attribute('cached', $results)
let $startTime := request:get-attribute("start-time")
let $elapsed :=
if ($startTime) then
let $current-time := current-time()
let $hours := hours-from-duration($current-time - xs:time($startTime))
let $minutes := minutes-from-duration($current-time - xs:time($startTime))
let $seconds := seconds-from-duration($current-time - xs:time($startTime))
return ($hours * 3600) + ($minutes * 60) + $seconds
let $current-time := current-time()
let $hours := hours-from-duration($current-time - xs:time($startTime))
let $minutes := minutes-from-duration($current-time - xs:time($startTime))
let $seconds := seconds-from-duration($current-time - xs:time($startTime))
return ($hours * 3600) + ($minutes * 60) + $seconds
else 0
return
<result hits="{count($results)}" elapsed="{$elapsed}"/>
return
<result hits="{count($results)}" elapsed="{$elapsed}"/>
};

(: When a query has been executed, its results will be passed into
this script in the request attribute 'results'. The result is then
stored into the HTTP session. Subsequent requests from the sandbox
can reference a result item in the session by passing parameter 'num'.
(: When a query has been executed by XQueryServlet, its results will be passed
into this script via the "results" request attribute. The script then stores
the results into the HTTP session for subsequent retrieval via individual
requests to this endpoint with a "num" parameter.

Error reporting must take into account how controller.xql handles
errors thrown by XQueryServlet. From https://exist-db.org/exist/apps/doc/urlrewrite#xq-servlet:

> Since controller.xql sets xquery.report-errors to "yes", an error
> in the XQuery will not result in an HTTP error. Instead, the string
> message of the error is enclosed in an element <error> which is
> then written to the response stream. The HTTP status is not changed.

Thus, this script accesses errors in the response stream via request:get-data(),
which supplies the <error> element wrapped in a document node.

Furthermore, certain low-level errors omit descriptions (i.e., the
description is null), so the <error> element is empty. To aid the user
in such situations, the script reports that an unidentified error was
raised. It also points the user to exist.log, where a hopefully a stack
trace shows the full error.
:)
session:create(),
let $xqueryservlet-error := request:get-data()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a new variable here

let $is-error := $xqueryservlet-error instance of document-node()

let $results := request:get-attribute("results")
let $pos := xs:integer(request:get-parameter("num", ()))
return
(: From https://exist-db.org/exist/apps/doc/urlrewrite#xq-servlet:

> Since controller.xql sets xquery.report-errors to "yes", an error
> in the XQuery will not result in an HTTP error. Instead, the string
> message of the error is enclosed in an element <error> which is
> then written to the response stream. The HTTP status is not changed.

This error is captured here as $xqueryservlet-error. Since not all errors
contain descriptions, though, we need to report that an unidentified
error was raised. We will just use the default output from try-catch on
fn:error.
:)
if (exists($xqueryservlet-error)) then
if (string-length($xqueryservlet-error) gt 0) then
$xqueryservlet-error
(
if ($xqueryservlet-error instance of document-node()) then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would combine the to conditions into one

if (
    $xqueryservlet-error instance of document-node() and
    $xqueryservlet-error/error => normalize-space() => string-length()
)

I also left out the "gt 0" because any integer greater zero evals to true

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$is-error and ...

Copy link
Member Author

@joewiz joewiz Dec 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but request:get-data() can return a string, in which case request:get-data()/error would throw an error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the first condition of the two is false the second is not evaluated

Copy link
Member Author

@joewiz joewiz Dec 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s implementation-dependent behavior, which I avoid without a compelling reason. From https://www.w3.org/TR/xquery-31/#id-logical-expressions:

The order in which the operands of a logical expression are evaluated is implementation-dependent. The tables above are defined in such a way that an or-expression can return true if the first expression evaluated is true, and it can raise an error if evaluation of the first expression raises an error. Similarly, an and-expression can return false if the first expression evaluated is false, and it can raise an error if evaluation of the first expression raises an error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(If that is an eXist optimization, it’s not documented, is it? If not, it should perhaps be noted in https://exist-db.org/exist/apps/doc/xquery and/or https://exist-db.org/exist/apps/doc/tuning.)

if (string-length(normalize-space($xqueryservlet-error/error)) gt 0) then
$xqueryservlet-error
else
Copy link
Member

@line-o line-o Dec 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else if ($xqueryservlet-error instance of document-node()) then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else if ($is-error)

element error {
"eXide tried to execute your query, but the XQueryServlet raised an error without a description.
Check exist.log for any associated errors."
}
else if ($pos) then
local:retrieve($pos)
else
element error { try { error() } catch * { $err:code || ": " || $err:description } }
else if ($pos) then
local:retrieve($pos)
else
local:store-in-session($results)
local:store-in-session($results)
)