Skip to content

Commit

Permalink
Adding some grammar tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
afoucret committed Oct 17, 2024
1 parent a2b78c3 commit 43d85c8
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@

package org.elasticsearch.xpack.kql.parser;

import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.test.AbstractBuilderTestCase;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.isA;
Expand All @@ -24,6 +34,32 @@ public void testEmptyQueryParsing() throws IOException {
assertThat(parser.parseKqlQuery("", searchExecutionContext), isA(MatchAllQueryBuilder.class));
}

public void testSupportedQueries() throws Exception {
KqlParser parser = new KqlParser();
SearchExecutionContext searchExecutionContext = createSearchExecutionContext();

for (String query : readQueries("/supported-queries")) {
try {
parser.parseKqlQuery(query, searchExecutionContext);
} catch (Throwable e) {
throw new AssertionError("Unexpected error during query parsing [ " + query + "]", e);
}
}
}

public void testUnsupportedQueries() throws Exception {
KqlParser parser = new KqlParser();
SearchExecutionContext searchExecutionContext = createSearchExecutionContext();

for (String query : readQueries("/unsupported-queries")) {
assertThrows(
"Was expecting a KqlParsingException exception to be thrown while parsing query [" + query + "]",
KqlParsingException.class,
() -> parser.parseKqlQuery(query, searchExecutionContext)
);
}
}

public void testSyntaxErrorsHandling() throws IOException {

KqlParser parser = new KqlParser();
Expand All @@ -49,4 +85,32 @@ public void testSyntaxErrorsHandling() throws IOException {
assertThat(e.getMessage(), equalTo("line 1:15: missing ')' at 'AND'"));
}
}

private static List<String> readQueries(String source) throws Exception {
URL url = KqlParserTests.class.getResource(source);
Objects.requireNonNull(source, "Cannot find resource " + url);

List<String> queries = new ArrayList<>();

try (BufferedReader reader = new BufferedReader(new InputStreamReader(readFromJarUrl(url), StandardCharsets.UTF_8))) {
String line;

while ((line = reader.readLine()) != null) {
String query = line.trim();
// ignore comments
if (query.isEmpty() == false && query.startsWith("//") == false) {
queries.add(line.trim());
}
}
}
return queries;
}

@SuppressForbidden(reason = "test reads from jar")
private static InputStream readFromJarUrl(URL source) throws IOException {
URLConnection con = source.openConnection();
// do not to cache files (to avoid keeping file handles around)
con.setUseCaches(false);
return con.getInputStream();
}
}
111 changes: 111 additions & 0 deletions x-pack/plugin/kql/src/test/resources/supported-queries
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Match all queries
*
*:*
(*:*)

// Queries with no field
200
foo
foo bar
(foo bar)
foo*
*foo
f*oo
"foo bar"

// Queries with all fields
*:200
*:foo
*:foo bar
*:foo*
*:f*oo
*: *foo
*:"foo bar"

// Querying a field
foo_field:200
foo_field:foo
foo_field:foo bar
foo_field:(foo bar)
foo_field:foo*
foo_field: f*oo
foo_field: *foo
foo_field:"foo bar"
foo_field.subfield:foo
foo_*_field:foo
foo_field:*
foo_*:*

// Range queries
foo_field<200
foo_field<foo
foo_field<"foo bar"
foo_field>=200
foo_field>=foo
foo_field>"foo bar"
foo_field<=foo
foo_field>=foo

// Boolean queries
NOT foo
NOT foo bar
NOT foo_field:foo
NOT foo_field<foo
foo_field:foo AND foo_field:foo bar
foo_field<foo AND foo_field>bar
(foo_field:foo) AND (foo_field:foo bar)
foo_field:foo OR foo_field:foo bar
NOT(foo_field:foo OR foo_field:foo bar)
NOT(foo_field:foo AND foo_field:foo bar)
NOT foo_field:foo AND NOT foo_field:foo bar
(NOT foo_field:foo) AND (NOT foo_field:foo bar)
NOT(foo_field:foo) AND NOT(foo_field:foo bar)
foo_field:foo AND foo_field:foo bar AND foo bar
foo_field:foo AND foo_field:foo bar OR foo bar
foo_field:foo OR foo_field:foo bar OR foo bar
foo_field:foo OR foo_field:foo bar AND foo bar
foo_field:foo AND (foo_field:foo bar OR foo bar)
foo_field:foo AND (foo_field:foo bar OR foo bar)
foo_field:foo OR (foo_field:foo bar OR foo bar)

// Nested queries
nested_field: { NOT foo }
nested_field: { NOT foo bar }
nested_field: { NOT foo_field:foo }
nested_field: { foo_field:foo AND foo_field:foo bar }
nested_field: { foo_field<foo AND foo_field>bar }
nested_field: { (foo_field:foo) AND (foo_field:foo bar) }
nested_field: { foo_field:foo OR foo_field:foo bar }
nested_field: { NOT(foo_field:foo OR foo_field:foo bar) }
nested_field: { NOT(foo_field:foo AND foo_field:foo bar) }
nested_field: { NOT foo_field:foo AND NOT foo_field:foo bar }
nested_field: { (NOT foo_field:foo) AND (NOT foo_field:foo bar) }
nested_field: { NOT(foo_field:foo) AND NOT(foo_field:foo bar) }
nested_field: { foo_field:foo AND foo_field:foo bar AND foo bar }
nested_field: { foo_field:foo AND foo_field:foo bar OR foo bar }
nested_field: { foo_field:foo OR foo_field:foo bar OR foo bar }
nested_field: { foo_field:foo OR foo_field:foo bar AND foo bar }
nested_field: { foo_field:foo AND (foo_field:foo bar OR foo bar) }
nested_field: { foo_field:foo AND (foo_field:foo bar OR foo bar) }
nested_field: { foo_field:foo OR (foo_field:foo bar OR foo bar) }
nested_field: { sub_nested_field : { foo_field:foo } AND foo_field:foo bar }

// Queries with escape sequences
foo_field : (foo\(bar\))
foo_field : foo\:bar
foo_field : (foo \and bar)
foo_field : (foo \or bar)
foo_field : foo \not bar
foo_field : foo \{bar\}
foo_field : foo \(bar\)
foo_field : foo \\ bar
foo_field : foo \"bar\"

foo_field : "foo and bar"
foo_field : "foo not bar"
foo_field : "foo or bar"
foo_field : "foo : bar"
foo_field : "foo { bar }"
foo_field : "foo (bar)"
foo_field : "foo \\ bar"
foo_field : "foo \"bar\""
42 changes: 42 additions & 0 deletions x-pack/plugin/kql/src/test/resources/unsupported-queries
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

// Incomplete expressions
foo_field :
foo_field <
foo_field >
foo_field >=
foo_field <=

// Parentheses mismatch
foo_field: (foo bar
foo_field: foo bar)
NOT foo_field:foo OR foo_field:foo bar)
NOT (foo_field:foo AND) foo_field:foo bar

// Quotes mismatch
foo_field: "foo bar
foo_field: foo bar"


// Invalid boolean queries
foo AND
AND foo
foo OR
OR foo
NOT foo:

// Can't nest grouping terms parentheses
foo_field:(foo (bar))

// Bad syntax for nested fields:
nested_field { foo: bar }

// Missing escape sequences:
foo_field: foo:bar
foo_field: (foo and bar)
foo_field: (foo or bar)
foo_field: foo not bar
foo_field: foo { bar }
foo_field: foo (bar)
foo_field: foo "bar"
foo_field: "foo\ bar"
foo_field: "foo "bar""

0 comments on commit 43d85c8

Please sign in to comment.