一、两种使用姿势概览
- 直接用 ES|QL API(client.esql.query)
- 最灵活:能选择 JSON / CSV / text 等返回格式,并细调分隔符、locale 等。
- 默认返回:
{"columns":[...], "values":[...]}
的 Hash。
- Ruby ES|QL Helper(Elasticsearch::Helpers::ESQLHelper)
- 把上面的原始结构映射成按列名键入的数组/哈希,更贴近业务代码。
- 还能对列指定 Proc 做类型转换(如时间→DateTime,IP→IPAddr)。
还可以配合 elastic-esql 这个 gem 用 Ruby 风格链式构建 ES|QL 语句。
二、方式一:直接调用 ES|QL API(最灵活)
ruby
query = <<~ESQL
FROM sample_data
| EVAL duration_ms = ROUND(event.duration / 1000000.0, 1)
ESQL
response = client.esql.query(body: { query: query })
puts response
默认响应结构(示意):
ruby
{
"columns" => [
{"name"=>"@timestamp", "type"=>"date"},
{"name"=>"client.ip", "type"=>"ip"},
{"name"=>"event.duration", "type"=>"long"},
{"name"=>"message", "type"=>"keyword"},
{"name"=>"duration_ms", "type"=>"double"}
],
"values" => [
["2023-10-23T12:15:03.360Z", "172.21.2.162", 3450233, "Connected to 10.1.0.3", 3.5],
...
]
}
适合需要控制返回格式、自己做映射/导出(如 CSV)的场景。
三、方式二:ES|QL Helper(拿到"即用型" Ruby 对象)
ruby
require 'elasticsearch/helpers/esql_helper'
response = Elasticsearch::Helpers::ESQLHelper.query(client, query)
response.each { |row| puts row }
输出示意(每行都是 Hash):
ruby
{"duration_ms"=>3.5, "message"=>"Connected to 10.1.0.3",
"event.duration"=>3450233, "client.ip"=>"172.21.2.162",
"@timestamp"=>"2023-10-23T12:15:03.360Z"}
列级类型转换(parser)
给每个列名配一个 Proc
,在"映射到对象"的同时完成转换:
ruby
require 'elasticsearch/helpers/esql_helper'
require 'ipaddr'
require 'date'
parser = {
'@timestamp' => ->(t) { DateTime.parse(t) },
'client.ip' => ->(i) { IPAddr.new(i) },
'event.duration'=> ->(d) { d.to_s }
}
rows = Elasticsearch::Helpers::ESQLHelper.query(client, query, parser: parser)
puts rows.first
# => {"duration_ms"=>3.5, "message"=>"Connected ...",
# "event.duration"=>"3450233", "client.ip"=>#<IPAddr: IPv4:...>,
# "@timestamp"=>#<DateTime: 2023-10-23T12:15:03+00:00 ...>}
这样你在业务层直接面对 Ruby 类型,而不是原始字符串。
四、用 elastic-esql 构建 ES|QL(更易读、可组合)
ruby
require 'elasticsearch'
require 'elastic/esql'
client = Elasticsearch::Client.new
index = 'sample_data'
query = Elastic::ESQL.from(index)
.sort('@timestamp').desc
.where('event_duration > 5000000')
.limit(3)
.eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })
查看生成的 ES|QL 字符串:
ruby
query.to_s
# "FROM sample_data | SORT @timestamp DESC | WHERE event_duration > 5000000 | LIMIT 3 | EVAL duration_ms = ROUND(event_duration/1000000.0, 1)"
该库不依赖 elasticsearch-ruby,本质是一个查询构建器 ;写好 query 后可直接喂给
client.esql.query
。
五、防注入:参数化查询是必须的
和 SQL 一样,不要把用户输入直接拼进查询 。ES|QL 支持把不可信数据作为 params 单独传入,用 ?
占位:
ruby
def find_employee_by_name(name)
query = Elastic::ESQL.from('employees')
.keep('first_name', 'last_name', 'height')
.where('first_name == ?')
@client.esql.query(body: { query: query, params: [name] })
end
params
会按顺序替换?
,从而避免代码注入风险。
六、选择响应格式:JSON / CSV / Text
- 默认 :JSON Hash(
columns
+values
) - 也可:请求里指定 CSV、text 等格式,并控制分隔符、locale 等,方便导出或兼容旧系统。
- 使用 Helper 时通常保留默认 JSON,然后映射成 Ruby 对象。
七、实战建议(踩坑少、性能好)
- 统一入口 :面向业务推荐使用 ES|QL Helper,保持代码简洁一致。
- 类型就地转换 :用
parser
在 Helper 层完成时间/IP/数值转换,避免在业务处"到处 parse"。 - 参数化一律默认开启 :所有用户输入都走
? + params
,别拼字符串。 - 日志与观测:对关键 ES|QL 查询加上上下文(如 request id),便于排障与压测对比。
- 导出/集成 :需要 CSV 或文本格式时,直接用原生 API 选对应
format
,少写转换代码。 - Query Builder 团队规范 :针对长查询/拼装查询,统一使用 elastic-esql,可读可测试。
八、完整小结
- 原生 API:最强灵活性,可控返回格式,但需要自己解析。
- ES|QL Helper :即开即用,返回 Ruby Hash/Array,并支持列级
Proc
转换。 - elastic-esql:优雅构建查询字符串,适合复杂、可组合的场景。
- 安全 :务必参数化 ,把用户输入放到
params
。
把这三者配起来,你就能在 Ruby 环境下把 ES|QL 用得又快又稳、又安全又优雅。