Search code examples
elasticsearchaws-elasticsearchopensearch

How to update nested numeric field value in Elasticsearch/ AWS Opensearch?


I am not too familiar with ES, so perhaps I am making some mistakes.

I have an index users with the following mapping.

{
   "mappings":{
      "dynamic":false,
      "properties":{
         "id":{
            "type":"keyword"
         },
         "userId":{
            "type":"text"
         },
         "total":{
            "type":"float"
         },
         "count":{
            "type":"float"
         },
         "earnings":{
            "properties":{
               "potential":{
                  "properties":{
                     "pending":{
                        "type":"float"
                     },
                     "finished":{
                        "type":"float"
                     }
                  }
               },
               "completed":{
                  "properties":{
                     "pending":{
                        "type":"float"
                     },
                     "paid":{
                        "type":"float"
                     }
                  }
               }
            }
         }
      }
   }
}

I would like to update the nested fields such as earnings.potential.pending using Update API. I have a script similar to below using Update API.

{
  "script" : {
    "source": "ctx._source.earnings.potential.pending += params.total",
    "lang": "painless",
    "params" : {
      "total" : 4
    }
  }
}


However, I encouter illegal_argument_exception, with the dot notations being the supposed issue as below.

"ctx._source.earnings.potential.pending += params.total",
            ^---- HERE'

Solution

  • If earnings is null on the document you are trying to update, you will get an error:

    ...
    "caused_by": {
      "type": "null_pointer_exception",
      "reason": "cannot access method/field [potential] from a null def reference"
    }
    

    So in essence you need to set a default value on the field, if it is null. The easiest way would be to do it at indexing time, or run a one-time query to set the default value everywhere. But if it's not an option, you could extend your script to check and assign a default:

    {
        "script": {
            "lang": "painless",
            "source": "if(ctx._source.earnings == null) { ctx._source.earnings = [:] }\n if(ctx._source.earnings.potential == null) { ctx._source.earnings.potential = [:] }\n if(ctx._source.earnings.potential.pending == null) { ctx._source.earnings.potential.pending = 0 } \n ctx._source.earnings.potential.pending += params.total",
            "params": {
                "total": 5
            }
        }
    }