Elasticsearch:Painless 编程调试「建议收藏」

Painless也就是无痛的意思。这是一个专为Elasticsearch而设计的。当初的设计人员取名为“Painless”,表达的意思的是在编程的时候没有疼痛感,很方便设计人员使用。由于这是一个脚本的语言,在实际的使用中,我们很难找到这些编程的方法及使用。在今天的教程中,我来讲述一下该如何来进行调试。Debug.ExplainPainless没有REPL,虽然很高兴有一天,但它不会告诉你调试Elasticsearch中嵌入的Painless脚本的全部过程,因为脚本可以访问或“上

大家好,又见面了,我是你们的朋友全栈君。

Painless 也就是无痛的意思。这是一个专为 Elasticsearch 而设计的。当初的设计人员取名为 “Painless”,表达的意思的是在编程的时候没有疼痛感,很方便设计人员使用。由于这是一个脚本的语言,在实际的使用中,我们很难找到这些编程的方法及使用。在今天的教程中,我来讲述一下该如何来进行调试。

Debug.Explain

Painless 没有 REPL,很希望将来有一天会有,但它不会告诉你调试 Elasticsearch 中嵌入的 Painless 脚本的全部过程,因为脚本可以访问或 “上下文”访问数据非常重要。 目前,调试嵌入式脚本的最佳方法是在选择的位置抛出异常。 尽管你可以抛出自己的异常(抛出新的Exception(’whatever’)),但 Painless 的沙箱可阻止你访问有用的信息,例如对象的类型。 因此,Painless 具有实用程序方法 Debug.explain,它可以为你引发异常。 例如,你可以使用 _explain 探索脚本查询可用的上下文。

例子一

我们在 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"
}

上面的命令将生成如下的 mapping:

GET  hockey/_mapping
{
  "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"
        }
      }
    }
  }
}

如上图所示,我们可以看到有几种类型的数据:long, text, keyword 以及 date。那么现在问题来了,在实际的 script 编程中,我们该如何对这些数据进行操作。它们的数据类型有什么可以供我们使用的方法呢?

我们使用如下的  _explain  终点:

POST /hockey/_explain/1
{
  "query": {
    "script": {
      "script": "Debug.explain(doc.goals)"
    }
  }
}

上面命令的相应结果为:

{
  "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
}

上面显示了一个 exception。同时它也显示了 doc.goals 是一个 这样类型的数据:

"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",

接下来,我们来参考链接 Painless API Reference | Painless Scripting Language [7.0] | Elastic。我们寻找 org.elasticsearch.index.fielddata.ScriptDocValues.Longs:我们可以看到如下的一些说明:

这里展示了它的一些可供使用的 API 接口。从上面,我们可以看出来这个数据是一个 List 类型的数据,我们可以使用如下的方法来进行统计:

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;
          """
        }
      }
    }
  }
}

在这里,我们使用了 getLength() 以及 get(i)。这些方法都是 List 类型数据的方法。我们也可以使用精简的方法:

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;
          """
        }
      }
    }
  }
}

上面的显示的结果为:

{
  "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"
        }
      }
    ]
  }
}

例子二

接下来例子,我们可以使用 date 数据类型为例。在 Kibana 中运行如下的命令:

POST /hockey/_explain/1
{
  "query": {
    "script": {
      "script": "Debug.explain(doc['time'])"
    }
  }
}

上面的命令将抛出如下的 exception:

{
  "error" : {
    "root_cause" : [
      {
        "type" : "script_exception",
        "reason" : "runtime error",
        "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates",
        "to_string" : "[2020-08-30T00:00:00.000Z]",
        "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" : "[2020-08-30T00:00:00.000Z]",
    "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
}

从上面的 exception 中,我们可以看出来这个抛出的异常含有:

"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates",

它表明这个是一个 org.elasticsearch.index.fielddata.ScriptDocValues.Dates 类型的数据。我们查看 Painless API Reference | Painless Scripting Language [7.0] | Elastic, 并定位 org.elasticsearch.index.fielddata.ScriptDocValues.Dates,我们可以看到如下的方法描述:

org.elasticsearch.index.fielddata.ScriptDocValues.Dates

显然它是一个叫做 org.joda.time.ReadableDateTime 类型的数据。我们点击上面的链接,我们可以看到更多的关于这个类型的方法:

org.joda.time.ReadableDateTime

我们从上面的列表中,我们看到非常丰富的方法供我们来使用。基于上面的理解,我们可以使用如下的脚本来进行搜索:

POST /hockey/_search
{
  "query": {
    "script": {
      "script": """
       doc['time'].value.getYear() > 2000
     
      """
    }
  }
}

在上面,我们使用了 getYear() 来获取年份。上面搜索返回的结果是:

{
  "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"
        }
      }
    ]
  }
}

当然,如果我们把搜索的年份修改为:

POST /hockey/_search
{
  "query": {
    "script": {
      "script": """
       doc['time'].value.getYear() < 2000
     
      """
    }
  }
}

我们将什么也搜索不到,这是因为文档的日期是 2020-08-30。

我们也可以直接使用最简洁的方法:

POST /hockey/_search
{
  "query": {
    "script": {
      "script": """
       return doc['time'].value.year > 1999
      """
    }
  }
}

这里,直接使用 year 作为一个属性来获取。

例子三

我们也可以直接对 _source 进行操作:

POST /hockey/_update/1
{
  "script": "Debug.explain(ctx._source)"
}

上面的显示结果为:

{
  "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
}

我们可以看到它是一个叫做 java.util.LinkedHashMap 类型的数据。我们可以直接查找 Java 的 API 文档来使用相应的方法。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/126513.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Java基础语法(六)循环控制语句不得不说的那些事儿

    Java基础语法(六)循环控制语句不得不说的那些事儿

  • 进销存ERP源码 小程序源码 APP源码

    进销存ERP源码 小程序源码 APP源码进销存ERP源码+小程序源码+APP源码+H5系统简介:常规管理系统配置 附件管理 个人资料 数据库管理分类管理用于统一管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加权限管理管理员管理 管理员日志 角色组会员管理会员管理 会员分组 会员规则进销存管理1、商品管理商品分类商品信息商品单位2、库存管理商品存库库存流水盘点单

  • Java NIO详解[通俗易懂]

    Java NIO详解[通俗易懂]JAVANIO简介NIO与IO的对比

  • windows无法连接到打印机错误为0x0000011b的解决办法[通俗易懂]

    windows无法连接到打印机错误为0x0000011b的解决办法[通俗易懂]今年微软更新了一波新补丁,然后很多人发现打印机或者共享打印机突然连接不上了,卸载驱动、删除打印机都没用。添加打印机出现0x0000011b的错误代码第一步:点击《控制面板》第二步:选择《程序和功能》第三步:查看已安装的更新第四步:不同版本对应的补丁名不同,参考如下:Windows10版本1507–KB5005569Windows10版本1607–KB5005573Windows10版本1809–KB5005568Windows10版本1909–KB5005566Windows1

  • JAVA中ResourceBundle使用详解

    JAVA中ResourceBundle使用详解JAVA中ResourceBundle使用详解这个类主要用来解决国际化和本地化问题。国际化和本地化可不是两个概念,两者都是一起出现的。可以说,国际化的目的就是为了实现本地化。比如对于“取消”,中文中

  • JS跳转代码_javascript跳转代码

    JS跳转代码_javascript跳转代码<scriptlanguage=”javascript”type=”text/javascript”>functionrequest(paras){varurl=location.href;varparaString=url.substring(url.indexOf(“?”)+1,url.length).split(“&”);varparaObj={}for(i=0;j=paraString[i];i++){paraObj[j.substrin

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号