4

I have the impression that the XQuery and the Server-side JavaScript APIs in MarkLogic are largely equivalent. But there seems to be a big difference in cts:search vs cts.search. In cts:search, I am able to specify an element to be searched and returned. For example, I can retrieve all recipes using cinnaomon as ingredient from a recipe book:

cts:search(//recipe, cts:element-word-query(xs:QName('ingredients'), 'cinnamon'))

Whereas cts.search doesn't accept a path expression and will return the whole recipe book document:

cts.search(cts.elementWordQuery(xs.QName('ingredients'), 'cinnamon'))

The same question has been asked in MarkLogic mailing list but I don't see an answer there: https://developer.marklogic.com/pipermail/general/2015-March/016508.html

Below is a minimal example:

<book>
  <recipe>
    <ingredients>cinnamon, peppermint</ingredients>
    <instruction/>
  </recipe>
  <recipe>
    <ingredients>sugar, peppermint</ingredients>
    <instruction/>
  </recipe>
  <recipe>
    <ingredients>coconut oil</ingredients>
    <instruction/>
  </recipe>
</book>

The xquery would be:

cts:search(//recipe, cts:element-word-query(xs:QName('ingredients'), 'cinnamon'))

and the response:

<recipe>
  <ingredients>cinnamon, peppermint</ingredients>
  <instruction></instruction>
</recipe>
Fan Li
  • 1,057
  • 7
  • 11

4 Answers4

5

There are technical reasons why this is so. The cts:search function in XQuery is actually not a function but a special form that has a function syntax. What that means is that the first argument doesn't actually get evaluated and then passed in to the function (if you think about it, that would be a very inefficient way to proceed!). In Javascript, the cts.search function is a real function. To avoid the inefficiency, we dropped the first parameter, so you need to pull the part you care about off the result.

If you want to constrain the set of results to those that are within the element recipe, wrap your query with a cts:element-query(xs:QName("recipe"), $your-query)

mholstege
  • 4,902
  • 11
  • 7
  • 2
    Thanks for your prompt response! I see how to use `cts.elementQuery` to limit the scope of the search to the `recipe` node. But it still returns the whole document instead of the only matched nodes. Suppose my cookbook has 1000 recipes and only a few use cinnamon, how to get the matched recipes in a sequence? – Fan Li Mar 21 '19 at 16:11
  • @FanLi, Can you share me an example of a recipe book that contains 'ingredients' element – Akbar aLI Mar 22 '19 at 09:13
  • Hello @AkbaraLI, I have added an example in my question. Thanks for the suggestion. – Fan Li Mar 22 '19 at 17:05
3

This should get you closer

https://docs.marklogic.com/cts.elementQuery

Applied with cts.andQuery as needed.

It is largely true that the JS and XQuery interfaces are functionally equivalent, but there are a few places (this is one) where the language itself does not support equivalence directly. Another is XQuery sequences which have no native equivalent in JS -- so are provided via extra JS classes.

Any cts (complex) query can be constructed out of primitive cts query objects/methods. The first parameter in the XQuery cts::search() is 'searchable expression' -- which is essentially the same as a constraining scope -- can be combined with cts.andQuery to produce the same effect (in both XQuery and JS). Depending on exactly what expression you used in XQuery, you need to find the equivalent cts.query for it for JS (or xquery).

Hence cts.elementQuery which is analogous to cts::search(//element-name, ..)

DALDEI
  • 3,722
  • 13
  • 9
  • Thanks for your explanation! I still have some doubts as in my reply to @mholstege's answer. – Fan Li Mar 21 '19 at 16:12
2

Given that the javascript version of cts:search() is missing the xquery's version' first parameter -- I don't see how it could return anything except document nodes. The indexing optimizations that cts:search and cts.search use has the granularity of 'fragments' (typically documents) -- designed to find the few matching documents of a potentially unbounded set. From there you need to traverse into the structure of the document. XQuery is particularly good at this -- path traversal is native to the language, JavaScript not so much.

I suggest you use search.search instead of cts:search -- its a higher level API designed to make tasks such as this easier.

DALDEI
  • 3,722
  • 13
  • 9
1

Building up on DALDEI's answer you can use the search api to return only recipe elements:

const search = require('/MarkLogic/appservices/search/search');

let options = fn.head(xdmp.unquote(`
<options xmlns="http://marklogic.com/appservices/search">
  <return-results>true</return-results>
  <searchable-expression>//recipe</searchable-expression>
  <extract-document-data>all</extract-document-data>
  <additional-query>
    <cts:element-word-query xmlns:cts="http://marklogic.com/cts">
      <cts:element>ingredients</cts:element>
      <cts:text xml:lang="en">cinnamon</cts:text>
    </cts:element-word-query>
  </additional-query>
</options>`)).root;

search.search("", options)        // returns a Sequence of search:response
  .toArray()[0]                   // get the first result
  .getElementsByTagName("recipe") // find all recipe elements 

This code returns a NodeList of recipe elements. The result for your provided book would be this single element node:

<recipe xmlns:search="http://marklogic.com/appservices/search">
  <ingredients>cinnamon, peppermint</ingredients>
  <instruction/>
</recipe>

This is not really a nice solution (in terms of quick and easy) but it might work as a workaround.

I also tried using the jsearch functions but didn't find a way to pass a searchable-expression parameter. I might have missed that because I have not used this alot yet.

Further readings:

Query Options Reference search:search

Wagner Michael
  • 2,172
  • 1
  • 15
  • 29