elasticsearchelasticsearch-dslelasticsearch-painless

Elasticsearch sort script can only access keyword field as String


This is my query,Elasticsearch version is 7.10.0

{
    "query": {
      "bool": {
        "must": [
          {
            "match_all": {}
          }
        ]
      }
    },
    "sort": {
      "_script": {
        "type": "number",
        "script": {
          "lang": "painless",
          "source": "doc['my_field'].value - params.paramVal",
          "params": {
            "paramVal": 1
          }
        },
        "order": "asc"
      }
    },
    "profile": false
  }

which my_field is an Integer keyword field.But this query result in an error

  "reason": {
    "type": "script_exception",
    "reason": "runtime error",
    "script_stack": [
      "doc['my_field'].value - params.paramVal",
      "                              ^---- HERE"
    ],
    "script": "doc['my_field'].value - params.paramVal",
    "lang": "painless",
    "position": {
      "offset": 33,
      "start": 0,
      "end": 49
    },
    "caused_by": {
      "type": "class_cast_exception",
      "reason": "class_cast_exception: Cannot apply [-] operation to types [java.lang.String] and [java.lang.Integer]."
    }
  }

Now I'm using Integer.parseInt(doc['my_field'].value) to workaround.Any idea to use the int field value directly?Thanks.


Solution

  • If my_field is an "Integer keyword field", I assume that it is stored as a string. If that's the case, then doc['my_field'].value returns a string and Painless won't allow you to subtract an integer from a string. You need to parse that string, like this:

    Integer.parseInt(doc['my_field'].value) - params.paramVal
    

    If my_field is supposed to contain an integer value you should probably store it as an integer type instead of keyword, especially if you need to run range queries on that field.

    UPDATE:

    If you don't want or can't reindex and want to still keep your current index, another thing you can do is to add an integer sub-field and update your index in place. It goes like this:

    # First add an integer sub-field to your mapping
    PUT index/_mapping
    {
       "properties": {
          "my_field": {
             "type": "keyword",
             "fields": {
                "numeric": {
                   "type": "integer"
                }
             }
          }
       }
    }
    
    # then update your index in-place
    POST index/_update_by_query
    

    When the update is done, the new my_field.numeric field will have been populated using the existing data in the keyword field and you can use it in your Painless script like this:

    doc['my_field.numeric'].value - params.paramVal