ES|QL|QSTR 不再只靠默认规则,9.0 加了 options 命名参数

9.0 给 QSTR 加了 options 命名参数。以前它只能传一个查询串,行为完全由默认规则决定;现在可以通过命名参数调整匹配行为——比如指定默认字段、默认操作符这些。

说明

实测环境:本地起的 Elasticsearch 9.4.2 单节点实例(官方 tar 包直接跑,堆内存 512MB,安全与机器学习关闭),curl 连本地 9200 端口,_query 接口走 ES|QL;5 条测试文档在实验时现场写入。图 1 出自这个实例。

官方条目

9.0 的 changelog 标为 feature(9.0.0.yml 第 2017–2021 行 @ v9.4.2),area: Search,关联 issue 120933。

只有 summary,没有 details。好在函数文档里写清楚了,下面按文档核。

函数本身

QSTR 的作用(functions/description/qstr.md @ v9.4.2):

Performs a query string query. Returns true if the provided query string matches the row.

它执行的是一个 query string 查询,匹配则返回 true。

Query string 语法就是 Lucene 那套——支持 field:value、AND/OR/NOT、通配符、短语等。这是 ES 里很早就有的一套查询语法,现在在 ES|QL 里通过 QSTR 函数可用。

新增的 options 参数

参数文档(functions/parameters/qstr.md @ v9.4.2):

`query`
:   Query string in Lucene query string format.

`options`
:   (Optional) Additional options for Query String as function named parameters.
    See query string query for more information.

两个参数:

  • query——Lucene 查询串,这个是原有的;
  • options——可选的命名参数,就是这次加的。

文档说得很明白:options 的具体可选项要看 query string query 的文档。也就是说 QSTR 把它委托给了 Query DSL 里 query string 查询——两边共享同一套选项。

这个设计合理:同一个查询语法不该有两套不同的选项定义。

命名参数的写法

文档里给了链接指向"函数命名参数"(esql-syntax.md#esql-function-named-params)。ES|QL 的命名参数语法大致是这样(示意,具体以你版本文档为准):

FROM logs-*
| WHERE QSTR("error AND timeout", { "default_field": "message" })

第二个参数是个花括号包起来的键值对结构。

(说明:上面这个示例是按命名参数的一般写法构造的,未实测,也未能在文档里找到 QSTR 的具体示例。可用的键名要以 query string query 文档为准。用之前建议先跑一次确认语法。)

为什么这个功能有用

不加 options 时,QSTR 的行为由默认规则决定。这在几个地方会不够用:

一,没指定字段的查询要有落点。 查询串写 error timeout(不带字段名)时,ES 得知道去哪些字段找。这由默认的 default_field 决定——通常是 * 或者索引配置里的设定。想改就得用 options。

二,多词之间的默认关系。 error timeout 是两个词都要匹配(AND)还是任一匹配(OR)?这是 default_operator 控制的,默认通常是 OR。要做精确筛选得改成 AND。

三,通配符、模糊匹配这类行为的开关。 这些也都在 query string 的选项里。

前两条最常用——尤其是第二条。默认 OR 意味着"包含任一词",很多人以为写两个词是"都要有",结果召回比预期宽。明确设 default_operator: AND 能避免这个误解。

QSTR 的 options 支持哪些键

写这篇时想确认 ES|QL 里 QSTR 的 options 到底支持哪些键。

按文档的说法是"看 query string query 文档"——该文档里列了很多选项(default_field、default_operator、analyzer、quote_analyzer、allow_leading_wildcard、fuzzy_max_expansions 等等)。

但不确定 ES|QL 这边是不是全部支持。 可能存在的情况:

  • 全部支持(最理想);
  • 只支持一部分(ES|QL 的实现可能有取舍);
  • 或者随着版本增加。

未逐个核实。如果要用到某个特定选项,建议:

  1. 先在测试环境跑一次,看是否被接受;
  2. 不被接受的话,看报错信息——ES|QL 对不支持的命名参数通常会给出明确提示。

这个"先试一次"的办法在前面几篇里反复用过,对这类"文档指向另一处文档"的情况尤其适用。

和 Query DSL 的关系

QSTR 本质上是把 Query DSL 的 query string 查询包装成了 ES|QL 函数。

这个模式在 ES|QL 里不止一处——MATCH(match 表达式 里写过)也是类似的:把 Query DSL 的能力带进 ES|QL。

理解这一点有个实际用处:如果你熟悉 Query DSL,那这些函数的行为基本可以对照着理解。文档说 QSTR 的选项"see query string query",正是这个关系的体现。

所以建议是:用 QSTR 时拿 Query DSL 的 query string 文档当参考,而不是指望 ES|QL 侧重复一份完整说明。

什么场景会用到

按判断,下面两类场景会用到 options:

一,查询串里不带字段名。 比如用户输入一个搜索框,内容是自然的词而不是 field:value 形式。这时候要明确指定搜哪些字段。

二,需要控制多词之间的逻辑关系。 前面说的 AND/OR 问题。

反过来,如果查询串里每个词都带字段名(level:error AND message:timeout),那 options 的作用就小很多——默认规则的影响被显式写法覆盖了。

默认字段这件事容易被忽略

展开说一下 default_field,因为它是最容易出问题的默认值。

不带字段名写查询(error timeout)时,ES 要决定去哪些字段找。这个落点由默认字段决定。

问题在于:默认行为是什么,取决于索引配置。ES 有个 index.query.default_field 索引设置,配了就按它来;没配的话,行为可能是"搜所有字段"。

这带来两个实际影响:

  • 性能:搜所有字段比搜指定字段慢,字段多的时候差别明显;
  • 结果意外:某些字段(比如 ID、状态标记)被纳入搜索范围,可能匹配出你没想到的文档。

所以倾向是显式指定,而不是依赖默认。这也正是 options 的用途之一——default_field 让你能把范围说清楚。

(说明:index.query.default_field 这个设置名是按记忆写的,未在 v9.4.2 文档里核实。如果你要配它,先查一下你版本的文档确认设置名。默认字段的具体行为也建议实测确认——用 _explain 或者看返回结果能判断出实际搜了哪些字段。)

一个安全上的提醒

用 QSTR 有个必须说清楚的风险:查询串是用户输入时的注入问题。

Query string 语法功能很强——支持通配符、模糊匹配、范围、字段指定等。如果把它直接暴露给终端用户输入(比如一个搜索框),用户可能构造出:

  • 非常耗资源的查询(*、? 开头的通配);
  • 访问本不该访问的字段(显式指定字段名);
  • 语法错误导致查询失败。

第一类后果最严重——前导通配符的查询在大数据量上会很慢,可能拖垮集群。这种影响是会占用大量资源波及整个集群,不是单个查询慢。

建议是:如果要接用户输入,要么不用 QSTR,要么做严格的前端限制:

  • 限制查询串长度;
  • 禁用前导通配符(这个正好是 query string 的一个选项);
  • 限定可搜索字段(通过 options 的 default_field,而不是让用户在串里指定)。

第三条尤其重要:让用户只能在你指定的字段里搜,而不是让他用 field:value 语法访问任意字段。

(说明:这段是通用安全建议,不是针对本次改动的官方说明。但用 QSTR 时绕不开这个问题,所以写在这里。)

和 MATCH 怎么选

ES|QL 里有两个做全文匹配的函数:QSTR 和 MATCH。按理解(match 表达式 里写过 MATCH):

MATCHQSTR
输入单个词或短语Lucene 查询串语法
表达能力简单强(布尔、通配、字段指定)
适合明确的匹配条件需要复杂表达式时

选择依据是你要表达多复杂的条件:

  • 简单的"这个字段包含这个词" → MATCH;
  • 需要"字段A包含X 且 字段B不包含Y"这类组合 → QSTR 更直接,一行就写完。

但能用 MATCH 就用 MATCH。 理由是查询串语法复杂,容易出错,而且有上面说的安全问题。QSTR 适合内部使用、表达式可控的场景,不太适合直接接用户输入。

在 9.4.2 上跑一遍

QSTR 的 options 是个命名参数:把 Query DSL 的 query string 选项以 map 的形式传进去。用五条文档、一个「两个词」的查询就能看出它确实生效。

图 1:QSTR("hello a") 不指定 options 命中 2 条;同一个查询带上 {"default_operator": "AND"} 命中 0 条;换成 "OR" 又回到 2 条。本地实测输出。

curl -s -XPOST 'http://localhost:9200/_query' \
  -H 'Content-Type: application/json' \
  -d '{"query": "FROM i1 | WHERE QSTR(\"hello a\", {\"default_operator\": \"AND\"}) | STATS n = COUNT(*)"}'
# [[0]]

hello 和 a 分别落在两条不同的文档上,所以 AND 必然是 0、OR 是 2——这组对照说明 options 里的 default_operator 真的被读进去了,不是被忽略的装饰参数。同一个例子也量出了默认行为:不写 options 时按 OR 走。

我的建议是 QSTR 里凡是依赖默认值的规则(默认字段、默认操作符、lenient)都显式写进 options:查询文本本身是一段字符串,读代码的人看不出它会被哪种规则解释,把这些规则与查询串放在同一处,排查时不用再翻 Query DSL 文档猜默认值。

边界:

  • 图 1 出自 9.4.2 单节点实例,5 条文档在实验时现场写入;
  • 只验了 default_operator 这一个键,options 在 ES|QL 侧还支持哪些键没有逐个核实(文档指向 Query DSL 的 query string 文档);
  • 命名参数里的 map 写法是按一般命名参数构造的,本机跑通了,但不代表所有键都能这样传。

写在最后

这条 feature 不大——给一个函数加了可选参数。

这条值得写是因为它解决了一个实际容易踩的默认行为问题:多词查询默认是 OR。很多人以为写两个词是"都要匹配",实际是"任一匹配",结果是召回比预期宽得多。

一,QSTR 有了 options 命名参数,可以调整默认字段、默认操作符等行为。具体可选项看 Query DSL 的 query string 文档——两边共享同一套定义。

二,注意多词的默认关系是 OR。 想要"都要匹配"就显式设 default_operator: AND,别依赖默认——这个默认值和很多人的直觉相反,是召回偏宽的常见原因。

三,别把 QSTR 直接接用户输入。 查询串语法太强,前导通配符可能拖垮集群,字段指定语法可能访问到不该访问的字段。要接就做限制,并且用 default_field 限定范围。

最后补一句:具体哪些键在 ES|QL 侧可用,未逐个核实。用之前先跑一次确认,这是最省事的办法。同理,本文里该带 options 的示例也是按语法构造的,不是官方文档里的例子——照着写之前先在环境里试一次。

参考

本文对照 Elasticsearch v9.4.2 标签的 changelog 与函数文档核对,引文逐字取自原文。文中 QSTR 带 options 的查询写法已在本机 9.4.2 单节点实例上执行通过(default_operator 的 AND/OR 对照已实测,见「在 9.4.2 上跑一遍」);options 在 ES|QL 侧还支持哪些键,文档指向 Query DSL 的 query string 文档,本文未逐个核实。

0
0
0
0
评论
未登录
暂无评论