Painless means Painless. This is a design for Elasticsearch. The original designers called it “Painless”, which meant that programming was Painless and easy for designers to use. Because this is a scripting language, in the actual use, we can hardly find these programming methods and use. In today’s tutorial, I’ll show you how to debug.
Debug.Explain
Painless doesn’t have a REPL, and while it’s nice one day, it won’t tell you the whole process of debugging a Painless script embedded in Elasticsearch, because it’s important that the script can access or “contextualize” the data. Currently, the best way to debug an embedded script is to throw an exception at the chosen location. Although you can throw your own exceptions (throw a new Exception (‘whatever’)), Painless’s sandbox prevents you from accessing useful information, such as the type of the object. Therefore, Painless has the utility method debug.explain, which can throw exceptions for you. For example, you can use the _explain exploration script to query the available context.
Example a
We typed the following command in Kibana:
PUT /hockey/_doc/1? refresh { "first": "johnny", "last": "gaudreau", "goals": [ 9, 27, 1 ], "assists": [ 17, 46, 0 ], "gp": [ 26, 82, 1 ], "time": "2020-08-30" }Copy the code
The command above generates the following mapping:
GET hockey/_mapping
Copy the code
{
"hockey" : {
"mappings" : {
"properties" : {
"assists" : {
"type" : "long"
},
"first" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"goals" : {
"type" : "long"
},
"gp" : {
"type" : "long"
},
"last" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"time" : {
"type" : "date"
}
}
}
}
}
Copy the code
As you can see in the figure above, there are several types of data: long, text, keyword, and date. Now the question is, how do we manipulate this data in actual script programming? What methods can we use for their data types?
We use the following _explain endpoint:
POST /hockey/_explain/1
{
"query": {
"script": {
"script": "Debug.explain(doc.goals)"
}
}
}
Copy the code
The corresponding result of the above command is:
{
"error" : {
"root_cause" : [
{
"type" : "script_exception",
"reason" : "runtime error",
"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
"to_string" : "[1, 9, 27]",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
"script_stack" : [
"Debug.explain(doc.goals)",
" ^---- HERE"
],
"script" : "Debug.explain(doc.goals)",
"lang" : "painless",
"position" : {
"offset" : 17,
"start" : 0,
"end" : 24
}
}
],
"type" : "script_exception",
"reason" : "runtime error",
"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
"to_string" : "[1, 9, 27]",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
"script_stack" : [
"Debug.explain(doc.goals)",
" ^---- HERE"
],
"script" : "Debug.explain(doc.goals)",
"lang" : "painless",
"position" : {
"offset" : 17,
"start" : 0,
"end" : 24
},
"caused_by" : {
"type" : "painless_explain_error",
"reason" : null
}
},
"status" : 400
}
Copy the code
An exception is displayed above. It also shows that doc.goals is a type of data:
"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
Copy the code
Next, let’s refer to the link www.elastic.co/guide/en/el… . We are looking for org. Elasticsearch. Index. Fielddata. ScriptDocValues. Longs: we can see some of the following description:
Long get(int)
org.joda.time.ReadableDateTime getDate()
List getDates()
long getValue()
List getValues()
- Inherits methods from
Collection
.可迭代
.List
.Object
Some of its available APIS are shown here. From the above, we can see that this data is a List of data, we can use the following method to conduct statistics:
GET hockey/_search { "query": { "function_score": { "script_score": { "script": { "lang": "painless", "source": """ int total = 0; for (int i = 0; i < doc['goals'].getLength(); ++i) { total += doc['goals'].get(i); } return total; """}}}}}Copy the code
Here, we use getLength() and get(I). These methods are all methods for data of type List. We can also use a simplified approach:
GET hockey/_search { "query": { "function_score": { "script_score": { "script": { "lang": "painless", "source": """ int total = 0; for (int i = 0; i < doc['goals'].length; ++i) { total += doc['goals'][i]; } return total; """}}}}}Copy the code
The result shown above is:
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 37.0,
"hits" : [
{
"_index" : "hockey",
"_type" : "_doc",
"_id" : "1",
"_score" : 37.0,
"_source" : {
"first" : "johnny",
"last" : "gaudreau",
"goals" : [
9,
27,
1
],
"assists" : [
17,
46,
0
],
"gp" : [
26,
82,
1
],
"time" : "2020-08-30"
}
}
]
}
}
Copy the code
Example 2
For the next example, we can use the date data type as an example. Run the following command in Kibana:
POST /hockey/_explain/1
{
"query": {
"script": {
"script": "Debug.explain(doc['time'])"
}
}
}
Copy the code
The command above will throw the following exception:
{ "error" : { "root_cause" : [ { "type" : "script_exception", "reason" : "runtime error", "painless_class" : "Org. Elasticsearch. Index. Fielddata. ScriptDocValues. Dates", "to_string" : "[the 2020-08-30 T00:00:00) 000 z]", "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates", "script_stack" : [ "Debug.explain(doc['time'])", " ^---- HERE" ], "script" : "Debug.explain(doc['time'])", "lang" : "painless", "position" : { "offset" : 17, "start" : 0, "end" : 26 } } ], "type" : "script_exception", "reason" : "runtime error", "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates", "to_string" : "[the 2020-08-30 T00:00:00) 000 z]", "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates", "script_stack" : [ "Debug.explain(doc['time'])", " ^---- HERE" ], "script" : "Debug.explain(doc['time'])", "lang" : "painless", "position" : { "offset" : 17, "start" : 0, "end" : 26 }, "caused_by" : { "type" : "painless_explain_error", "reason" : null } }, "status" : 400 }Copy the code
From the exception above, we can see that this raised exception contains:
"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates",
Copy the code
It shows that this is an org. Elasticsearch. Index. Fielddata. ScriptDocValues. Dates types of data. We see www.elastic.co/guide/en/el… And locate org. Elasticsearch. Index. Fielddata. ScriptDocValues. Dates, the way in which we can see the following description:
org.elasticsearch.index.fielddata.ScriptDocValues.Dates
org.joda.time.ReadableDateTime get(int)
org.joda.time.ReadableDateTime getDate()
List getDates()
org.joda.time.ReadableDateTime getValue()
List getValues()
- Inherits methods from
Collection
.可迭代
.List
.Object
It is called a org. Joda. Time. ReadableDateTime types of data. We can see more about this type of method by clicking on the link above:
org.joda.time.ReadableDateTime
int getCenturyOfEra()
int getDayOfMonth()
int getDayOfWeek()
int getDayOfYear()
int getEra()
int getHourOfDay()
int getMillisOfDay()
int getMillisOfSecond()
int getMinuteOfDay()
int getMinuteOfHour()
int getMonthOfYear()
int getSecondOfDay()
int getSecondOfMinute()
int getWeekOfWeekyear()
int getWeekyear()
int getYear()
int getYearOfCentury()
int getYearOfEra()
String toString(String)
String toString(String, Locale)
- Inherits methods from
Comparable
.org.joda.time.ReadableInstant
From the list above, we see a wealth of methods at our disposal. Based on the above understanding, we can use the following script to search:
POST /hockey/_search
{
"query": {
"script": {
"script": """
doc['time'].value.getYear() > 2000
"""
}
}
}
Copy the code
Above, we used getYear() to get the year. The above search returns:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "hockey",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"first" : "johnny",
"last" : "gaudreau",
"goals" : [
9,
27,
1
],
"assists" : [
17,
46,
0
],
"gp" : [
26,
82,
1
],
"time" : "2020-08-30"
}
}
]
}
}
Copy the code
Of course, if we change the search year to:
POST /hockey/_search
{
"query": {
"script": {
"script": """
doc['time'].value.getYear() < 2000
"""
}
}
}
Copy the code
We will find nothing because the document is dated 2020-08-30.
We can also use the simplest method:
POST /hockey/_search
{
"query": {
"script": {
"script": """
return doc['time'].value.year > 1999
"""
}
}
}
Copy the code
Here, you get it directly using year as an attribute.
Example 3
We can also operate on _source directly:
POST /hockey/_update/1
{
"script": "Debug.explain(ctx._source)"
}
Copy the code
The result is as follows:
{
"error" : {
"root_cause" : [
{
"type" : "illegal_argument_exception",
"reason" : "failed to execute script"
}
],
"type" : "illegal_argument_exception",
"reason" : "failed to execute script",
"caused_by" : {
"type" : "script_exception",
"reason" : "runtime error",
"painless_class" : "java.util.LinkedHashMap",
"to_string" : "{first=johnny, last=gaudreau, goals=[9, 27, 1], assists=[17, 46, 0], gp=[26, 82, 1], time=2020-08-30}",
"java_class" : "java.util.LinkedHashMap",
"script_stack" : [
"Debug.explain(ctx._source)",
" ^---- HERE"
],
"script" : "Debug.explain(ctx._source)",
"lang" : "painless",
"position" : {
"offset" : 17,
"start" : 0,
"end" : 26
},
"caused_by" : {
"type" : "painless_explain_error",
"reason" : null
}
}
},
"status" : 400
}
Copy the code
We can see that it is a data of type java.util.linkedhashMap. We can directly look up the Java API documentation to use the corresponding method.