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 的实现可能有取舍);
- 或者随着版本增加。
未逐个核实。如果要用到某个特定选项,建议:
- 先在测试环境跑一次,看是否被接受;
- 不被接受的话,看报错信息——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):
MATCH |
QSTR |
|
|---|---|---|
| 输入 | 单个词或短语 | 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 的示例也是按语法构造的,不是官方文档里的例子——照着写之前先在环境里试一次。
参考
- changelog 9.0.0:Added optional parameters to QSTR ES|QL function(第 2017–2021 行)@ v9.4.2
functions/parameters/qstr.md:query与options参数说明 @ v9.4.2functions/description/qstr.md:函数说明 @ v9.4.2- Elasticsearch issue 120933
本文对照 Elasticsearch
v9.4.2标签的 changelog 与函数文档核对,引文逐字取自原文。文中QSTR带options的查询写法已在本机 9.4.2 单节点实例上执行通过(default_operator的 AND/OR 对照已实测,见「在 9.4.2 上跑一遍」);options在 ES|QL 侧还支持哪些键,文档指向 Query DSL 的 query string 文档,本文未逐个核实。