99
1010import org .antlr .v4 .runtime .ParserRuleContext ;
1111import org .antlr .v4 .runtime .Token ;
12+ import org .elasticsearch .common .regex .Regex ;
1213import org .elasticsearch .index .mapper .MappedFieldType ;
1314import org .elasticsearch .index .query .BoolQueryBuilder ;
1415import org .elasticsearch .index .query .MatchAllQueryBuilder ;
1516import org .elasticsearch .index .query .MatchNoneQueryBuilder ;
1617import org .elasticsearch .index .query .MultiMatchQueryBuilder ;
1718import org .elasticsearch .index .query .QueryBuilder ;
1819import org .elasticsearch .index .query .QueryBuilders ;
20+ import org .elasticsearch .index .query .QueryStringQueryBuilder ;
1921import org .elasticsearch .index .query .RangeQueryBuilder ;
2022
23+ import java .util .Set ;
2124import java .util .function .BiConsumer ;
2225import java .util .function .BiFunction ;
2326
2427import static org .elasticsearch .common .logging .LoggerMessageFormat .format ;
25- import static org .elasticsearch .xpack .kql .parser .KqlParserExecutionContext .isDateField ;
26- import static org .elasticsearch .xpack .kql .parser .KqlParserExecutionContext .isKeywordField ;
27- import static org .elasticsearch .xpack .kql .parser .KqlParserExecutionContext .isRuntimeField ;
28+ import static org .elasticsearch .xpack .kql .parser .KqlParsingContext .isDateField ;
29+ import static org .elasticsearch .xpack .kql .parser .KqlParsingContext .isKeywordField ;
30+ import static org .elasticsearch .xpack .kql .parser .KqlParsingContext .isRuntimeField ;
31+ import static org .elasticsearch .xpack .kql .parser .KqlParsingContext .isSearchableField ;
2832import static org .elasticsearch .xpack .kql .parser .ParserUtils .escapeLuceneQueryString ;
33+ import static org .elasticsearch .xpack .kql .parser .ParserUtils .extractText ;
2934import static org .elasticsearch .xpack .kql .parser .ParserUtils .hasWildcard ;
35+ import static org .elasticsearch .xpack .kql .parser .ParserUtils .typedParsing ;
3036
3137class KqlAstBuilder extends KqlBaseBaseVisitor <QueryBuilder > {
32- private final KqlParserExecutionContext kqlParserExecutionContext ;
38+ private final KqlParsingContext kqlParsingContext ;
3339
34- KqlAstBuilder (KqlParserExecutionContext kqlParserExecutionContext ) {
35- this .kqlParserExecutionContext = kqlParserExecutionContext ;
40+ KqlAstBuilder (KqlParsingContext kqlParsingContext ) {
41+ this .kqlParsingContext = kqlParsingContext ;
3642 }
3743
3844 public QueryBuilder toQueryBuilder (ParserRuleContext ctx ) {
3945 if (ctx instanceof KqlBaseParser .TopLevelQueryContext topLeveQueryContext ) {
4046 if (topLeveQueryContext .query () != null ) {
41- return ParserUtils . typedParsing (this , topLeveQueryContext .query (), QueryBuilder .class );
47+ return typedParsing (this , topLeveQueryContext .query (), QueryBuilder .class );
4248 }
4349
4450 return new MatchAllQueryBuilder ();
@@ -59,9 +65,9 @@ public QueryBuilder visitAndBooleanQuery(KqlBaseParser.BooleanQueryContext ctx)
5965 // TODO: KQLContext has an option to wrap the clauses into a filter instead of a must clause. Do we need it?
6066 for (ParserRuleContext subQueryCtx : ctx .query ()) {
6167 if (subQueryCtx instanceof KqlBaseParser .BooleanQueryContext booleanSubQueryCtx && isAndQuery (booleanSubQueryCtx )) {
62- ParserUtils . typedParsing (this , subQueryCtx , BoolQueryBuilder .class ).must ().forEach (builder ::must );
68+ typedParsing (this , subQueryCtx , BoolQueryBuilder .class ).must ().forEach (builder ::must );
6369 } else {
64- builder .must (ParserUtils . typedParsing (this , subQueryCtx , QueryBuilder .class ));
70+ builder .must (typedParsing (this , subQueryCtx , QueryBuilder .class ));
6571 }
6672 }
6773
@@ -73,9 +79,9 @@ public QueryBuilder visitOrBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) {
7379
7480 for (ParserRuleContext subQueryCtx : ctx .query ()) {
7581 if (subQueryCtx instanceof KqlBaseParser .BooleanQueryContext booleanSubQueryCtx && isOrQuery (booleanSubQueryCtx )) {
76- ParserUtils . typedParsing (this , subQueryCtx , BoolQueryBuilder .class ).should ().forEach (builder ::should );
82+ typedParsing (this , subQueryCtx , BoolQueryBuilder .class ).should ().forEach (builder ::should );
7783 } else {
78- builder .should (ParserUtils . typedParsing (this , subQueryCtx , QueryBuilder .class ));
84+ builder .should (typedParsing (this , subQueryCtx , QueryBuilder .class ));
7985 }
8086 }
8187
@@ -84,12 +90,12 @@ public QueryBuilder visitOrBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) {
8490
8591 @ Override
8692 public QueryBuilder visitNotQuery (KqlBaseParser .NotQueryContext ctx ) {
87- return QueryBuilders .boolQuery ().mustNot (ParserUtils . typedParsing (this , ctx .simpleQuery (), QueryBuilder .class ));
93+ return QueryBuilders .boolQuery ().mustNot (typedParsing (this , ctx .simpleQuery (), QueryBuilder .class ));
8894 }
8995
9096 @ Override
9197 public QueryBuilder visitParenthesizedQuery (KqlBaseParser .ParenthesizedQueryContext ctx ) {
92- return ParserUtils . typedParsing (this , ctx .query (), QueryBuilder .class );
98+ return typedParsing (this , ctx .query (), QueryBuilder .class );
9399 }
94100
95101 @ Override
@@ -121,12 +127,16 @@ public QueryBuilder visitExistsQuery(KqlBaseParser.ExistsQueryContext ctx) {
121127 public QueryBuilder visitRangeQuery (KqlBaseParser .RangeQueryContext ctx ) {
122128 BoolQueryBuilder boolQueryBuilder = QueryBuilders .boolQuery ().minimumShouldMatch (1 );
123129
124- String queryText = ParserUtils . extractText (ctx .rangeQueryValue ());
130+ String queryText = extractText (ctx .rangeQueryValue ());
125131 BiFunction <RangeQueryBuilder , String , RangeQueryBuilder > rangeOperation = rangeOperation (ctx .operator );
126132
127133 withFields (ctx .fieldName (), (fieldName , mappedFieldType ) -> {
128134 RangeQueryBuilder rangeQuery = rangeOperation .apply (QueryBuilders .rangeQuery (fieldName ), queryText );
129- // TODO: add timezone for date fields
135+
136+ if (kqlParsingContext .timeZone () != null ) {
137+ rangeQuery .timeZone (kqlParsingContext .timeZone ().getId ());
138+ }
139+
130140 boolQueryBuilder .should (rangeQuery );
131141 });
132142
@@ -135,42 +145,54 @@ public QueryBuilder visitRangeQuery(KqlBaseParser.RangeQueryContext ctx) {
135145
136146 @ Override
137147 public QueryBuilder visitFieldLessQuery (KqlBaseParser .FieldLessQueryContext ctx ) {
138- String queryText = ParserUtils . extractText (ctx .fieldQueryValue ());
148+ String queryText = extractText (ctx .fieldQueryValue ());
139149
140150 if (hasWildcard (ctx .fieldQueryValue ())) {
141- // TODO: set default fields.
142- return QueryBuilders .queryStringQuery (escapeLuceneQueryString (queryText , true ));
151+ QueryStringQueryBuilder queryString = QueryBuilders .queryStringQuery (escapeLuceneQueryString (queryText , true ));
152+ if (kqlParsingContext .defaultField () != null ) {
153+ queryString .defaultField (kqlParsingContext .defaultField ());
154+ }
155+ return queryString ;
143156 }
144157
145158 boolean isPhraseMatch = ctx .fieldQueryValue ().QUOTED_STRING () != null ;
146159
147- return QueryBuilders .multiMatchQuery (queryText )
148- // TODO: add default fields?
160+ MultiMatchQueryBuilder multiMatchQuery = QueryBuilders .multiMatchQuery (queryText )
149161 .type (isPhraseMatch ? MultiMatchQueryBuilder .Type .PHRASE : MultiMatchQueryBuilder .Type .BEST_FIELDS )
150162 .lenient (true );
163+
164+ if (kqlParsingContext .defaultField () != null ) {
165+ kqlParsingContext .resolveDefaultFieldNames ()
166+ .stream ()
167+ .filter (kqlParsingContext ::isSearchableField )
168+ .forEach (multiMatchQuery ::field );
169+ }
170+
171+ return multiMatchQuery ;
151172 }
152173
153174 @ Override
154175 public QueryBuilder visitFieldQuery (KqlBaseParser .FieldQueryContext ctx ) {
155176
156177 BoolQueryBuilder boolQueryBuilder = QueryBuilders .boolQuery ().minimumShouldMatch (1 );
157- String queryText = ParserUtils . extractText (ctx .fieldQueryValue ());
178+ String queryText = extractText (ctx .fieldQueryValue ());
158179 boolean hasWildcard = hasWildcard (ctx .fieldQueryValue ());
159180
160181 withFields (ctx .fieldName (), (fieldName , mappedFieldType ) -> {
161182 QueryBuilder fieldQuery = null ;
162183
163184 if (hasWildcard && isKeywordField (mappedFieldType )) {
164- fieldQuery = QueryBuilders .wildcardQuery (fieldName , queryText )
165- .caseInsensitive (kqlParserExecutionContext .isCaseSensitive () == false );
185+ fieldQuery = QueryBuilders .wildcardQuery (fieldName , queryText ).caseInsensitive (kqlParsingContext .caseInsensitive ());
166186 } else if (hasWildcard ) {
167187 fieldQuery = QueryBuilders .queryStringQuery (escapeLuceneQueryString (queryText , true )).field (fieldName );
168188 } else if (isDateField (mappedFieldType )) {
169- // TODO: add timezone
170- fieldQuery = QueryBuilders .rangeQuery (fieldName ).gte (queryText ).lte (queryText );
189+ RangeQueryBuilder rangeFieldQuery = QueryBuilders .rangeQuery (fieldName ).gte (queryText ).lte (queryText );
190+ if (kqlParsingContext .timeZone () != null ) {
191+ rangeFieldQuery .timeZone (kqlParsingContext .timeZone ().getId ());
192+ }
193+ fieldQuery = rangeFieldQuery ;
171194 } else if (isKeywordField (mappedFieldType )) {
172- fieldQuery = QueryBuilders .termQuery (fieldName , queryText )
173- .caseInsensitive (kqlParserExecutionContext .isCaseSensitive () == false );
195+ fieldQuery = QueryBuilders .termQuery (fieldName , queryText ).caseInsensitive (kqlParsingContext .caseInsensitive ());
174196 } else if (ctx .fieldQueryValue ().QUOTED_STRING () != null ) {
175197 fieldQuery = QueryBuilders .matchPhraseQuery (fieldName , queryText );
176198 } else {
@@ -194,7 +216,26 @@ private static boolean isOrQuery(KqlBaseParser.BooleanQueryContext ctx) {
194216 }
195217
196218 private void withFields (KqlBaseParser .FieldNameContext ctx , BiConsumer <String , MappedFieldType > fieldConsummer ) {
197- kqlParserExecutionContext .resolveFields (ctx ).forEach (fieldDef -> fieldConsummer .accept (fieldDef .v1 (), fieldDef .v2 ()));
219+ assert ctx != null : "Field ctx cannot be null" ;
220+ String fieldNamePattern = extractText (ctx );
221+ Set <String > fieldNames = kqlParsingContext .resolveFieldNames (fieldNamePattern );
222+
223+ if (ctx .value .getType () == KqlBaseParser .QUOTED_STRING && Regex .isSimpleMatchPattern (fieldNamePattern )) {
224+ // When using quoted string, wildcards are not expanded.
225+ // No field can match and we can return early.
226+ return ;
227+ }
228+
229+ if (ctx .value .getType () == KqlBaseParser .QUOTED_STRING ) {
230+ assert fieldNames .size () < 2 : "expecting only one matching field" ;
231+ }
232+
233+ fieldNames .forEach (fieldName -> {
234+ MappedFieldType fieldType = kqlParsingContext .fieldType (fieldName );
235+ if (isSearchableField (fieldName , fieldType )) {
236+ fieldConsummer .accept (fieldName , fieldType );
237+ }
238+ });
198239 }
199240
200241 private QueryBuilder rewriteDisjunctionQuery (BoolQueryBuilder boolQueryBuilder ) {
0 commit comments