Search code examples
javascriptxquerymarklogic

Convert XQuery Search API bucket generation to JSearch in MarkLogic


I wanted to "convert" one of my old XQuery examples that uses buckets (bucketed search) to JSearch:

import module namespace search = 
  "http://marklogic.com/appservices/search"
  at "/MarkLogic/appservices/search/search.xqy";

declare variable $options := <options xmlns="http://marklogic.com/appservices/search">
  <constraint name="height">
    <range type="xs:double" facet="true">
      <bucket ge="1.9" name="supertall">1.90m + </bucket>
      <bucket lt="1.9" ge="1.7" name="tall">1.70m - 1.90m</bucket>
      <bucket lt="1.7" ge="1.2" name="normalish">1.20m - 1.70m</bucket>
      <bucket lt="1.2" name="short">0m - 1.20m</bucket>
      <facet-option>limit=20</facet-option>
      <json-property>height</json-property>
    </range>
  </constraint>
</options>;

let $results := search:search("height:short", $options)
for $facet in $results/search:facet
return $results;

The above allows the definition of buckets as well as allows the usage of 'height' as part of the search grammar, meaning that a search such as search:search('height:short') works just as fine.

Unfortunately I couldn't get the JSearch version working, this is what I have tried:

var jsearch = require('/MarkLogic/jsearch');
jsearch.documents(
  jsearch.facet('Height', 'height').groupInto([
    jsearch.bucketName('short'), 1.60,
    jsearch.bucketName('normal'), 1.90,
    jsearch.bucketName('tall'), 4.00
  ]))
.where(cts.parse('height:short'))
.result();

The above code returns:

{ "results": null, "estimate": 0 }

I have also tried to add a reference to the JSON property 'height' but that didn't work either:

var jsearch = require('/MarkLogic/jsearch');
var reference = { height: cts.jsonPropertyReference('height') };
jsearch.documents(
  jsearch.facet('Height', 'height').groupInto([
    jsearch.bucketName('short'), 1.60,
    jsearch.bucketName('normal'), 1.90,
    jsearch.bucketName('tall'), 4.00
  ]))
.where(cts.parse('height:short', reference))
.result();

However when I remove the .where() constraint I get my buckets generated just fine. Any suggestions?


Solution

  • I believe I have found a solution to this. The values of height are numbers and in my JSearch query I am trying to match the string 'short' against those numbers. In order to overcome this I had to use a callback function which was documented on this page http://docs.marklogic.com/cts.parse

    Essentially the solution was to create my own query using a set of cts query constructors (cts.andQuery and cts.jsonPropertyRangeQuery). The solution now looks like this:

    var jsearch = require('/MarkLogic/jsearch');
    
    var short = 1.60;
    var normal = 1.80;
    var tall = 1.90;
    
    var refCallback = function(operator, values, options) {
      if (values === 'short') {
        return  cts.jsonPropertyRangeQuery('height', '<=', short)
      } else if (values === 'normal') {
        return cts.andQuery([
          cts.jsonPropertyRangeQuery('height', '>=', normal),
          cts.jsonPropertyRangeQuery('height', '<', tall)
        ])
      } else {
        return cts.jsonPropertyRangeQuery('height', '>=', tall)
      }
    };
    var reference = { height: refCallback };
    
    jsearch.documents(
      jsearch.facet('height').groupInto([
        jsearch.bucketName('short'), short,
        jsearch.bucketName('normal'), normal,
        jsearch.bucketName('tall'), tall
      ]))
    .where(cts.parse('height:tall', reference))
    .result();
    

    Note that I also had to externalise the variable declarations as now I can reuse those within the bucket definitions as well as the callback function.