1
0
Эх сурвалжийг харах

[8.x] KQL query nested field support (#116467) (#116910)

* KQL query nested field support (#116467)

* Fix compile error in 8.x branch
Aurélien FOUCRET 11 сар өмнө
parent
commit
61c9add011
17 өөрчлөгдсөн 1332 нэмэгдсэн , 208 устгасан
  1. 7 0
      server/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java
  2. 18 1
      x-pack/plugin/kql/src/main/antlr/KqlBase.g4
  3. 1 0
      x-pack/plugin/kql/src/main/java/module-info.java
  4. 69 16
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlAstBuilder.java
  5. 3 0
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase.interp
  6. 48 0
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseBaseListener.java
  7. 28 0
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseBaseVisitor.java
  8. 44 0
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseListener.java
  9. 493 176
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseParser.java
  10. 26 0
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseVisitor.java
  11. 50 2
      x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParsingContext.java
  12. 0 2
      x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/AbstractKqlParserTestCase.java
  13. 297 0
      x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/KqlNestedFieldQueryTests.java
  14. 16 4
      x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/KqlParserExistsQueryTests.java
  15. 0 7
      x-pack/plugin/kql/src/test/resources/supported-queries
  16. 14 0
      x-pack/plugin/kql/src/test/resources/unsupported-queries
  17. 218 0
      x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/50_kql_nested_fields_query.yml

+ 7 - 0
server/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java

@@ -112,6 +112,13 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         return query;
     }
 
+    /**
+     * Returns path to the searched nested object.
+     */
+    public String path() {
+        return path;
+    }
+
     /**
      * Returns inner hit definition in the scope of this query and reusing the defined type and query.
      */

+ 18 - 1
x-pack/plugin/kql/src/main/antlr/KqlBase.g4

@@ -46,9 +46,26 @@ notQuery:
    ;
 
 nestedQuery
-    : fieldName COLON LEFT_CURLY_BRACKET query RIGHT_CURLY_BRACKET
+    : fieldName COLON LEFT_CURLY_BRACKET nestedSubQuery RIGHT_CURLY_BRACKET
     ;
 
+nestedSubQuery
+    : <assoc=right> nestedSubQuery operator=(AND|OR) nestedSubQuery #booleanNestedQuery
+    | nestedSimpleSubQuery                                          #defaultNestedQuery
+    ;
+
+nestedSimpleSubQuery
+    : notQuery
+    | nestedQuery
+    | matchAllQuery
+    | nestedParenthesizedQuery
+    | existsQuery
+    | rangeQuery
+    | fieldQuery;
+
+nestedParenthesizedQuery
+    : LEFT_PARENTHESIS nestedSubQuery RIGHT_PARENTHESIS;
+
 matchAllQuery
     : (WILDCARD COLON)? WILDCARD
     ;

+ 1 - 0
x-pack/plugin/kql/src/main/java/module-info.java

@@ -13,6 +13,7 @@ module org.elasticsearch.kql {
     requires org.apache.lucene.queryparser;
     requires org.elasticsearch.logging;
     requires org.apache.lucene.core;
+    requires org.apache.lucene.join;
 
     exports org.elasticsearch.xpack.kql;
     exports org.elasticsearch.xpack.kql.parser;

+ 69 - 16
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlAstBuilder.java

@@ -9,6 +9,7 @@ package org.elasticsearch.xpack.kql.parser;
 
 import org.antlr.v4.runtime.ParserRuleContext;
 import org.antlr.v4.runtime.Token;
+import org.apache.lucene.search.join.ScoreMode;
 import org.elasticsearch.common.regex.Regex;
 import org.elasticsearch.index.mapper.MappedFieldType;
 import org.elasticsearch.index.query.BoolQueryBuilder;
@@ -20,6 +21,7 @@ import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.QueryStringQueryBuilder;
 import org.elasticsearch.index.query.RangeQueryBuilder;
 
+import java.util.List;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
@@ -56,15 +58,15 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
     @Override
     public QueryBuilder visitBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) {
         assert ctx.operator != null;
-        return isAndQuery(ctx) ? visitAndBooleanQuery(ctx) : visitOrBooleanQuery(ctx);
+        return isAndQuery(ctx) ? visitAndBooleanQuery(ctx.query()) : visitOrBooleanQuery(ctx.query());
     }
 
-    public QueryBuilder visitAndBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) {
+    public QueryBuilder visitAndBooleanQuery(List<? extends ParserRuleContext> clauses) {
         BoolQueryBuilder builder = QueryBuilders.boolQuery();
 
         // TODO: KQLContext has an option to wrap the clauses into a filter instead of a must clause. Do we need it?
-        for (ParserRuleContext subQueryCtx : ctx.query()) {
-            if (subQueryCtx instanceof KqlBaseParser.BooleanQueryContext booleanSubQueryCtx && isAndQuery(booleanSubQueryCtx)) {
+        for (ParserRuleContext subQueryCtx : clauses) {
+            if (isAndQuery(subQueryCtx)) {
                 typedParsing(this, subQueryCtx, BoolQueryBuilder.class).must().forEach(builder::must);
             } else {
                 builder.must(typedParsing(this, subQueryCtx, QueryBuilder.class));
@@ -74,11 +76,11 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
         return rewriteConjunctionQuery(builder);
     }
 
-    public QueryBuilder visitOrBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) {
+    public QueryBuilder visitOrBooleanQuery(List<? extends ParserRuleContext> clauses) {
         BoolQueryBuilder builder = QueryBuilders.boolQuery().minimumShouldMatch(1);
 
-        for (ParserRuleContext subQueryCtx : ctx.query()) {
-            if (subQueryCtx instanceof KqlBaseParser.BooleanQueryContext booleanSubQueryCtx && isOrQuery(booleanSubQueryCtx)) {
+        for (ParserRuleContext subQueryCtx : clauses) {
+            if (isOrQuery(subQueryCtx)) {
                 typedParsing(this, subQueryCtx, BoolQueryBuilder.class).should().forEach(builder::should);
             } else {
                 builder.should(typedParsing(this, subQueryCtx, QueryBuilder.class));
@@ -100,8 +102,40 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
 
     @Override
     public QueryBuilder visitNestedQuery(KqlBaseParser.NestedQueryContext ctx) {
-        // TODO: implementation
-        return new MatchNoneQueryBuilder();
+        String nestedFieldName = extractText(ctx.fieldName());
+
+        if (kqlParsingContext.isNestedField(nestedFieldName) == false) {
+            throw new KqlParsingException(
+                "[{}] is not a valid nested field name.",
+                ctx.start.getLine(),
+                ctx.start.getCharPositionInLine(),
+                nestedFieldName
+            );
+        }
+        QueryBuilder subQuery = kqlParsingContext.withNestedPath(
+            nestedFieldName,
+            () -> typedParsing(this, ctx.nestedSubQuery(), QueryBuilder.class)
+        );
+
+        if (subQuery instanceof MatchNoneQueryBuilder) {
+            return subQuery;
+        }
+
+        return wrapWithNestedQuery(
+            nestedFieldName,
+            QueryBuilders.nestedQuery(kqlParsingContext.fullFieldName(nestedFieldName), subQuery, ScoreMode.None)
+        );
+    }
+
+    @Override
+    public QueryBuilder visitBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx) {
+        assert ctx.operator != null;
+        return isAndQuery(ctx) ? visitAndBooleanQuery(ctx.nestedSubQuery()) : visitOrBooleanQuery(ctx.nestedSubQuery());
+    }
+
+    @Override
+    public QueryBuilder visitNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx) {
+        return typedParsing(this, ctx.nestedSubQuery(), QueryBuilder.class);
     }
 
     @Override
@@ -116,7 +150,7 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
         BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().minimumShouldMatch(1);
         withFields(ctx.fieldName(), (fieldName, mappedFieldType) -> {
             if (isRuntimeField(mappedFieldType) == false) {
-                boolQueryBuilder.should(QueryBuilders.existsQuery(fieldName));
+                boolQueryBuilder.should(wrapWithNestedQuery(fieldName, QueryBuilders.existsQuery(fieldName)));
             }
         });
 
@@ -137,7 +171,7 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
                 rangeQuery.timeZone(kqlParsingContext.timeZone().getId());
             }
 
-            boolQueryBuilder.should(rangeQuery);
+            boolQueryBuilder.should(wrapWithNestedQuery(fieldName, rangeQuery));
         });
 
         return rewriteDisjunctionQuery(boolQueryBuilder);
@@ -200,24 +234,33 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
             }
 
             if (fieldQuery != null) {
-                boolQueryBuilder.should(fieldQuery);
+                boolQueryBuilder.should(wrapWithNestedQuery(fieldName, fieldQuery));
             }
         });
 
         return rewriteDisjunctionQuery(boolQueryBuilder);
     }
 
-    private static boolean isAndQuery(KqlBaseParser.BooleanQueryContext ctx) {
-        return ctx.operator.getType() == KqlBaseParser.AND;
+    private static boolean isAndQuery(ParserRuleContext ctx) {
+        if (ctx instanceof KqlBaseParser.BooleanQueryContext booleanQueryCtx && booleanQueryCtx.operator.getType() == KqlBaseParser.AND) {
+            return true;
+        }
+        return ctx instanceof KqlBaseParser.BooleanNestedQueryContext booleanNestedCtx
+            && booleanNestedCtx.operator.getType() == KqlBaseParser.AND;
     }
 
-    private static boolean isOrQuery(KqlBaseParser.BooleanQueryContext ctx) {
-        return ctx.operator.getType() == KqlBaseParser.OR;
+    private static boolean isOrQuery(ParserRuleContext ctx) {
+        if (ctx instanceof KqlBaseParser.BooleanQueryContext booleanQueryCtx && booleanQueryCtx.operator.getType() == KqlBaseParser.OR) {
+            return true;
+        }
+        return ctx instanceof KqlBaseParser.BooleanNestedQueryContext booleanNestedCtx
+            && booleanNestedCtx.operator.getType() == KqlBaseParser.OR;
     }
 
     private void withFields(KqlBaseParser.FieldNameContext ctx, BiConsumer<String, MappedFieldType> fieldConsummer) {
         assert ctx != null : "Field ctx cannot be null";
         String fieldNamePattern = extractText(ctx);
+
         Set<String> fieldNames = kqlParsingContext.resolveFieldNames(fieldNamePattern);
 
         if (ctx.value.getType() == KqlBaseParser.QUOTED_STRING && Regex.isSimpleMatchPattern(fieldNamePattern)) {
@@ -267,4 +310,14 @@ class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
             default -> throw new IllegalArgumentException(format(null, "Invalid range operator {}\"", operator.getText()));
         };
     }
+
+    private QueryBuilder wrapWithNestedQuery(String fieldName, QueryBuilder query) {
+        String nestedPath = kqlParsingContext.nestedPath(fieldName);
+
+        if (nestedPath == null || nestedPath.equals(kqlParsingContext.currentNestedPath())) {
+            return query;
+        }
+
+        return wrapWithNestedQuery(nestedPath, QueryBuilders.nestedQuery(nestedPath, query, ScoreMode.None));
+    }
 }

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 3 - 0
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase.interp


+ 48 - 0
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseBaseListener.java

@@ -92,6 +92,54 @@ class KqlBaseBaseListener implements KqlBaseListener {
      * <p>The default implementation does nothing.</p>
      */
     @Override public void exitNestedQuery(KqlBaseParser.NestedQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void enterBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void exitBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void enterDefaultNestedQuery(KqlBaseParser.DefaultNestedQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void exitDefaultNestedQuery(KqlBaseParser.DefaultNestedQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void enterNestedSimpleSubQuery(KqlBaseParser.NestedSimpleSubQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void exitNestedSimpleSubQuery(KqlBaseParser.NestedSimpleSubQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void enterNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx) { }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation does nothing.</p>
+     */
+    @Override public void exitNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx) { }
     /**
      * {@inheritDoc}
      *

+ 28 - 0
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseBaseVisitor.java

@@ -62,6 +62,34 @@ class KqlBaseBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements KqlBa
      * {@link #visitChildren} on {@code ctx}.</p>
      */
     @Override public T visitNestedQuery(KqlBaseParser.NestedQueryContext ctx) { return visitChildren(ctx); }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns the result of calling
+     * {@link #visitChildren} on {@code ctx}.</p>
+     */
+    @Override public T visitBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx) { return visitChildren(ctx); }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns the result of calling
+     * {@link #visitChildren} on {@code ctx}.</p>
+     */
+    @Override public T visitDefaultNestedQuery(KqlBaseParser.DefaultNestedQueryContext ctx) { return visitChildren(ctx); }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns the result of calling
+     * {@link #visitChildren} on {@code ctx}.</p>
+     */
+    @Override public T visitNestedSimpleSubQuery(KqlBaseParser.NestedSimpleSubQueryContext ctx) { return visitChildren(ctx); }
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns the result of calling
+     * {@link #visitChildren} on {@code ctx}.</p>
+     */
+    @Override public T visitNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx) { return visitChildren(ctx); }
     /**
      * {@inheritDoc}
      *

+ 44 - 0
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseListener.java

@@ -79,6 +79,50 @@ interface KqlBaseListener extends ParseTreeListener {
      * @param ctx the parse tree
      */
     void exitNestedQuery(KqlBaseParser.NestedQueryContext ctx);
+    /**
+     * Enter a parse tree produced by the {@code booleanNestedQuery}
+     * labeled alternative in {@link KqlBaseParser#nestedSubQuery}.
+     * @param ctx the parse tree
+     */
+    void enterBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx);
+    /**
+     * Exit a parse tree produced by the {@code booleanNestedQuery}
+     * labeled alternative in {@link KqlBaseParser#nestedSubQuery}.
+     * @param ctx the parse tree
+     */
+    void exitBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx);
+    /**
+     * Enter a parse tree produced by the {@code defaultNestedQuery}
+     * labeled alternative in {@link KqlBaseParser#nestedSubQuery}.
+     * @param ctx the parse tree
+     */
+    void enterDefaultNestedQuery(KqlBaseParser.DefaultNestedQueryContext ctx);
+    /**
+     * Exit a parse tree produced by the {@code defaultNestedQuery}
+     * labeled alternative in {@link KqlBaseParser#nestedSubQuery}.
+     * @param ctx the parse tree
+     */
+    void exitDefaultNestedQuery(KqlBaseParser.DefaultNestedQueryContext ctx);
+    /**
+     * Enter a parse tree produced by {@link KqlBaseParser#nestedSimpleSubQuery}.
+     * @param ctx the parse tree
+     */
+    void enterNestedSimpleSubQuery(KqlBaseParser.NestedSimpleSubQueryContext ctx);
+    /**
+     * Exit a parse tree produced by {@link KqlBaseParser#nestedSimpleSubQuery}.
+     * @param ctx the parse tree
+     */
+    void exitNestedSimpleSubQuery(KqlBaseParser.NestedSimpleSubQueryContext ctx);
+    /**
+     * Enter a parse tree produced by {@link KqlBaseParser#nestedParenthesizedQuery}.
+     * @param ctx the parse tree
+     */
+    void enterNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx);
+    /**
+     * Exit a parse tree produced by {@link KqlBaseParser#nestedParenthesizedQuery}.
+     * @param ctx the parse tree
+     */
+    void exitNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx);
     /**
      * Enter a parse tree produced by {@link KqlBaseParser#matchAllQuery}.
      * @param ctx the parse tree

+ 493 - 176
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseParser.java

@@ -30,12 +30,15 @@ class KqlBaseParser extends Parser {
         RIGHT_CURLY_BRACKET=13, UNQUOTED_LITERAL=14, QUOTED_STRING=15, WILDCARD=16;
     public static final int
         RULE_topLevelQuery = 0, RULE_query = 1, RULE_simpleQuery = 2, RULE_notQuery = 3, 
-        RULE_nestedQuery = 4, RULE_matchAllQuery = 5, RULE_parenthesizedQuery = 6, 
-        RULE_rangeQuery = 7, RULE_rangeQueryValue = 8, RULE_existsQuery = 9, RULE_fieldQuery = 10, 
-        RULE_fieldLessQuery = 11, RULE_fieldQueryValue = 12, RULE_fieldName = 13;
+        RULE_nestedQuery = 4, RULE_nestedSubQuery = 5, RULE_nestedSimpleSubQuery = 6, 
+        RULE_nestedParenthesizedQuery = 7, RULE_matchAllQuery = 8, RULE_parenthesizedQuery = 9, 
+        RULE_rangeQuery = 10, RULE_rangeQueryValue = 11, RULE_existsQuery = 12, 
+        RULE_fieldQuery = 13, RULE_fieldLessQuery = 14, RULE_fieldQueryValue = 15, 
+        RULE_fieldName = 16;
     private static String[] makeRuleNames() {
         return new String[] {
-            "topLevelQuery", "query", "simpleQuery", "notQuery", "nestedQuery", "matchAllQuery", 
+            "topLevelQuery", "query", "simpleQuery", "notQuery", "nestedQuery", "nestedSubQuery", 
+            "nestedSimpleSubQuery", "nestedParenthesizedQuery", "matchAllQuery", 
             "parenthesizedQuery", "rangeQuery", "rangeQueryValue", "existsQuery", 
             "fieldQuery", "fieldLessQuery", "fieldQueryValue", "fieldName"
         };
@@ -139,17 +142,17 @@ class KqlBaseParser extends Parser {
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(29);
+            setState(35);
             _errHandler.sync(this);
             _la = _input.LA(1);
             if ((((_la) & ~0x3f) == 0 && ((1L << _la) & 115740L) != 0)) {
                 {
-                setState(28);
+                setState(34);
                 query(0);
                 }
             }
 
-            setState(31);
+            setState(37);
             match(EOF);
             }
         }
@@ -244,11 +247,11 @@ class KqlBaseParser extends Parser {
             _ctx = _localctx;
             _prevctx = _localctx;
 
-            setState(34);
+            setState(40);
             simpleQuery();
             }
             _ctx.stop = _input.LT(-1);
-            setState(41);
+            setState(47);
             _errHandler.sync(this);
             _alt = getInterpreter().adaptivePredict(_input,1,_ctx);
             while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
@@ -259,9 +262,9 @@ class KqlBaseParser extends Parser {
                     {
                     _localctx = new BooleanQueryContext(new QueryContext(_parentctx, _parentState));
                     pushNewRecursionContext(_localctx, _startState, RULE_query);
-                    setState(36);
+                    setState(42);
                     if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)");
-                    setState(37);
+                    setState(43);
                     ((BooleanQueryContext)_localctx).operator = _input.LT(1);
                     _la = _input.LA(1);
                     if ( !(_la==AND || _la==OR) ) {
@@ -272,12 +275,12 @@ class KqlBaseParser extends Parser {
                         _errHandler.reportMatch(this);
                         consume();
                     }
-                    setState(38);
+                    setState(44);
                     query(2);
                     }
                     } 
                 }
-                setState(43);
+                setState(49);
                 _errHandler.sync(this);
                 _alt = getInterpreter().adaptivePredict(_input,1,_ctx);
             }
@@ -343,62 +346,62 @@ class KqlBaseParser extends Parser {
         SimpleQueryContext _localctx = new SimpleQueryContext(_ctx, getState());
         enterRule(_localctx, 4, RULE_simpleQuery);
         try {
-            setState(52);
+            setState(58);
             _errHandler.sync(this);
             switch ( getInterpreter().adaptivePredict(_input,2,_ctx) ) {
             case 1:
                 enterOuterAlt(_localctx, 1);
                 {
-                setState(44);
+                setState(50);
                 notQuery();
                 }
                 break;
             case 2:
                 enterOuterAlt(_localctx, 2);
                 {
-                setState(45);
+                setState(51);
                 nestedQuery();
                 }
                 break;
             case 3:
                 enterOuterAlt(_localctx, 3);
                 {
-                setState(46);
+                setState(52);
                 parenthesizedQuery();
                 }
                 break;
             case 4:
                 enterOuterAlt(_localctx, 4);
                 {
-                setState(47);
+                setState(53);
                 matchAllQuery();
                 }
                 break;
             case 5:
                 enterOuterAlt(_localctx, 5);
                 {
-                setState(48);
+                setState(54);
                 existsQuery();
                 }
                 break;
             case 6:
                 enterOuterAlt(_localctx, 6);
                 {
-                setState(49);
+                setState(55);
                 rangeQuery();
                 }
                 break;
             case 7:
                 enterOuterAlt(_localctx, 7);
                 {
-                setState(50);
+                setState(56);
                 fieldQuery();
                 }
                 break;
             case 8:
                 enterOuterAlt(_localctx, 8);
                 {
-                setState(51);
+                setState(57);
                 fieldLessQuery();
                 }
                 break;
@@ -447,9 +450,9 @@ class KqlBaseParser extends Parser {
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(54);
+            setState(60);
             match(NOT);
-            setState(55);
+            setState(61);
             ((NotQueryContext)_localctx).subQuery = simpleQuery();
             }
         }
@@ -471,8 +474,8 @@ class KqlBaseParser extends Parser {
         }
         public TerminalNode COLON() { return getToken(KqlBaseParser.COLON, 0); }
         public TerminalNode LEFT_CURLY_BRACKET() { return getToken(KqlBaseParser.LEFT_CURLY_BRACKET, 0); }
-        public QueryContext query() {
-            return getRuleContext(QueryContext.class,0);
+        public NestedSubQueryContext nestedSubQuery() {
+            return getRuleContext(NestedSubQueryContext.class,0);
         }
         public TerminalNode RIGHT_CURLY_BRACKET() { return getToken(KqlBaseParser.RIGHT_CURLY_BRACKET, 0); }
         public NestedQueryContext(ParserRuleContext parent, int invokingState) {
@@ -500,15 +503,15 @@ class KqlBaseParser extends Parser {
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(57);
+            setState(63);
             fieldName();
-            setState(58);
+            setState(64);
             match(COLON);
-            setState(59);
+            setState(65);
             match(LEFT_CURLY_BRACKET);
-            setState(60);
-            query(0);
-            setState(61);
+            setState(66);
+            nestedSubQuery(0);
+            setState(67);
             match(RIGHT_CURLY_BRACKET);
             }
         }
@@ -523,6 +526,288 @@ class KqlBaseParser extends Parser {
         return _localctx;
     }
 
+    @SuppressWarnings("CheckReturnValue")
+    public static class NestedSubQueryContext extends ParserRuleContext {
+        public NestedSubQueryContext(ParserRuleContext parent, int invokingState) {
+            super(parent, invokingState);
+        }
+        @Override public int getRuleIndex() { return RULE_nestedSubQuery; }
+     
+        public NestedSubQueryContext() { }
+        public void copyFrom(NestedSubQueryContext ctx) {
+            super.copyFrom(ctx);
+        }
+    }
+    @SuppressWarnings("CheckReturnValue")
+    public static class BooleanNestedQueryContext extends NestedSubQueryContext {
+        public Token operator;
+        public List<NestedSubQueryContext> nestedSubQuery() {
+            return getRuleContexts(NestedSubQueryContext.class);
+        }
+        public NestedSubQueryContext nestedSubQuery(int i) {
+            return getRuleContext(NestedSubQueryContext.class,i);
+        }
+        public TerminalNode AND() { return getToken(KqlBaseParser.AND, 0); }
+        public TerminalNode OR() { return getToken(KqlBaseParser.OR, 0); }
+        public BooleanNestedQueryContext(NestedSubQueryContext ctx) { copyFrom(ctx); }
+        @Override
+        public void enterRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).enterBooleanNestedQuery(this);
+        }
+        @Override
+        public void exitRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).exitBooleanNestedQuery(this);
+        }
+        @Override
+        public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
+            if ( visitor instanceof KqlBaseVisitor ) return ((KqlBaseVisitor<? extends T>)visitor).visitBooleanNestedQuery(this);
+            else return visitor.visitChildren(this);
+        }
+    }
+    @SuppressWarnings("CheckReturnValue")
+    public static class DefaultNestedQueryContext extends NestedSubQueryContext {
+        public NestedSimpleSubQueryContext nestedSimpleSubQuery() {
+            return getRuleContext(NestedSimpleSubQueryContext.class,0);
+        }
+        public DefaultNestedQueryContext(NestedSubQueryContext ctx) { copyFrom(ctx); }
+        @Override
+        public void enterRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).enterDefaultNestedQuery(this);
+        }
+        @Override
+        public void exitRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).exitDefaultNestedQuery(this);
+        }
+        @Override
+        public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
+            if ( visitor instanceof KqlBaseVisitor ) return ((KqlBaseVisitor<? extends T>)visitor).visitDefaultNestedQuery(this);
+            else return visitor.visitChildren(this);
+        }
+    }
+
+    public final NestedSubQueryContext nestedSubQuery() throws RecognitionException {
+        return nestedSubQuery(0);
+    }
+
+    private NestedSubQueryContext nestedSubQuery(int _p) throws RecognitionException {
+        ParserRuleContext _parentctx = _ctx;
+        int _parentState = getState();
+        NestedSubQueryContext _localctx = new NestedSubQueryContext(_ctx, _parentState);
+        NestedSubQueryContext _prevctx = _localctx;
+        int _startState = 10;
+        enterRecursionRule(_localctx, 10, RULE_nestedSubQuery, _p);
+        int _la;
+        try {
+            int _alt;
+            enterOuterAlt(_localctx, 1);
+            {
+            {
+            _localctx = new DefaultNestedQueryContext(_localctx);
+            _ctx = _localctx;
+            _prevctx = _localctx;
+
+            setState(70);
+            nestedSimpleSubQuery();
+            }
+            _ctx.stop = _input.LT(-1);
+            setState(77);
+            _errHandler.sync(this);
+            _alt = getInterpreter().adaptivePredict(_input,3,_ctx);
+            while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+                if ( _alt==1 ) {
+                    if ( _parseListeners!=null ) triggerExitRuleEvent();
+                    _prevctx = _localctx;
+                    {
+                    {
+                    _localctx = new BooleanNestedQueryContext(new NestedSubQueryContext(_parentctx, _parentState));
+                    pushNewRecursionContext(_localctx, _startState, RULE_nestedSubQuery);
+                    setState(72);
+                    if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)");
+                    setState(73);
+                    ((BooleanNestedQueryContext)_localctx).operator = _input.LT(1);
+                    _la = _input.LA(1);
+                    if ( !(_la==AND || _la==OR) ) {
+                        ((BooleanNestedQueryContext)_localctx).operator = (Token)_errHandler.recoverInline(this);
+                    }
+                    else {
+                        if ( _input.LA(1)==Token.EOF ) matchedEOF = true;
+                        _errHandler.reportMatch(this);
+                        consume();
+                    }
+                    setState(74);
+                    nestedSubQuery(2);
+                    }
+                    } 
+                }
+                setState(79);
+                _errHandler.sync(this);
+                _alt = getInterpreter().adaptivePredict(_input,3,_ctx);
+            }
+            }
+        }
+        catch (RecognitionException re) {
+            _localctx.exception = re;
+            _errHandler.reportError(this, re);
+            _errHandler.recover(this, re);
+        }
+        finally {
+            unrollRecursionContexts(_parentctx);
+        }
+        return _localctx;
+    }
+
+    @SuppressWarnings("CheckReturnValue")
+    public static class NestedSimpleSubQueryContext extends ParserRuleContext {
+        public NotQueryContext notQuery() {
+            return getRuleContext(NotQueryContext.class,0);
+        }
+        public NestedQueryContext nestedQuery() {
+            return getRuleContext(NestedQueryContext.class,0);
+        }
+        public NestedParenthesizedQueryContext nestedParenthesizedQuery() {
+            return getRuleContext(NestedParenthesizedQueryContext.class,0);
+        }
+        public ExistsQueryContext existsQuery() {
+            return getRuleContext(ExistsQueryContext.class,0);
+        }
+        public RangeQueryContext rangeQuery() {
+            return getRuleContext(RangeQueryContext.class,0);
+        }
+        public FieldQueryContext fieldQuery() {
+            return getRuleContext(FieldQueryContext.class,0);
+        }
+        public NestedSimpleSubQueryContext(ParserRuleContext parent, int invokingState) {
+            super(parent, invokingState);
+        }
+        @Override public int getRuleIndex() { return RULE_nestedSimpleSubQuery; }
+        @Override
+        public void enterRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).enterNestedSimpleSubQuery(this);
+        }
+        @Override
+        public void exitRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).exitNestedSimpleSubQuery(this);
+        }
+        @Override
+        public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
+            if ( visitor instanceof KqlBaseVisitor ) return ((KqlBaseVisitor<? extends T>)visitor).visitNestedSimpleSubQuery(this);
+            else return visitor.visitChildren(this);
+        }
+    }
+
+    public final NestedSimpleSubQueryContext nestedSimpleSubQuery() throws RecognitionException {
+        NestedSimpleSubQueryContext _localctx = new NestedSimpleSubQueryContext(_ctx, getState());
+        enterRule(_localctx, 12, RULE_nestedSimpleSubQuery);
+        try {
+            setState(86);
+            _errHandler.sync(this);
+            switch ( getInterpreter().adaptivePredict(_input,4,_ctx) ) {
+            case 1:
+                enterOuterAlt(_localctx, 1);
+                {
+                setState(80);
+                notQuery();
+                }
+                break;
+            case 2:
+                enterOuterAlt(_localctx, 2);
+                {
+                setState(81);
+                nestedQuery();
+                }
+                break;
+            case 3:
+                enterOuterAlt(_localctx, 3);
+                {
+                setState(82);
+                nestedParenthesizedQuery();
+                }
+                break;
+            case 4:
+                enterOuterAlt(_localctx, 4);
+                {
+                setState(83);
+                existsQuery();
+                }
+                break;
+            case 5:
+                enterOuterAlt(_localctx, 5);
+                {
+                setState(84);
+                rangeQuery();
+                }
+                break;
+            case 6:
+                enterOuterAlt(_localctx, 6);
+                {
+                setState(85);
+                fieldQuery();
+                }
+                break;
+            }
+        }
+        catch (RecognitionException re) {
+            _localctx.exception = re;
+            _errHandler.reportError(this, re);
+            _errHandler.recover(this, re);
+        }
+        finally {
+            exitRule();
+        }
+        return _localctx;
+    }
+
+    @SuppressWarnings("CheckReturnValue")
+    public static class NestedParenthesizedQueryContext extends ParserRuleContext {
+        public TerminalNode LEFT_PARENTHESIS() { return getToken(KqlBaseParser.LEFT_PARENTHESIS, 0); }
+        public NestedSubQueryContext nestedSubQuery() {
+            return getRuleContext(NestedSubQueryContext.class,0);
+        }
+        public TerminalNode RIGHT_PARENTHESIS() { return getToken(KqlBaseParser.RIGHT_PARENTHESIS, 0); }
+        public NestedParenthesizedQueryContext(ParserRuleContext parent, int invokingState) {
+            super(parent, invokingState);
+        }
+        @Override public int getRuleIndex() { return RULE_nestedParenthesizedQuery; }
+        @Override
+        public void enterRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).enterNestedParenthesizedQuery(this);
+        }
+        @Override
+        public void exitRule(ParseTreeListener listener) {
+            if ( listener instanceof KqlBaseListener ) ((KqlBaseListener)listener).exitNestedParenthesizedQuery(this);
+        }
+        @Override
+        public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
+            if ( visitor instanceof KqlBaseVisitor ) return ((KqlBaseVisitor<? extends T>)visitor).visitNestedParenthesizedQuery(this);
+            else return visitor.visitChildren(this);
+        }
+    }
+
+    public final NestedParenthesizedQueryContext nestedParenthesizedQuery() throws RecognitionException {
+        NestedParenthesizedQueryContext _localctx = new NestedParenthesizedQueryContext(_ctx, getState());
+        enterRule(_localctx, 14, RULE_nestedParenthesizedQuery);
+        try {
+            enterOuterAlt(_localctx, 1);
+            {
+            setState(88);
+            match(LEFT_PARENTHESIS);
+            setState(89);
+            nestedSubQuery(0);
+            setState(90);
+            match(RIGHT_PARENTHESIS);
+            }
+        }
+        catch (RecognitionException re) {
+            _localctx.exception = re;
+            _errHandler.reportError(this, re);
+            _errHandler.recover(this, re);
+        }
+        finally {
+            exitRule();
+        }
+        return _localctx;
+    }
+
     @SuppressWarnings("CheckReturnValue")
     public static class MatchAllQueryContext extends ParserRuleContext {
         public List<TerminalNode> WILDCARD() { return getTokens(KqlBaseParser.WILDCARD); }
@@ -551,23 +836,23 @@ class KqlBaseParser extends Parser {
 
     public final MatchAllQueryContext matchAllQuery() throws RecognitionException {
         MatchAllQueryContext _localctx = new MatchAllQueryContext(_ctx, getState());
-        enterRule(_localctx, 10, RULE_matchAllQuery);
+        enterRule(_localctx, 16, RULE_matchAllQuery);
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(65);
+            setState(94);
             _errHandler.sync(this);
-            switch ( getInterpreter().adaptivePredict(_input,3,_ctx) ) {
+            switch ( getInterpreter().adaptivePredict(_input,5,_ctx) ) {
             case 1:
                 {
-                setState(63);
+                setState(92);
                 match(WILDCARD);
-                setState(64);
+                setState(93);
                 match(COLON);
                 }
                 break;
             }
-            setState(67);
+            setState(96);
             match(WILDCARD);
             }
         }
@@ -610,15 +895,15 @@ class KqlBaseParser extends Parser {
 
     public final ParenthesizedQueryContext parenthesizedQuery() throws RecognitionException {
         ParenthesizedQueryContext _localctx = new ParenthesizedQueryContext(_ctx, getState());
-        enterRule(_localctx, 12, RULE_parenthesizedQuery);
+        enterRule(_localctx, 18, RULE_parenthesizedQuery);
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(69);
+            setState(98);
             match(LEFT_PARENTHESIS);
-            setState(70);
+            setState(99);
             query(0);
-            setState(71);
+            setState(100);
             match(RIGHT_PARENTHESIS);
             }
         }
@@ -667,14 +952,14 @@ class KqlBaseParser extends Parser {
 
     public final RangeQueryContext rangeQuery() throws RecognitionException {
         RangeQueryContext _localctx = new RangeQueryContext(_ctx, getState());
-        enterRule(_localctx, 14, RULE_rangeQuery);
+        enterRule(_localctx, 20, RULE_rangeQuery);
         int _la;
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(73);
+            setState(102);
             fieldName();
-            setState(74);
+            setState(103);
             ((RangeQueryContext)_localctx).operator = _input.LT(1);
             _la = _input.LA(1);
             if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 960L) != 0)) ) {
@@ -685,7 +970,7 @@ class KqlBaseParser extends Parser {
                 _errHandler.reportMatch(this);
                 consume();
             }
-            setState(75);
+            setState(104);
             rangeQueryValue();
             }
         }
@@ -732,18 +1017,18 @@ class KqlBaseParser extends Parser {
 
     public final RangeQueryValueContext rangeQueryValue() throws RecognitionException {
         RangeQueryValueContext _localctx = new RangeQueryValueContext(_ctx, getState());
-        enterRule(_localctx, 16, RULE_rangeQueryValue);
+        enterRule(_localctx, 22, RULE_rangeQueryValue);
         int _la;
         try {
             int _alt;
-            setState(83);
+            setState(112);
             _errHandler.sync(this);
             switch (_input.LA(1)) {
             case UNQUOTED_LITERAL:
             case WILDCARD:
                 enterOuterAlt(_localctx, 1);
                 {
-                setState(78); 
+                setState(107); 
                 _errHandler.sync(this);
                 _alt = 1;
                 do {
@@ -751,7 +1036,7 @@ class KqlBaseParser extends Parser {
                     case 1:
                         {
                         {
-                        setState(77);
+                        setState(106);
                         _la = _input.LA(1);
                         if ( !(_la==UNQUOTED_LITERAL || _la==WILDCARD) ) {
                         _errHandler.recoverInline(this);
@@ -767,16 +1052,16 @@ class KqlBaseParser extends Parser {
                     default:
                         throw new NoViableAltException(this);
                     }
-                    setState(80); 
+                    setState(109); 
                     _errHandler.sync(this);
-                    _alt = getInterpreter().adaptivePredict(_input,4,_ctx);
+                    _alt = getInterpreter().adaptivePredict(_input,6,_ctx);
                 } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER );
                 }
                 break;
             case QUOTED_STRING:
                 enterOuterAlt(_localctx, 2);
                 {
-                setState(82);
+                setState(111);
                 match(QUOTED_STRING);
                 }
                 break;
@@ -823,15 +1108,15 @@ class KqlBaseParser extends Parser {
 
     public final ExistsQueryContext existsQuery() throws RecognitionException {
         ExistsQueryContext _localctx = new ExistsQueryContext(_ctx, getState());
-        enterRule(_localctx, 18, RULE_existsQuery);
+        enterRule(_localctx, 24, RULE_existsQuery);
         try {
             enterOuterAlt(_localctx, 1);
             {
-            setState(85);
+            setState(114);
             fieldName();
-            setState(86);
+            setState(115);
             match(COLON);
-            setState(87);
+            setState(116);
             match(WILDCARD);
             }
         }
@@ -878,34 +1163,34 @@ class KqlBaseParser extends Parser {
 
     public final FieldQueryContext fieldQuery() throws RecognitionException {
         FieldQueryContext _localctx = new FieldQueryContext(_ctx, getState());
-        enterRule(_localctx, 20, RULE_fieldQuery);
+        enterRule(_localctx, 26, RULE_fieldQuery);
         try {
-            setState(99);
+            setState(128);
             _errHandler.sync(this);
-            switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) {
+            switch ( getInterpreter().adaptivePredict(_input,8,_ctx) ) {
             case 1:
                 enterOuterAlt(_localctx, 1);
                 {
-                setState(89);
+                setState(118);
                 fieldName();
-                setState(90);
+                setState(119);
                 match(COLON);
-                setState(91);
+                setState(120);
                 fieldQueryValue();
                 }
                 break;
             case 2:
                 enterOuterAlt(_localctx, 2);
                 {
-                setState(93);
+                setState(122);
                 fieldName();
-                setState(94);
+                setState(123);
                 match(COLON);
-                setState(95);
+                setState(124);
                 match(LEFT_PARENTHESIS);
-                setState(96);
+                setState(125);
                 fieldQueryValue();
-                setState(97);
+                setState(126);
                 match(RIGHT_PARENTHESIS);
                 }
                 break;
@@ -950,9 +1235,9 @@ class KqlBaseParser extends Parser {
 
     public final FieldLessQueryContext fieldLessQuery() throws RecognitionException {
         FieldLessQueryContext _localctx = new FieldLessQueryContext(_ctx, getState());
-        enterRule(_localctx, 22, RULE_fieldLessQuery);
+        enterRule(_localctx, 28, RULE_fieldLessQuery);
         try {
-            setState(106);
+            setState(135);
             _errHandler.sync(this);
             switch (_input.LA(1)) {
             case AND:
@@ -963,18 +1248,18 @@ class KqlBaseParser extends Parser {
             case WILDCARD:
                 enterOuterAlt(_localctx, 1);
                 {
-                setState(101);
+                setState(130);
                 fieldQueryValue();
                 }
                 break;
             case LEFT_PARENTHESIS:
                 enterOuterAlt(_localctx, 2);
                 {
-                setState(102);
+                setState(131);
                 match(LEFT_PARENTHESIS);
-                setState(103);
+                setState(132);
                 fieldQueryValue();
-                setState(104);
+                setState(133);
                 match(RIGHT_PARENTHESIS);
                 }
                 break;
@@ -1037,22 +1322,22 @@ class KqlBaseParser extends Parser {
 
     public final FieldQueryValueContext fieldQueryValue() throws RecognitionException {
         FieldQueryValueContext _localctx = new FieldQueryValueContext(_ctx, getState());
-        enterRule(_localctx, 24, RULE_fieldQueryValue);
+        enterRule(_localctx, 30, RULE_fieldQueryValue);
         int _la;
         try {
             int _alt;
-            setState(128);
+            setState(157);
             _errHandler.sync(this);
-            switch ( getInterpreter().adaptivePredict(_input,13,_ctx) ) {
+            switch ( getInterpreter().adaptivePredict(_input,15,_ctx) ) {
             case 1:
                 enterOuterAlt(_localctx, 1);
                 {
-                setState(109);
+                setState(138);
                 _errHandler.sync(this);
                 _la = _input.LA(1);
                 if ((((_la) & ~0x3f) == 0 && ((1L << _la) & 28L) != 0)) {
                     {
-                    setState(108);
+                    setState(137);
                     _la = _input.LA(1);
                     if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 28L) != 0)) ) {
                     _errHandler.recoverInline(this);
@@ -1065,7 +1350,7 @@ class KqlBaseParser extends Parser {
                     }
                 }
 
-                setState(112); 
+                setState(141); 
                 _errHandler.sync(this);
                 _alt = 1;
                 do {
@@ -1073,7 +1358,7 @@ class KqlBaseParser extends Parser {
                     case 1:
                         {
                         {
-                        setState(111);
+                        setState(140);
                         _la = _input.LA(1);
                         if ( !(_la==UNQUOTED_LITERAL || _la==WILDCARD) ) {
                         _errHandler.recoverInline(this);
@@ -1089,16 +1374,16 @@ class KqlBaseParser extends Parser {
                     default:
                         throw new NoViableAltException(this);
                     }
-                    setState(114); 
+                    setState(143); 
                     _errHandler.sync(this);
-                    _alt = getInterpreter().adaptivePredict(_input,9,_ctx);
+                    _alt = getInterpreter().adaptivePredict(_input,11,_ctx);
                 } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER );
-                setState(117);
+                setState(146);
                 _errHandler.sync(this);
-                switch ( getInterpreter().adaptivePredict(_input,10,_ctx) ) {
+                switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) {
                 case 1:
                     {
-                    setState(116);
+                    setState(145);
                     _la = _input.LA(1);
                     if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 28L) != 0)) ) {
                     _errHandler.recoverInline(this);
@@ -1116,7 +1401,7 @@ class KqlBaseParser extends Parser {
             case 2:
                 enterOuterAlt(_localctx, 2);
                 {
-                setState(119);
+                setState(148);
                 _la = _input.LA(1);
                 if ( !(_la==AND || _la==OR) ) {
                 _errHandler.recoverInline(this);
@@ -1126,12 +1411,12 @@ class KqlBaseParser extends Parser {
                     _errHandler.reportMatch(this);
                     consume();
                 }
-                setState(121);
+                setState(150);
                 _errHandler.sync(this);
-                switch ( getInterpreter().adaptivePredict(_input,11,_ctx) ) {
+                switch ( getInterpreter().adaptivePredict(_input,13,_ctx) ) {
                 case 1:
                     {
-                    setState(120);
+                    setState(149);
                     _la = _input.LA(1);
                     if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 28L) != 0)) ) {
                     _errHandler.recoverInline(this);
@@ -1149,14 +1434,14 @@ class KqlBaseParser extends Parser {
             case 3:
                 enterOuterAlt(_localctx, 3);
                 {
-                setState(123);
+                setState(152);
                 match(NOT);
-                setState(125);
+                setState(154);
                 _errHandler.sync(this);
-                switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) {
+                switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) {
                 case 1:
                     {
-                    setState(124);
+                    setState(153);
                     _la = _input.LA(1);
                     if ( !(_la==AND || _la==OR) ) {
                     _errHandler.recoverInline(this);
@@ -1174,7 +1459,7 @@ class KqlBaseParser extends Parser {
             case 4:
                 enterOuterAlt(_localctx, 4);
                 {
-                setState(127);
+                setState(156);
                 match(QUOTED_STRING);
                 }
                 break;
@@ -1218,29 +1503,29 @@ class KqlBaseParser extends Parser {
 
     public final FieldNameContext fieldName() throws RecognitionException {
         FieldNameContext _localctx = new FieldNameContext(_ctx, getState());
-        enterRule(_localctx, 26, RULE_fieldName);
+        enterRule(_localctx, 32, RULE_fieldName);
         try {
-            setState(133);
+            setState(162);
             _errHandler.sync(this);
             switch (_input.LA(1)) {
             case UNQUOTED_LITERAL:
                 enterOuterAlt(_localctx, 1);
                 {
-                setState(130);
+                setState(159);
                 ((FieldNameContext)_localctx).value = match(UNQUOTED_LITERAL);
                 }
                 break;
             case QUOTED_STRING:
                 enterOuterAlt(_localctx, 2);
                 {
-                setState(131);
+                setState(160);
                 ((FieldNameContext)_localctx).value = match(QUOTED_STRING);
                 }
                 break;
             case WILDCARD:
                 enterOuterAlt(_localctx, 3);
                 {
-                setState(132);
+                setState(161);
                 ((FieldNameContext)_localctx).value = match(WILDCARD);
                 }
                 break;
@@ -1263,6 +1548,8 @@ class KqlBaseParser extends Parser {
         switch (ruleIndex) {
         case 1:
             return query_sempred((QueryContext)_localctx, predIndex);
+        case 5:
+            return nestedSubQuery_sempred((NestedSubQueryContext)_localctx, predIndex);
         }
         return true;
     }
@@ -1273,87 +1560,117 @@ class KqlBaseParser extends Parser {
         }
         return true;
     }
+    private boolean nestedSubQuery_sempred(NestedSubQueryContext _localctx, int predIndex) {
+        switch (predIndex) {
+        case 1:
+            return precpred(_ctx, 2);
+        }
+        return true;
+    }
 
     public static final String _serializedATN =
-        "\u0004\u0001\u0010\u0088\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+
+        "\u0004\u0001\u0010\u00a5\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+
         "\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004"+
         "\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007"+
         "\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b"+
-        "\u0002\f\u0007\f\u0002\r\u0007\r\u0001\u0000\u0003\u0000\u001e\b\u0000"+
-        "\u0001\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
-        "\u0001\u0001\u0001\u0001\u0005\u0001(\b\u0001\n\u0001\f\u0001+\t\u0001"+
-        "\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002"+
-        "\u0001\u0002\u0001\u0002\u0003\u00025\b\u0002\u0001\u0003\u0001\u0003"+
-        "\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004"+
-        "\u0001\u0004\u0001\u0005\u0001\u0005\u0003\u0005B\b\u0005\u0001\u0005"+
-        "\u0001\u0005\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0007"+
-        "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\b\u0004\bO\b\b\u000b\b\f\b"+
-        "P\u0001\b\u0003\bT\b\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\n\u0001\n"+
-        "\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0003"+
-        "\nd\b\n\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0003"+
-        "\u000bk\b\u000b\u0001\f\u0003\fn\b\f\u0001\f\u0004\fq\b\f\u000b\f\f\f"+
-        "r\u0001\f\u0003\fv\b\f\u0001\f\u0001\f\u0003\fz\b\f\u0001\f\u0001\f\u0003"+
-        "\f~\b\f\u0001\f\u0003\f\u0081\b\f\u0001\r\u0001\r\u0001\r\u0003\r\u0086"+
-        "\b\r\u0001\r\u0000\u0001\u0002\u000e\u0000\u0002\u0004\u0006\b\n\f\u000e"+
-        "\u0010\u0012\u0014\u0016\u0018\u001a\u0000\u0004\u0001\u0000\u0002\u0003"+
-        "\u0001\u0000\u0006\t\u0002\u0000\u000e\u000e\u0010\u0010\u0001\u0000\u0002"+
-        "\u0004\u0091\u0000\u001d\u0001\u0000\u0000\u0000\u0002!\u0001\u0000\u0000"+
-        "\u0000\u00044\u0001\u0000\u0000\u0000\u00066\u0001\u0000\u0000\u0000\b"+
-        "9\u0001\u0000\u0000\u0000\nA\u0001\u0000\u0000\u0000\fE\u0001\u0000\u0000"+
-        "\u0000\u000eI\u0001\u0000\u0000\u0000\u0010S\u0001\u0000\u0000\u0000\u0012"+
-        "U\u0001\u0000\u0000\u0000\u0014c\u0001\u0000\u0000\u0000\u0016j\u0001"+
-        "\u0000\u0000\u0000\u0018\u0080\u0001\u0000\u0000\u0000\u001a\u0085\u0001"+
-        "\u0000\u0000\u0000\u001c\u001e\u0003\u0002\u0001\u0000\u001d\u001c\u0001"+
-        "\u0000\u0000\u0000\u001d\u001e\u0001\u0000\u0000\u0000\u001e\u001f\u0001"+
-        "\u0000\u0000\u0000\u001f \u0005\u0000\u0000\u0001 \u0001\u0001\u0000\u0000"+
-        "\u0000!\"\u0006\u0001\uffff\uffff\u0000\"#\u0003\u0004\u0002\u0000#)\u0001"+
-        "\u0000\u0000\u0000$%\n\u0002\u0000\u0000%&\u0007\u0000\u0000\u0000&(\u0003"+
-        "\u0002\u0001\u0002\'$\u0001\u0000\u0000\u0000(+\u0001\u0000\u0000\u0000"+
-        ")\'\u0001\u0000\u0000\u0000)*\u0001\u0000\u0000\u0000*\u0003\u0001\u0000"+
-        "\u0000\u0000+)\u0001\u0000\u0000\u0000,5\u0003\u0006\u0003\u0000-5\u0003"+
-        "\b\u0004\u0000.5\u0003\f\u0006\u0000/5\u0003\n\u0005\u000005\u0003\u0012"+
-        "\t\u000015\u0003\u000e\u0007\u000025\u0003\u0014\n\u000035\u0003\u0016"+
-        "\u000b\u00004,\u0001\u0000\u0000\u00004-\u0001\u0000\u0000\u00004.\u0001"+
-        "\u0000\u0000\u00004/\u0001\u0000\u0000\u000040\u0001\u0000\u0000\u0000"+
-        "41\u0001\u0000\u0000\u000042\u0001\u0000\u0000\u000043\u0001\u0000\u0000"+
-        "\u00005\u0005\u0001\u0000\u0000\u000067\u0005\u0004\u0000\u000078\u0003"+
-        "\u0004\u0002\u00008\u0007\u0001\u0000\u0000\u00009:\u0003\u001a\r\u0000"+
-        ":;\u0005\u0005\u0000\u0000;<\u0005\f\u0000\u0000<=\u0003\u0002\u0001\u0000"+
-        "=>\u0005\r\u0000\u0000>\t\u0001\u0000\u0000\u0000?@\u0005\u0010\u0000"+
-        "\u0000@B\u0005\u0005\u0000\u0000A?\u0001\u0000\u0000\u0000AB\u0001\u0000"+
-        "\u0000\u0000BC\u0001\u0000\u0000\u0000CD\u0005\u0010\u0000\u0000D\u000b"+
-        "\u0001\u0000\u0000\u0000EF\u0005\n\u0000\u0000FG\u0003\u0002\u0001\u0000"+
-        "GH\u0005\u000b\u0000\u0000H\r\u0001\u0000\u0000\u0000IJ\u0003\u001a\r"+
-        "\u0000JK\u0007\u0001\u0000\u0000KL\u0003\u0010\b\u0000L\u000f\u0001\u0000"+
-        "\u0000\u0000MO\u0007\u0002\u0000\u0000NM\u0001\u0000\u0000\u0000OP\u0001"+
-        "\u0000\u0000\u0000PN\u0001\u0000\u0000\u0000PQ\u0001\u0000\u0000\u0000"+
-        "QT\u0001\u0000\u0000\u0000RT\u0005\u000f\u0000\u0000SN\u0001\u0000\u0000"+
-        "\u0000SR\u0001\u0000\u0000\u0000T\u0011\u0001\u0000\u0000\u0000UV\u0003"+
-        "\u001a\r\u0000VW\u0005\u0005\u0000\u0000WX\u0005\u0010\u0000\u0000X\u0013"+
-        "\u0001\u0000\u0000\u0000YZ\u0003\u001a\r\u0000Z[\u0005\u0005\u0000\u0000"+
-        "[\\\u0003\u0018\f\u0000\\d\u0001\u0000\u0000\u0000]^\u0003\u001a\r\u0000"+
-        "^_\u0005\u0005\u0000\u0000_`\u0005\n\u0000\u0000`a\u0003\u0018\f\u0000"+
-        "ab\u0005\u000b\u0000\u0000bd\u0001\u0000\u0000\u0000cY\u0001\u0000\u0000"+
-        "\u0000c]\u0001\u0000\u0000\u0000d\u0015\u0001\u0000\u0000\u0000ek\u0003"+
-        "\u0018\f\u0000fg\u0005\n\u0000\u0000gh\u0003\u0018\f\u0000hi\u0005\u000b"+
-        "\u0000\u0000ik\u0001\u0000\u0000\u0000je\u0001\u0000\u0000\u0000jf\u0001"+
-        "\u0000\u0000\u0000k\u0017\u0001\u0000\u0000\u0000ln\u0007\u0003\u0000"+
-        "\u0000ml\u0001\u0000\u0000\u0000mn\u0001\u0000\u0000\u0000np\u0001\u0000"+
-        "\u0000\u0000oq\u0007\u0002\u0000\u0000po\u0001\u0000\u0000\u0000qr\u0001"+
-        "\u0000\u0000\u0000rp\u0001\u0000\u0000\u0000rs\u0001\u0000\u0000\u0000"+
-        "su\u0001\u0000\u0000\u0000tv\u0007\u0003\u0000\u0000ut\u0001\u0000\u0000"+
-        "\u0000uv\u0001\u0000\u0000\u0000v\u0081\u0001\u0000\u0000\u0000wy\u0007"+
-        "\u0000\u0000\u0000xz\u0007\u0003\u0000\u0000yx\u0001\u0000\u0000\u0000"+
-        "yz\u0001\u0000\u0000\u0000z\u0081\u0001\u0000\u0000\u0000{}\u0005\u0004"+
-        "\u0000\u0000|~\u0007\u0000\u0000\u0000}|\u0001\u0000\u0000\u0000}~\u0001"+
-        "\u0000\u0000\u0000~\u0081\u0001\u0000\u0000\u0000\u007f\u0081\u0005\u000f"+
-        "\u0000\u0000\u0080m\u0001\u0000\u0000\u0000\u0080w\u0001\u0000\u0000\u0000"+
-        "\u0080{\u0001\u0000\u0000\u0000\u0080\u007f\u0001\u0000\u0000\u0000\u0081"+
-        "\u0019\u0001\u0000\u0000\u0000\u0082\u0086\u0005\u000e\u0000\u0000\u0083"+
-        "\u0086\u0005\u000f\u0000\u0000\u0084\u0086\u0005\u0010\u0000\u0000\u0085"+
-        "\u0082\u0001\u0000\u0000\u0000\u0085\u0083\u0001\u0000\u0000\u0000\u0085"+
-        "\u0084\u0001\u0000\u0000\u0000\u0086\u001b\u0001\u0000\u0000\u0000\u000f"+
-        "\u001d)4APScjmruy}\u0080\u0085";
+        "\u0002\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007"+
+        "\u000f\u0002\u0010\u0007\u0010\u0001\u0000\u0003\u0000$\b\u0000\u0001"+
+        "\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
+        "\u0001\u0001\u0001\u0005\u0001.\b\u0001\n\u0001\f\u00011\t\u0001\u0001"+
+        "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+
+        "\u0002\u0001\u0002\u0003\u0002;\b\u0002\u0001\u0003\u0001\u0003\u0001"+
+        "\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001"+
+        "\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+
+        "\u0005\u0005\u0005L\b\u0005\n\u0005\f\u0005O\t\u0005\u0001\u0006\u0001"+
+        "\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0003\u0006W\b"+
+        "\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\b\u0001\b"+
+        "\u0003\b_\b\b\u0001\b\u0001\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\n"+
+        "\u0001\n\u0001\n\u0001\n\u0001\u000b\u0004\u000bl\b\u000b\u000b\u000b"+
+        "\f\u000bm\u0001\u000b\u0003\u000bq\b\u000b\u0001\f\u0001\f\u0001\f\u0001"+
+        "\f\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001"+
+        "\r\u0001\r\u0003\r\u0081\b\r\u0001\u000e\u0001\u000e\u0001\u000e\u0001"+
+        "\u000e\u0001\u000e\u0003\u000e\u0088\b\u000e\u0001\u000f\u0003\u000f\u008b"+
+        "\b\u000f\u0001\u000f\u0004\u000f\u008e\b\u000f\u000b\u000f\f\u000f\u008f"+
+        "\u0001\u000f\u0003\u000f\u0093\b\u000f\u0001\u000f\u0001\u000f\u0003\u000f"+
+        "\u0097\b\u000f\u0001\u000f\u0001\u000f\u0003\u000f\u009b\b\u000f\u0001"+
+        "\u000f\u0003\u000f\u009e\b\u000f\u0001\u0010\u0001\u0010\u0001\u0010\u0003"+
+        "\u0010\u00a3\b\u0010\u0001\u0010\u0000\u0002\u0002\n\u0011\u0000\u0002"+
+        "\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a\u001c\u001e"+
+        " \u0000\u0004\u0001\u0000\u0002\u0003\u0001\u0000\u0006\t\u0002\u0000"+
+        "\u000e\u000e\u0010\u0010\u0001\u0000\u0002\u0004\u00b1\u0000#\u0001\u0000"+
+        "\u0000\u0000\u0002\'\u0001\u0000\u0000\u0000\u0004:\u0001\u0000\u0000"+
+        "\u0000\u0006<\u0001\u0000\u0000\u0000\b?\u0001\u0000\u0000\u0000\nE\u0001"+
+        "\u0000\u0000\u0000\fV\u0001\u0000\u0000\u0000\u000eX\u0001\u0000\u0000"+
+        "\u0000\u0010^\u0001\u0000\u0000\u0000\u0012b\u0001\u0000\u0000\u0000\u0014"+
+        "f\u0001\u0000\u0000\u0000\u0016p\u0001\u0000\u0000\u0000\u0018r\u0001"+
+        "\u0000\u0000\u0000\u001a\u0080\u0001\u0000\u0000\u0000\u001c\u0087\u0001"+
+        "\u0000\u0000\u0000\u001e\u009d\u0001\u0000\u0000\u0000 \u00a2\u0001\u0000"+
+        "\u0000\u0000\"$\u0003\u0002\u0001\u0000#\"\u0001\u0000\u0000\u0000#$\u0001"+
+        "\u0000\u0000\u0000$%\u0001\u0000\u0000\u0000%&\u0005\u0000\u0000\u0001"+
+        "&\u0001\u0001\u0000\u0000\u0000\'(\u0006\u0001\uffff\uffff\u0000()\u0003"+
+        "\u0004\u0002\u0000)/\u0001\u0000\u0000\u0000*+\n\u0002\u0000\u0000+,\u0007"+
+        "\u0000\u0000\u0000,.\u0003\u0002\u0001\u0002-*\u0001\u0000\u0000\u0000"+
+        ".1\u0001\u0000\u0000\u0000/-\u0001\u0000\u0000\u0000/0\u0001\u0000\u0000"+
+        "\u00000\u0003\u0001\u0000\u0000\u00001/\u0001\u0000\u0000\u00002;\u0003"+
+        "\u0006\u0003\u00003;\u0003\b\u0004\u00004;\u0003\u0012\t\u00005;\u0003"+
+        "\u0010\b\u00006;\u0003\u0018\f\u00007;\u0003\u0014\n\u00008;\u0003\u001a"+
+        "\r\u00009;\u0003\u001c\u000e\u0000:2\u0001\u0000\u0000\u0000:3\u0001\u0000"+
+        "\u0000\u0000:4\u0001\u0000\u0000\u0000:5\u0001\u0000\u0000\u0000:6\u0001"+
+        "\u0000\u0000\u0000:7\u0001\u0000\u0000\u0000:8\u0001\u0000\u0000\u0000"+
+        ":9\u0001\u0000\u0000\u0000;\u0005\u0001\u0000\u0000\u0000<=\u0005\u0004"+
+        "\u0000\u0000=>\u0003\u0004\u0002\u0000>\u0007\u0001\u0000\u0000\u0000"+
+        "?@\u0003 \u0010\u0000@A\u0005\u0005\u0000\u0000AB\u0005\f\u0000\u0000"+
+        "BC\u0003\n\u0005\u0000CD\u0005\r\u0000\u0000D\t\u0001\u0000\u0000\u0000"+
+        "EF\u0006\u0005\uffff\uffff\u0000FG\u0003\f\u0006\u0000GM\u0001\u0000\u0000"+
+        "\u0000HI\n\u0002\u0000\u0000IJ\u0007\u0000\u0000\u0000JL\u0003\n\u0005"+
+        "\u0002KH\u0001\u0000\u0000\u0000LO\u0001\u0000\u0000\u0000MK\u0001\u0000"+
+        "\u0000\u0000MN\u0001\u0000\u0000\u0000N\u000b\u0001\u0000\u0000\u0000"+
+        "OM\u0001\u0000\u0000\u0000PW\u0003\u0006\u0003\u0000QW\u0003\b\u0004\u0000"+
+        "RW\u0003\u000e\u0007\u0000SW\u0003\u0018\f\u0000TW\u0003\u0014\n\u0000"+
+        "UW\u0003\u001a\r\u0000VP\u0001\u0000\u0000\u0000VQ\u0001\u0000\u0000\u0000"+
+        "VR\u0001\u0000\u0000\u0000VS\u0001\u0000\u0000\u0000VT\u0001\u0000\u0000"+
+        "\u0000VU\u0001\u0000\u0000\u0000W\r\u0001\u0000\u0000\u0000XY\u0005\n"+
+        "\u0000\u0000YZ\u0003\n\u0005\u0000Z[\u0005\u000b\u0000\u0000[\u000f\u0001"+
+        "\u0000\u0000\u0000\\]\u0005\u0010\u0000\u0000]_\u0005\u0005\u0000\u0000"+
+        "^\\\u0001\u0000\u0000\u0000^_\u0001\u0000\u0000\u0000_`\u0001\u0000\u0000"+
+        "\u0000`a\u0005\u0010\u0000\u0000a\u0011\u0001\u0000\u0000\u0000bc\u0005"+
+        "\n\u0000\u0000cd\u0003\u0002\u0001\u0000de\u0005\u000b\u0000\u0000e\u0013"+
+        "\u0001\u0000\u0000\u0000fg\u0003 \u0010\u0000gh\u0007\u0001\u0000\u0000"+
+        "hi\u0003\u0016\u000b\u0000i\u0015\u0001\u0000\u0000\u0000jl\u0007\u0002"+
+        "\u0000\u0000kj\u0001\u0000\u0000\u0000lm\u0001\u0000\u0000\u0000mk\u0001"+
+        "\u0000\u0000\u0000mn\u0001\u0000\u0000\u0000nq\u0001\u0000\u0000\u0000"+
+        "oq\u0005\u000f\u0000\u0000pk\u0001\u0000\u0000\u0000po\u0001\u0000\u0000"+
+        "\u0000q\u0017\u0001\u0000\u0000\u0000rs\u0003 \u0010\u0000st\u0005\u0005"+
+        "\u0000\u0000tu\u0005\u0010\u0000\u0000u\u0019\u0001\u0000\u0000\u0000"+
+        "vw\u0003 \u0010\u0000wx\u0005\u0005\u0000\u0000xy\u0003\u001e\u000f\u0000"+
+        "y\u0081\u0001\u0000\u0000\u0000z{\u0003 \u0010\u0000{|\u0005\u0005\u0000"+
+        "\u0000|}\u0005\n\u0000\u0000}~\u0003\u001e\u000f\u0000~\u007f\u0005\u000b"+
+        "\u0000\u0000\u007f\u0081\u0001\u0000\u0000\u0000\u0080v\u0001\u0000\u0000"+
+        "\u0000\u0080z\u0001\u0000\u0000\u0000\u0081\u001b\u0001\u0000\u0000\u0000"+
+        "\u0082\u0088\u0003\u001e\u000f\u0000\u0083\u0084\u0005\n\u0000\u0000\u0084"+
+        "\u0085\u0003\u001e\u000f\u0000\u0085\u0086\u0005\u000b\u0000\u0000\u0086"+
+        "\u0088\u0001\u0000\u0000\u0000\u0087\u0082\u0001\u0000\u0000\u0000\u0087"+
+        "\u0083\u0001\u0000\u0000\u0000\u0088\u001d\u0001\u0000\u0000\u0000\u0089"+
+        "\u008b\u0007\u0003\u0000\u0000\u008a\u0089\u0001\u0000\u0000\u0000\u008a"+
+        "\u008b\u0001\u0000\u0000\u0000\u008b\u008d\u0001\u0000\u0000\u0000\u008c"+
+        "\u008e\u0007\u0002\u0000\u0000\u008d\u008c\u0001\u0000\u0000\u0000\u008e"+
+        "\u008f\u0001\u0000\u0000\u0000\u008f\u008d\u0001\u0000\u0000\u0000\u008f"+
+        "\u0090\u0001\u0000\u0000\u0000\u0090\u0092\u0001\u0000\u0000\u0000\u0091"+
+        "\u0093\u0007\u0003\u0000\u0000\u0092\u0091\u0001\u0000\u0000\u0000\u0092"+
+        "\u0093\u0001\u0000\u0000\u0000\u0093\u009e\u0001\u0000\u0000\u0000\u0094"+
+        "\u0096\u0007\u0000\u0000\u0000\u0095\u0097\u0007\u0003\u0000\u0000\u0096"+
+        "\u0095\u0001\u0000\u0000\u0000\u0096\u0097\u0001\u0000\u0000\u0000\u0097"+
+        "\u009e\u0001\u0000\u0000\u0000\u0098\u009a\u0005\u0004\u0000\u0000\u0099"+
+        "\u009b\u0007\u0000\u0000\u0000\u009a\u0099\u0001\u0000\u0000\u0000\u009a"+
+        "\u009b\u0001\u0000\u0000\u0000\u009b\u009e\u0001\u0000\u0000\u0000\u009c"+
+        "\u009e\u0005\u000f\u0000\u0000\u009d\u008a\u0001\u0000\u0000\u0000\u009d"+
+        "\u0094\u0001\u0000\u0000\u0000\u009d\u0098\u0001\u0000\u0000\u0000\u009d"+
+        "\u009c\u0001\u0000\u0000\u0000\u009e\u001f\u0001\u0000\u0000\u0000\u009f"+
+        "\u00a3\u0005\u000e\u0000\u0000\u00a0\u00a3\u0005\u000f\u0000\u0000\u00a1"+
+        "\u00a3\u0005\u0010\u0000\u0000\u00a2\u009f\u0001\u0000\u0000\u0000\u00a2"+
+        "\u00a0\u0001\u0000\u0000\u0000\u00a2\u00a1\u0001\u0000\u0000\u0000\u00a3"+
+        "!\u0001\u0000\u0000\u0000\u0011#/:MV^mp\u0080\u0087\u008a\u008f\u0092"+
+        "\u0096\u009a\u009d\u00a2";
     public static final ATN _ATN =
         new ATNDeserializer().deserialize(_serializedATN.toCharArray());
     static {

+ 26 - 0
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseVisitor.java

@@ -56,6 +56,32 @@ interface KqlBaseVisitor<T> extends ParseTreeVisitor<T> {
      * @return the visitor result
      */
     T visitNestedQuery(KqlBaseParser.NestedQueryContext ctx);
+    /**
+     * Visit a parse tree produced by the {@code booleanNestedQuery}
+     * labeled alternative in {@link KqlBaseParser#nestedSubQuery}.
+     * @param ctx the parse tree
+     * @return the visitor result
+     */
+    T visitBooleanNestedQuery(KqlBaseParser.BooleanNestedQueryContext ctx);
+    /**
+     * Visit a parse tree produced by the {@code defaultNestedQuery}
+     * labeled alternative in {@link KqlBaseParser#nestedSubQuery}.
+     * @param ctx the parse tree
+     * @return the visitor result
+     */
+    T visitDefaultNestedQuery(KqlBaseParser.DefaultNestedQueryContext ctx);
+    /**
+     * Visit a parse tree produced by {@link KqlBaseParser#nestedSimpleSubQuery}.
+     * @param ctx the parse tree
+     * @return the visitor result
+     */
+    T visitNestedSimpleSubQuery(KqlBaseParser.NestedSimpleSubQueryContext ctx);
+    /**
+     * Visit a parse tree produced by {@link KqlBaseParser#nestedParenthesizedQuery}.
+     * @param ctx the parse tree
+     * @return the visitor result
+     */
+    T visitNestedParenthesizedQuery(KqlBaseParser.NestedParenthesizedQueryContext ctx);
     /**
      * Visit a parse tree produced by {@link KqlBaseParser#matchAllQuery}.
      * @param ctx the parse tree

+ 50 - 2
x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParsingContext.java

@@ -11,11 +11,18 @@ import org.elasticsearch.index.mapper.AbstractScriptFieldType;
 import org.elasticsearch.index.mapper.DateFieldMapper;
 import org.elasticsearch.index.mapper.KeywordFieldMapper;
 import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.mapper.NestedLookup;
+import org.elasticsearch.index.mapper.NestedObjectMapper;
 import org.elasticsearch.index.query.QueryRewriteContext;
+import org.elasticsearch.index.query.support.NestedScope;
 
 import java.time.ZoneId;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
+
+import static org.elasticsearch.common.Strings.format;
 
 public class KqlParsingContext {
 
@@ -32,10 +39,11 @@ public class KqlParsingContext {
         return new Builder(queryRewriteContext);
     }
 
-    private QueryRewriteContext queryRewriteContext;
+    private final QueryRewriteContext queryRewriteContext;
     private final boolean caseInsensitive;
     private final ZoneId timeZone;
     private final String defaultField;
+    private final NestedScope nestedScope = new NestedScope();
 
     public KqlParsingContext(QueryRewriteContext queryRewriteContext, boolean caseInsensitive, ZoneId timeZone, String defaultField) {
         this.queryRewriteContext = queryRewriteContext;
@@ -56,9 +64,17 @@ public class KqlParsingContext {
         return defaultField;
     }
 
+    public String nestedPath(String fieldName) {
+        return nestedLookup().getNestedParent(fieldName);
+    }
+
+    public boolean isNestedField(String fieldName) {
+        return nestedMappers().containsKey(fullFieldName(fieldName));
+    }
+
     public Set<String> resolveFieldNames(String fieldNamePattern) {
         assert fieldNamePattern != null && fieldNamePattern.isEmpty() == false : "fieldNamePattern cannot be null or empty";
-        return queryRewriteContext.getMatchingFieldNames(fieldNamePattern);
+        return queryRewriteContext.getMatchingFieldNames(fullFieldName(fieldNamePattern));
     }
 
     public Set<String> resolveDefaultFieldNames() {
@@ -89,6 +105,38 @@ public class KqlParsingContext {
         return isSearchableField(fieldName, fieldType(fieldName));
     }
 
+    public NestedScope nestedScope() {
+        return nestedScope;
+    }
+
+    public <T> T withNestedPath(String nestedFieldName, Supplier<T> supplier) {
+        assert isNestedField(nestedFieldName);
+        nestedScope.nextLevel(nestedMappers().get(fullFieldName(nestedFieldName)));
+        T result = supplier.get();
+        nestedScope.previousLevel();
+        return result;
+    }
+
+    public String currentNestedPath() {
+        return nestedScope().getObjectMapper() != null ? nestedScope().getObjectMapper().fullPath() : null;
+    }
+
+    public String fullFieldName(String fieldName) {
+        if (nestedScope.getObjectMapper() == null) {
+            return fieldName;
+        }
+
+        return format("%s.%s", nestedScope.getObjectMapper().fullPath(), fieldName);
+    }
+
+    private NestedLookup nestedLookup() {
+        return queryRewriteContext.getMappingLookup().nestedLookup();
+    }
+
+    private Map<String, NestedObjectMapper> nestedMappers() {
+        return nestedLookup().getNestedMappers();
+    }
+
     public static class Builder {
         private final QueryRewriteContext queryRewriteContext;
         private boolean caseInsensitive = true;

+ 0 - 2
x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/AbstractKqlParserTestCase.java

@@ -46,11 +46,9 @@ import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.equalTo;
 
 public abstract class AbstractKqlParserTestCase extends AbstractBuilderTestCase {
-
     protected static final String SUPPORTED_QUERY_FILE_PATH = "/supported-queries";
     protected static final String UNSUPPORTED_QUERY_FILE_PATH = "/unsupported-queries";
     protected static final Predicate<String> BOOLEAN_QUERY_FILTER = (q) -> q.matches("(?i)[^{]*[^\\\\]*(NOT|AND|OR)[^}]*");
-
     protected static final String NESTED_FIELD_NAME = "mapped_nested";
 
     @Override

+ 297 - 0
x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/KqlNestedFieldQueryTests.java

@@ -0,0 +1,297 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.kql.parser;
+
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.MatchQueryBuilder;
+import org.elasticsearch.index.query.NestedQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.RangeQueryBuilder;
+import org.elasticsearch.index.query.TermQueryBuilder;
+import org.elasticsearch.test.ESTestCase;
+import org.hamcrest.Matchers;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.elasticsearch.common.Strings.format;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+
+public class KqlNestedFieldQueryTests extends AbstractKqlParserTestCase {
+    public void testInvalidNestedFieldName() {
+        for (String invalidFieldName : List.of(OBJECT_FIELD_NAME, TEXT_FIELD_NAME, "not_a_field", "mapped_nest*")) {
+            KqlParsingException e = assertThrows(
+                KqlParsingException.class,
+                () -> parseKqlQuery(format("%s : { %s: foo AND %s < 10 } ", invalidFieldName, TEXT_FIELD_NAME, INT_FIELD_NAME))
+            );
+            assertThat(e.getMessage(), Matchers.containsString(invalidFieldName));
+            assertThat(e.getMessage(), Matchers.containsString("is not a valid nested field name"));
+        }
+    }
+
+    public void testInlineNestedFieldMatchTextQuery() {
+        for (String fieldName : List.of(TEXT_FIELD_NAME, INT_FIELD_NAME)) {
+            {
+                // Querying a nested text subfield.
+                String nestedFieldName = format("%s.%s", NESTED_FIELD_NAME, fieldName);
+                String searchTerms = randomSearchTerms();
+                String kqlQueryString = format("%s: %s", nestedFieldName, searchTerms);
+
+                NestedQueryBuilder nestedQuery = asInstanceOf(NestedQueryBuilder.class, parseKqlQuery(kqlQueryString));
+
+                assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+                assertMatchQueryBuilder(nestedQuery.query(), nestedFieldName, searchTerms);
+            }
+
+            {
+                // Several levels of nested fields.
+                String nestedFieldName = format("%s.%s.%s", NESTED_FIELD_NAME, NESTED_FIELD_NAME, fieldName);
+                String searchTerms = randomSearchTerms();
+                String kqlQueryString = format("%s: %s", nestedFieldName, searchTerms);
+
+                NestedQueryBuilder nestedQuery = asInstanceOf(NestedQueryBuilder.class, parseKqlQuery(kqlQueryString));
+                assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+                NestedQueryBuilder nestedSubQuery = asInstanceOf(NestedQueryBuilder.class, nestedQuery.query());
+                assertThat(nestedSubQuery.path(), equalTo(format("%s.%s", NESTED_FIELD_NAME, NESTED_FIELD_NAME)));
+
+                assertMatchQueryBuilder(nestedSubQuery.query(), nestedFieldName, searchTerms);
+            }
+        }
+    }
+
+    public void testInlineNestedFieldMatchKeywordFieldQuery() {
+        {
+            // Querying a nested text subfield.
+            String nestedFieldName = format("%s.%s", NESTED_FIELD_NAME, KEYWORD_FIELD_NAME);
+            String searchTerms = randomSearchTerms();
+            String kqlQueryString = format("%s: %s", nestedFieldName, searchTerms);
+
+            NestedQueryBuilder nestedQuery = asInstanceOf(NestedQueryBuilder.class, parseKqlQuery(kqlQueryString));
+
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+            assertTermQueryBuilder(nestedQuery.query(), nestedFieldName, searchTerms);
+        }
+
+        {
+            // Several levels of nested fields.
+            String nestedFieldName = format("%s.%s.%s", NESTED_FIELD_NAME, NESTED_FIELD_NAME, KEYWORD_FIELD_NAME);
+            String searchTerms = randomSearchTerms();
+            String kqlQueryString = format("%s: %s", nestedFieldName, searchTerms);
+
+            NestedQueryBuilder nestedQuery = asInstanceOf(NestedQueryBuilder.class, parseKqlQuery(kqlQueryString));
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+            NestedQueryBuilder nestedSubQuery = asInstanceOf(NestedQueryBuilder.class, nestedQuery.query());
+            assertThat(nestedSubQuery.path(), equalTo(format("%s.%s", NESTED_FIELD_NAME, NESTED_FIELD_NAME)));
+
+            assertTermQueryBuilder(nestedSubQuery.query(), nestedFieldName, searchTerms);
+        }
+    }
+
+    public void testInlineNestedFieldRangeQuery() {
+        {
+            // Querying a nested text subfield.
+            String nestedFieldName = format("%s.%s", NESTED_FIELD_NAME, INT_FIELD_NAME);
+            String operator = randomFrom(">", ">=", "<", "<=");
+            String kqlQueryString = format("%s %s %s", nestedFieldName, operator, randomDouble());
+
+            NestedQueryBuilder nestedQuery = asInstanceOf(NestedQueryBuilder.class, parseKqlQuery(kqlQueryString));
+
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+            assertRangeQueryBuilder(nestedQuery.query(), nestedFieldName, rangeQueryBuilder -> {});
+        }
+
+        {
+            // Several levels of nested fields.
+            String nestedFieldName = format("%s.%s.%s", NESTED_FIELD_NAME, NESTED_FIELD_NAME, INT_FIELD_NAME);
+            String operator = randomFrom(">", ">=", "<", "<=");
+            String kqlQueryString = format("%s %s %s", nestedFieldName, operator, randomDouble());
+
+            NestedQueryBuilder nestedQuery = asInstanceOf(NestedQueryBuilder.class, parseKqlQuery(kqlQueryString));
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+            NestedQueryBuilder nestedSubQuery = asInstanceOf(NestedQueryBuilder.class, nestedQuery.query());
+            assertThat(nestedSubQuery.path(), equalTo(format("%s.%s", NESTED_FIELD_NAME, NESTED_FIELD_NAME)));
+
+            assertRangeQueryBuilder(nestedSubQuery.query(), nestedFieldName, rangeQueryBuilder -> {});
+        }
+    }
+
+    public void testNestedQuerySyntax() {
+        // Single word - Keyword & text field
+        List.of(KEYWORD_FIELD_NAME, TEXT_FIELD_NAME)
+            .forEach(
+                fieldName -> assertThat(
+                    parseKqlQuery(format("%s : { %s : %s }", NESTED_FIELD_NAME, fieldName, "foo")),
+                    equalTo(parseKqlQuery(format("%s.%s : %s", NESTED_FIELD_NAME, fieldName, "foo")))
+                )
+            );
+
+        // Multiple words - Keyword & text field
+        List.of(KEYWORD_FIELD_NAME, TEXT_FIELD_NAME)
+            .forEach(
+                fieldName -> assertThat(
+                    parseKqlQuery(format("%s : { %s : %s }", NESTED_FIELD_NAME, fieldName, "foo bar")),
+                    equalTo(parseKqlQuery(format("%s.%s : %s", NESTED_FIELD_NAME, fieldName, "foo bar")))
+                )
+            );
+
+        // Range syntax
+        {
+            String operator = randomFrom("<", "<=", ">", ">=");
+            double rangeValue = randomDouble();
+            assertThat(
+                parseKqlQuery(format("%s : { %s %s %s }", NESTED_FIELD_NAME, INT_FIELD_NAME, operator, rangeValue)),
+                equalTo(parseKqlQuery(format("%s.%s %s %s", NESTED_FIELD_NAME, INT_FIELD_NAME, operator, rangeValue)))
+            );
+        }
+
+        // Several level of nesting
+        {
+            QueryBuilder inlineQuery = parseKqlQuery(
+                format("%s.%s.%s : %s", NESTED_FIELD_NAME, NESTED_FIELD_NAME, TEXT_FIELD_NAME, "foo bar")
+            );
+
+            assertThat(
+                parseKqlQuery(format("%s : { %s : { %s : %s } }", NESTED_FIELD_NAME, NESTED_FIELD_NAME, TEXT_FIELD_NAME, "foo bar")),
+                equalTo(inlineQuery)
+            );
+
+            assertThat(
+                parseKqlQuery(format("%s.%s :  { %s : %s }", NESTED_FIELD_NAME, NESTED_FIELD_NAME, TEXT_FIELD_NAME, "foo bar")),
+                equalTo(inlineQuery)
+            );
+
+            assertThat(
+                parseKqlQuery(format("%s : { %s.%s : %s }", NESTED_FIELD_NAME, NESTED_FIELD_NAME, TEXT_FIELD_NAME, "foo bar")),
+                equalTo(inlineQuery)
+            );
+        }
+    }
+
+    public void testBooleanAndNestedQuerySyntax() {
+        NestedQueryBuilder nestedQuery = asInstanceOf(
+            NestedQueryBuilder.class,
+            parseKqlQuery(
+                format("%s: { %s : foo AND %s: bar AND %s > 3}", NESTED_FIELD_NAME, TEXT_FIELD_NAME, KEYWORD_FIELD_NAME, INT_FIELD_NAME)
+            )
+        );
+        assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+        BoolQueryBuilder subQuery = asInstanceOf(BoolQueryBuilder.class, nestedQuery.query());
+        assertThat(subQuery.should(), empty());
+        assertThat(subQuery.filter(), empty());
+        assertThat(subQuery.mustNot(), empty());
+        assertThat(subQuery.must(), hasSize(3));
+        assertMatchQueryBuilder(
+            subQuery.must().stream().filter(q -> q instanceof MatchQueryBuilder).findFirst().get(),
+            format("%s.%s", NESTED_FIELD_NAME, TEXT_FIELD_NAME),
+            "foo"
+        );
+        assertTermQueryBuilder(
+            subQuery.must().stream().filter(q -> q instanceof TermQueryBuilder).findFirst().get(),
+            format("%s.%s", NESTED_FIELD_NAME, KEYWORD_FIELD_NAME),
+            "bar"
+        );
+        assertRangeQueryBuilder(
+            subQuery.must().stream().filter(q -> q instanceof RangeQueryBuilder).findAny().get(),
+            format("%s.%s", NESTED_FIELD_NAME, INT_FIELD_NAME),
+            q -> {}
+        );
+    }
+
+    public void testBooleanOrNestedQuerySyntax() {
+        NestedQueryBuilder nestedQuery = asInstanceOf(
+            NestedQueryBuilder.class,
+            parseKqlQuery(
+                format("%s: { %s : foo OR %s: bar OR %s > 3 }", NESTED_FIELD_NAME, TEXT_FIELD_NAME, KEYWORD_FIELD_NAME, INT_FIELD_NAME)
+            )
+        );
+
+        assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+        BoolQueryBuilder subQuery = asInstanceOf(BoolQueryBuilder.class, nestedQuery.query());
+        assertThat(subQuery.must(), empty());
+        assertThat(subQuery.filter(), empty());
+        assertThat(subQuery.mustNot(), empty());
+        assertThat(subQuery.should(), hasSize(3));
+        assertMatchQueryBuilder(
+            subQuery.should().stream().filter(q -> q instanceof MatchQueryBuilder).findFirst().get(),
+            format("%s.%s", NESTED_FIELD_NAME, TEXT_FIELD_NAME),
+            "foo"
+        );
+        assertTermQueryBuilder(
+            subQuery.should().stream().filter(q -> q instanceof TermQueryBuilder).findFirst().get(),
+            format("%s.%s", NESTED_FIELD_NAME, KEYWORD_FIELD_NAME),
+            "bar"
+        );
+        assertRangeQueryBuilder(
+            subQuery.should().stream().filter(q -> q instanceof RangeQueryBuilder).findAny().get(),
+            format("%s.%s", NESTED_FIELD_NAME, INT_FIELD_NAME),
+            q -> {}
+        );
+    }
+
+    public void testBooleanNotNestedQuerySyntax() {
+        {
+            NestedQueryBuilder nestedQuery = asInstanceOf(
+                NestedQueryBuilder.class,
+                parseKqlQuery(format("%s: { NOT %s : foo }", NESTED_FIELD_NAME, TEXT_FIELD_NAME))
+            );
+
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+            BoolQueryBuilder subQuery = asInstanceOf(BoolQueryBuilder.class, nestedQuery.query());
+            assertThat(subQuery.must(), empty());
+            assertThat(subQuery.filter(), empty());
+            assertThat(subQuery.should(), empty());
+            assertThat(subQuery.mustNot(), hasSize(1));
+            assertMatchQueryBuilder(subQuery.mustNot().get(0), format("%s.%s", NESTED_FIELD_NAME, TEXT_FIELD_NAME), "foo");
+        }
+
+        {
+            NestedQueryBuilder nestedQuery = asInstanceOf(
+                NestedQueryBuilder.class,
+                parseKqlQuery(format("%s: { NOT %s : foo }", NESTED_FIELD_NAME, KEYWORD_FIELD_NAME))
+            );
+
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+            BoolQueryBuilder subQuery = asInstanceOf(BoolQueryBuilder.class, nestedQuery.query());
+            assertThat(subQuery.must(), empty());
+            assertThat(subQuery.filter(), empty());
+            assertThat(subQuery.should(), empty());
+            assertThat(subQuery.mustNot(), hasSize(1));
+            assertTermQueryBuilder(subQuery.mustNot().get(0), format("%s.%s", NESTED_FIELD_NAME, KEYWORD_FIELD_NAME), "foo");
+        }
+
+        {
+            NestedQueryBuilder nestedQuery = asInstanceOf(
+                NestedQueryBuilder.class,
+                parseKqlQuery(format("%s: { NOT %s < 3 }", NESTED_FIELD_NAME, INT_FIELD_NAME))
+            );
+
+            assertThat(nestedQuery.path(), equalTo(NESTED_FIELD_NAME));
+
+            BoolQueryBuilder subQuery = asInstanceOf(BoolQueryBuilder.class, nestedQuery.query());
+            assertThat(subQuery.must(), empty());
+            assertThat(subQuery.filter(), empty());
+            assertThat(subQuery.should(), empty());
+            assertThat(subQuery.mustNot(), hasSize(1));
+            assertRangeQueryBuilder(subQuery.mustNot().get(0), format("%s.%s", NESTED_FIELD_NAME, INT_FIELD_NAME), q -> {});
+        }
+    }
+
+    private static String randomSearchTerms() {
+        return Stream.generate(ESTestCase::randomIdentifier).limit(randomIntBetween(1, 10)).collect(Collectors.joining(" "));
+    }
+}

+ 16 - 4
x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/KqlParserExistsQueryTests.java

@@ -10,7 +10,10 @@ package org.elasticsearch.xpack.kql.parser;
 import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.ExistsQueryBuilder;
 import org.elasticsearch.index.query.MatchNoneQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.NestedQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+
+import java.util.regex.Pattern;
 
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
@@ -35,11 +38,18 @@ public class KqlParserExistsQueryTests extends AbstractKqlParserTestCase {
 
     public void testParseExistsQueryWithASingleField() {
         for (String fieldName : searchableFields()) {
-            ExistsQueryBuilder parsedQuery = asInstanceOf(ExistsQueryBuilder.class, parseKqlQuery(kqlExistsQuery(fieldName)));
-            assertThat(parsedQuery.fieldName(), equalTo(fieldName));
+            QueryBuilder parsedQuery = parseKqlQuery(kqlExistsQuery(fieldName));
 
             // Using quotes to wrap the field name does not change the result.
             assertThat(parseKqlQuery(kqlExistsQuery("\"" + fieldName + "\"")), equalTo(parsedQuery));
+
+            long nestingLevel = Pattern.compile("[.]").splitAsStream(fieldName).takeWhile(s -> s.equals(NESTED_FIELD_NAME)).count();
+            for (int i = 0; i < nestingLevel; i++) {
+                parsedQuery = asInstanceOf(NestedQueryBuilder.class, parsedQuery).query();
+            }
+
+            ExistsQueryBuilder existsQuery = asInstanceOf(ExistsQueryBuilder.class, parsedQuery);
+            assertThat(existsQuery.fieldName(), equalTo(fieldName));
         }
     }
 
@@ -53,7 +63,9 @@ public class KqlParserExistsQueryTests extends AbstractKqlParserTestCase {
 
         assertThat(
             parsedQuery.should(),
-            containsInAnyOrder(searchableFields(fieldNamePattern).stream().map(QueryBuilders::existsQuery).toArray())
+            containsInAnyOrder(
+                searchableFields(fieldNamePattern).stream().map(fieldName -> parseKqlQuery(kqlExistsQuery(fieldName))).toArray()
+            )
         );
     }
 

+ 0 - 7
x-pack/plugin/kql/src/test/resources/supported-queries

@@ -91,13 +91,6 @@ mapped_nested: { NOT(mapped_string:foo AND mapped_string_2:foo bar) }
 mapped_nested: { NOT mapped_string:foo AND NOT mapped_string_2:foo bar }
 mapped_nested: { (NOT mapped_string:foo) AND (NOT mapped_string_2:foo bar) }
 mapped_nested: { NOT(mapped_string:foo) AND NOT(mapped_string_2:foo bar) }
-mapped_nested: { mapped_string:foo AND mapped_string_2:foo bar AND foo bar }
-mapped_nested: { mapped_string:foo AND mapped_string_2:foo bar OR foo bar }
-mapped_nested: { mapped_string:foo OR mapped_string_2:foo bar OR foo bar }
-mapped_nested: { mapped_string:foo OR mapped_string_2:foo bar AND foo bar }
-mapped_nested: { mapped_string:foo AND (mapped_string_2:foo bar OR foo bar) }
-mapped_nested: { mapped_string:foo AND (mapped_string_2:foo bar OR foo bar) }
-mapped_nested: { mapped_string:foo OR (mapped_string_2:foo bar OR foo bar) }
 mapped_nested: { mapped_str*:foo }
 mapped_nested: { mapped_nested : { mapped_string:foo AND mapped_int < 3 } AND mapped_string_2:foo bar }
 mapped_nested: { mapped_nested.mapped_string:foo AND mapped_string_2:foo bar }

+ 14 - 0
x-pack/plugin/kql/src/test/resources/unsupported-queries

@@ -25,6 +25,20 @@ mapped_string:(foo (bar))
 // Bad syntax for nested fields:
 mapped_nested { mapped_string: bar }
 
+// Unknown nested field or not a nested field
+not_nested : { mapped_string: bar }
+mapped_string: { mapped_string: bar }
+
+// Nested query can not use fieldless subqueries
+mapped_nested: { foo }
+mapped_nested: { mapped_string:foo AND mapped_string_2:foo bar AND foo bar }
+mapped_nested: { mapped_string:foo AND mapped_string_2:foo bar OR foo bar }
+mapped_nested: { mapped_string:foo OR mapped_string_2:foo bar OR foo bar }
+mapped_nested: { mapped_string:foo OR mapped_string_2:foo bar AND foo bar }
+mapped_nested: { mapped_string:foo AND (mapped_string_2:foo bar OR foo bar) }
+mapped_nested: { mapped_string:foo AND (mapped_string_2:foo bar OR foo bar) }
+mapped_nested: { mapped_string:foo OR (mapped_string_2:foo bar OR foo bar) }
+
 // Missing escape sequences:
 mapped_string: foo:bar
 mapped_string: (foo and bar)

+ 218 - 0
x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/50_kql_nested_fields_query.yml

@@ -0,0 +1,218 @@
+setup:
+  - requires:
+      capabilities:
+        - method: POST
+          path: /_search
+          capabilities: [ kql_query ]
+      test_runner_features: [ capabilities, contains ]
+      reason: KQL query is not available
+
+  - requires:
+      "test_runner_features": "contains"
+
+  - do:
+      indices.create:
+        index: test-index
+        body:
+          mappings:
+            properties:
+              department:
+                type: keyword
+              staff:
+                type: integer
+              courses:
+                type: nested
+                properties:
+                  name:
+                    type: text
+                  credits:
+                    type: integer
+                  sessions:
+                    type: nested
+                    properties:
+                      semester:
+                        type: keyword
+                      students:
+                        type: integer
+
+  - do:
+      bulk:
+        index: test-index
+        refresh: true
+        body: |
+          { "index" : { "_id": "doc-1" } }
+          { "department": "compsci", "staff": 12, "courses": [ { "name": "Object Oriented Programming", "credits": 3, "sessions": [ { "semester": "spr2021", "students": 37 }, { "semester": "fall2020", "students": 45} ] }, { "name": "Theory of Computation", "credits": 4, "sessions": [ { "semester": "spr2021", "students": 19 }, { "semester": "fall2020", "students": 14 } ] } ] }
+          { "index" : { "_id": "doc-42" } }
+          { "department": "math", "staff": 20, "courses": [ { "name": "Precalculus", "credits": 1, "sessions": [ { "semester": "spr2021", "students": 100 }, { "semester": "fall2020", "students": 134 } ] }, { "name": "Linear Algebra", "credits": 3, "sessions": [ { "semester": "spr2021", "students": 29 }, { "semester": "fall2020", "students": 23 } ] } ] }
+
+---
+"Inline syntax":
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses.name: object oriented programming"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses.name: object oriented programming AND courses.credits > 3"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses.name: object oriented programming OR courses.credits > 3"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+
+---
+"Nested field syntax":
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses : { name: object oriented programming }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { name: object oriented programming AND credits > 3 }"
+              }
+            }
+          }
+  - match: { hits.total: 0 }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { name: object oriented programming AND credits >= 3 }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { name: object oriented programming OR credits > 3 }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { NOT name: object oriented programming AND credits < 4 }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-42" }
+
+
+---
+"Several level of nesting field syntax":
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { name: object oriented programming AND sessions.semester: spr2021 }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { sessions : { semester: spr2021 AND students < 20 } }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }
+
+  - do:
+      search:
+        index: test-index
+        rest_total_hits_as_int: true
+        body: >
+          {
+            "query": {
+              "kql": {
+                "query": "courses: { name: computation AND sessions : { semester: spr2021 AND students < 20 } }"
+              }
+            }
+          }
+  - match: { hits.total: 1 }
+  - match: { hits.hits.0._id: "doc-1" }

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно