From 0875ab0a37a632235d3ad9d24dd4400a60a8d7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 8 Nov 2024 10:48:40 +0100 Subject: [PATCH] Add kql query to the DSL (#116262) (cherry picked from commit e2c29f54874f8b6bdf9953b54055c82388bffa0f) # Conflicts: # server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java --- .../org/elasticsearch/TransportVersions.java | 1 + .../action/search/SearchCapabilities.java | 30 +- x-pack/plugin/kql/build.gradle | 18 +- x-pack/plugin/kql/src/main/antlr/KqlBase.g4 | 2 +- .../plugin/kql/src/main/java/module-info.java | 1 + .../elasticsearch/xpack/kql/KqlPlugin.java | 11 + .../xpack/kql/parser/KqlAstBuilder.java | 97 +++-- .../xpack/kql/parser/KqlBase.interp | 2 +- .../xpack/kql/parser/KqlBaseParser.java | 155 ++++---- .../xpack/kql/parser/KqlParser.java | 24 +- .../kql/parser/KqlParserExecutionContext.java | 79 ---- .../xpack/kql/parser/KqlParsingContext.java | 121 ++++++ .../xpack/kql/query/KqlQueryBuilder.java | 199 ++++++++++ .../kql/parser/AbstractKqlParserTestCase.java | 6 +- .../xpack/kql/query/KqlQueryBuilderTests.java | 286 +++++++++++++++ .../elasticsearch/xpack/kql/KqlRestIT.java | 40 ++ .../test/kql/10_kql_basic_query.yml | 212 +++++++++++ .../test/kql/20_kql_match_query.yml | 266 ++++++++++++++ .../test/kql/30_kql_range_query.yml | 343 ++++++++++++++++++ .../test/kql/40_kql_exist_query.yml | 182 ++++++++++ 20 files changed, 1846 insertions(+), 229 deletions(-) delete mode 100644 x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParserExecutionContext.java create mode 100644 x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParsingContext.java create mode 100644 x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilder.java create mode 100644 x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilderTests.java create mode 100644 x-pack/plugin/kql/src/yamlRestTest/java/org/elasticsearch/xpack/kql/KqlRestIT.java create mode 100644 x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/10_kql_basic_query.yml create mode 100644 x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/20_kql_match_query.yml create mode 100644 x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/30_kql_range_query.yml create mode 100644 x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/40_kql_exist_query.yml diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 871e5aa48e3e4..3df6b54110549 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -188,6 +188,7 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_CCS_EXEC_INFO_WITH_FAILURES = def(8_783_00_0); public static final TransportVersion LOGSDB_TELEMETRY = def(8_784_00_0); public static final TransportVersion LOGSDB_TELEMETRY_STATS = def(8_785_00_0); + public static final TransportVersion KQL_QUERY_ADDED = def(8_786_00_0); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java b/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java index 4efdfc66e8b5e..fcc4edae0ae9a 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java @@ -9,6 +9,10 @@ package org.elasticsearch.rest.action.search; +import org.elasticsearch.Build; +import org.elasticsearch.common.util.set.Sets; + +import java.util.Collections; import java.util.Set; /** @@ -24,10 +28,26 @@ private SearchCapabilities() {} private static final String BIT_DENSE_VECTOR_SYNTHETIC_SOURCE_CAPABILITY = "bit_dense_vector_synthetic_source"; /** Support Byte and Float with Bit dot product. */ private static final String BYTE_FLOAT_BIT_DOT_PRODUCT_CAPABILITY = "byte_float_bit_dot_product"; + /** Support kql query. */ + private static final String KQL_QUERY_SUPPORTED = "kql_query"; + + public static final Set CAPABILITIES = capabilities(); + + private static Set capabilities() { + Set capabilities = Set.of( + RANGE_REGEX_INTERVAL_QUERY_CAPABILITY, + BIT_DENSE_VECTOR_SYNTHETIC_SOURCE_CAPABILITY, + BYTE_FLOAT_BIT_DOT_PRODUCT_CAPABILITY, + ); + + if (Build.current().isSnapshot()) { + return Collections.unmodifiableSet(Sets.union(capabilities, snapshotBuildCapabilities())); + } + + return capabilities; + } - public static final Set CAPABILITIES = Set.of( - RANGE_REGEX_INTERVAL_QUERY_CAPABILITY, - BIT_DENSE_VECTOR_SYNTHETIC_SOURCE_CAPABILITY, - BYTE_FLOAT_BIT_DOT_PRODUCT_CAPABILITY - ); + private static Set snapshotBuildCapabilities() { + return Set.of(KQL_QUERY_SUPPORTED); + } } diff --git a/x-pack/plugin/kql/build.gradle b/x-pack/plugin/kql/build.gradle index 9d0860346b188..7e4df5654f225 100644 --- a/x-pack/plugin/kql/build.gradle +++ b/x-pack/plugin/kql/build.gradle @@ -1,8 +1,10 @@ import org.elasticsearch.gradle.internal.info.BuildParams + import static org.elasticsearch.gradle.util.PlatformUtils.normalize apply plugin: 'elasticsearch.internal-es-plugin' apply plugin: 'elasticsearch.internal-cluster-test' +apply plugin: 'elasticsearch.internal-yaml-rest-test' apply plugin: 'elasticsearch.publish' esplugin { @@ -17,19 +19,21 @@ base { dependencies { compileOnly project(path: xpackModule('core')) - api "org.antlr:antlr4-runtime:${versions.antlr4}" + implementation "org.antlr:antlr4-runtime:${versions.antlr4}" testImplementation "org.antlr:antlr4-runtime:${versions.antlr4}" testImplementation project(':test:framework') testImplementation(testArtifact(project(xpackModule('core')))) } -/**************************************************************** - * Enable QA/rest integration tests for snapshot builds only * - * TODO: Enable for all builds upon this feature release * - ****************************************************************/ -if (BuildParams.isSnapshotBuild()) { - addQaCheckDependencies(project) +tasks.named('yamlRestTest') { + usesDefaultDistribution() +}.configure { + /**************************************************************** + * Enable QA/rest integration tests for snapshot builds only * + * TODO: Enable for all builds upon this feature release * + ****************************************************************/ + enabled = BuildParams.isSnapshotBuild() } /********************************** diff --git a/x-pack/plugin/kql/src/main/antlr/KqlBase.g4 b/x-pack/plugin/kql/src/main/antlr/KqlBase.g4 index 52a70b9d4c018..da015b699cb15 100644 --- a/x-pack/plugin/kql/src/main/antlr/KqlBase.g4 +++ b/x-pack/plugin/kql/src/main/antlr/KqlBase.g4 @@ -88,7 +88,7 @@ fieldQueryValue ; fieldName - : value=UNQUOTED_LITERAL+ + : value=UNQUOTED_LITERAL | value=QUOTED_STRING | value=WILDCARD ; diff --git a/x-pack/plugin/kql/src/main/java/module-info.java b/x-pack/plugin/kql/src/main/java/module-info.java index c4dd539508f39..41e51033b9c70 100644 --- a/x-pack/plugin/kql/src/main/java/module-info.java +++ b/x-pack/plugin/kql/src/main/java/module-info.java @@ -16,4 +16,5 @@ exports org.elasticsearch.xpack.kql; exports org.elasticsearch.xpack.kql.parser; + exports org.elasticsearch.xpack.kql.query; } diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/KqlPlugin.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/KqlPlugin.java index 4734924b23618..217513bd2c0da 100644 --- a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/KqlPlugin.java +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/KqlPlugin.java @@ -7,10 +7,21 @@ package org.elasticsearch.xpack.kql; +import org.elasticsearch.Build; import org.elasticsearch.plugins.ExtensiblePlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.SearchPlugin; +import org.elasticsearch.xpack.kql.query.KqlQueryBuilder; + +import java.util.List; public class KqlPlugin extends Plugin implements SearchPlugin, ExtensiblePlugin { + @Override + public List> getQueries() { + if (Build.current().isSnapshot()) { + return List.of(new SearchPlugin.QuerySpec<>(KqlQueryBuilder.NAME, KqlQueryBuilder::new, KqlQueryBuilder::fromXContent)); + } + return List.of(); + } } diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlAstBuilder.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlAstBuilder.java index 5f1106522e47f..67d7da0381d67 100644 --- a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlAstBuilder.java +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlAstBuilder.java @@ -9,6 +9,7 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; +import org.elasticsearch.common.regex.Regex; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; @@ -16,29 +17,34 @@ import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.index.query.RangeQueryBuilder; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; import static org.elasticsearch.common.logging.LoggerMessageFormat.format; -import static org.elasticsearch.xpack.kql.parser.KqlParserExecutionContext.isDateField; -import static org.elasticsearch.xpack.kql.parser.KqlParserExecutionContext.isKeywordField; -import static org.elasticsearch.xpack.kql.parser.KqlParserExecutionContext.isRuntimeField; +import static org.elasticsearch.xpack.kql.parser.KqlParsingContext.isDateField; +import static org.elasticsearch.xpack.kql.parser.KqlParsingContext.isKeywordField; +import static org.elasticsearch.xpack.kql.parser.KqlParsingContext.isRuntimeField; +import static org.elasticsearch.xpack.kql.parser.KqlParsingContext.isSearchableField; import static org.elasticsearch.xpack.kql.parser.ParserUtils.escapeLuceneQueryString; +import static org.elasticsearch.xpack.kql.parser.ParserUtils.extractText; import static org.elasticsearch.xpack.kql.parser.ParserUtils.hasWildcard; +import static org.elasticsearch.xpack.kql.parser.ParserUtils.typedParsing; class KqlAstBuilder extends KqlBaseBaseVisitor { - private final KqlParserExecutionContext kqlParserExecutionContext; + private final KqlParsingContext kqlParsingContext; - KqlAstBuilder(KqlParserExecutionContext kqlParserExecutionContext) { - this.kqlParserExecutionContext = kqlParserExecutionContext; + KqlAstBuilder(KqlParsingContext kqlParsingContext) { + this.kqlParsingContext = kqlParsingContext; } public QueryBuilder toQueryBuilder(ParserRuleContext ctx) { if (ctx instanceof KqlBaseParser.TopLevelQueryContext topLeveQueryContext) { if (topLeveQueryContext.query() != null) { - return ParserUtils.typedParsing(this, topLeveQueryContext.query(), QueryBuilder.class); + return typedParsing(this, topLeveQueryContext.query(), QueryBuilder.class); } return new MatchAllQueryBuilder(); @@ -59,9 +65,9 @@ public QueryBuilder visitAndBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) // 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)) { - ParserUtils.typedParsing(this, subQueryCtx, BoolQueryBuilder.class).must().forEach(builder::must); + typedParsing(this, subQueryCtx, BoolQueryBuilder.class).must().forEach(builder::must); } else { - builder.must(ParserUtils.typedParsing(this, subQueryCtx, QueryBuilder.class)); + builder.must(typedParsing(this, subQueryCtx, QueryBuilder.class)); } } @@ -73,9 +79,9 @@ public QueryBuilder visitOrBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) { for (ParserRuleContext subQueryCtx : ctx.query()) { if (subQueryCtx instanceof KqlBaseParser.BooleanQueryContext booleanSubQueryCtx && isOrQuery(booleanSubQueryCtx)) { - ParserUtils.typedParsing(this, subQueryCtx, BoolQueryBuilder.class).should().forEach(builder::should); + typedParsing(this, subQueryCtx, BoolQueryBuilder.class).should().forEach(builder::should); } else { - builder.should(ParserUtils.typedParsing(this, subQueryCtx, QueryBuilder.class)); + builder.should(typedParsing(this, subQueryCtx, QueryBuilder.class)); } } @@ -84,12 +90,12 @@ public QueryBuilder visitOrBooleanQuery(KqlBaseParser.BooleanQueryContext ctx) { @Override public QueryBuilder visitNotQuery(KqlBaseParser.NotQueryContext ctx) { - return QueryBuilders.boolQuery().mustNot(ParserUtils.typedParsing(this, ctx.simpleQuery(), QueryBuilder.class)); + return QueryBuilders.boolQuery().mustNot(typedParsing(this, ctx.simpleQuery(), QueryBuilder.class)); } @Override public QueryBuilder visitParenthesizedQuery(KqlBaseParser.ParenthesizedQueryContext ctx) { - return ParserUtils.typedParsing(this, ctx.query(), QueryBuilder.class); + return typedParsing(this, ctx.query(), QueryBuilder.class); } @Override @@ -121,12 +127,16 @@ public QueryBuilder visitExistsQuery(KqlBaseParser.ExistsQueryContext ctx) { public QueryBuilder visitRangeQuery(KqlBaseParser.RangeQueryContext ctx) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().minimumShouldMatch(1); - String queryText = ParserUtils.extractText(ctx.rangeQueryValue()); + String queryText = extractText(ctx.rangeQueryValue()); BiFunction rangeOperation = rangeOperation(ctx.operator); withFields(ctx.fieldName(), (fieldName, mappedFieldType) -> { RangeQueryBuilder rangeQuery = rangeOperation.apply(QueryBuilders.rangeQuery(fieldName), queryText); - // TODO: add timezone for date fields + + if (kqlParsingContext.timeZone() != null) { + rangeQuery.timeZone(kqlParsingContext.timeZone().getId()); + } + boolQueryBuilder.should(rangeQuery); }); @@ -135,42 +145,54 @@ public QueryBuilder visitRangeQuery(KqlBaseParser.RangeQueryContext ctx) { @Override public QueryBuilder visitFieldLessQuery(KqlBaseParser.FieldLessQueryContext ctx) { - String queryText = ParserUtils.extractText(ctx.fieldQueryValue()); + String queryText = extractText(ctx.fieldQueryValue()); if (hasWildcard(ctx.fieldQueryValue())) { - // TODO: set default fields. - return QueryBuilders.queryStringQuery(escapeLuceneQueryString(queryText, true)); + QueryStringQueryBuilder queryString = QueryBuilders.queryStringQuery(escapeLuceneQueryString(queryText, true)); + if (kqlParsingContext.defaultField() != null) { + queryString.defaultField(kqlParsingContext.defaultField()); + } + return queryString; } boolean isPhraseMatch = ctx.fieldQueryValue().QUOTED_STRING() != null; - return QueryBuilders.multiMatchQuery(queryText) - // TODO: add default fields? + MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(queryText) .type(isPhraseMatch ? MultiMatchQueryBuilder.Type.PHRASE : MultiMatchQueryBuilder.Type.BEST_FIELDS) .lenient(true); + + if (kqlParsingContext.defaultField() != null) { + kqlParsingContext.resolveDefaultFieldNames() + .stream() + .filter(kqlParsingContext::isSearchableField) + .forEach(multiMatchQuery::field); + } + + return multiMatchQuery; } @Override public QueryBuilder visitFieldQuery(KqlBaseParser.FieldQueryContext ctx) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().minimumShouldMatch(1); - String queryText = ParserUtils.extractText(ctx.fieldQueryValue()); + String queryText = extractText(ctx.fieldQueryValue()); boolean hasWildcard = hasWildcard(ctx.fieldQueryValue()); withFields(ctx.fieldName(), (fieldName, mappedFieldType) -> { QueryBuilder fieldQuery = null; if (hasWildcard && isKeywordField(mappedFieldType)) { - fieldQuery = QueryBuilders.wildcardQuery(fieldName, queryText) - .caseInsensitive(kqlParserExecutionContext.isCaseSensitive() == false); + fieldQuery = QueryBuilders.wildcardQuery(fieldName, queryText).caseInsensitive(kqlParsingContext.caseInsensitive()); } else if (hasWildcard) { fieldQuery = QueryBuilders.queryStringQuery(escapeLuceneQueryString(queryText, true)).field(fieldName); } else if (isDateField(mappedFieldType)) { - // TODO: add timezone - fieldQuery = QueryBuilders.rangeQuery(fieldName).gte(queryText).lte(queryText); + RangeQueryBuilder rangeFieldQuery = QueryBuilders.rangeQuery(fieldName).gte(queryText).lte(queryText); + if (kqlParsingContext.timeZone() != null) { + rangeFieldQuery.timeZone(kqlParsingContext.timeZone().getId()); + } + fieldQuery = rangeFieldQuery; } else if (isKeywordField(mappedFieldType)) { - fieldQuery = QueryBuilders.termQuery(fieldName, queryText) - .caseInsensitive(kqlParserExecutionContext.isCaseSensitive() == false); + fieldQuery = QueryBuilders.termQuery(fieldName, queryText).caseInsensitive(kqlParsingContext.caseInsensitive()); } else if (ctx.fieldQueryValue().QUOTED_STRING() != null) { fieldQuery = QueryBuilders.matchPhraseQuery(fieldName, queryText); } else { @@ -194,7 +216,26 @@ private static boolean isOrQuery(KqlBaseParser.BooleanQueryContext ctx) { } private void withFields(KqlBaseParser.FieldNameContext ctx, BiConsumer fieldConsummer) { - kqlParserExecutionContext.resolveFields(ctx).forEach(fieldDef -> fieldConsummer.accept(fieldDef.v1(), fieldDef.v2())); + assert ctx != null : "Field ctx cannot be null"; + String fieldNamePattern = extractText(ctx); + Set fieldNames = kqlParsingContext.resolveFieldNames(fieldNamePattern); + + if (ctx.value.getType() == KqlBaseParser.QUOTED_STRING && Regex.isSimpleMatchPattern(fieldNamePattern)) { + // When using quoted string, wildcards are not expanded. + // No field can match and we can return early. + return; + } + + if (ctx.value.getType() == KqlBaseParser.QUOTED_STRING) { + assert fieldNames.size() < 2 : "expecting only one matching field"; + } + + fieldNames.forEach(fieldName -> { + MappedFieldType fieldType = kqlParsingContext.fieldType(fieldName); + if (isSearchableField(fieldName, fieldType)) { + fieldConsummer.accept(fieldName, fieldType); + } + }); } private QueryBuilder rewriteDisjunctionQuery(BoolQueryBuilder boolQueryBuilder) { diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase.interp b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase.interp index 2b09dd52e95b0..7af37d7e3c3b5 100644 --- a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase.interp +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase.interp @@ -54,4 +54,4 @@ fieldName atn: -[4, 1, 16, 140, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 1, 0, 3, 0, 30, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 40, 8, 1, 10, 1, 12, 1, 43, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 53, 8, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 3, 5, 66, 8, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 4, 8, 79, 8, 8, 11, 8, 12, 8, 80, 1, 8, 3, 8, 84, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 100, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 107, 8, 11, 1, 12, 3, 12, 110, 8, 12, 1, 12, 4, 12, 113, 8, 12, 11, 12, 12, 12, 114, 1, 12, 3, 12, 118, 8, 12, 1, 12, 1, 12, 3, 12, 122, 8, 12, 1, 12, 1, 12, 3, 12, 126, 8, 12, 1, 12, 3, 12, 129, 8, 12, 1, 13, 4, 13, 132, 8, 13, 11, 13, 12, 13, 133, 1, 13, 1, 13, 3, 13, 138, 8, 13, 1, 13, 0, 1, 2, 14, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 0, 4, 1, 0, 2, 3, 1, 0, 6, 9, 2, 0, 14, 14, 16, 16, 1, 0, 2, 4, 150, 0, 29, 1, 0, 0, 0, 2, 33, 1, 0, 0, 0, 4, 52, 1, 0, 0, 0, 6, 54, 1, 0, 0, 0, 8, 57, 1, 0, 0, 0, 10, 65, 1, 0, 0, 0, 12, 69, 1, 0, 0, 0, 14, 73, 1, 0, 0, 0, 16, 83, 1, 0, 0, 0, 18, 85, 1, 0, 0, 0, 20, 99, 1, 0, 0, 0, 22, 106, 1, 0, 0, 0, 24, 128, 1, 0, 0, 0, 26, 137, 1, 0, 0, 0, 28, 30, 3, 2, 1, 0, 29, 28, 1, 0, 0, 0, 29, 30, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 32, 5, 0, 0, 1, 32, 1, 1, 0, 0, 0, 33, 34, 6, 1, -1, 0, 34, 35, 3, 4, 2, 0, 35, 41, 1, 0, 0, 0, 36, 37, 10, 2, 0, 0, 37, 38, 7, 0, 0, 0, 38, 40, 3, 2, 1, 2, 39, 36, 1, 0, 0, 0, 40, 43, 1, 0, 0, 0, 41, 39, 1, 0, 0, 0, 41, 42, 1, 0, 0, 0, 42, 3, 1, 0, 0, 0, 43, 41, 1, 0, 0, 0, 44, 53, 3, 6, 3, 0, 45, 53, 3, 8, 4, 0, 46, 53, 3, 12, 6, 0, 47, 53, 3, 10, 5, 0, 48, 53, 3, 18, 9, 0, 49, 53, 3, 14, 7, 0, 50, 53, 3, 20, 10, 0, 51, 53, 3, 22, 11, 0, 52, 44, 1, 0, 0, 0, 52, 45, 1, 0, 0, 0, 52, 46, 1, 0, 0, 0, 52, 47, 1, 0, 0, 0, 52, 48, 1, 0, 0, 0, 52, 49, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 52, 51, 1, 0, 0, 0, 53, 5, 1, 0, 0, 0, 54, 55, 5, 4, 0, 0, 55, 56, 3, 4, 2, 0, 56, 7, 1, 0, 0, 0, 57, 58, 3, 26, 13, 0, 58, 59, 5, 5, 0, 0, 59, 60, 5, 12, 0, 0, 60, 61, 3, 2, 1, 0, 61, 62, 5, 13, 0, 0, 62, 9, 1, 0, 0, 0, 63, 64, 5, 16, 0, 0, 64, 66, 5, 5, 0, 0, 65, 63, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 67, 1, 0, 0, 0, 67, 68, 5, 16, 0, 0, 68, 11, 1, 0, 0, 0, 69, 70, 5, 10, 0, 0, 70, 71, 3, 2, 1, 0, 71, 72, 5, 11, 0, 0, 72, 13, 1, 0, 0, 0, 73, 74, 3, 26, 13, 0, 74, 75, 7, 1, 0, 0, 75, 76, 3, 16, 8, 0, 76, 15, 1, 0, 0, 0, 77, 79, 7, 2, 0, 0, 78, 77, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 78, 1, 0, 0, 0, 80, 81, 1, 0, 0, 0, 81, 84, 1, 0, 0, 0, 82, 84, 5, 15, 0, 0, 83, 78, 1, 0, 0, 0, 83, 82, 1, 0, 0, 0, 84, 17, 1, 0, 0, 0, 85, 86, 3, 26, 13, 0, 86, 87, 5, 5, 0, 0, 87, 88, 5, 16, 0, 0, 88, 19, 1, 0, 0, 0, 89, 90, 3, 26, 13, 0, 90, 91, 5, 5, 0, 0, 91, 92, 3, 24, 12, 0, 92, 100, 1, 0, 0, 0, 93, 94, 3, 26, 13, 0, 94, 95, 5, 5, 0, 0, 95, 96, 5, 10, 0, 0, 96, 97, 3, 24, 12, 0, 97, 98, 5, 11, 0, 0, 98, 100, 1, 0, 0, 0, 99, 89, 1, 0, 0, 0, 99, 93, 1, 0, 0, 0, 100, 21, 1, 0, 0, 0, 101, 107, 3, 24, 12, 0, 102, 103, 5, 10, 0, 0, 103, 104, 3, 24, 12, 0, 104, 105, 5, 11, 0, 0, 105, 107, 1, 0, 0, 0, 106, 101, 1, 0, 0, 0, 106, 102, 1, 0, 0, 0, 107, 23, 1, 0, 0, 0, 108, 110, 7, 3, 0, 0, 109, 108, 1, 0, 0, 0, 109, 110, 1, 0, 0, 0, 110, 112, 1, 0, 0, 0, 111, 113, 7, 2, 0, 0, 112, 111, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 112, 1, 0, 0, 0, 114, 115, 1, 0, 0, 0, 115, 117, 1, 0, 0, 0, 116, 118, 7, 3, 0, 0, 117, 116, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 129, 1, 0, 0, 0, 119, 121, 7, 0, 0, 0, 120, 122, 7, 3, 0, 0, 121, 120, 1, 0, 0, 0, 121, 122, 1, 0, 0, 0, 122, 129, 1, 0, 0, 0, 123, 125, 5, 4, 0, 0, 124, 126, 7, 0, 0, 0, 125, 124, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 129, 5, 15, 0, 0, 128, 109, 1, 0, 0, 0, 128, 119, 1, 0, 0, 0, 128, 123, 1, 0, 0, 0, 128, 127, 1, 0, 0, 0, 129, 25, 1, 0, 0, 0, 130, 132, 5, 14, 0, 0, 131, 130, 1, 0, 0, 0, 132, 133, 1, 0, 0, 0, 133, 131, 1, 0, 0, 0, 133, 134, 1, 0, 0, 0, 134, 138, 1, 0, 0, 0, 135, 138, 5, 15, 0, 0, 136, 138, 5, 16, 0, 0, 137, 131, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 137, 136, 1, 0, 0, 0, 138, 27, 1, 0, 0, 0, 16, 29, 41, 52, 65, 80, 83, 99, 106, 109, 114, 117, 121, 125, 128, 133, 137] \ No newline at end of file +[4, 1, 16, 136, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 1, 0, 3, 0, 30, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 40, 8, 1, 10, 1, 12, 1, 43, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 53, 8, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 3, 5, 66, 8, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 4, 8, 79, 8, 8, 11, 8, 12, 8, 80, 1, 8, 3, 8, 84, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 100, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 107, 8, 11, 1, 12, 3, 12, 110, 8, 12, 1, 12, 4, 12, 113, 8, 12, 11, 12, 12, 12, 114, 1, 12, 3, 12, 118, 8, 12, 1, 12, 1, 12, 3, 12, 122, 8, 12, 1, 12, 1, 12, 3, 12, 126, 8, 12, 1, 12, 3, 12, 129, 8, 12, 1, 13, 1, 13, 1, 13, 3, 13, 134, 8, 13, 1, 13, 0, 1, 2, 14, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 0, 4, 1, 0, 2, 3, 1, 0, 6, 9, 2, 0, 14, 14, 16, 16, 1, 0, 2, 4, 145, 0, 29, 1, 0, 0, 0, 2, 33, 1, 0, 0, 0, 4, 52, 1, 0, 0, 0, 6, 54, 1, 0, 0, 0, 8, 57, 1, 0, 0, 0, 10, 65, 1, 0, 0, 0, 12, 69, 1, 0, 0, 0, 14, 73, 1, 0, 0, 0, 16, 83, 1, 0, 0, 0, 18, 85, 1, 0, 0, 0, 20, 99, 1, 0, 0, 0, 22, 106, 1, 0, 0, 0, 24, 128, 1, 0, 0, 0, 26, 133, 1, 0, 0, 0, 28, 30, 3, 2, 1, 0, 29, 28, 1, 0, 0, 0, 29, 30, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 32, 5, 0, 0, 1, 32, 1, 1, 0, 0, 0, 33, 34, 6, 1, -1, 0, 34, 35, 3, 4, 2, 0, 35, 41, 1, 0, 0, 0, 36, 37, 10, 2, 0, 0, 37, 38, 7, 0, 0, 0, 38, 40, 3, 2, 1, 2, 39, 36, 1, 0, 0, 0, 40, 43, 1, 0, 0, 0, 41, 39, 1, 0, 0, 0, 41, 42, 1, 0, 0, 0, 42, 3, 1, 0, 0, 0, 43, 41, 1, 0, 0, 0, 44, 53, 3, 6, 3, 0, 45, 53, 3, 8, 4, 0, 46, 53, 3, 12, 6, 0, 47, 53, 3, 10, 5, 0, 48, 53, 3, 18, 9, 0, 49, 53, 3, 14, 7, 0, 50, 53, 3, 20, 10, 0, 51, 53, 3, 22, 11, 0, 52, 44, 1, 0, 0, 0, 52, 45, 1, 0, 0, 0, 52, 46, 1, 0, 0, 0, 52, 47, 1, 0, 0, 0, 52, 48, 1, 0, 0, 0, 52, 49, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 52, 51, 1, 0, 0, 0, 53, 5, 1, 0, 0, 0, 54, 55, 5, 4, 0, 0, 55, 56, 3, 4, 2, 0, 56, 7, 1, 0, 0, 0, 57, 58, 3, 26, 13, 0, 58, 59, 5, 5, 0, 0, 59, 60, 5, 12, 0, 0, 60, 61, 3, 2, 1, 0, 61, 62, 5, 13, 0, 0, 62, 9, 1, 0, 0, 0, 63, 64, 5, 16, 0, 0, 64, 66, 5, 5, 0, 0, 65, 63, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 67, 1, 0, 0, 0, 67, 68, 5, 16, 0, 0, 68, 11, 1, 0, 0, 0, 69, 70, 5, 10, 0, 0, 70, 71, 3, 2, 1, 0, 71, 72, 5, 11, 0, 0, 72, 13, 1, 0, 0, 0, 73, 74, 3, 26, 13, 0, 74, 75, 7, 1, 0, 0, 75, 76, 3, 16, 8, 0, 76, 15, 1, 0, 0, 0, 77, 79, 7, 2, 0, 0, 78, 77, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 78, 1, 0, 0, 0, 80, 81, 1, 0, 0, 0, 81, 84, 1, 0, 0, 0, 82, 84, 5, 15, 0, 0, 83, 78, 1, 0, 0, 0, 83, 82, 1, 0, 0, 0, 84, 17, 1, 0, 0, 0, 85, 86, 3, 26, 13, 0, 86, 87, 5, 5, 0, 0, 87, 88, 5, 16, 0, 0, 88, 19, 1, 0, 0, 0, 89, 90, 3, 26, 13, 0, 90, 91, 5, 5, 0, 0, 91, 92, 3, 24, 12, 0, 92, 100, 1, 0, 0, 0, 93, 94, 3, 26, 13, 0, 94, 95, 5, 5, 0, 0, 95, 96, 5, 10, 0, 0, 96, 97, 3, 24, 12, 0, 97, 98, 5, 11, 0, 0, 98, 100, 1, 0, 0, 0, 99, 89, 1, 0, 0, 0, 99, 93, 1, 0, 0, 0, 100, 21, 1, 0, 0, 0, 101, 107, 3, 24, 12, 0, 102, 103, 5, 10, 0, 0, 103, 104, 3, 24, 12, 0, 104, 105, 5, 11, 0, 0, 105, 107, 1, 0, 0, 0, 106, 101, 1, 0, 0, 0, 106, 102, 1, 0, 0, 0, 107, 23, 1, 0, 0, 0, 108, 110, 7, 3, 0, 0, 109, 108, 1, 0, 0, 0, 109, 110, 1, 0, 0, 0, 110, 112, 1, 0, 0, 0, 111, 113, 7, 2, 0, 0, 112, 111, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 112, 1, 0, 0, 0, 114, 115, 1, 0, 0, 0, 115, 117, 1, 0, 0, 0, 116, 118, 7, 3, 0, 0, 117, 116, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 129, 1, 0, 0, 0, 119, 121, 7, 0, 0, 0, 120, 122, 7, 3, 0, 0, 121, 120, 1, 0, 0, 0, 121, 122, 1, 0, 0, 0, 122, 129, 1, 0, 0, 0, 123, 125, 5, 4, 0, 0, 124, 126, 7, 0, 0, 0, 125, 124, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 129, 5, 15, 0, 0, 128, 109, 1, 0, 0, 0, 128, 119, 1, 0, 0, 0, 128, 123, 1, 0, 0, 0, 128, 127, 1, 0, 0, 0, 129, 25, 1, 0, 0, 0, 130, 134, 5, 14, 0, 0, 131, 134, 5, 15, 0, 0, 132, 134, 5, 16, 0, 0, 133, 130, 1, 0, 0, 0, 133, 131, 1, 0, 0, 0, 133, 132, 1, 0, 0, 0, 134, 27, 1, 0, 0, 0, 15, 29, 41, 52, 65, 80, 83, 99, 106, 109, 114, 117, 121, 125, 128, 133] \ No newline at end of file diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseParser.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseParser.java index b4b0a69a82387..118ac32aadd61 100644 --- a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseParser.java +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlBaseParser.java @@ -1194,10 +1194,7 @@ public final FieldQueryValueContext fieldQueryValue() throws RecognitionExceptio @SuppressWarnings("CheckReturnValue") public static class FieldNameContext extends ParserRuleContext { public Token value; - public List UNQUOTED_LITERAL() { return getTokens(KqlBaseParser.UNQUOTED_LITERAL); } - public TerminalNode UNQUOTED_LITERAL(int i) { - return getToken(KqlBaseParser.UNQUOTED_LITERAL, i); - } + public TerminalNode UNQUOTED_LITERAL() { return getToken(KqlBaseParser.UNQUOTED_LITERAL, 0); } public TerminalNode QUOTED_STRING() { return getToken(KqlBaseParser.QUOTED_STRING, 0); } public TerminalNode WILDCARD() { return getToken(KqlBaseParser.WILDCARD, 0); } public FieldNameContext(ParserRuleContext parent, int invokingState) { @@ -1222,41 +1219,28 @@ public T accept(ParseTreeVisitor visitor) { public final FieldNameContext fieldName() throws RecognitionException { FieldNameContext _localctx = new FieldNameContext(_ctx, getState()); enterRule(_localctx, 26, RULE_fieldName); - int _la; try { - setState(137); + setState(133); _errHandler.sync(this); switch (_input.LA(1)) { case UNQUOTED_LITERAL: enterOuterAlt(_localctx, 1); { - setState(131); - _errHandler.sync(this); - _la = _input.LA(1); - do { - { - { - setState(130); - ((FieldNameContext)_localctx).value = match(UNQUOTED_LITERAL); - } - } - setState(133); - _errHandler.sync(this); - _la = _input.LA(1); - } while ( _la==UNQUOTED_LITERAL ); + setState(130); + ((FieldNameContext)_localctx).value = match(UNQUOTED_LITERAL); } break; case QUOTED_STRING: enterOuterAlt(_localctx, 2); { - setState(135); + setState(131); ((FieldNameContext)_localctx).value = match(QUOTED_STRING); } break; case WILDCARD: enterOuterAlt(_localctx, 3); { - setState(136); + setState(132); ((FieldNameContext)_localctx).value = match(WILDCARD); } break; @@ -1291,7 +1275,7 @@ private boolean query_sempred(QueryContext _localctx, int predIndex) { } public static final String _serializedATN = - "\u0004\u0001\u0010\u008c\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u0010\u0088\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"+ @@ -1309,70 +1293,67 @@ private boolean query_sempred(QueryContext _localctx, int predIndex) { "\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\u0004\r\u0084\b\r\u000b\r\f"+ - "\r\u0085\u0001\r\u0001\r\u0003\r\u008a\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\u0096\u0000\u001d\u0001\u0000"+ - "\u0000\u0000\u0002!\u0001\u0000\u0000\u0000\u00044\u0001\u0000\u0000\u0000"+ - "\u00066\u0001\u0000\u0000\u0000\b9\u0001\u0000\u0000\u0000\nA\u0001\u0000"+ - "\u0000\u0000\fE\u0001\u0000\u0000\u0000\u000eI\u0001\u0000\u0000\u0000"+ - "\u0010S\u0001\u0000\u0000\u0000\u0012U\u0001\u0000\u0000\u0000\u0014c"+ - "\u0001\u0000\u0000\u0000\u0016j\u0001\u0000\u0000\u0000\u0018\u0080\u0001"+ - "\u0000\u0000\u0000\u001a\u0089\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\u000041\u0001\u0000\u0000\u0000"+ - "42\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\u0000"+ - "EF\u0005\n\u0000\u0000FG\u0003\u0002\u0001\u0000GH\u0005\u000b\u0000\u0000"+ - "H\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\u0000QT\u0001\u0000\u0000\u0000"+ - "RT\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\u0000"+ - "YZ\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\u0000ab\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\u0000"+ - "k\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\u0000"+ - "rp\u0001\u0000\u0000\u0000rs\u0001\u0000\u0000\u0000su\u0001\u0000\u0000"+ - "\u0000tv\u0007\u0003\u0000\u0000ut\u0001\u0000\u0000\u0000uv\u0001\u0000"+ - "\u0000\u0000v\u0081\u0001\u0000\u0000\u0000wy\u0007\u0000\u0000\u0000"+ - "xz\u0007\u0003\u0000\u0000yx\u0001\u0000\u0000\u0000yz\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\u0080"+ - "m\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\u0084\u0005\u000e\u0000\u0000\u0083\u0082\u0001"+ - "\u0000\u0000\u0000\u0084\u0085\u0001\u0000\u0000\u0000\u0085\u0083\u0001"+ - "\u0000\u0000\u0000\u0085\u0086\u0001\u0000\u0000\u0000\u0086\u008a\u0001"+ - "\u0000\u0000\u0000\u0087\u008a\u0005\u000f\u0000\u0000\u0088\u008a\u0005"+ - "\u0010\u0000\u0000\u0089\u0083\u0001\u0000\u0000\u0000\u0089\u0087\u0001"+ - "\u0000\u0000\u0000\u0089\u0088\u0001\u0000\u0000\u0000\u008a\u001b\u0001"+ - "\u0000\u0000\u0000\u0010\u001d)4APScjmruy}\u0080\u0085\u0089"; + "\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"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParser.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParser.java index 1064f901cacb8..6c2d30860221a 100644 --- a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParser.java +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParser.java @@ -14,8 +14,8 @@ import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.atn.PredictionMode; +import org.elasticsearch.core.Strings; import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -25,22 +25,14 @@ public class KqlParser { private static final Logger log = LogManager.getLogger(KqlParser.class); - public QueryBuilder parseKqlQuery(String kqlQuery, SearchExecutionContext searchExecutionContext) { - if (log.isDebugEnabled()) { - log.debug("Parsing KQL query: {}", kqlQuery); - } - - return invokeParser( - kqlQuery, - new KqlParserExecutionContext(searchExecutionContext), - KqlBaseParser::topLevelQuery, - KqlAstBuilder::toQueryBuilder - ); + public QueryBuilder parseKqlQuery(String kqlQuery, KqlParsingContext kqlParserContext) { + log.trace("Parsing KQL query: {}", kqlQuery); + return invokeParser(kqlQuery, kqlParserContext, KqlBaseParser::topLevelQuery, KqlAstBuilder::toQueryBuilder); } private T invokeParser( String kqlQuery, - KqlParserExecutionContext kqlParserExecutionContext, + KqlParsingContext kqlParsingContext, Function parseFunction, BiFunction visitor ) { @@ -59,11 +51,9 @@ private T invokeParser( ParserRuleContext tree = parseFunction.apply(parser); - if (log.isTraceEnabled()) { - log.trace("Parse tree: {}", tree.toStringTree()); - } + log.trace(() -> Strings.format("Parse tree: %s", tree.toStringTree())); - return visitor.apply(new KqlAstBuilder(kqlParserExecutionContext), tree); + return visitor.apply(new KqlAstBuilder(kqlParsingContext), tree); } private static final BaseErrorListener ERROR_LISTENER = new BaseErrorListener() { diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParserExecutionContext.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParserExecutionContext.java deleted file mode 100644 index d05c70c6b933f..0000000000000 --- a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParserExecutionContext.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.core.Tuple; -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.query.SearchExecutionContext; - -import java.time.ZoneId; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import static org.elasticsearch.core.Tuple.tuple; - -class KqlParserExecutionContext extends SearchExecutionContext { - - private static final List IGNORED_METADATA_FIELDS = List.of( - "_seq_no", - "_index_mode", - "_routing", - "_ignored", - "_nested_path", - "_field_names" - ); - - private static Predicate> searchableFieldFilter = (fieldDef) -> fieldDef.v2().isSearchable(); - - private static Predicate> ignoredFieldFilter = (fieldDef) -> IGNORED_METADATA_FIELDS.contains( - fieldDef.v1() - ); - - KqlParserExecutionContext(SearchExecutionContext source) { - super(source); - } - - public Iterable> resolveFields(KqlBaseParser.FieldNameContext fieldNameContext) { - // TODO: use index settings default field. - String fieldNamePattern = fieldNameContext != null ? ParserUtils.extractText(fieldNameContext) : "*"; - - if (fieldNameContext != null && fieldNameContext.value != null && fieldNameContext.value.getType() == KqlBaseParser.QUOTED_STRING) { - return isFieldMapped(fieldNamePattern) ? List.of(tuple(fieldNamePattern, getFieldType(fieldNamePattern))) : List.of(); - } - - return getMatchingFieldNames(fieldNamePattern).stream() - .map(fieldName -> tuple(fieldName, getFieldType(fieldName))) - .filter(searchableFieldFilter.and(Predicate.not(ignoredFieldFilter))) - .collect(Collectors.toList()); - } - - public boolean isCaseSensitive() { - // TODO: implementation - return false; - } - - public ZoneId timeZone() { - return null; - } - - public static boolean isRuntimeField(MappedFieldType fieldType) { - return fieldType instanceof AbstractScriptFieldType; - } - - public static boolean isDateField(MappedFieldType fieldType) { - return fieldType.typeName().equals(DateFieldMapper.CONTENT_TYPE); - } - - public static boolean isKeywordField(MappedFieldType fieldType) { - return fieldType.typeName().equals(KeywordFieldMapper.CONTENT_TYPE); - } -} diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParsingContext.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParsingContext.java new file mode 100644 index 0000000000000..5f88080fb3ed4 --- /dev/null +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/parser/KqlParsingContext.java @@ -0,0 +1,121 @@ +/* + * 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.mapper.AbstractScriptFieldType; +import org.elasticsearch.index.mapper.DateFieldMapper; +import org.elasticsearch.index.mapper.KeywordFieldMapper; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.query.QueryRewriteContext; + +import java.time.ZoneId; +import java.util.List; +import java.util.Set; + +public class KqlParsingContext { + + private static final List IGNORED_METADATA_FIELDS = List.of( + "_seq_no", + "_index_mode", + "_routing", + "_ignored", + "_nested_path", + "_field_names" + ); + + public static Builder builder(QueryRewriteContext queryRewriteContext) { + return new Builder(queryRewriteContext); + } + + private QueryRewriteContext queryRewriteContext; + private final boolean caseInsensitive; + private final ZoneId timeZone; + private final String defaultField; + + public KqlParsingContext(QueryRewriteContext queryRewriteContext, boolean caseInsensitive, ZoneId timeZone, String defaultField) { + this.queryRewriteContext = queryRewriteContext; + this.caseInsensitive = caseInsensitive; + this.timeZone = timeZone; + this.defaultField = defaultField; + } + + public boolean caseInsensitive() { + return caseInsensitive; + } + + public ZoneId timeZone() { + return timeZone; + } + + public String defaultField() { + return defaultField; + } + + public Set resolveFieldNames(String fieldNamePattern) { + assert fieldNamePattern != null && fieldNamePattern.isEmpty() == false : "fieldNamePattern cannot be null or empty"; + return queryRewriteContext.getMatchingFieldNames(fieldNamePattern); + } + + public Set resolveDefaultFieldNames() { + return resolveFieldNames(defaultField); + } + + public MappedFieldType fieldType(String fieldName) { + return queryRewriteContext.getFieldType(fieldName); + } + + public static boolean isRuntimeField(MappedFieldType fieldType) { + return fieldType instanceof AbstractScriptFieldType; + } + + public static boolean isDateField(MappedFieldType fieldType) { + return fieldType.typeName().equals(DateFieldMapper.CONTENT_TYPE); + } + + public static boolean isKeywordField(MappedFieldType fieldType) { + return fieldType.typeName().equals(KeywordFieldMapper.CONTENT_TYPE); + } + + public static boolean isSearchableField(String fieldName, MappedFieldType fieldType) { + return IGNORED_METADATA_FIELDS.contains(fieldName) == false && fieldType.isSearchable(); + } + + public boolean isSearchableField(String fieldName) { + return isSearchableField(fieldName, fieldType(fieldName)); + } + + public static class Builder { + private final QueryRewriteContext queryRewriteContext; + private boolean caseInsensitive = true; + private ZoneId timeZone = null; + private String defaultField = null; + + private Builder(QueryRewriteContext queryRewriteContext) { + this.queryRewriteContext = queryRewriteContext; + } + + public KqlParsingContext build() { + return new KqlParsingContext(queryRewriteContext, caseInsensitive, timeZone, defaultField); + } + + public Builder caseInsensitive(boolean caseInsensitive) { + this.caseInsensitive = caseInsensitive; + return this; + } + + public Builder timeZone(ZoneId timeZone) { + this.timeZone = timeZone; + return this; + } + + public Builder defaultField(String defaultField) { + this.defaultField = defaultField; + return this; + } + } +} diff --git a/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilder.java b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilder.java new file mode 100644 index 0000000000000..5dff9126b6be4 --- /dev/null +++ b/x-pack/plugin/kql/src/main/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilder.java @@ -0,0 +1,199 @@ +/* + * 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.query; + +import org.apache.lucene.search.Query; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.index.query.AbstractQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryRewriteContext; +import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xpack.kql.parser.KqlParser; +import org.elasticsearch.xpack.kql.parser.KqlParsingContext; + +import java.io.IOException; +import java.time.ZoneId; +import java.util.Objects; + +import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class KqlQueryBuilder extends AbstractQueryBuilder { + public static final String NAME = "kql"; + public static final ParseField QUERY_FIELD = new ParseField("query"); + private static final ParseField CASE_INSENSITIVE_FIELD = new ParseField("case_insensitive"); + private static final ParseField TIME_ZONE_FIELD = new ParseField("time_zone"); + private static final ParseField DEFAULT_FIELD_FIELD = new ParseField("default_field"); + + private static final Logger log = LogManager.getLogger(KqlQueryBuilder.class); + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, a -> { + KqlQueryBuilder kqlQuery = new KqlQueryBuilder((String) a[0]); + + if (a[1] != null) { + kqlQuery.caseInsensitive((Boolean) a[1]); + } + + if (a[2] != null) { + kqlQuery.timeZone((String) a[2]); + } + + if (a[3] != null) { + kqlQuery.defaultField((String) a[3]); + } + + return kqlQuery; + }); + + static { + PARSER.declareString(constructorArg(), QUERY_FIELD); + PARSER.declareBoolean(optionalConstructorArg(), CASE_INSENSITIVE_FIELD); + PARSER.declareString(optionalConstructorArg(), TIME_ZONE_FIELD); + PARSER.declareString(optionalConstructorArg(), DEFAULT_FIELD_FIELD); + declareStandardFields(PARSER); + } + + private final String query; + private boolean caseInsensitive = true; + private ZoneId timeZone; + private String defaultField; + + public KqlQueryBuilder(String query) { + this.query = Objects.requireNonNull(query, "query can not be null"); + } + + public KqlQueryBuilder(StreamInput in) throws IOException { + super(in); + query = in.readString(); + caseInsensitive = in.readBoolean(); + timeZone = in.readOptionalZoneId(); + defaultField = in.readOptionalString(); + } + + public static KqlQueryBuilder fromXContent(XContentParser parser) { + try { + return PARSER.apply(parser, null); + } catch (IllegalArgumentException e) { + throw new ParsingException(parser.getTokenLocation(), e.getMessage(), e); + } + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.KQL_QUERY_ADDED; + } + + public String queryString() { + return query; + } + + public boolean caseInsensitive() { + return caseInsensitive; + } + + public KqlQueryBuilder caseInsensitive(boolean caseInsensitive) { + this.caseInsensitive = caseInsensitive; + return this; + } + + public ZoneId timeZone() { + return timeZone; + } + + public KqlQueryBuilder timeZone(String timeZone) { + this.timeZone = timeZone != null ? ZoneId.of(timeZone) : null; + return this; + } + + public String defaultField() { + return defaultField; + } + + public KqlQueryBuilder defaultField(String defaultField) { + this.defaultField = defaultField; + return this; + } + + @Override + protected void doXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(NAME); + { + builder.field(QUERY_FIELD.getPreferredName(), query); + builder.field(CASE_INSENSITIVE_FIELD.getPreferredName(), caseInsensitive); + + if (defaultField != null) { + builder.field(DEFAULT_FIELD_FIELD.getPreferredName(), defaultField); + } + + if (timeZone != null) { + builder.field(TIME_ZONE_FIELD.getPreferredName(), timeZone.getId()); + } + + boostAndQueryNameToXContent(builder); + } + builder.endObject(); + } + + @Override + protected QueryBuilder doIndexMetadataRewrite(QueryRewriteContext context) throws IOException { + KqlParser parser = new KqlParser(); + QueryBuilder rewrittenQuery = parser.parseKqlQuery(query, createKqlParserContext(context)); + + log.trace(() -> Strings.format("KQL query %s translated to Query DSL: %s", query, Strings.toString(rewrittenQuery))); + + return rewrittenQuery; + } + + @Override + protected Query doToQuery(SearchExecutionContext context) throws IOException { + throw new IllegalStateException("The query should have been rewritten"); + } + + protected void doWriteTo(StreamOutput out) throws IOException { + out.writeString(query); + out.writeBoolean(caseInsensitive); + out.writeOptionalZoneId(timeZone); + out.writeOptionalString(defaultField); + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + protected int doHashCode() { + return Objects.hash(query, caseInsensitive, timeZone, defaultField); + } + + @Override + protected boolean doEquals(KqlQueryBuilder other) { + return Objects.equals(query, other.query) + && Objects.equals(timeZone, other.timeZone) + && Objects.equals(defaultField, other.defaultField) + && caseInsensitive == other.caseInsensitive; + } + + private KqlParsingContext createKqlParserContext(QueryRewriteContext queryRewriteContext) { + return KqlParsingContext.builder(queryRewriteContext) + .caseInsensitive(caseInsensitive) + .timeZone(timeZone) + .defaultField(defaultField) + .build(); + } +} diff --git a/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/AbstractKqlParserTestCase.java b/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/AbstractKqlParserTestCase.java index 88c63e9a2585b..ac06a96d49eb4 100644 --- a/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/AbstractKqlParserTestCase.java +++ b/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/parser/AbstractKqlParserTestCase.java @@ -16,7 +16,6 @@ import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.index.query.RangeQueryBuilder; -import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.query.WildcardQueryBuilder; import org.elasticsearch.test.AbstractBuilderTestCase; @@ -111,9 +110,8 @@ protected List searchableFields(String fieldNamePattern) { protected QueryBuilder parseKqlQuery(String kqlQuery) { KqlParser parser = new KqlParser(); - SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); - - return parser.parseKqlQuery(kqlQuery, searchExecutionContext); + KqlParsingContext kqlParserContext = KqlParsingContext.builder(createQueryRewriteContext()).build(); + return parser.parseKqlQuery(kqlQuery, kqlParserContext); } protected static void assertMultiMatchQuery(QueryBuilder query, String expectedValue, MultiMatchQueryBuilder.Type expectedType) { diff --git a/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilderTests.java b/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilderTests.java new file mode 100644 index 0000000000000..2bc23c7d457dd --- /dev/null +++ b/x-pack/plugin/kql/src/test/java/org/elasticsearch/xpack/kql/query/KqlQueryBuilderTests.java @@ -0,0 +1,286 @@ +/* + * 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.query; + +import org.apache.lucene.search.Query; +import org.elasticsearch.core.Strings; +import org.elasticsearch.index.query.MultiMatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryRewriteContext; +import org.elasticsearch.index.query.QueryStringQueryBuilder; +import org.elasticsearch.index.query.RangeQueryBuilder; +import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.index.query.TermQueryBuilder; +import org.elasticsearch.index.query.WildcardQueryBuilder; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.test.AbstractQueryTestCase; +import org.elasticsearch.xpack.kql.KqlPlugin; +import org.hamcrest.Matchers; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; + +public class KqlQueryBuilderTests extends AbstractQueryTestCase { + + @Override + protected Collection> getPlugins() { + return List.of(KqlPlugin.class); + } + + @Override + protected KqlQueryBuilder doCreateTestQueryBuilder() { + KqlQueryBuilder kqlQueryBuilder = new KqlQueryBuilder(generateRandomKqlQuery()); + + if (randomBoolean()) { + kqlQueryBuilder.caseInsensitive(randomBoolean()); + } + + if (randomBoolean()) { + kqlQueryBuilder.timeZone(randomTimeZone().getID()); + } + + if (randomBoolean()) { + kqlQueryBuilder.defaultField(randomFrom("*", "mapped_*", KEYWORD_FIELD_NAME, TEXT_FIELD_NAME)); + } + + return kqlQueryBuilder; + } + + @Override + public KqlQueryBuilder mutateInstance(KqlQueryBuilder instance) throws IOException { + if (randomBoolean()) { + // Change name or boost. + return super.mutateInstance(instance); + } + + KqlQueryBuilder kqlQueryBuilder = new KqlQueryBuilder(randomValueOtherThan(instance.queryString(), this::generateRandomKqlQuery)) + .caseInsensitive(instance.caseInsensitive()) + .timeZone(instance.timeZone() != null ? instance.timeZone().getId() : null) + .defaultField(instance.defaultField()); + + if (kqlQueryBuilder.queryString().equals(instance.queryString()) == false) { + return kqlQueryBuilder; + } + + switch (randomInt() % 3) { + case 0 -> { + kqlQueryBuilder.caseInsensitive(instance.caseInsensitive() == false); + } + case 1 -> { + if (randomBoolean() && instance.defaultField() != null) { + kqlQueryBuilder.defaultField(null); + } else { + kqlQueryBuilder.defaultField( + randomValueOtherThan( + instance.defaultField(), + () -> randomFrom("*", "mapped_*", KEYWORD_FIELD_NAME, TEXT_FIELD_NAME) + ) + ); + } + } + default -> { + if (randomBoolean() && instance.timeZone() != null) { + kqlQueryBuilder.timeZone(null); + } else if (instance.timeZone() != null) { + kqlQueryBuilder.timeZone(randomValueOtherThan(instance.timeZone().getId(), () -> randomTimeZone().getID())); + } else { + kqlQueryBuilder.timeZone(randomTimeZone().getID()); + } + } + } + ; + + return kqlQueryBuilder; + } + + @Override + protected void doAssertLuceneQuery(KqlQueryBuilder queryBuilder, Query query, SearchExecutionContext context) throws IOException { + // We're not validating the query content here because it would be too complex. + // Instead, we use ad-hoc parser tests with a predictable output. + } + + private String generateRandomKqlQuery() { + return Stream.generate(() -> { + Stream terms = Stream.generate( + () -> randomValueOtherThanMany(s -> s.toLowerCase(Locale.ROOT).contains("now"), () -> randomAlphaOfLengthBetween(4, 10)) + ).limit(randomIntBetween(1, 5)); + + String subQuery = terms.collect(Collectors.joining(" ")); + + if (randomBoolean() && subQuery.isEmpty() == false) { + String operator = randomFrom(":", "<", "<=", ">", ">="); + String fieldName = randomFrom(KEYWORD_FIELD_NAME, TEXT_FIELD_NAME); + if (operator.equals(":")) { + subQuery = switch (randomFrom(0, 2)) { + case 0 -> subQuery; + case 1 -> '(' + subQuery + ')'; + default -> '"' + subQuery + '"'; + }; + } else { + fieldName = randomFrom(KEYWORD_FIELD_NAME, TEXT_FIELD_NAME, DOUBLE_FIELD_NAME, INT_FIELD_NAME); + if (List.of(DOUBLE_FIELD_NAME, INT_FIELD_NAME).contains(fieldName)) { + subQuery = String.valueOf(randomDouble()); + } + subQuery = randomBoolean() ? '"' + subQuery + '"' : subQuery; + } + + subQuery = fieldName + operator + subQuery; + } + + if (randomBoolean() && subQuery.isEmpty() == false) { + subQuery = '(' + subQuery + ')'; + } + + if (randomBoolean()) { + subQuery = "NOT " + subQuery; + } + + if (randomBoolean() && subQuery.isEmpty() == false) { + subQuery = '(' + subQuery + ')'; + } + + return subQuery; + }).limit(randomIntBetween(0, 5)).collect(Collectors.joining(randomFrom(" OR ", " AND "))); + } + + @Override + public void testMustRewrite() throws IOException { + SearchExecutionContext context = createSearchExecutionContext(); + context.setAllowUnmappedFields(true); + KqlQueryBuilder queryBuilder = createTestQueryBuilder(); + IllegalStateException e = assertThrows(IllegalStateException.class, () -> queryBuilder.toQuery(context)); + assertThat(e.getMessage(), Matchers.containsString("The query should have been rewritten")); + } + + public void testCaseInsensitiveWildcardQuery() throws IOException { + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + + for (boolean caseInsensitive : List.of(true, false)) { + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(KEYWORD_FIELD_NAME + ": foo*"); + // Check case case_insensitive is true by default + assertThat(kqlQuery.caseInsensitive(), equalTo(true)); + + kqlQuery.caseInsensitive(caseInsensitive); + + ; + assertThat( + asInstanceOf(WildcardQueryBuilder.class, rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext)) + .caseInsensitive(), + equalTo(caseInsensitive) + ); + } + } + + public void testCaseInsensitiveTermQuery() throws IOException { + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + + for (boolean caseInsensitive : List.of(true, false)) { + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(KEYWORD_FIELD_NAME + ": foo"); + // Check case case_insensitive is true by default + assertThat(kqlQuery.caseInsensitive(), equalTo(true)); + + kqlQuery.caseInsensitive(caseInsensitive); + + assertThat( + asInstanceOf(TermQueryBuilder.class, rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext)).caseInsensitive(), + equalTo(caseInsensitive) + ); + } + } + + public void testTimeZone() throws IOException { + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + String timeZone = randomTimeZone().getID(); + + for (String operator : List.of(":", "<", "<=", ">", ">=")) { + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(Strings.format("%s %s %s", DATE_FIELD_NAME, operator, "2018-03-28")); + assertThat(kqlQuery.timeZone(), nullValue()); // timeZone is not set by default. + kqlQuery.timeZone(timeZone); + + assertThat( + asInstanceOf(RangeQueryBuilder.class, rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext)).timeZone(), + equalTo(timeZone) + ); + } + } + + public void testDefaultFieldWildcardQuery() throws IOException { + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(Strings.format("foo*")); + assertThat(kqlQuery.defaultField(), nullValue()); // default_field is not set by default. + + kqlQuery.defaultField(TEXT_FIELD_NAME); + + assertThat( + asInstanceOf(QueryStringQueryBuilder.class, rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext)).defaultField(), + equalTo(TEXT_FIELD_NAME) + ); + } + + public void testDefaultFieldMatchQuery() throws IOException { + + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + + { + // Using a specific field name + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(Strings.format("foo")); + assertThat(kqlQuery.defaultField(), nullValue()); // default_field is not set by default. + + kqlQuery.defaultField(TEXT_FIELD_NAME); + MultiMatchQueryBuilder rewritenQuery = asInstanceOf( + MultiMatchQueryBuilder.class, + rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext) + ); + assertThat(rewritenQuery.fields().keySet(), contains(TEXT_FIELD_NAME)); + } + + { + // Using a pattern for as the field name + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(Strings.format("foo")); + assertThat(kqlQuery.defaultField(), nullValue()); // default_field is not set by default. + + kqlQuery.defaultField("mapped_object.*"); + MultiMatchQueryBuilder rewritenQuery = asInstanceOf( + MultiMatchQueryBuilder.class, + rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext) + ); + assertThat(rewritenQuery.fields().keySet(), contains("mapped_object.mapped_date", "mapped_object.mapped_int")); + } + } + + public void testQueryNameIsPreserved() throws IOException { + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(generateRandomKqlQuery()).queryName(randomIdentifier()); + QueryBuilder rewrittenQuery = rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext); + assertThat(rewrittenQuery.queryName(), equalTo(kqlQuery.queryName())); + } + + public void testQueryBoostIsPreserved() throws IOException { + QueryRewriteContext queryRewriteContext = createQueryRewriteContext(); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + + KqlQueryBuilder kqlQuery = new KqlQueryBuilder(generateRandomKqlQuery()).boost(randomFloatBetween(0, Float.MAX_VALUE, true)); + QueryBuilder rewrittenQuery = rewriteQuery(kqlQuery, queryRewriteContext, searchExecutionContext); + assertThat(rewrittenQuery.boost(), equalTo(kqlQuery.boost())); + } +} diff --git a/x-pack/plugin/kql/src/yamlRestTest/java/org/elasticsearch/xpack/kql/KqlRestIT.java b/x-pack/plugin/kql/src/yamlRestTest/java/org/elasticsearch/xpack/kql/KqlRestIT.java new file mode 100644 index 0000000000000..35df46b0fdcbb --- /dev/null +++ b/x-pack/plugin/kql/src/yamlRestTest/java/org/elasticsearch/xpack/kql/KqlRestIT.java @@ -0,0 +1,40 @@ +/* + * 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; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.junit.ClassRule; + +public class KqlRestIT extends ESClientYamlSuiteTestCase { + + @ClassRule + public static ElasticsearchCluster cluster = ElasticsearchCluster.local() + .setting("xpack.security.enabled", "false") + .setting("xpack.security.http.ssl.enabled", "false") + .distribution(DistributionType.DEFAULT) + .build(); + + public KqlRestIT(final ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + + @Override + protected String getTestRestCluster() { + return cluster.getHttpAddresses(); + } + + @ParametersFactory + public static Iterable parameters() throws Exception { + return ESClientYamlSuiteTestCase.createParameters(); + } +} diff --git a/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/10_kql_basic_query.yml b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/10_kql_basic_query.yml new file mode 100644 index 0000000000000..bb59c6a48b612 --- /dev/null +++ b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/10_kql_basic_query.yml @@ -0,0 +1,212 @@ +setup: + - requires: + capabilities: + - method: POST + path: /_search + capabilities: [ kql_query ] + test_runner_features: capabilities + reason: KQL query is not available + + - do: + indices.create: + index: test-index + body: + mappings: + properties: + date_field: + type: date + text_field: + type: text + keyword_field: + type: keyword + integer_field: + type: integer + double_field: + type: double + + - do: + bulk: + index: test-index + refresh: true + body: | + { "index" : { "_id": "doc-1" } } + { "text_field": "foo bar", "integer_field": 1, "double_field": 3.5, "date_field": "2010-03-06T14:15:00", "keyword_field": "foo bar" } + { "index" : { "_id": "doc-42" } } + { "text_field": "foo baz", "integer_field": 2, "double_field": 18.9, "date_field": "2018-03-28T20:30:00", "keyword_field": "foo baz" } + +--- +"KQL match all queries": + # KQL empty query are supposed to match all. + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "" } } + } + - match: { hits.total: 2 } + + # Using the *:* syntax + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "*" } } + } + - match: { hits.total: 2 } + + # Using the *:* syntax + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "*:*" } } + } + - match: { hits.total: 2 } + +--- +"KQL match term queries (no field specified)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "bar" } } + } + - 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": "foo bar" } } + } + - match: { hits.total: 2 } + - match: { hits.hits.0._id: "doc-1" } + + # KQL does not match on the _id field when no field is specified. + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "doc-42" } } + } + - match: { hits.total: 0 } + +--- +"KQL match multiple terms queries (no field specified)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "foo bar" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "(foo bar)" } } + } + - match: { hits.total: 2 } + +--- +"KQL match phrase queries (no field specified)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "\"foo bar\"" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + +--- +"KQL match number queries (no field specified)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "2" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "3.5" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + +--- +"KQL match multiple terms queries (no matches)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "no match" } } + } + - match: { hits.total: 0 } + + +--- +"KQL boolean queries": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field: foo AND integer_field > 1" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field: baz OR keyword_field: foo bar" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "NOT text_field: baz" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + diff --git a/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/20_kql_match_query.yml b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/20_kql_match_query.yml new file mode 100644 index 0000000000000..2e40c73ecf829 --- /dev/null +++ b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/20_kql_match_query.yml @@ -0,0 +1,266 @@ +setup: + - requires: + capabilities: + - method: POST + path: /_search + capabilities: [ kql_query ] + test_runner_features: capabilities + reason: KQL query is not available + + - requires: + "test_runner_features": "contains" + + - do: + indices.create: + index: test-index + body: + mappings: + properties: + date_field: + type: date + text_field: + type: text + keyword_field: + type: keyword + integer_field: + type: integer + double_field: + type: double + + - do: + bulk: + index: test-index + refresh: true + body: | + { "index" : { "_id": "doc-1" } } + { "text_field": "foo bar", "integer_field": 1, "double_field": 3.5, "date_field": "2010-03-06T14:15:00", "keyword_field": "foo bar" } + { "index" : { "_id": "doc-42" } } + { "text_field": "foo baz", "integer_field": 2, "double_field": 18.9, "date_field": "2018-03-28T20:30:00", "keyword_field": "foo baz" } + + +--- +"KQL match term queries (text field)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field:bar" } } + } + - 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": "text_field: foo bar" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field: (foo bar)" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field: \"foo bar\"" } } + } + - 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": "text_field: bar*" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + + +--- +"KQL match term queries (integer field)": + - do: + catch: bad_request + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field: foo" } } + } + - match: { error.type: "search_phase_execution_exception" } + - match: { error.root_cause.0.type: "query_shard_exception" } + - match: { error.root_cause.0.reason: "failed to create query: For input string: \"foo\"" } + - contains: { error.root_cause.0.stack_trace: "Caused by: java.lang.NumberFormatException: For input string: \"foo\"" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field: 2" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field: \"2\"" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + +--- +"KQL match term queries (double field)": + - do: + catch: bad_request + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field: foo" } } + } + - match: { error.type: "search_phase_execution_exception" } + - match: { error.root_cause.0.type: "query_shard_exception" } + - match: { error.root_cause.0.reason: "failed to create query: For input string: \"foo\"" } + - contains: { error.root_cause.0.stack_trace: "Caused by: java.lang.NumberFormatException: For input string: \"foo\"" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field: 18.9" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field: \"18.9\"" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + +--- +"KQL match term queries (keyword field)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "keyword_field:foo bar" } } + } + - 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": "keyword_field: \"foo bar\"" } } + } + - 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": "keyword_field: foo ba*" } } + } + - match: { hits.total: 2 } + + +--- +"KQL match term queries (date field)": + - do: + catch: bad_request + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field: foo" } } + } + - match: { error.type: "search_phase_execution_exception" } + - match: { error.root_cause.0.type: "parse_exception" } + - contains: { error.root_cause.0.reason: "failed to parse date field [foo]" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field: 2010-03-06" } } + } + - 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": "date_field: now" } } + } + - match: { hits.total: 0 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field: now/1d" } } + } + - match: { hits.total: 0 } + +--- +"KQL match term queries (search by id)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "_id:doc-1" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } diff --git a/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/30_kql_range_query.yml b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/30_kql_range_query.yml new file mode 100644 index 0000000000000..e03fd41306ba9 --- /dev/null +++ b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/30_kql_range_query.yml @@ -0,0 +1,343 @@ +setup: + - requires: + capabilities: + - method: POST + path: /_search + capabilities: [ kql_query ] + test_runner_features: capabilities + reason: KQL query is not available + + - requires: + "test_runner_features": "contains" + + - do: + indices.create: + index: test-index + body: + mappings: + properties: + date_field: + type: date + text_field: + type: text + keyword_field: + type: keyword + integer_field: + type: integer + double_field: + type: double + + - do: + bulk: + index: test-index + refresh: true + body: | + { "index" : { "_id": "doc-1" } } + { "text_field": "bar", "integer_field": 1, "double_field": 3.5, "date_field": "2010-03-06T14:15:00", "keyword_field": "foo bar" } + { "index" : { "_id": "doc-42" } } + { "text_field": "baz", "integer_field": 2, "double_field": 18.9, "date_field": "2018-03-28T20:30:00", "keyword_field": "foo baz" } + + +--- +"KQL match term queries (text field)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field < baz" } } + } + - 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": "text_field <= baz" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field > bar" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field >= bar" } } + } + - match: { hits.total: 2 } + + +--- +"KQL match term queries (integer field)": + - do: + catch: bad_request + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field < foo" } } + } + - match: { error.type: "search_phase_execution_exception" } + - match: { error.root_cause.0.type: "query_shard_exception" } + - match: { error.root_cause.0.reason: "failed to create query: For input string: \"foo\"" } + - contains: { error.root_cause.0.stack_trace: "Caused by: java.lang.NumberFormatException: For input string: \"foo\"" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field >= 1" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field > 1" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field <= 2" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "integer_field < 2" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + + +--- +"KQL match term queries (double field)": + - do: + catch: bad_request + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field < foo" } } + } + - match: { error.type: "search_phase_execution_exception" } + - match: { error.root_cause.0.type: "query_shard_exception" } + - match: { error.root_cause.0.reason: "failed to create query: For input string: \"foo\"" } + - contains: { error.root_cause.0.stack_trace: "Caused by: java.lang.NumberFormatException: For input string: \"foo\"" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field >= 3.5" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field > 3.5" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field <= 18.9" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "double_field < 18.9" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + +--- +"KQL match term queries (keyword field)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "keyword_field < foo baz" } } + } + - 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": "keyword_field <= foo baz" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "keyword_field > foo bar" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "keyword_field >= foo bar" } } + } + - match: { hits.total: 2 } + + +--- +"KQL match term queries (date field)": + - do: + catch: bad_request + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field: foo" } } + } + - match: { error.type: "search_phase_execution_exception" } + - match: { error.root_cause.0.type: "parse_exception" } + - contains: { error.root_cause.0.reason: "failed to parse date field [foo]" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field < 2018-03-28" } } + } + - 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": "date_field <= 2018-03-28" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field > 2010-03-06" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-42" } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field >= 2010-03-06" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field < now" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field <= now" } } + } + - match: { hits.total: 2 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field > now" } } + } + - match: { hits.total: 0 } + + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "date_field >= now" } } + } + - match: { hits.total: 0 } diff --git a/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/40_kql_exist_query.yml b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/40_kql_exist_query.yml new file mode 100644 index 0000000000000..ca9197d382f64 --- /dev/null +++ b/x-pack/plugin/kql/src/yamlRestTest/resources/rest-api-spec/test/kql/40_kql_exist_query.yml @@ -0,0 +1,182 @@ +setup: + - requires: + capabilities: + - method: POST + path: /_search + capabilities: [ kql_query ] + test_runner_features: capabilities + reason: KQL query is not available + + - do: + indices.create: + index: test-index + body: + mappings: + properties: + date_field: + type: date + text_field: + type: text + keyword_field: + type: keyword + integer_field: + type: integer + double_field: + type: double + + - do: + bulk: + index: test-index + refresh: true + body: | + { "index" : { "_id": "doc-1" } } + { "text_field": "foo bar", "integer_field": 1, "double_field": 3.5, "date_field": "2010-03-06T14:15:00", "keyword_field": "foo bar" } + { "index" : { "_id": "doc-42" } } + { "another_field": "foo"} + +--- +"KQL exists queries - Existing field": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field:*" } } + } + - 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": "integer_field:*" } } + } + - 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": "double_field:*" } } + } + - 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": "date_field:*" } } + } + - 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": "keyword_field:*" } } + } + - 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": "text_*:*" } } + } + - 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": "*_field:*" } } + } + - match: { hits.total: 2 } + +--- +"KQL exists queries (existing field)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "text_field:*" } } + } + - 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": "integer_field:*" } } + } + - 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": "double_field:*" } } + } + - 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": "date_field:*" } } + } + - 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": "keyword_field:*" } } + } + - match: { hits.total: 1 } + - match: { hits.hits.0._id: "doc-1" } + +--- +"KQL exists queries (non-existing field)": + - do: + search: + index: test-index + rest_total_hits_as_int: true + body: > + { + "query": { "kql": { "query": "non_existing_field:*" } } + } + - match: { hits.total: 0 }