Skip to content

Commit

Permalink
More tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
afoucret committed Oct 24, 2024
1 parent 78096e8 commit bc61c99
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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.ParserUtils.escapeQueryString;
import static org.elasticsearch.xpack.kql.parser.ParserUtils.hasWildcard;

class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
Expand Down Expand Up @@ -138,9 +139,7 @@ public QueryBuilder visitFieldLessQuery(KqlBaseParser.FieldLessQueryContext ctx)

if (hasWildcard(ctx.fieldQueryValue())) {
// TODO: set default fields.
// TODO: escape query_string chars but keep wildcard
// str.replace(/[+\-=&|><!(){}[\]^"~*?:\\/]/g, '\\$&');
return QueryBuilders.queryStringQuery(queryText);
return QueryBuilders.queryStringQuery(escapeQueryString(queryText, true));
}

boolean isPhraseMatch = ctx.fieldQueryValue().QUOTED_STRING() != null;
Expand All @@ -165,8 +164,7 @@ public QueryBuilder visitFieldQuery(KqlBaseParser.FieldQueryContext ctx) {
fieldQuery = QueryBuilders.wildcardQuery(fieldName, queryText)
.caseInsensitive(kqlParserExecutionContext.isCaseSensitive() == false);
} else if (hasWildcard) {
// TODO: escape query_string chars but keep wildcard
fieldQuery = QueryBuilders.queryStringQuery(queryText).field(fieldName);
fieldQuery = QueryBuilders.queryStringQuery(escapeQueryString(queryText, true)).field(fieldName);
} else if (isDateField(mappedFieldType)) {
// TODO: add timezone
fieldQuery = QueryBuilders.rangeQuery(fieldName).gte(queryText).lte(queryText);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public final class ParserUtils {

Expand All @@ -28,6 +29,24 @@ public final class ParserUtils {
private static final String UNQUOTED_LITERAL_TERM_DELIMITER = " ";
private static final char ESCAPE_CHAR = '\\';
private static final char QUOTE_CHAR = '"';
private static final Set<Character> QUERY_STRING_SPECIAL_CHARS = Set.of(
'*',
'+',
'-',
'!',
'(',
')',
'{',
'}',
'[',
']',
'^',
'"',
'~',
'?',
':',
'\\'
);

private ParserUtils() {

Expand Down Expand Up @@ -71,6 +90,26 @@ public static boolean hasWildcard(ParserRuleContext ctx) {
});
}

public static String escapeQueryString(String queryText, boolean preseveWildcards) {
StringBuilder sb = new StringBuilder();

for (int i = 0; i < queryText.length();) {
char currentChar = queryText.charAt(i++);

if ((currentChar == '&' || currentChar == '|') && i < queryText.length() && currentChar == queryText.charAt(i)) {
sb.append('\\').append(currentChar).append(queryText.charAt(i++));
} else if (currentChar == '*' && preseveWildcards) {
sb.append(currentChar);
} else if (QUERY_STRING_SPECIAL_CHARS.contains(currentChar)) {
sb.append('\\').append(currentChar);
} else {
sb.append(currentChar);
}
}

return sb.toString();
}

private static List<String> extractTextTokems(ParserRuleContext ctx) {
assert ctx.children != null;
List<String> textTokens = new ArrayList<>(ctx.children.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@

import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryStringQueryBuilder;

import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.equalTo;

public class KqlParserFieldlessQueryTests extends AbstractKqlParserTestCase {

public void testFieldlessQuery() {
public void testLiteralFieldlessQuery() {
// Single word
assertMultiMatchQuery(parseKqlQuery("foo"), "foo", MultiMatchQueryBuilder.Type.BEST_FIELDS);
// Multiple words
Expand All @@ -26,6 +27,23 @@ public void testFieldlessQuery() {
assertMultiMatchQuery(parseKqlQuery("foo baz"), "foo baz", MultiMatchQueryBuilder.Type.BEST_FIELDS);
}

public void testWildcardFieldlessQuery() {
// Single word
assertQueryStringBuilder(parseKqlQuery("foo*"), "foo*");
assertQueryStringBuilder(parseKqlQuery("*foo"), "*foo");
assertQueryStringBuilder(parseKqlQuery("fo*o"), "fo*o");

// Multiple words
assertQueryStringBuilder(parseKqlQuery("fo* bar"), "fo* bar");
assertQueryStringBuilder(parseKqlQuery("foo * bar"), "foo * bar");
assertQueryStringBuilder(parseKqlQuery("* foo bar"), "* foo bar");
assertQueryStringBuilder(parseKqlQuery("foo bar *"), "foo bar *");

// Check query string special chars are escaped
assertQueryStringBuilder(parseKqlQuery("foo*[bar]"), "foo*\\[bar\\]");
assertQueryStringBuilder(parseKqlQuery("+foo* -bar"), "\\+foo* \\-bar");
}

public void testMatchPhraseQuery() {
// Single word
assertMultiMatchQuery(parseKqlQuery("\"foo\""), "foo", MultiMatchQueryBuilder.Type.PHRASE);
Expand All @@ -44,4 +62,9 @@ private void assertMultiMatchQuery(QueryBuilder query, String expectedValue, Mul
assertThat(multiMatchQuery.type(), equalTo(expectedType));
assertThat(multiMatchQuery.value(), equalTo(expectedValue));
}

private void assertQueryStringBuilder(QueryBuilder query, String expectedValue) {
QueryStringQueryBuilder queryStringQuery = asInstanceOf(QueryStringQueryBuilder.class, query);
assertThat(queryStringQuery.queryString(), equalTo(expectedValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.elasticsearch.xpack.kql.parser.KqlBaseParser.QUOTED_STRING;
import static org.elasticsearch.xpack.kql.parser.KqlBaseParser.UNQUOTED_LITERAL;
import static org.elasticsearch.xpack.kql.parser.KqlBaseParser.WILDCARD;
import static org.elasticsearch.xpack.kql.parser.ParserUtils.escapeQueryString;
import static org.elasticsearch.xpack.kql.parser.ParserUtils.extractText;
import static org.elasticsearch.xpack.kql.parser.ParserUtils.hasWildcard;
import static org.hamcrest.Matchers.equalTo;
Expand Down Expand Up @@ -132,30 +133,57 @@ public void testExtractTestWithUnquotedLiteral() {
}

public void testHasWildcard() {
{
// No children
assertFalse(hasWildcard(parserRuleContext(List.of())));
// No children
assertFalse(hasWildcard(parserRuleContext(List.of())));

// Lone wildcard
assertTrue(hasWildcard(parserRuleContext(wildcardNode())));
assertTrue(hasWildcard(parserRuleContext(randomTextNodeListWithNode(wildcardNode()))));
// Lone wildcard
assertTrue(hasWildcard(parserRuleContext(wildcardNode())));
assertTrue(hasWildcard(parserRuleContext(randomTextNodeListWithNode(wildcardNode()))));

// All children are literals
assertFalse(hasWildcard(parserRuleContext(randomList(1, randomInt(100), this::randomLiteralNode))));
// All children are literals
assertFalse(hasWildcard(parserRuleContext(randomList(1, randomInt(100), this::randomLiteralNode))));

// Quoted string
assertFalse(hasWildcard(parserRuleContext(randomQuotedStringNode())));
// Quoted string
assertFalse(hasWildcard(parserRuleContext(randomQuotedStringNode())));

// Literal node containing the wildcard character
assertTrue(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "f*oo"))));
assertTrue(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "*foo"))));
assertTrue(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "foo*"))));
// Literal node containing the wildcard character
assertTrue(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "f*oo"))));
assertTrue(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "*foo"))));
assertTrue(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "foo*"))));

// Literal node containing the wildcard characters (escaped)
assertFalse(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "f\\*oo"))));
assertFalse(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "\\*foo"))));
assertFalse(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "foo\\*"))));
}
// Literal node containing the wildcard characters (escaped)
assertFalse(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "f\\*oo"))));
assertFalse(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "\\*foo"))));
assertFalse(hasWildcard(parserRuleContext(terminalNode(UNQUOTED_LITERAL, "foo\\*"))));
}

public void testEscapeQueryString() {
// Quotes
assertThat(escapeQueryString("\"The Pink Panther\"", true), equalTo("\\\"The Pink Panther\\\""));

// Escape chars
assertThat(escapeQueryString("The Pink \\ Panther", true), equalTo("The Pink \\\\ Panther"));

// Field operations
assertThat(escapeQueryString("title:Do it right", true), equalTo("title\\:Do it right"));
assertThat(escapeQueryString("title:(pink panther)", true), equalTo("title\\:\\(pink panther\\)"));
assertThat(escapeQueryString("title:-pink", true), equalTo("title\\:\\-pink"));
assertThat(escapeQueryString("title:+pink", true), equalTo("title\\:\\+pink"));
assertThat(escapeQueryString("title:pink~", true), equalTo("title\\:pink\\~"));
assertThat(escapeQueryString("title:pink~3.5", true), equalTo("title\\:pink\\~3.5"));
assertThat(escapeQueryString("title:pink panther^4", true), equalTo("title\\:pink panther\\^4"));
assertThat(escapeQueryString("rating:[0 TO 5]", true), equalTo("rating\\:\\[0 TO 5\\]"));
assertThat(escapeQueryString("rating:{0 TO 5}", true), equalTo("rating\\:\\{0 TO 5\\}"));

// Boolean operators
assertThat(escapeQueryString("foo || bar", true), equalTo("foo \\|| bar"));
assertThat(escapeQueryString("foo && bar", true), equalTo("foo \\&& bar"));
assertThat(escapeQueryString("!foo", true), equalTo("\\!foo"));

// Wildcards:
assertThat(escapeQueryString("te?t", true), equalTo("te\\?t"));
assertThat(escapeQueryString("foo*", true), equalTo("foo*"));
assertThat(escapeQueryString("foo*", false), equalTo("foo\\*"));
}

private ParserRuleContext parserRuleContext(ParseTree child) {
Expand Down

0 comments on commit bc61c99

Please sign in to comment.