Skip to content

Commit fefe6a0

Browse files
committed
Introduce at high level which other params such as , etc.. follow it. It validates the shape to send a valid query type to the ES.
1 parent 789f467 commit fefe6a0

File tree

4 files changed

+55
-46
lines changed

4 files changed

+55
-46
lines changed

docs/index.asciidoc

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -231,23 +231,23 @@ The next scheduled run:
231231
* updates the value of the field at the end of the pagination.
232232

233233
[id="plugins-{type}s-{plugin}-esql"]
234-
==== ES|QL support
235-
{es} Query Language (ES|QL) provides a SQL-like interface for querying your {es} data.
234+
==== {esql} support
235+
{es} Query Language ({esql}) provides a SQL-like interface for querying your {es} data.
236236

237237
To use {esql}, this plugin needs to be installed in {ls} 8.17.4 or newer, and must be connected to {es} 8.11 or newer.
238238

239-
To configure {esql} query in the plugin, set the `response_type` to `esql` and provide your {esql} query in the `query` parameter.
239+
To configure {esql} query in the plugin, set the `query_type` to `esql` and provide your {esql} query in the `query` parameter.
240240

241241
IMPORTANT: {esql} is evolving and may still have limitations with regard to result size or supported field types. We recommend understanding https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html[ES|QL current limitations] before using it in production environments.
242242

243-
The following is a basic scheduled ES|QL query that runs hourly:
243+
The following is a basic scheduled {esql} query that runs hourly:
244244
[source, ruby]
245245
input {
246246
elasticsearch {
247247
id => hourly_cron_job
248248
hosts => [ 'https://..']
249249
api_key => '....'
250-
response_type => 'esql'
250+
query_type => 'esql'
251251
query => '
252252
FROM food-index
253253
| WHERE spicy_level = "hot" AND @timestamp > NOW() - 1 hour
@@ -259,11 +259,11 @@ The following is a basic scheduled ES|QL query that runs hourly:
259259

260260
Set `config.support_escapes: true` in `logstash.yml` if you need to escape special chars in the query.
261261

262-
NOTE: With ES|QL query, {ls} doesn't generate `event.original`.
262+
NOTE: With {esql} query, {ls} doesn't generate `event.original`.
263263

264264
[id="plugins-{type}s-{plugin}-esql-event-mapping"]
265-
===== Mapping ES|QL result to {ls} event
266-
ES|QL returns query results in a structured tabular format, where data is organized into _columns_ (fields) and _values_ (entries).
265+
===== Mapping {esql} result to {ls} event
266+
{esql} returns query results in a structured tabular format, where data is organized into _columns_ (fields) and _values_ (entries).
267267
The plugin maps each value entry to an event, populating corresponding fields.
268268
For example, a query might produce a table like:
269269

@@ -303,9 +303,9 @@ NOTE: If your index has a mapping with sub-objects where `status.code` and `stat
303303
[id="plugins-{type}s-{plugin}-esql-multifields"]
304304
===== Conflict on multi-fields
305305

306-
ES|QL query fetches all parent and sub-fields fields if your {es} index has https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/multi-fields[multi-fields] or https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/subobjects[subobjects].
306+
{esql} query fetches all parent and sub-fields fields if your {es} index has https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/multi-fields[multi-fields] or https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/subobjects[subobjects].
307307
Since {ls} events cannot contain parent field's concrete value and sub-field values together, the plugin ignores sub-fields with warning and includes parent.
308-
We recommend using the `RENAME` (or `DROP` to avoid warnings) keyword in your ES|QL query explicitly rename the fields to include sub-fields into the event.
308+
We recommend using the `RENAME` (or `DROP` to avoid warnings) keyword in your {esql} query explicitly rename the fields to include sub-fields into the event.
309309

310310
This a common occurrence if your template or mapping follows the pattern of always indexing strings as "text" (`field`) + " keyword" (`field.keyword`) multi-field.
311311
In this case it's recommended to do `KEEP field` if the string is identical and there is only one subfield as the engine will optimize and retrieve the keyword, otherwise you can do `KEEP field.keyword | RENAME field.keyword as field`.
@@ -318,14 +318,14 @@ To illustrate the situation with example, assuming your mapping has a time `time
318318
"time.max": { "type": "long" }
319319
}
320320

321-
The ES|QL result will contain all three fields but the plugin cannot map them into {ls} event.
321+
The {esql} result will contain all three fields but the plugin cannot map them into {ls} event.
322322
To avoid this, you can use the `RENAME` keyword to rename the `time` parent field to get all three fields with unique fields.
323323
[source, ruby]
324324
...
325325
query => 'FROM my-index | RENAME time AS time.current'
326326
...
327327

328-
For comprehensive ES|QL syntax reference and best practices, see the https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-syntax.html[{es} ES|QL documentation].
328+
For comprehensive {esql} syntax reference and best practices, see the https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-syntax.html[{esql} documentation].
329329

330330
[id="plugins-{type}s-{plugin}-options"]
331331
==== Elasticsearch Input configuration options
@@ -354,7 +354,8 @@ Please check out <<plugins-{type}s-{plugin}-obsolete-options>> for details.
354354
| <<plugins-{type}s-{plugin}-password>> |<<password,password>>|No
355355
| <<plugins-{type}s-{plugin}-proxy>> |<<uri,uri>>|No
356356
| <<plugins-{type}s-{plugin}-query>> |<<string,string>>|No
357-
| <<plugins-{type}s-{plugin}-response_type>> |<<string,string>>, one of `["hits","aggregations","esql"]`|No
357+
| <<plugins-{type}s-{plugin}-query_type>> |<<string,string>>, one of `["dsl","esql"]`|No
358+
| <<plugins-{type}s-{plugin}-response_type>> |<<string,string>>, one of `["hits","aggregations"]`|No
358359
| <<plugins-{type}s-{plugin}-request_timeout_seconds>> | <<number,number>>|No
359360
| <<plugins-{type}s-{plugin}-schedule>> |<<string,string>>|No
360361
| <<plugins-{type}s-{plugin}-schedule_overlap>> |<<boolean,boolean>>|No
@@ -596,12 +597,22 @@ environment variables e.g. `proxy => '${LS_PROXY:}'`.
596597
* Default value is `'{ "sort": [ "_doc" ] }'`
597598

598599
The query to be executed.
599-
Accepted query shape is DSL or ES|QL (when `response_type => 'esql'`).
600-
Read the {ref}/query-dsl.html[{es} query DSL documentation] or {ref}/esql.html[{es} ES|QL documentation] for more information.
600+
Accepted query shape is DSL or {esql} (when `query_type => 'esql'`).
601+
Read the {ref}/query-dsl.html[{es} query DSL documentation] or {ref}/esql.html[{esql} documentation] for more information.
601602

602603
When <<plugins-{type}s-{plugin}-search_api>> resolves to `search_after` and the query does not specify `sort`,
603604
the default sort `'{ "sort": { "_shard_doc": "asc" } }'` will be added to the query. Please refer to the {ref}/paginate-search-results.html#search-after[Elasticsearch search_after] parameter to know more.
604605

606+
[id="plugins-{type}s-{plugin}-query_type"]
607+
===== `query_type`
608+
609+
* Value can be `dsl` or `esql`
610+
* Default value is `dsl`
611+
612+
Defines the <<plugins-{type}s-{plugin}-query>> shape.
613+
When `dsl`, the query shape must be valid {es} JSON-style string.
614+
When `esql`, the query shape must be a valid {esql} string and `index`, `size`, `slices`, `search_api`, `docinfo`, `docinfo_target`, `docinfo_fields`, `response_type` and `tracking_field` parameters are not allowed.
615+
605616
[id="plugins-{type}s-{plugin}-response_type"]
606617
===== `response_type`
607618

@@ -613,14 +624,11 @@ response from the query.
613624

614625
The default `hits` will generate one event per returned document (i.e. "hit").
615626

616-
When set to `aggregations`, a single Logstash event will be generated with the
627+
When set to `aggregations`, a single {ls} event will be generated with the
617628
contents of the `aggregations` object of the query's response. In this case the
618629
`hits` object will be ignored. The parameter `size` will be always be set to
619630
0 regardless of the default or user-defined value set in this plugin.
620631

621-
When using the `esql` setting, the query must be a valid ES|QL string.
622-
When this setting is active, `index`, `size`, `slices`, `search_api`, `docinfo`, `docinfo_target` and `docinfo_fields` parameters are not allowed.
623-
624632
[id="plugins-{type}s-{plugin}-request_timeout_seconds"]
625633
===== `request_timeout_seconds`
626634

lib/logstash/inputs/elasticsearch.rb

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,21 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
9797
# The index or alias to search.
9898
config :index, :validate => :string, :default => "logstash-*"
9999

100-
# The query to be executed. DSL or ES|QL (when `response_type => 'esql'`) query shape is accepted.
100+
# A type of Elasticsearch query, provided by @query. This will validate query shape and other params.
101+
config :query_type, :validate => %w[dsl esql], :default => 'dsl'
102+
103+
# The query to be executed. DSL or ES|QL (when `query_type => 'esql'`) query shape is accepted.
101104
# Read the following documentations for more info
102105
# Query DSL: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
103106
# ES|QL: https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html
104107
config :query, :validate => :string, :default => '{ "sort": [ "_doc" ] }'
105108

106-
# This allows you to speccify the response type: one of [hits, aggregations, esql]
109+
# This allows you to specify the DSL response type: one of [hits, aggregations]
107110
# where
108111
# hits: normal search request
109112
# aggregations: aggregation request
110-
# esql: ES|QL request
111-
config :response_type, :validate => %w[hits aggregations esql], :default => 'hits'
113+
# Note that this param is invalid when `query_type => 'esql'`, ES|QL response shape is always a tabular format
114+
config :response_type, :validate => %w[hits aggregations], :default => 'hits'
112115

113116
# This allows you to set the maximum number of hits returned per scroll.
114117
config :size, :validate => :number, :default => 1000
@@ -309,10 +312,10 @@ def register
309312
fill_hosts_from_cloud_id
310313
setup_ssl_params!
311314

312-
if @response_type == 'esql'
315+
if @query_type == 'esql'
313316
validate_ls_version_for_esql_support!
314317
validate_esql_query!
315-
not_allowed_options = original_params.keys & %w(index size slices search_api, docinfo, docinfo_target, docinfo_fields)
318+
not_allowed_options = original_params.keys & %w(index size slices search_api docinfo docinfo_target docinfo_fields response_type tracking_field)
316319
raise(LogStash::ConfigurationError, "Configured #{not_allowed_options} params are not allowed while using ES|QL query") if not_allowed_options&.size > 1
317320
else
318321
@base_query = LogStash::Json.load(@query)
@@ -361,7 +364,7 @@ def register
361364

362365
setup_search_api
363366

364-
setup_query_executor
367+
@query_executor = create_query_executor
365368

366369
setup_cursor_tracker
367370

@@ -396,7 +399,7 @@ def decorate_event(event)
396399
private
397400

398401
def get_query_object
399-
return @query if @response_type == 'esql'
402+
return @query if @query_type == 'esql'
400403
if @cursor_tracker
401404
query = @cursor_tracker.inject_cursor(@query)
402405
@logger.debug("new query is #{query}")
@@ -685,20 +688,16 @@ def setup_search_api
685688

686689
end
687690

688-
def setup_query_executor
689-
@query_executor = case @response_type
690-
when 'hits'
691-
if @resolved_search_api == "search_after"
692-
LogStash::Inputs::Elasticsearch::SearchAfter.new(@client, self)
693-
else
694-
logger.warn("scroll API is no longer recommended for pagination. Consider using search_after instead.") if es_major_version >= 8
695-
LogStash::Inputs::Elasticsearch::Scroll.new(@client, self)
696-
end
697-
when 'aggregations'
698-
LogStash::Inputs::Elasticsearch::Aggregation.new(@client, self)
699-
when 'esql'
700-
LogStash::Inputs::Elasticsearch::Esql.new(@client, self)
701-
end
691+
def create_query_executor
692+
return LogStash::Inputs::Elasticsearch::Esql.new(@client, self) if @query_type == 'esql'
693+
694+
# DSL query executor
695+
return LogStash::Inputs::Elasticsearch::Aggregation.new(@client, self) if @response_type == 'aggregations'
696+
# response_type is hits, executor can be search_after or scroll type
697+
return LogStash::Inputs::Elasticsearch::SearchAfter.new(@client, self) if @resolved_search_api == "search_after"
698+
699+
logger.warn("scroll API is no longer recommended for pagination. Consider using search_after instead.") if es_major_version >= 8
700+
LogStash::Inputs::Elasticsearch::Scroll.new(@client, self)
702701
end
703702

704703
def setup_cursor_tracker
@@ -751,7 +750,7 @@ def validate_esql_query!
751750
end
752751

753752
def validate_es_for_esql_support!
754-
return unless @response_type == 'esql'
753+
return unless @query_type == 'esql'
755754
# make sure connected ES supports ES|QL (8.11+)
756755
es_supports_esql = Gem::Version.create(es_version) >= Gem::Version.create(ES_ESQL_SUPPORT_VERSION)
757756
fail("Connected Elasticsearch #{es_version} version does not supports ES|QL. ES|QL feature requires at least Elasticsearch #{ES_ESQL_SUPPORT_VERSION} version.") unless es_supports_esql

spec/inputs/elasticsearch_spec.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,7 +1374,7 @@ def extract_transport(client) # on 7.x client.transport is a ES::Transport::Clie
13741374
let(:config) do
13751375
{
13761376
"query" => "FROM test-index | STATS count() BY field",
1377-
"response_type" => "esql",
1377+
"query_type" => "esql",
13781378
"retries" => 3
13791379
}
13801380
end
@@ -1387,8 +1387,8 @@ def extract_transport(client) # on 7.x client.transport is a ES::Transport::Clie
13871387

13881388
describe "#initialize" do
13891389
it "sets up the ESQL client with correct parameters" do
1390+
expect(plugin.instance_variable_get(:@query_type)).to eq(config["query_type"])
13901391
expect(plugin.instance_variable_get(:@query)).to eq(config["query"])
1391-
expect(plugin.instance_variable_get(:@response_type)).to eq(config["response_type"])
13921392
expect(plugin.instance_variable_get(:@retries)).to eq(config["retries"])
13931393
end
13941394
end
@@ -1449,10 +1449,12 @@ def extract_transport(client) # on 7.x client.transport is a ES::Transport::Clie
14491449
"docinfo" => true,
14501450
"docinfo_target" => "[@metadata][docinfo]",
14511451
"docinfo_fields" => ["_index"],
1452+
"response_type" => "hits",
1453+
"tracking_field" => "[@metadata][tracking]"
14521454
})}
14531455

14541456
it "raises a config error" do
1455-
mixed_fields = %w[index size slices docinfo_fields]
1457+
mixed_fields = %w[index size slices docinfo_fields response_type tracking_field]
14561458
expect { plugin.register }.to raise_error(LogStash::ConfigurationError, /Configured #{mixed_fields} params are not allowed while using ES|QL query/)
14571459
end
14581460
end

spec/inputs/integration/elasticsearch_esql_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
let(:config) do
2424
{
2525
"hosts" => ES_HOSTS,
26-
"response_type" => "esql"
26+
"query_type" => "esql"
2727
}
2828
end
2929
let(:es_client) do

0 commit comments

Comments
 (0)