elasticsearchmustacheelasticsearch-template

Elasticsearch indexed search template generates empty strings in array


First of all, this is taken from documentation:

Passing an array of strings

GET /_search/template
{
  "template": {
    "query": {
      "terms": {
        "status": [
          "{{#status}}",
          "{{.}}",
          "{{/status}}"
        ]
      }
    }
  },
  "params": {
    "status": [ "pending", "published" ]
  }
}

which is rendered as:

{
"query": {
  "terms": {
    "status": [ "pending", "published" ]
  }
}

However, in my scenario I've done exactly the same template (at least I think so), but it produces a slightly different output for me:

.."filter" : {
  "bool" : {
    "must" : [{
        "terms" : {
          "myTerms" : [
            "{{#myTerms}}",
            "{{.}}",
            "{{/myTerms}}"
          ],
          "_cache" : true
        }
      }
    ]
  }
}..

That's how I call it later:

GET /passport/_search/template
{
    "template": {
        "id": "myTemplate" 
    },
    "params": {
        "myTerms": ["1", "2"]
    }
}

However it's rendered as:

.."myTerms" : ["", "1", "2", ""]..

That wouldn't a be a issue, but myTerms are stored as integers and I would like to keep it this way (but if only this is solution, then fine, I can live with it), but then query throws exception that it cannot convert "" into integer type, which is expected behaviour

NumberFormatException[For input string: \"\"]

How should I deal with that? I don't want to store my templates as files, I prefer them being indexed.

This SO question was promising: Pass an array of integers to ElasticSeach template but it's not clear and answer didn't solve my issue (I wasn't allowed to store my template like that).

Elasticsearch version used: 1.6.0

Please advice.


Solution

  • I've seen this requirement before and the solution looks hacky, but it works. Basically, the commas in the template are the problem because Mustache will go over the array and for each element in the array will put the element - {{.}} - but also the comma you are specifying inside {{#myTerms}} and {{/myTerms}}.

    And, also, in your case you shouldn't use double quotes - "{{.}}" because the element itself will be surrounded with double quotes. That's why you are seeing "1" in the result. But, if you want to match numbers that should be a list of numbers, not strings.

    So, first of all, get rid of the double quotes. This means, surrounding the template with double quotes and escape any double quotes that should make it in the final result (you'll understand shortly by seeing the example below).

    Secondly, the hacky part is to simulate the commas in the result and to skip the last comma. Meaning, 1,2,3, shouldn't contain the last comma. The solution is to provide the parameters as a list of tuples - one element of the tuple is the value itself, the other element is a boolean: [{"value":1,"comma":true},{"value":2,"comma":true},{"value":4}]. If comma is true then Mustache should put the ,, otherwise not (this case is for the last element in the array).

    POST /_search/template/myTemplate
    {"template":"{\"filter\":{\"bool\":{\"must\":[{\"terms\":{\"myTerms\":[{{#myTerms}}{{value}}{{#comma}},{{/comma}}{{/myTerms}}],\"_cache\":true}}]}}}"}
    

    And this is how you should pass the parameters:

    {
      "template": {
        "id": "myTemplate"
      },
      "params": {
        "myTerms": [{"value":1,"comma":true},{"value":2,"comma":true},{"value":4}]
      }
    }
    

    What this does is to generate something like this:

    {
      "filter": {
        "bool": {
          "must": [
            {
              "terms": {
                "myTerms": [1,2,4],
                "_cache": true
              }
            }
          ]
        }
      }
    }