77
88package org .elasticsearch .xpack .kql .parser ;
99
10+ import org .elasticsearch .index .query .BoolQueryBuilder ;
11+ import org .elasticsearch .index .query .MatchQueryBuilder ;
1012import org .elasticsearch .index .query .NestedQueryBuilder ;
13+ import org .elasticsearch .index .query .QueryBuilder ;
14+ import org .elasticsearch .index .query .RangeQueryBuilder ;
15+ import org .elasticsearch .index .query .TermQueryBuilder ;
1116import org .elasticsearch .test .ESTestCase ;
1217import org .hamcrest .Matchers ;
1318
1621import java .util .stream .Stream ;
1722
1823import static org .elasticsearch .common .Strings .format ;
24+ import static org .hamcrest .Matchers .empty ;
1925import static org .hamcrest .Matchers .equalTo ;
26+ import static org .hamcrest .Matchers .hasSize ;
2027
2128public class KqlNestedFieldQueryTests extends AbstractKqlParserTestCase {
2229 public void testInvalidNestedFieldName () {
@@ -35,9 +42,7 @@ public void testInlineNestedFieldMatchTextQuery() {
3542 {
3643 // Querying a nested text subfield.
3744 String nestedFieldName = format ("%s.%s" , NESTED_FIELD_NAME , fieldName );
38- String searchTerms = Stream .generate (ESTestCase ::randomIdentifier )
39- .limit (randomIntBetween (1 , 10 ))
40- .collect (Collectors .joining (" " ));
45+ String searchTerms = randomSearchTerms ();
4146 String kqlQueryString = format ("%s: %s" , nestedFieldName , searchTerms );
4247
4348 NestedQueryBuilder nestedQuery = asInstanceOf (NestedQueryBuilder .class , parseKqlQuery (kqlQueryString ));
@@ -49,9 +54,7 @@ public void testInlineNestedFieldMatchTextQuery() {
4954 {
5055 // Several levels of nested fields.
5156 String nestedFieldName = format ("%s.%s.%s" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , fieldName );
52- String searchTerms = Stream .generate (ESTestCase ::randomIdentifier )
53- .limit (randomIntBetween (1 , 10 ))
54- .collect (Collectors .joining (" " ));
57+ String searchTerms = randomSearchTerms ();
5558 String kqlQueryString = format ("%s: %s" , nestedFieldName , searchTerms );
5659
5760 NestedQueryBuilder nestedQuery = asInstanceOf (NestedQueryBuilder .class , parseKqlQuery (kqlQueryString ));
@@ -69,9 +72,7 @@ public void testInlineNestedFieldMatchKeywordFieldQuery() {
6972 {
7073 // Querying a nested text subfield.
7174 String nestedFieldName = format ("%s.%s" , NESTED_FIELD_NAME , KEYWORD_FIELD_NAME );
72- String searchTerms = Stream .generate (ESTestCase ::randomIdentifier )
73- .limit (randomIntBetween (1 , 10 ))
74- .collect (Collectors .joining (" " ));
75+ String searchTerms = randomSearchTerms ();
7576 String kqlQueryString = format ("%s: %s" , nestedFieldName , searchTerms );
7677
7778 NestedQueryBuilder nestedQuery = asInstanceOf (NestedQueryBuilder .class , parseKqlQuery (kqlQueryString ));
@@ -83,9 +84,7 @@ public void testInlineNestedFieldMatchKeywordFieldQuery() {
8384 {
8485 // Several levels of nested fields.
8586 String nestedFieldName = format ("%s.%s.%s" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , KEYWORD_FIELD_NAME );
86- String searchTerms = Stream .generate (ESTestCase ::randomIdentifier )
87- .limit (randomIntBetween (1 , 10 ))
88- .collect (Collectors .joining (" " ));
87+ String searchTerms = randomSearchTerms ();
8988 String kqlQueryString = format ("%s: %s" , nestedFieldName , searchTerms );
9089
9190 NestedQueryBuilder nestedQuery = asInstanceOf (NestedQueryBuilder .class , parseKqlQuery (kqlQueryString ));
@@ -97,4 +96,202 @@ public void testInlineNestedFieldMatchKeywordFieldQuery() {
9796 assertTermQueryBuilder (nestedSubQuery .query (), nestedFieldName , searchTerms );
9897 }
9998 }
99+
100+ public void testInlineNestedFieldRangeQuery () {
101+ {
102+ // Querying a nested text subfield.
103+ String nestedFieldName = format ("%s.%s" , NESTED_FIELD_NAME , INT_FIELD_NAME );
104+ String operator = randomFrom (">" , ">=" , "<" , "<=" );
105+ String kqlQueryString = format ("%s %s %s" , nestedFieldName , operator , randomDouble ());
106+
107+ NestedQueryBuilder nestedQuery = asInstanceOf (NestedQueryBuilder .class , parseKqlQuery (kqlQueryString ));
108+
109+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
110+ assertRangeQueryBuilder (nestedQuery .query (), nestedFieldName , rangeQueryBuilder -> {});
111+ }
112+
113+ {
114+ // Several levels of nested fields.
115+ String nestedFieldName = format ("%s.%s.%s" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , INT_FIELD_NAME );
116+ String operator = randomFrom (">" , ">=" , "<" , "<=" );
117+ String kqlQueryString = format ("%s %s %s" , nestedFieldName , operator , randomDouble ());
118+
119+ NestedQueryBuilder nestedQuery = asInstanceOf (NestedQueryBuilder .class , parseKqlQuery (kqlQueryString ));
120+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
121+
122+ NestedQueryBuilder nestedSubQuery = asInstanceOf (NestedQueryBuilder .class , nestedQuery .query ());
123+ assertThat (nestedSubQuery .path (), equalTo (format ("%s.%s" , NESTED_FIELD_NAME , NESTED_FIELD_NAME )));
124+
125+ assertRangeQueryBuilder (nestedSubQuery .query (), nestedFieldName , rangeQueryBuilder -> {});
126+ }
127+ }
128+
129+ public void testNestedQuerySyntax () {
130+ // Single word - Keyword & text field
131+ List .of (KEYWORD_FIELD_NAME , TEXT_FIELD_NAME )
132+ .forEach (
133+ fieldName -> assertThat (
134+ parseKqlQuery (format ("%s : { %s : %s }" , NESTED_FIELD_NAME , fieldName , "foo" )),
135+ equalTo (parseKqlQuery (format ("%s.%s : %s" , NESTED_FIELD_NAME , fieldName , "foo" )))
136+ )
137+ );
138+
139+ // Multiple words - Keyword & text field
140+ List .of (KEYWORD_FIELD_NAME , TEXT_FIELD_NAME )
141+ .forEach (
142+ fieldName -> assertThat (
143+ parseKqlQuery (format ("%s : { %s : %s }" , NESTED_FIELD_NAME , fieldName , "foo bar" )),
144+ equalTo (parseKqlQuery (format ("%s.%s : %s" , NESTED_FIELD_NAME , fieldName , "foo bar" )))
145+ )
146+ );
147+
148+ // Range syntax
149+ {
150+ String operator = randomFrom ("<" , "<=" , ">" , ">=" );
151+ double rangeValue = randomDouble ();
152+ assertThat (
153+ parseKqlQuery (format ("%s : { %s %s %s }" , NESTED_FIELD_NAME , INT_FIELD_NAME , operator , rangeValue )),
154+ equalTo (parseKqlQuery (format ("%s.%s %s %s" , NESTED_FIELD_NAME , INT_FIELD_NAME , operator , rangeValue )))
155+ );
156+ }
157+
158+ // Several level of nesting
159+ {
160+ QueryBuilder inlineQuery = parseKqlQuery (
161+ format ("%s.%s.%s : %s" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , TEXT_FIELD_NAME , "foo bar" )
162+ );
163+
164+ assertThat (
165+ parseKqlQuery (format ("%s : { %s : { %s : %s } }" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , TEXT_FIELD_NAME , "foo bar" )),
166+ equalTo (inlineQuery )
167+ );
168+
169+ assertThat (
170+ parseKqlQuery (format ("%s.%s : { %s : %s }" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , TEXT_FIELD_NAME , "foo bar" )),
171+ equalTo (inlineQuery )
172+ );
173+
174+ assertThat (
175+ parseKqlQuery (format ("%s : { %s.%s : %s }" , NESTED_FIELD_NAME , NESTED_FIELD_NAME , TEXT_FIELD_NAME , "foo bar" )),
176+ equalTo (inlineQuery )
177+ );
178+ }
179+ }
180+
181+ public void testBooleanAndNestedQuerySyntax () {
182+ NestedQueryBuilder nestedQuery = asInstanceOf (
183+ NestedQueryBuilder .class ,
184+ parseKqlQuery (
185+ format ("%s: { %s : foo AND %s: bar AND %s > 3}" , NESTED_FIELD_NAME , TEXT_FIELD_NAME , KEYWORD_FIELD_NAME , INT_FIELD_NAME )
186+ )
187+ );
188+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
189+
190+ BoolQueryBuilder subQuery = asInstanceOf (BoolQueryBuilder .class , nestedQuery .query ());
191+ assertThat (subQuery .should (), empty ());
192+ assertThat (subQuery .filter (), empty ());
193+ assertThat (subQuery .mustNot (), empty ());
194+ assertThat (subQuery .must (), hasSize (3 ));
195+ assertMatchQueryBuilder (
196+ subQuery .must ().stream ().filter (q -> q instanceof MatchQueryBuilder ).findFirst ().get (),
197+ format ("%s.%s" , NESTED_FIELD_NAME , TEXT_FIELD_NAME ),
198+ "foo"
199+ );
200+ assertTermQueryBuilder (
201+ subQuery .must ().stream ().filter (q -> q instanceof TermQueryBuilder ).findFirst ().get (),
202+ format ("%s.%s" , NESTED_FIELD_NAME , KEYWORD_FIELD_NAME ),
203+ "bar"
204+ );
205+ assertRangeQueryBuilder (
206+ subQuery .must ().stream ().filter (q -> q instanceof RangeQueryBuilder ).findAny ().get (),
207+ format ("%s.%s" , NESTED_FIELD_NAME , INT_FIELD_NAME ),
208+ q -> {}
209+ );
210+ }
211+
212+ public void testBooleanOrNestedQuerySyntax () {
213+ NestedQueryBuilder nestedQuery = asInstanceOf (
214+ NestedQueryBuilder .class ,
215+ parseKqlQuery (
216+ format ("%s: { %s : foo OR %s: bar OR %s > 3 }" , NESTED_FIELD_NAME , TEXT_FIELD_NAME , KEYWORD_FIELD_NAME , INT_FIELD_NAME )
217+ )
218+ );
219+
220+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
221+
222+ BoolQueryBuilder subQuery = asInstanceOf (BoolQueryBuilder .class , nestedQuery .query ());
223+ assertThat (subQuery .must (), empty ());
224+ assertThat (subQuery .filter (), empty ());
225+ assertThat (subQuery .mustNot (), empty ());
226+ assertThat (subQuery .should (), hasSize (3 ));
227+ assertMatchQueryBuilder (
228+ subQuery .should ().stream ().filter (q -> q instanceof MatchQueryBuilder ).findFirst ().get (),
229+ format ("%s.%s" , NESTED_FIELD_NAME , TEXT_FIELD_NAME ),
230+ "foo"
231+ );
232+ assertTermQueryBuilder (
233+ subQuery .should ().stream ().filter (q -> q instanceof TermQueryBuilder ).findFirst ().get (),
234+ format ("%s.%s" , NESTED_FIELD_NAME , KEYWORD_FIELD_NAME ),
235+ "bar"
236+ );
237+ assertRangeQueryBuilder (
238+ subQuery .should ().stream ().filter (q -> q instanceof RangeQueryBuilder ).findAny ().get (),
239+ format ("%s.%s" , NESTED_FIELD_NAME , INT_FIELD_NAME ),
240+ q -> {}
241+ );
242+ }
243+
244+ public void testBooleanNotNestedQuerySyntax () {
245+ {
246+ NestedQueryBuilder nestedQuery = asInstanceOf (
247+ NestedQueryBuilder .class ,
248+ parseKqlQuery (format ("%s: { NOT %s : foo }" , NESTED_FIELD_NAME , TEXT_FIELD_NAME ))
249+ );
250+
251+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
252+
253+ BoolQueryBuilder subQuery = asInstanceOf (BoolQueryBuilder .class , nestedQuery .query ());
254+ assertThat (subQuery .must (), empty ());
255+ assertThat (subQuery .filter (), empty ());
256+ assertThat (subQuery .should (), empty ());
257+ assertThat (subQuery .mustNot (), hasSize (1 ));
258+ assertMatchQueryBuilder (subQuery .mustNot ().get (0 ), format ("%s.%s" , NESTED_FIELD_NAME , TEXT_FIELD_NAME ), "foo" );
259+ }
260+
261+ {
262+ NestedQueryBuilder nestedQuery = asInstanceOf (
263+ NestedQueryBuilder .class ,
264+ parseKqlQuery (format ("%s: { NOT %s : foo }" , NESTED_FIELD_NAME , KEYWORD_FIELD_NAME ))
265+ );
266+
267+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
268+
269+ BoolQueryBuilder subQuery = asInstanceOf (BoolQueryBuilder .class , nestedQuery .query ());
270+ assertThat (subQuery .must (), empty ());
271+ assertThat (subQuery .filter (), empty ());
272+ assertThat (subQuery .should (), empty ());
273+ assertThat (subQuery .mustNot (), hasSize (1 ));
274+ assertTermQueryBuilder (subQuery .mustNot ().get (0 ), format ("%s.%s" , NESTED_FIELD_NAME , KEYWORD_FIELD_NAME ), "foo" );
275+ }
276+
277+ {
278+ NestedQueryBuilder nestedQuery = asInstanceOf (
279+ NestedQueryBuilder .class ,
280+ parseKqlQuery (format ("%s: { NOT %s < 3 }" , NESTED_FIELD_NAME , INT_FIELD_NAME ))
281+ );
282+
283+ assertThat (nestedQuery .path (), equalTo (NESTED_FIELD_NAME ));
284+
285+ BoolQueryBuilder subQuery = asInstanceOf (BoolQueryBuilder .class , nestedQuery .query ());
286+ assertThat (subQuery .must (), empty ());
287+ assertThat (subQuery .filter (), empty ());
288+ assertThat (subQuery .should (), empty ());
289+ assertThat (subQuery .mustNot (), hasSize (1 ));
290+ assertRangeQueryBuilder (subQuery .mustNot ().get (0 ), format ("%s.%s" , NESTED_FIELD_NAME , INT_FIELD_NAME ), q -> {});
291+ }
292+ }
293+
294+ private static String randomSearchTerms () {
295+ return Stream .generate (ESTestCase ::randomIdentifier ).limit (randomIntBetween (1 , 10 )).collect (Collectors .joining (" " ));
296+ }
100297}
0 commit comments