I would like to profile a RestAPI call to a custom function. It seems that I have a couple of options. Please comment on each:
- Extract the RestAPI call from the access log or request log for a
given application server port and copy it to the query console. I
don't know if this is possible, unless I invoke the RestAPI call
with the
xdmp:http-postorxdmp.httpPostAPI calls. I'm just not sure that profile a Rest call makes sense. - I have a custom "zsearch" module/library that handles a search request and compresses
the result. I think I can add the
return:planandreturn:metricsoption to the parameters for the search query in thezsearch:get()function, but I don't understand the syntax for doing so in the code sample below where I want to hard-code the return calls. Can someone provide an example?
In addition, this module makes use of the search-model-query Rest API call at /MarkLogic/rest-api/models/search-model-query.xqy. I'm wondering where this module is located because I'd like to review it and add some logging to it. I'm not sure why the code uses the search-model-query.xqy library instead of the search:search call. It seems that the search:search invocation would be more efficient than what appears to be a subsequent Rest API call from within a database module.
I would also like to understand what would be required to port this code from using search-model-query.xqy to the search:search functionality described in https://docs.marklogic.com/search:search.
Here is the zsearch code:
module namespace zsearch = "http://marklogic.com/rest-api/resource/zsearch";
import module namespace lid = "http://marklogic.com/util/log-id"
at "/MarkLogic/appservices/utils/log-id.xqy";
import module namespace searchmodq = "http://marklogic.com/rest-api/models/search-model-query"
at "/MarkLogic/rest-api/models/search-model-query.xqy";
import module namespace sut = "http://marklogic.com/rest-api/lib/search-util"
at "/MarkLogic/rest-api/lib/search-util.xqy";
import module namespace eput = "http://marklogic.com/rest-api/lib/endpoint-util"
at "/MarkLogic/rest-api/lib/endpoint-util.xqy";
declare default function namespace "http://www.w3.org/2005/xpath-functions";
declare option xdmp:mapping "false";
(:
declare option xdmp:transaction-mode "auto";
:)
declare private function zsearch:response-callback(
$response-type as xs:string?
) as empty-sequence()
{
eput:response-type-callback($response-type),
let $timestamp := xdmp:request-timestamp()
return if (exists($timestamp)) then eput:add-response-header("ML-Effective-Timestamp",string($timestamp)) else ()
};
declare private function zsearch:query-parameter(
$paramMap as map:map,
$params as map:map,
$name as xs:string,
$required as xs:boolean,
$repeatable as xs:boolean,
$allowed as xs:string*,
$privileges as xs:string*,
$converter as function(*)?
) as map:map
{
let $values := map:get($params,$name)
return (
if (not($required) or exists($values[. ne ""])) then ()
else error((),"REST-REQUIREDPARAM",$name),
if (empty($values))
then $paramMap
else (
if (empty($privileges) or xdmp:has-privilege($privileges, "execute")) then ()
else error((),"REST-INVALIDPARAM", concat(
$name," parameter requires at least one privilege: ",string-join($privileges,", ")
)),
if ($repeatable or count($values) lt 2) then ()
else error((),"REST-REPEATEDPARAM",$name),
map:with($paramMap,$name,
if (empty($allowed) and empty($converter))
then $values
else
let $fname :=
if (empty($converter)) then ()
else function-name($converter)
let $fns :=
if (empty($fname)) then ()
else namespace-uri-from-QName($fname)
let $flocal :=
if (not($fns = ("http://www.w3.org/2001/XMLSchema"))) then ()
else local-name-from-QName($fname)
for $val in $values
let $converted :=
if (empty($converter))
then $val
else if (empty($flocal) or xdmp:castable-as($fns,$flocal,$val))
then $converter($val)
else error((),"REST-INVALIDPARAM", concat(
$name, " parameter not convertible to xs:",
local-name-from-QName($fname)," value: ",string($val)
))
return
if (exists($allowed) and not($converted = $allowed))
then error((),"REST-INVALIDTYPE",
($name, $val, "is not one of", string-join($allowed, "|")))
else $converted
)
)
)
};
declare private function zsearch:validate-parameter-names(
$paramMap as map:map,
$params as map:map,
$ignore-patterns as xs:string*
) as xs:string*
{
for $name in map:keys($params)
return
if (map:contains($paramMap,$name)) then ()
else zsearch:check-ignore($name,$ignore-patterns)
};
declare private function zsearch:check-ignore(
$name as xs:string,
$ignore-patterns as xs:string*
) as xs:string?
{
if (exists($ignore-patterns) and exists(
for $pattern in $ignore-patterns
return
if (matches($name,$pattern))
then true()
else ()
))
then ()
else $name
};
declare private function zsearch:compress(
$data as document-node()*
) as document-node()
{
let $zip := xdmp:zip-create(
<parts xmlns="xdmp:zip"><part>result.json</part></parts>,
($data)
)
return document { $zip }
};
declare function zsearch:get($context as map:map, $params as map:map) as document-node()* {
let $headers := eput:get-request-headers()
let $accept := eput:get-accept-types($headers)
let $method := eput:get-request-method($headers)
let $env := eput:response-callback-map(zsearch:response-callback#1)
let $paramMap := map:new()
=>zsearch:query-parameter($params,"q",false(),false(),(),(),())
=>zsearch:query-parameter($params,"category",false(),true(),("content","metadata","collections","permissions","properties","quality","metadata-values"),(),())
=>zsearch:query-parameter($params,"format",false(),false(),("json","xml"),(),())
=>zsearch:query-parameter($params,"start",false(),false(),(),(),xs:unsignedLong#1)
=>zsearch:query-parameter($params,"pageLength",false(),false(),(),(),xs:unsignedLong#1)
=>zsearch:query-parameter($params,"options",false(),false(),(),(),())
=>zsearch:query-parameter($params,"collection",false(),true(),(),(),())
=>zsearch:query-parameter($params,"directory",false(),false(),(),(),())
=>zsearch:query-parameter($params,"view",false(),true(),("none","results","metadata","facets","all"),(),())
=>zsearch:query-parameter($params,"txid",false(),false(),(),(),())
=>zsearch:query-parameter($params,"database",false(),false(),(),(),())
=>zsearch:query-parameter($params,"forest-name",false(),true(),(),(),())
=>zsearch:query-parameter($params,"transform",false(),false(),(),(),())
=>zsearch:query-parameter($params,"timestamp",false(),false(),(),(),())
=>zsearch:query-parameter($params,"trace",false(),true(),(),"http://marklogic.com/xdmp/privileges/rest-tracer",())
let $extra-names := zsearch:validate-parameter-names(
if ($method = ("GET", "HEAD"))
then zsearch:query-parameter($paramMap,$params,"structuredQuery",false(),false(),(),(),())
else $paramMap,
$params,
('name')
)
return (
if (empty($extra-names)) then ()
else error((),"REST-UNSUPPORTEDPARAM", concat(
"invalid parameters: ",string-join($extra-names,", ")
)),
lid:enable(map:get($paramMap,"trace")),
if (searchmodq:check-untraced()) then ()
else lid:log(
$searchmodq:trace-id,"zearch",
map:entry("method",$method)=> map:with("headers",$headers)=> map:with("parameters",$paramMap)
),
if (empty($accept)) then ()
else if ($accept = ("application/json", "text/json", "application/xml", "text/xml")) then
if (map:contains($paramMap,"category"))
then error((),"REST-UNSUPPORTEDPARAM",
"Can use the 'category' parameter only with multipart/mixed accept")
else
let $view := map:get($paramMap,"view")
return
if (not($view eq "none")) then ()
else error((),"REST-UNSUPPORTEDPARAM",
"Can use the 'none' value for the 'view' parameter only with multipart/mixed accept")
else if (starts-with(head($accept),"multipart/mixed"))
then map:put($env,"add-header",eput:add-response-header#2)
else error((), "REST-UNACCEPTABLETYPE", string-join($accept,", ")),
let $_ := 1
return (
let $response := searchmodq:search-get($headers,$paramMap,$env)
let $has-matches := map:get($env, "has-matches")
return
if (exists($response)) then
(:
let $_ := map:put($context,"output-type","application/octet-stream")
:)
let $_ := xdmp:set-response-content-type("application/octet-stream")
return zsearch:compress($response)
else if ($has-matches) then ()
else xdmp:set-response-code(404,"Not Found")
)
)
};```