Skip to content

Commit 3f64f0b

Browse files
committed
[Gem] ES|QL Helper - Adds parser parameter to query
1 parent 38caac1 commit 3f64f0b

File tree

3 files changed

+78
-19
lines changed

3 files changed

+78
-19
lines changed

docs/helpers.asciidoc

+24-7
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,28 @@ require 'elasticsearch/helpers/esql_helper'
211211
response = Elasticsearch::Helpers::ESQLHelper.query(client, query)
212212
213213
puts response
214-
{"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"}
215-
{"duration_ms"=>2.8, "message"=>"Connected to 10.1.0.2", "event.duration"=>2764889, "client.ip"=>"172.21.2.113", "@timestamp"=>"2023-10-23T12:27:28.948Z"}
216-
{"duration_ms"=>1.2, "message"=>"Disconnected", "event.duration"=>1232382, "client.ip"=>"172.21.0.5", "@timestamp"=>"2023-10-23T13:33:34.937Z"}
217-
{"duration_ms"=>0.7, "message"=>"Connection error", "event.duration"=>725448, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:51:54.732Z"}
218-
{"duration_ms"=>8.3, "message"=>"Connection error", "event.duration"=>8268153, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:52:55.015Z"}
219-
{"duration_ms"=>5.0, "message"=>"Connection error", "event.duration"=>5033755, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:53:55.832Z"}
220-
{"duration_ms"=>1.8, "message"=>"Connected to 10.1.0.1", "event.duration"=>1756467, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:55:01.543Z"}
214+
[
215+
{"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"}
216+
{"duration_ms"=>2.8, "message"=>"Connected to 10.1.0.2", "event.duration"=>2764889, "client.ip"=>"172.21.2.113", "@timestamp"=>"2023-10-23T12:27:28.948Z"}
217+
{"duration_ms"=>1.2, "message"=>"Disconnected", "event.duration"=>1232382, "client.ip"=>"172.21.0.5", "@timestamp"=>"2023-10-23T13:33:34.937Z"}
218+
{"duration_ms"=>0.7, "message"=>"Connection error", "event.duration"=>725448, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:51:54.732Z"}
219+
{"duration_ms"=>8.3, "message"=>"Connection error", "event.duration"=>8268153, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:52:55.015Z"}
220+
{"duration_ms"=>5.0, "message"=>"Connection error", "event.duration"=>5033755, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:53:55.832Z"}
221+
{"duration_ms"=>1.8, "message"=>"Connected to 10.1.0.1", "event.duration"=>1756467, "client.ip"=>"172.21.3.15", "@timestamp"=>"2023-10-23T13:55:01.543Z"}
222+
]
223+
----
224+
225+
Additionally, a block can be specified to work on the response data. Pass in a block to `query` and it will yield each item in the Array of responses.
226+
227+
You could use this for example to convert '@timestamp' into a DateTime object:
228+
[source,ruby]
229+
----
230+
require 'elasticsearch/helpers/esql_helper'
231+
232+
response = esql_helper.query(client, query).each do |r|
233+
r['@timestamp'] = DateTime.parse(r['@timestamp'])
234+
end
235+
236+
response.first['@timestamp']
237+
# <DateTime: 2023-10-23T12:15:03+00:00 ((2460241j,44103s,360000000n),+0s,2299161j)>
221238
----

elasticsearch/lib/elasticsearch/helpers/esql_helper.rb

+25-7
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,37 @@ module Helpers
2222
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-query-api.html
2323
#
2424
module ESQLHelper
25-
# By default, the `query` API returns a Hash response with a `columns` key whose value is an
26-
# Array of { name: type } Hashes for each column and a `values` key whose value is an Array of
27-
# Arrays with the values for each row.
25+
# Query helper for ES|QL
26+
#
27+
# By default, the `esql.query` API returns a Hash response with the following keys:
28+
# - `columns` with the value being an Array of { name: type } Hashes for each column
29+
# - `values` with the value being an Array of Arrays with the values for each row.
2830
# This helper function returns an Array of hashes with the columns as keys and the respective
29-
# values: { column['name'] => value }
31+
# values: { column['name'] => value }.
32+
#
33+
# @param client [Elasticsearch::Client] an instance of the Client to use for the query.
34+
# @param query [Hash, String] The query to be passed to the ES|QL query API.
35+
# @param params [Hash] options to pass to the ES|QL query API.
36+
# @param convert_datetime [Boolean] Converts datetime values from the response into DateTime
37+
# objects in Ruby. Default: true.
38+
#
39+
# @example Using the ES|QL helper with a parser
40+
# response = Elasticsearch::Helpers::ESQLHelper.query(
41+
# client,
42+
# query,
43+
# parser: { '@timestamp' => Proc.new { |t| DateTime.parse(t) } }
44+
# )
3045
#
31-
def self.query(client, query, params = {})
46+
# @see https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/Helpers.html#_esql_helper
47+
#
48+
def self.query(client, query, params = {}, parser: {})
3249
response = client.esql.query({ body: { query: query }, format: 'json' }.merge(params))
33-
3450
columns = response['columns']
3551
response['values'].map do |value|
3652
(value.length - 1).downto(0).map do |index|
37-
{ columns[index]['name'] => value[index] }
53+
key = columns[index]['name']
54+
value[index] = parser[key].call value[index] if parser[key]
55+
{ key => value[index] }
3856
end.reduce({}, :merge)
3957
end
4058
end

elasticsearch/spec/integration/helpers/esql_helper_spec.rb

+29-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
let(:index) { 'esql_helper_test' }
2222
let(:body) { { size: 12, query: { match_all: {} } } }
2323
let(:esql_helper) { Elasticsearch::Helpers::ESQLHelper }
24+
let(:query) do
25+
<<~ESQL
26+
FROM #{index}
27+
| EVAL duration_ms = ROUND(event.duration / 1000000.0, 1)
28+
ESQL
29+
end
2430

2531
before do
2632
client.indices.create(
@@ -58,13 +64,31 @@
5864
end
5965

6066
it 'returns an ESQL response as a relational key/value object' do
61-
query = <<~ESQL
62-
FROM #{index}
63-
| EVAL duration_ms = ROUND(event.duration / 1000000.0, 1)
64-
ESQL
6567
response = esql_helper.query(client, query)
66-
6768
expect(response.count).to eq 7
6869
expect(response.first.keys).to eq ['duration_ms', 'message', 'event.duration', 'client.ip', '@timestamp']
70+
response.each do |r|
71+
expect(r['@timestamp']).to be_a String
72+
expect(r['client.ip']).to be_a String
73+
expect(r['message']).to be_a String
74+
expect(r['event.duration']).to be_a Integer
75+
end
76+
end
77+
78+
it 'parses iterated objects when procs are passed in' do
79+
require 'ipaddr'
80+
81+
parser = {
82+
'@timestamp' => Proc.new { |t| DateTime.parse(t) },
83+
'client.ip' => Proc.new { |i| IPAddr.new(i) },
84+
'event.duration' => Proc.new { |d| d.to_s }
85+
}
86+
response = esql_helper.query(client, query, parser: parser)
87+
response.each do |r|
88+
expect(r['@timestamp']).to be_a DateTime
89+
expect(r['client.ip']).to be_a IPAddr
90+
expect(r['message']).to be_a String
91+
expect(r['event.duration']).to be_a String
92+
end
6993
end
7094
end

0 commit comments

Comments
 (0)