Skip to content

Commit c3a2b19

Browse files
authored
[8.x] ESQL QSTR function (elastic#112590) (elastic#113189)
1 parent 18d4f68 commit c3a2b19

File tree

24 files changed

+995
-1
lines changed

24 files changed

+995
-1
lines changed

docs/reference/esql/functions/description/qstr.asciidoc

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/examples/qstr.asciidoc

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/definition/qstr.json

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/docs/qstr.md

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/layout/qstr.asciidoc

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/parameters/qstr.asciidoc

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/signature/qstr.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/reference/esql/functions/types/qstr.asciidoc

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
###############################################
2+
# Tests for QSTR function
3+
#
4+
5+
qstrWithField
6+
required_capability: qstr_function
7+
8+
// tag::qstr-with-field[]
9+
from books
10+
| where qstr("author: Faulkner")
11+
| keep book_no, author
12+
| sort book_no
13+
| limit 5;
14+
// end::qstr-with-field[]
15+
16+
// tag::qstr-with-field-result[]
17+
book_no:keyword | author:text
18+
2378 | [Carol Faulkner, Holly Byers Ochoa, Lucretia Mott]
19+
2713 | William Faulkner
20+
2847 | Colleen Faulkner
21+
2883 | William Faulkner
22+
3293 | Danny Faulkner
23+
;
24+
// end::qstr-with-field-result[]
25+
26+
qstrWithMultipleFields
27+
required_capability: qstr_function
28+
29+
from books
30+
| where qstr("title:Return* AND author:*Tolkien")
31+
| keep book_no, title;
32+
ignoreOrder:true
33+
34+
book_no:keyword | title:text
35+
2714 | Return of the King Being the Third Part of The Lord of the Rings
36+
7350 | Return of the Shadow
37+
;
38+
39+
qstrWithQueryExpressions
40+
required_capability: qstr_function
41+
42+
from books
43+
| where qstr(CONCAT("title:Return*", " AND author:*Tolkien"))
44+
| keep book_no, title;
45+
ignoreOrder:true
46+
47+
book_no:keyword | title:text
48+
2714 | Return of the King Being the Third Part of The Lord of the Rings
49+
7350 | Return of the Shadow
50+
;
51+
52+
qstrWithDisjunction
53+
required_capability: qstr_function
54+
55+
from books
56+
| where qstr("title:Return") or year > 2020
57+
| keep book_no, title;
58+
ignoreOrder:true
59+
60+
book_no:keyword | title:text
61+
2714 | Return of the King Being the Third Part of The Lord of the Rings
62+
6818 | Hadji Murad
63+
7350 | Return of the Shadow
64+
;
65+
66+
qstrWithConjunction
67+
required_capability: qstr_function
68+
69+
from books
70+
| where qstr("title: Rings") and ratings > 4.6
71+
| keep book_no, title;
72+
ignoreOrder:true
73+
74+
book_no:keyword | title:text
75+
4023 |A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings
76+
7140 |The Lord of the Rings Poster Collection: Six Paintings by Alan Lee (No. 1)
77+
;
78+
79+
qstrWithFunctionPushedToLucene
80+
required_capability: qstr_function
81+
82+
from hosts
83+
| where qstr("host: beta") and cidr_match(ip1, "127.0.0.2/32", "127.0.0.3/32")
84+
| keep card, host, ip0, ip1;
85+
ignoreOrder:true
86+
87+
card:keyword |host:keyword |ip0:ip |ip1:ip
88+
eth1 |beta |127.0.0.1 |127.0.0.2
89+
;
90+
91+
qstrWithFunctionNotPushedToLucene
92+
required_capability: qstr_function
93+
94+
from books
95+
| where qstr("title: rings") and length(description) > 600
96+
| keep book_no, title;
97+
ignoreOrder:true
98+
99+
book_no:keyword | title:text
100+
2675 | The Lord of the Rings - Boxed Set
101+
2714 | Return of the King Being the Third Part of The Lord of the Rings
102+
;
103+
104+
qstrWithMultipleWhereClauses
105+
required_capability: qstr_function
106+
107+
from books
108+
| where qstr("title: rings")
109+
| where qstr("year: [1 TO 2005]")
110+
| keep book_no, title;
111+
ignoreOrder:true
112+
113+
book_no:keyword | title:text
114+
4023 | A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings
115+
7140 | The Lord of the Rings Poster Collection: Six Paintings by Alan Lee (No. 1)
116+
;
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.plugin;
9+
10+
import org.elasticsearch.action.index.IndexRequest;
11+
import org.elasticsearch.action.support.WriteRequest;
12+
import org.elasticsearch.common.settings.Settings;
13+
import org.elasticsearch.index.query.QueryShardException;
14+
import org.elasticsearch.xpack.esql.VerificationException;
15+
import org.elasticsearch.xpack.esql.action.AbstractEsqlIntegTestCase;
16+
import org.elasticsearch.xpack.esql.action.ColumnInfoImpl;
17+
import org.elasticsearch.xpack.esql.action.EsqlCapabilities;
18+
import org.elasticsearch.xpack.esql.action.EsqlQueryRequest;
19+
import org.elasticsearch.xpack.esql.action.EsqlQueryResponse;
20+
import org.elasticsearch.xpack.esql.core.type.DataType;
21+
import org.junit.Before;
22+
23+
import java.util.List;
24+
25+
import static org.elasticsearch.test.ListMatcher.matchesList;
26+
import static org.elasticsearch.test.MapMatcher.assertMap;
27+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
28+
import static org.elasticsearch.xpack.esql.EsqlTestUtils.getValuesList;
29+
import static org.hamcrest.CoreMatchers.containsString;
30+
import static org.hamcrest.Matchers.equalTo;
31+
32+
public class QueryStringFunctionIT extends AbstractEsqlIntegTestCase {
33+
34+
@Before
35+
public void setupIndex() {
36+
createAndPopulateIndex();
37+
}
38+
39+
@Override
40+
protected EsqlQueryResponse run(EsqlQueryRequest request) {
41+
assumeTrue("qstr function available in snapshot builds only", EsqlCapabilities.Cap.QSTR_FUNCTION.isEnabled());
42+
return super.run(request);
43+
}
44+
45+
public void testSimpleQueryString() {
46+
var query = """
47+
FROM test
48+
| WHERE qstr("content: dog")
49+
| KEEP id
50+
| SORT id
51+
""";
52+
53+
try (var resp = run(query)) {
54+
assertThat(resp.columns().stream().map(ColumnInfoImpl::name).toList(), equalTo(List.of("id")));
55+
assertThat(resp.columns().stream().map(ColumnInfoImpl::type).map(DataType::toString).toList(), equalTo(List.of("INTEGER")));
56+
// values
57+
List<List<Object>> values = getValuesList(resp);
58+
assertMap(values, matchesList().item(List.of(1)).item(List.of(3)).item(List.of(4)).item(List.of(5)));
59+
}
60+
}
61+
62+
public void testMultiFieldQueryString() {
63+
var query = """
64+
FROM test
65+
| WHERE qstr("dog OR canine")
66+
| KEEP id
67+
""";
68+
69+
try (var resp = run(query)) {
70+
assertThat(resp.columns().stream().map(ColumnInfoImpl::name).toList(), equalTo(List.of("id")));
71+
assertThat(resp.columns().stream().map(ColumnInfoImpl::type).map(DataType::toString).toList(), equalTo(List.of("INTEGER")));
72+
// values
73+
List<List<Object>> values = getValuesList(resp);
74+
assertThat(values.size(), equalTo(5));
75+
}
76+
}
77+
78+
public void testQueryStringWithinEval() {
79+
var query = """
80+
FROM test
81+
| EVAL matches_query = qstr("title: fox")
82+
""";
83+
84+
var error = expectThrows(VerificationException.class, () -> run(query));
85+
assertThat(error.getMessage(), containsString("[QSTR] function is only supported in WHERE commands"));
86+
}
87+
88+
public void testInvalidQueryStringEof() {
89+
var query = """
90+
FROM test
91+
| WHERE qstr("content: ((((dog")
92+
""";
93+
94+
var error = expectThrows(QueryShardException.class, () -> run(query));
95+
assertThat(error.getMessage(), containsString("Failed to parse query [content: ((((dog]"));
96+
assertThat(error.getRootCause().getMessage(), containsString("Encountered \"<EOF>\" at line 1, column 16"));
97+
}
98+
99+
public void testInvalidQueryStringLexicalError() {
100+
var query = """
101+
FROM test
102+
| WHERE qstr("/")
103+
""";
104+
105+
var error = expectThrows(QueryShardException.class, () -> run(query));
106+
assertThat(error.getMessage(), containsString("Failed to parse query [/]"));
107+
assertThat(
108+
error.getRootCause().getMessage(),
109+
containsString("Lexical error at line 1, column 2. Encountered: <EOF> (in lexical state 2)")
110+
);
111+
}
112+
113+
private void createAndPopulateIndex() {
114+
var indexName = "test";
115+
var client = client().admin().indices();
116+
var CreateRequest = client.prepareCreate(indexName)
117+
.setSettings(Settings.builder().put("index.number_of_shards", 1))
118+
.setMapping("id", "type=integer", "content", "type=text");
119+
assertAcked(CreateRequest);
120+
client().prepareBulk()
121+
.add(
122+
new IndexRequest(indexName).id("1")
123+
.source("id", 1, "content", "The quick brown animal swiftly jumps over a lazy dog", "title", "A Swift Fox's Journey")
124+
)
125+
.add(
126+
new IndexRequest(indexName).id("2")
127+
.source("id", 2, "content", "A speedy brown fox hops effortlessly over a sluggish canine", "title", "The Fox's Leap")
128+
)
129+
.add(
130+
new IndexRequest(indexName).id("3")
131+
.source("id", 3, "content", "Quick and nimble, the fox vaults over the lazy dog", "title", "Brown Fox in Action")
132+
)
133+
.add(
134+
new IndexRequest(indexName).id("4")
135+
.source(
136+
"id",
137+
4,
138+
"content",
139+
"A fox that is quick and brown jumps over a dog that is quite lazy",
140+
"title",
141+
"Speedy Animals"
142+
)
143+
)
144+
.add(
145+
new IndexRequest(indexName).id("5")
146+
.source(
147+
"id",
148+
5,
149+
"content",
150+
"With agility, a quick brown fox bounds over a slow-moving dog",
151+
"title",
152+
"Foxes and Canines"
153+
)
154+
)
155+
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
156+
.get();
157+
ensureYellow(indexName);
158+
}
159+
}

0 commit comments

Comments
 (0)