4

I've been struggling with Elasticsearch templates, specifically in optional parameters. I'd like to add optional filters there. This is the code snippet I was trying out:

{
  "filter" : {
    "bool" : {
      "must" : [
        {{#ProductIDs.0}}
        { "terms" : { "Product.ProductID" : [{{#ProductIDs}}{{.}},{{/ProductIDs}}] } }
        {{/ProductIDs.0}}
      ]
    }
  }
}

Of course I replaced " with \", uglified it, wrapped it up in { "template" :"_snippet_above_" }.

Now when I'm trying to call it using the following:

GET /statistic/_search/template
{
    "template": {
        "id": "test" 
    },
    "params": {
        "ProductIDs": [1,2]
    }
}

It ignores parameter that I've provided, however when I try to do that in official mustache.io demo page - it works just fine.

I tried {{#ProductIDs.length}} option too - it didn't work out. After doing some research I've found out that there is one difference between mustache.js and mustache.java. I assumed that Elasticsearch uses JAVA version and it doesn't support length parameter, so I have to rely on isEmpty. So I've rewritten my query as follows:

{
  "filter" : {
    "bool" : {
      "must" : [
        {{^ProductIDs.isEmpty}}
        { "terms" : { "Product.ProductID" : [{{#ProductIDs}}{{.}},{{/ProductIDs}}] } }
        {{/ProductIDs.isEmpty}}
      ]
    }
  }
}

Now when I query template with ProductIDs list - it works fine, however if I remove parameter, it brings no results. I assume it generates this:

{
  "filter" : {
    "bool" : {
      "must" : [
        { "terms" : { "Product.ProductID" : [] } }
      ]
    }
  }
}

If I send empty array as Parameter - it works fine.

GET /statistic/_search/template
{
    "template": {
        "id": "test" 
    },
    "params": {
        "ProductIDs": []
    }
}

I assume this happens because "ProductIDs" are undefined and not empty.

Is there a way to cath this condition in mustache.java so I can ignore these parameters?

tl;dr; The issue is that if I don't specify parameter in my search request via template, my condition is rendered as an empty array, see this:

{
  "filter" : {
    "bool" : {
      "must" : [
        { "terms" : { "Product.ProductID" : [] } }
      ]
    }
  }
}

If I pass empty array as a parameter, see this:

GET /statistic/_search/template
{
    "template": {
        "id": "test" 
    },
    "params": {
        "ProductIDs": []
    }
}

It works as expected and doesn't generate filter condition as described in my template, because array doesn't have any data in it.

I want this:

GET /statistic/_search/template
{
    "template": {
        "id": "test" 
    },
    "params": {
    }
}

To work same as this:

GET /statistic/_search/template
{
    "template": {
        "id": "test" 
    },
    "params": {
        "ProductIDs": []
    }
}
Evaldas Buinauskas
  • 13,739
  • 11
  • 55
  • 107

3 Answers3

3

A workaround probably not the most elegant would be to change template query to be a should clause and add a match_all clause for empty list.

example:

{
    "filter" : { 
        "bool" : {
            "should" : [  
                { "terms" : { "status" : [ "{{#ProductIDs}}","{{.}}","{{/ProductIDs}}"] }} 
                {{^ProductIDs}},
                {"match_all":{}}
                {{/ProductIDs}}
            ]
        }
    }
}
keety
  • 17,231
  • 4
  • 51
  • 56
  • Well, this is workaround I thought about. But I am interested in the actual solution. If I not pass any parameter - not to render it at all. :( – Evaldas Buinauskas Sep 10 '15 at 13:31
  • given that mustache.java has no support for [accessing array elements](https://github.com/spullara/mustache.java/issues/68) i don't think there is a way to get around this but to restructure your template query as above or use an additional param that has the length of list and use that as the conditional to introduce the must clause – keety Sep 10 '15 at 14:18
  • I'm kinda only interested in checking if given parameter exists or is not empty. Seems that passing an empty array/list is only solution, no? – Evaldas Buinauskas Sep 10 '15 at 14:35
  • yeah true that works too however if you want a result when no `ProductsID` is passed you would require the workaround of changing the query or using a flag param – keety Sep 10 '15 at 16:41
1

Didn't try it, but shouldn't something like this work?

{
  "filter" : {
    "bool" : {
      "must" : [
        {{#ProductIDs}}
          {{^ProductIDs.isEmpty}}
            { "terms" : { "Product.ProductID" : [{{#ProductIDs}}{{.}},{{/ProductIDs}}] } }
          {{/ProductIDs.isEmpty}}
          {{#ProductIDs.isEmpty}}
            {"match_all":{}}
          {{/ProductIDs.isEmpty}}
        {{/ProductIDs}}
        {{^ProductIDs}}
          {"match_all":{}}
        {{/ProductIDs}}
      ]
    }
  }
}

Ain't pretty, maybe there's better way.

slawek
  • 2,709
  • 1
  • 25
  • 29
1

My suggestion to overcome this using a JSON template is:

{
   "query": {
      "bool": {
        "must": [
            {
                "script": {
                    "script": {
                        "inline": "1==1 {{#ProductIDs}} &&  [\"{{#ProductIDs}}\",\"{{.}}\",\"{{/ProductIDs}}\"].contains(doc['Product.ProductID'].value){{/ProductIDs}}",
                        "lang": "painless"
                    }
                }
            }
        ]
    }
}