Skip to content

Commit

Permalink
Add integration test for the KQL function.
Browse files Browse the repository at this point in the history
  • Loading branch information
afoucret committed Nov 18, 2024
1 parent 27afe86 commit 5f6e321
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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.esql.plugin;

import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.xpack.esql.VerificationException;
import org.elasticsearch.xpack.esql.action.AbstractEsqlIntegTestCase;
import org.elasticsearch.xpack.esql.action.EsqlCapabilities;
import org.elasticsearch.xpack.esql.action.EsqlQueryRequest;
import org.elasticsearch.xpack.esql.action.EsqlQueryResponse;
import org.junit.Before;

import java.util.List;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.CoreMatchers.containsString;

public class KqlFunctionIT extends AbstractEsqlIntegTestCase {

@Before
public void setupIndex() {
createAndPopulateIndex();
}

@Override
protected EsqlQueryResponse run(EsqlQueryRequest request) {
assumeTrue("kql function capability not available", EsqlCapabilities.Cap.KQL_FUNCTION.isEnabled());
return super.run(request);
}

public void testSimpleKqlQuery() {
var query = """
FROM test
| WHERE kql("content: dog")
| KEEP id
| SORT id
""";

try (var resp = run(query)) {
assertColumnNames(resp.columns(), List.of("id"));
assertColumnTypes(resp.columns(), List.of("integer"));
assertValues(resp.values(), List.of(List.of(1), List.of(3), List.of(4), List.of(5)));
}
}

public void testMultiFieldKqlQuery() {
var query = """
FROM test
| WHERE kql("dog OR canine")
| KEEP id
""";

try (var resp = run(query)) {
assertColumnNames(resp.columns(), List.of("id"));
assertColumnTypes(resp.columns(), List.of("integer"));
assertValuesInAnyOrder(resp.values(), List.of(List.of(1), List.of(2), List.of(3), List.of(4), List.of(5)));
}
}

public void testKqlQueryWithinEval() {
var query = """
FROM test
| EVAL matches_query = kql("title: fox")
""";

var error = expectThrows(VerificationException.class, () -> run(query));
assertThat(error.getMessage(), containsString("[KQL] function is only supported in WHERE commands"));
}

public void testInvalidKqlQueryEof() {
var query = """
FROM test
| WHERE kql("content: ((((dog")
""";

var error = expectThrows(QueryShardException.class, () -> run(query));
assertThat(error.getMessage(), containsString("Failed to parse KQL query [content: ((((dog]"));
assertThat(error.getRootCause().getMessage(), containsString("line 1:11: mismatched input '('"));
}

public void testInvalidKqlQueryLexicalError() {
var query = """
FROM test
| WHERE kql(":")
""";

var error = expectThrows(QueryShardException.class, () -> run(query));
assertThat(error.getMessage(), containsString("Failed to parse KQL query [:]"));
assertThat(error.getRootCause().getMessage(), containsString("line 1:1: extraneous input ':' "));
}

private void createAndPopulateIndex() {
var indexName = "test";
var client = client().admin().indices();
var CreateRequest = client.prepareCreate(indexName)
.setSettings(Settings.builder().put("index.number_of_shards", 1))
.setMapping("id", "type=integer", "content", "type=text");
assertAcked(CreateRequest);
client().prepareBulk()
.add(
new IndexRequest(indexName).id("1")
.source("id", 1, "content", "The quick brown animal swiftly jumps over a lazy dog", "title", "A Swift Fox's Journey")
)
.add(
new IndexRequest(indexName).id("2")
.source("id", 2, "content", "A speedy brown fox hops effortlessly over a sluggish canine", "title", "The Fox's Leap")
)
.add(
new IndexRequest(indexName).id("3")
.source("id", 3, "content", "Quick and nimble, the fox vaults over the lazy dog", "title", "Brown Fox in Action")
)
.add(
new IndexRequest(indexName).id("4")
.source(
"id",
4,
"content",
"A fox that is quick and brown jumps over a dog that is quite lazy",
"title",
"Speedy Animals"
)
)
.add(
new IndexRequest(indexName).id("5")
.source(
"id",
5,
"content",
"With agility, a quick brown fox bounds over a slow-moving dog",
"title",
"Foxes and Canines"
)
)
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
.get();
ensureYellow(indexName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1283,10 +1283,7 @@ public void testKqlFunctionsNotAllowedAfterCommands() throws Exception {
);
assertEquals("1:27: [KQL] function cannot be used after KEEP", error("from test | keep emp_no | where kql(\"Anna\")"));
assertEquals("1:24: [KQL] function cannot be used after LIMIT", error("from test | limit 10 | where kql(\"Anna\")"));
assertEquals(
"1:35: [KQL] function cannot be used after MV_EXPAND",
error("from test | mv_expand last_name | where kql(\"Anna\")")
);
assertEquals("1:35: [KQL] function cannot be used after MV_EXPAND", error("from test | mv_expand last_name | where kql(\"Anna\")"));
assertEquals(
"1:45: [KQL] function cannot be used after RENAME",
error("from test | rename last_name as full_name | where kql(\"Anna\")")
Expand All @@ -1297,10 +1294,7 @@ public void testKqlFunctionsNotAllowedAfterCommands() throws Exception {
);

// Some combination of processing commands
assertEquals(
"1:38: [KQL] function cannot be used after LIMIT",
error("from test | keep emp_no | limit 10 | where kql(\"Anna\")")
);
assertEquals("1:38: [KQL] function cannot be used after LIMIT", error("from test | keep emp_no | limit 10 | where kql(\"Anna\")"));
assertEquals(
"1:46: [KQL] function cannot be used after MV_EXPAND",
error("from test | limit 10 | mv_expand last_name | where kql(\"Anna\")")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;
Expand All @@ -26,6 +27,7 @@
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xpack.kql.parser.KqlParser;
import org.elasticsearch.xpack.kql.parser.KqlParsingContext;
import org.elasticsearch.xpack.kql.parser.KqlParsingException;

import java.io.IOException;
import java.time.ZoneId;
Expand Down Expand Up @@ -151,12 +153,16 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep

@Override
protected QueryBuilder doIndexMetadataRewrite(QueryRewriteContext context) throws IOException {
KqlParser parser = new KqlParser();
QueryBuilder rewrittenQuery = parser.parseKqlQuery(query, createKqlParserContext(context));
try {
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)));
log.trace(() -> Strings.format("KQL query %s translated to Query DSL: %s", query, Strings.toString(rewrittenQuery)));

return rewrittenQuery;
return rewrittenQuery;
} catch (KqlParsingException e) {
throw new QueryShardException(context, "Failed to parse KQL query [{}]", e, query);
}
}

@Override
Expand Down

0 comments on commit 5f6e321

Please sign in to comment.