Null pointer exception in ES painless script? - elasticsearch

I am using painless script in ES and encountering a null pointer exception, here is my script and also I have added the response for better understanding, I use ES v7.3.
POST test_script/_update/1
{
"scripted_upsert": true,
"script": {
"lang": "painless",
"source": "int getBrowsersObjectIndex(def x, def y){for (int i = 0; i < x.length; i++) {if (x[i].deviceType == y.deviceType) {if (x[i].osName == y.osName) {if(x[i].browserName == y.browserName) {return i}}}}return -1} int getDevicesObjectIndex(def x, def y){for (int i = 0; i < x.length; i++) {if (x[i].deviceId == y.deviceId) {if (x[i].appName == y.appName) {if (x[i].appNameSpace == y.appNameSpace){return i}}}}return -1}if (!ctx._source.containsKey('browsers')) {ctx._source['browsers'] = []}if (!ctx._source.containsKey('devices')) {ctx._source['devices'] = []}for (int index = 0; index < params.iterate.length; index++) {if (params.iterate[index].browserObject) {int browserIndex = getBrowsersObjectIndex(ctx._source.browsers, params.iterate[index].browserObject);if (browserIndex >= 0) {ctx._source.browsers.remove(browserIndex);}ctx._source.browsers.add(params.iterate[index].browserObject);}if (params.iterate[index].deviceObject) {int deviceIndex = getDevicesObjectIndex(ctx._source.devices, params.iterate[index].deviceObject);if (deviceIndex >= 0) {ctx._source.devices.remove(deviceIndex);}ctx._source.devices.add(params.iterate[index].deviceObject);}}",
"params": {
"iterate": [
{
"deviceObject": {
"deviceId": "162c04e48832e338",
"appName": "test_app",
"appNameSpace": "com.test",
"appBuild": 55,
"appVersion": "4.6.3",
"deviceName": "OP4B80L1",
"manufacturer": "OPPO",
"model": "CPH1937",
"networkCarrier": "Jio",
"os": "Android",
"platform": "Android",
"timezone": "Asia/Kolkata",
"version": "10"
}
},
{
"browserObject": {
"deviceType": "mobile",
"osName": "Android",
"browserName": "Chrome",
"osVersion": 10,
"browserVersion": "80.0.3987.99"
}
}
]
}
},
"upsert": {}
}
Error is:
{
"error": {
"root_cause": [
{
"type": "remote_transport_exception",
"reason": "[connecto][192.168.36.235:9300][indices:data/write/update[s]]"
}
],
"type": "illegal_argument_exception",
"reason": "failed to execute script",
"caused_by": {
"type": "script_exception",
"reason": "runtime error",
"script_stack": [
"if (params.iterate[index].browserObject) {int ",
" ^---- HERE"
],
"script": "int getBrowsersObjectIndex(def x, def y){for (int i = 0; i < x.length; i++) {if (x[i].deviceType == y.deviceType) {if (x[i].osName == y.osName) {if(x[i].browserName == y.browserName) {return i}}}}return -1} int getDevicesObjectIndex(def x, def y){for (int i = 0; i < x.length; i++) {if (x[i].deviceId == y.deviceId) {if (x[i].appName == y.appName) {if (x[i].appNameSpace == y.appNameSpace){return i}}}}return -1}if (!ctx._source.containsKey('browsers')) {ctx._source['browsers'] = []}if (!ctx._source.containsKey('devices')) {ctx._source['devices'] = []}for (int index = 0; index < params.iterate.length; index++) {if (params.iterate[index].browserObject) {int browserIndex = getBrowsersObjectIndex(ctx._source.browsers, params.iterate[index].browserObject);if (browserIndex >= 0) {ctx._source.browsers.remove(browserIndex);}ctx._source.browsers.add(params.iterate[index].browserObject);}if (params.iterate[index].deviceObject) {int deviceIndex = getDevicesObjectIndex(ctx._source.devices, params.iterate[index].deviceObject);if (deviceIndex >= 0) {ctx._source.devices.remove(deviceIndex);}ctx._source.devices.add(params.iterate[index].deviceObject);}}",
"lang": "painless",
"caused_by": {
"type": "null_pointer_exception",
"reason": null
}
}
},
"status": 400
}
Seems like the error is in the if statement where I am checking if either of browserObject or deviceObject exists inside the iterate array of objects.

You need to perform an explicit null-check, i.e.
if (params.iterate[index].browserObject != null)
Besides, in Kibana Dev Tools, you can use triple quotes """ to properly format your code so it's more legible and easier to read/maintain:
POST test_script/_update/1
{
"scripted_upsert": true,
"script": {
"lang": "painless",
"source": """
int getBrowsersObjectIndex(def x, def y){
for (int i = 0; i < x.length; i++) {
if (x[i].deviceType == y.deviceType) {
if (x[i].osName == y.osName) {
if(x[i].browserName == y.browserName) {
return i
}
}
}
}
return -1
}
int getDevicesObjectIndex(def x, def y){
for (int i = 0; i < x.length; i++) {
if (x[i].deviceId == y.deviceId) {
if (x[i].appName == y.appName) {
if (x[i].appNameSpace == y.appNameSpace){
return i
}
}
}
}
return -1
}
if (!ctx._source.containsKey('browsers')) {
ctx._source['browsers'] = []
}
if (!ctx._source.containsKey('devices')) {
ctx._source['devices'] = []
}
for (int index = 0; index < params.iterate.length; index++) {
if (params.iterate[index].browserObject != null) {
int browserIndex = getBrowsersObjectIndex(ctx._source.browsers, params.iterate[index].browserObject);
if (browserIndex >= 0) {
ctx._source.browsers.remove(browserIndex);
}
ctx._source.browsers.add(params.iterate[index].browserObject);
}
if (params.iterate[index].deviceObject != null) {
int deviceIndex = getDevicesObjectIndex(ctx._source.devices, params.iterate[index].deviceObject);
if (deviceIndex >= 0) {
ctx._source.devices.remove(deviceIndex);
}
ctx._source.devices.add(params.iterate[index].deviceObject);
}
}
""",
"params": {
"iterate": [
{
"deviceObject": {
"deviceId": "162c04e48832e338",
"appName": "test_app",
"appNameSpace": "com.test",
"appBuild": 55,
"appVersion": "4.6.3",
"deviceName": "OP4B80L1",
"manufacturer": "OPPO",
"model": "CPH1937",
"networkCarrier": "Jio",
"os": "Android",
"platform": "Android",
"timezone": "Asia/Kolkata",
"version": "10"
}
},
{
"browserObject": {
"deviceType": "mobile",
"osName": "Android",
"browserName": "Chrome",
"osVersion": 10,
"browserVersion": "80.0.3987.99"
}
}
]
}
},
"upsert": {}
}

Related

Elasticsearch mapping boolean with value "0" and "1"

ElasticSearch version 7.13
Index already exist and I want to reindex with mapping, the field is a boolean. But when I'm trying to reindex, the field has "1" and "0" (string).
How can I evaluate if field = "1" set true (same for 0, but false)?
I have read about runtime, but can't figure out how does it work.
my mapping
{
mappings:{
"OPTIONS": {
"type": "nested",
"properties":{
"COMBINABLE": {
"type": "boolean"
}
}
}
}
}
and document
{
"options": [
{
"COMBINABLE": "0"
}
]
}
You might consider using pipeline ingestion to convert your number to a boolean value, you can do something like this:
POST _ingest/pipeline/_simulate
{
"pipeline": {
"description": "convert to boolean",
"processors": [
{
"script": {
"source": "def options = ctx.options;def pairs = new ArrayList();for (def pair : options) {def k = false;if (pair[\"COMBINABLE\"] == \"1\" || pair[\"COMBINABLE\"] == 1) {k = true;}pair[\"COMBINABLE\"] = k;}ctx.options = options;"
}
}
]
},
"docs": [
{
"_source": {
"options": [
{
"COMBINABLE": 1
}
]
}
}
]
}
The painless script above is pretty simple:
def options = ctx.options;
def pairs = new ArrayList();
for (def pair : options) {
def k = false;
if (pair["COMBINABLE"] == "1" || pair["COMBINABLE"] == 1) {
k = true;
}
pair["COMBINABLE"] = k;
}
ctx.options = options;
It simply loop through all your option under options, then if the COMBINABLE is 1 or "1", it will convert to true, otherwise, it will be false. You can set the pipeline as your default ingestion, see here

Find and replace String in elasticSearch using loop

i need help with finding and replacing a string from all the indexes and replace it with another string. I wanna change my attribut libelle when his value is 'Vol, Incendie' to 'Vol et incendie'.
here is my elasticJson example :
"devisdata": [
{
"nameWhoMadeIt": " ADMIN admin",
"Garanties": [
{
"libelle": "Vol, incendie",
"id": 2
},
{
"libelle": "bdg",
"id": 2
},
]
},
{
"nameWhoMadeIt": " ADMIN admin",
"Garanties": [
{
"libelle": "Vol, incendie",
"id": 2
},
{
"libelle": "bdg",
"id": 2
},
]
},
]
}
and here is my request :
{
"script": {
"source": "for (int i=0; i <= ctx._source.devisdata.length; i++) {
for (int j=0; j <= ctx._source.devisdata[i].Garanties.length; j++) {
if(ctx._source.devisdata[i].Garanties[j].libelle == 'Vol, Incendie') {
ctx._source.devisdata[i].Garanties[j].libelle = params.value; break
}
}
}",
"lang": "painless",
"params": {
"value": "Vol et incendie"
}
}
}
I find the answer, i just went out of the array. i only needed to do < not <=.
is there a better way to do this without looping ??

How to put parameters in query.script.script.params?

I have a query just like this:
{
"script": {
"script": {
"lang": "painless",
"params": {
"maxPrice": 1000000,
"minPrice": 50,
"now": "{{now}}"
},
"source": "(doc['selling_price'].value == 0 && doc['normal_price'].value >= params.minPrice && doc['normal_price'].value <= params.maxPrice) || (doc['selling_price'].value > 0 && doc['selling_price'].value >= params.minPrice && doc['selling_price'].value <= params.maxPrice && params.now <= doc['selling_price_end_time'].value.getMillis() && params.now >= doc['selling_price_start_time'].value.getMillis())"
}
}
}
When I trying to put minPrice and maxPrice into parameters in a template just like now like this :
"params": {
"maxPrice": "{{maxPrice}}",
"minPrice": "{{minPrice}}",
"now": "{{now}}"
},
Then when I run :
{
"id": "my_template",
"params": {
"query_all": "My goi",
"maxPrice": 100000000,
"minPrice": 50,
"now": 1421427600000,
I received this:
"failures": [
{
"shard": 4,
"index": "dev_index_sku_list",
"node": "jSpDqKHwSMeWsUxxoD9-5w",
"reason": {
"type": "script_exception",
"reason": "runtime error",
"script_stack": [
"(doc['selling_price'].value == 0 && doc['normal_price'].value >= params.minPrice && doc['normal_price'].value <= params.maxPrice) || (doc['selling_price'].value > 0 && doc['selling_price'].value >= params.minPrice && doc['selling_price'].value <= params.maxPrice &&
Ridiculously, when I set up now as a parameter it works, but not for the case for minPrice and maxPrice:
The logic behind the source is:
Our data have:
NormalPrice: 200 #this is the normal price
SellingPrice: 100 #this is actually the promotion price
selling_price_start_time: "2020-01-01" #duration of promotion price
selling_price_end_time: "2020-01-30"
The logic:
if selling_price != 0 and now in the promotion time then filter within minPrice and maxPrice
else then filter by normalPrice within minPrice and maxPrice
Currently working on version 7 of elasticSearch

Elasticsearch: Update/upsert an array field inside a document but ignore certain existing fields

GET _doc/1
"_source": {
"documents": [
{
"docid": "ID001",
"added_vals": [
{
"code": "123",
"label": "Abc"
},
{
"code": "113",
"label": "Xyz"
}
]
},
{
"docid": "ID002",
"added_vals": [
{
"code": "123",
"label": "Abc"
}
]
}
],
"id": "1"
}
POST /_bulk
{ "update": { "_id": "1"}}
{ "doc": { "documents": [ { "docid": "ID001", "status" : "cancelled" } ], "id": "1" }, "doc_as_upsert": true }
The problem above is when I run my bulk update script it replaces that document field, removing the added_vals list. Would I be able to achieve this using painless script? Thank you.
Using elasticsearch painless scripting
POST /_bulk
{ "update": { "_id": "1"} }
{ "scripted_upsert":true, "script" :{ "source": "if(ctx._version == null) { ctx._source = params; } else { def param = params; def src = ctx._source; for(s in src.documents) { boolean found = false; for(p in param.documents) { if (p.docid == s.docid) { found = true; if(s.added_vals != null) { p.added_vals = s.added_vals; } } } if(!found) param.documents.add(s); } ctx._source = param; }", "lang": "painless", "params" : { "documents": [ { "docid": "ID001", "status" : "cancelled" } ], "id": "1" } }, "upsert" : { } }
well, this one worked for me. I need to tweak a few more things that I require, but I will just leave it here for someone who may need it. Didnt know it was this simple. If there is any other answer that might be easier, please do submit so. Thanks.
"script" :
if(ctx._version == null)
{
ctx._source = params;
}
else
{
def param = params;
def src = ctx._source;
for(s in src.documents)
{
boolean found = false;
for(p in param.documents)
{
if (p.docid == s.docid)
{
found = true;
if(s.added_vals != null)
{
p.added_vals = s.added_vals;
}
}
}
if(!found) param.documents.add(s);
}
ctx._source = param;
}
I am not sure if I should modify the params directly so I used and pass the params to the param variable. I also used scripted_upsert: true with a ctx._version not null check.

Elasticsearch script - Extraneous if statement

I am using update_by_query with the following body:
POST /documents/_update_by_query
{
"script":{
"source":"for(int i = 0;i < ctx._source.fields.size();i++){for (int j = 0; j < params.field_uid.size();j++){ if(params.field_uid[j].type == \"group\" ){ } else{ if(ctx._source.fields[i].uid == params.field_uid[j].uid){ ctx._source.fields.remove(i);} } }}",
"params":{
"field_uid":[{"uid":"number","type":"number"},{"uid":"test","type":"group"}]
}
},
"query": {
"term": {
"name": "test"
}
}
}
It is giving me like Extraneous if statement. Here is the error message I got :
{
"error": {
"root_cause": [
{
"type": "script_exception",
"reason": "compile error",
"script_stack": [
"... s.field_uid.size();j++){ if(params.field_uid[j].ty ...",
" ^---- HERE"
],
"script": "for(int i = 0;i < ctx._source.fields.size();i++){for (int j = 0; j < params.field_uid.size();j++){ if(params.field_uid[j].type == 100 ){ } else{ if(ctx._source.fields[i].uid == params.field_uid[j].uid){ ctx._source.fields.remove(i);} } }}",
"lang": "painless"
}
],
"type": "script_exception",
"reason": "compile error",
"script_stack": [
"... s.field_uid.size();j++){ if(params.field_uid[j].ty ...",
" ^---- HERE"
],
"script": "for(int i = 0;i < ctx._source.fields.size();i++){for (int j = 0; j < params.field_uid.size();j++){ if(params.field_uid[j].type == 100 ){ } else{ if(ctx._source.fields[i].uid == params.field_uid[j].uid){ ctx._source.fields.remove(i);} } }}",
"lang": "painless",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Extraneous if statement."
}
},
"status": 500
}
Can anyone help me into this?
What is wrong here?
The if statement written is not doing any operation and is irrelevant as a result ES is throwing extraneous if statement error.
Remove it and update the condition accordingly as below.
for(int i = 0;i < ctx._source.fields.size();i++){ for (int j = 0;j <
params.field_uid.size();j++){ if(params.field_uid[j].type != \"group\"
&& ctx._source.fields[i].uid == params.field_uid[j].uid){
ctx._source.fields.remove(i);}}}

Resources