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.