Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[KQL Query] Create the ANTLR parser #114927

Merged
merged 18 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions x-pack/plugin/kql/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import org.elasticsearch.gradle.internal.info.BuildParams

apply plugin: 'elasticsearch.internal-es-plugin'
apply plugin: 'elasticsearch.internal-cluster-test'
apply plugin: 'elasticsearch.publish'

esplugin {
name 'x-pack-kql'
description 'Elasticsearch Expanded Pack Plugin - KQL query'
classname 'org.elasticsearch.xpack.kql.KqlPlugin'
extendedPlugins = ['x-pack-core']
}
base {
archivesName = 'x-pack-kql'
}

dependencies {
compileOnly project(path: xpackModule('core'))
compileOnly "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)
}

/**********************************
* KQL parser configuration *
**********************************/
configurations {
regenerate
}

dependencies {
regenerate "org.antlr:antlr4:${versions.antlr4}"
}

String grammarPath = 'src/main/antlr'
String outputPath = 'src/main/java/org/elasticsearch/xpack/kql/parser'

pluginManager.withPlugin('com.diffplug.spotless') {
spotless {
java {
// for some reason "${outputPath}/KqlBaser*.java" does not match the same files...
targetExclude "src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase*.java"
}
}
}
tasks.named('checkstyleMain').configure {
exclude { it.file.toString().contains("src/main/java/org/elasticsearch/xpack/kql/parser/KqlBase") }
}

tasks.register("cleanGenerated", Delete) {
delete fileTree(grammarPath) {
include '*.tokens'
}
delete fileTree(outputPath) {
include 'KqlBase*.java'
include 'KqlBase*.interp'
}
}

tasks.register("regenParser", JavaExec) {
dependsOn "cleanGenerated"
mainClass = 'org.antlr.v4.Tool'
classpath = configurations.regenerate
systemProperty 'file.encoding', 'UTF-8'
systemProperty 'user.language', 'en'
systemProperty 'user.country', 'US'
systemProperty 'user.variant', ''
args '-Werror',
'-package', 'org.elasticsearch.xpack.kql.parser',
'-listener',
'-visitor',
'-o', outputPath,
"${file(grammarPath)}/KqlBase.g4"
}

tasks.register("regen") {
dependsOn "regenParser"
doLast {
// moves token files to grammar directory for use with IDE's
ant.move(file: "${outputPath}/KqlBase.tokens", toDir: grammarPath)
ant.move(file: "${outputPath}/KqlBaseLexer.tokens", toDir: grammarPath)
// make the generated classes package private
ant.replaceregexp(match: 'public ((interface|class) \\QKqlBase\\E\\w+)',
replace: '\\1',
encoding: 'UTF-8') {
fileset(dir: outputPath, includes: 'KqlBase*.java')
}
// nuke timestamps/filenames in generated files
ant.replaceregexp(match: '\\Q// Generated from \\E.*',
replace: '\\/\\/ ANTLR GENERATED CODE: DO NOT EDIT',
encoding: 'UTF-8') {
fileset(dir: outputPath, includes: 'KqlBase*.java')
}
// remove tabs in antlr generated files
ant.replaceregexp(match: '\t', flags: 'g', replace: ' ', encoding: 'UTF-8') {
fileset(dir: outputPath, includes: 'KqlBase*.java')
}
// fix line endings
ant.fixcrlf(srcdir: outputPath, eol: 'lf') {
patternset(includes: 'KqlBase*.java')
}
}
}
145 changes: 145 additions & 0 deletions x-pack/plugin/kql/src/main/antlr/KqlBase.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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.
*/

grammar KqlBase;


@header {
/*
* 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.
*/
}

options {
caseInsensitive=true;
}

topLevelQuery
: query? EOF
;

query
: query (AND | OR) query #booleanQuery
| NOT subQuery=simpleQuery #notQuery
| simpleQuery #defaultQuery
;

simpleQuery
: nestedQuery
| expression
| parenthesizedQuery
;

expression
: fieldTermQuery
| fieldRangeQuery
;

nestedQuery
: fieldName COLON LEFT_CURLY_BRACKET query RIGHT_CURLY_BRACKET
;

parenthesizedQuery:
LEFT_PARENTHESIS query RIGHT_PARENTHESIS;

fieldRangeQuery
: fieldName operator=OP_COMPARE rangeQueryValue
;

fieldTermQuery
: (fieldName COLON)? termQueryValue
;

fieldName
: wildcardExpression
| unquotedLiteralExpression
| quotedStringExpression
;

rangeQueryValue
: unquotedLiteralExpression
| quotedStringExpression
;

termQueryValue
: wildcardExpression
| quotedStringExpression
| termValue=unquotedLiteralExpression
| groupingTermExpression;

groupingTermExpression
: LEFT_PARENTHESIS unquotedLiteralExpression RIGHT_PARENTHESIS
;

unquotedLiteralExpression
: UNQUOTED_LITERAL+
;

quotedStringExpression
: QUOTED_STRING
;

wildcardExpression
: WILDCARD
;


DEFAULT_SKIP: WHITESPACE -> skip;

AND: 'and';
OR: 'or';
NOT: 'not';

COLON: ':';
OP_COMPARE: OP_LESS | OP_MORE | OP_LESS_EQ | OP_MORE_EQ;

LEFT_PARENTHESIS: '(';
RIGHT_PARENTHESIS: ')';
LEFT_CURLY_BRACKET: '{';
RIGHT_CURLY_BRACKET: '}';

UNQUOTED_LITERAL: WILDCARD* UNQUOTED_LITERAL_CHAR+ WILDCARD*;

QUOTED_STRING: '"'QUOTED_CHAR*'"';

WILDCARD: WILDCARD_CHAR+;

fragment WILDCARD_CHAR: '*';
fragment OP_LESS: '<';
fragment OP_LESS_EQ: '<=';
fragment OP_MORE: '>';
fragment OP_MORE_EQ: '>=';

fragment UNQUOTED_LITERAL_CHAR
: ESCAPED_WHITESPACE
| ESCAPED_SPECIAL_CHAR
| ESCAPE_UNICODE_SEQUENCE
| '\\' (AND | OR | NOT)
| WILDCARD_CHAR UNQUOTED_LITERAL_CHAR
| NON_SPECIAL_CHAR
;

fragment QUOTED_CHAR
: ESCAPED_WHITESPACE
| ESCAPE_UNICODE_SEQUENCE
| ESCAPED_QUOTE
| ~["]
;

fragment WHITESPACE: [ \t\n\r\u3000];
fragment ESCAPED_WHITESPACE: '\\r' | '\\t' | '\\n';
fragment NON_SPECIAL_CHAR: ~[ \\():<>"*{}];
fragment ESCAPED_SPECIAL_CHAR: '\\'[ \\():<>"*{}];

fragment ESCAPED_QUOTE: '\\"';

fragment ESCAPE_UNICODE_SEQUENCE: '\\' UNICODE_SEQUENCE;
fragment UNICODE_SEQUENCE: 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT;
fragment HEX_DIGIT: [0-9a-f];
21 changes: 21 additions & 0 deletions x-pack/plugin/kql/src/main/antlr/KqlBase.tokens
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
DEFAULT_SKIP=1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Generated code

AND=2
OR=3
NOT=4
COLON=5
OP_COMPARE=6
LEFT_PARENTHESIS=7
RIGHT_PARENTHESIS=8
LEFT_CURLY_BRACKET=9
RIGHT_CURLY_BRACKET=10
UNQUOTED_LITERAL=11
QUOTED_STRING=12
WILDCARD=13
'and'=2
'or'=3
'not'=4
':'=5
'('=7
')'=8
'{'=9
'}'=10
21 changes: 21 additions & 0 deletions x-pack/plugin/kql/src/main/antlr/KqlBaseLexer.tokens
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
DEFAULT_SKIP=1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Generated code

AND=2
OR=3
NOT=4
COLON=5
OP_COMPARE=6
LEFT_PARENTHESIS=7
RIGHT_PARENTHESIS=8
LEFT_CURLY_BRACKET=9
RIGHT_CURLY_BRACKET=10
UNQUOTED_LITERAL=11
QUOTED_STRING=12
WILDCARD=13
'and'=2
'or'=3
'not'=4
':'=5
'('=7
')'=8
'{'=9
'}'=10
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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 org.elasticsearch.plugins.ExtensiblePlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.SearchPlugin;

public class KqlPlugin extends Plugin implements SearchPlugin, ExtensiblePlugin {
carlosdelest marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.antlr.v4.runtime.ParserRuleContext;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.SearchExecutionContext;

class KqlAstBuilder extends KqlBaseBaseVisitor<QueryBuilder> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ The KqlAstBuilder will transform the AST into a query builder.

The actual implementation will come in a later PR (the current behavior is to always return a match_all query).

private final SearchExecutionContext searchExecutionContext;

KqlAstBuilder(SearchExecutionContext searchExecutionContext) {
this.searchExecutionContext = searchExecutionContext;
}

public QueryBuilder toQueryBuilder(ParserRuleContext ctx) {
if (ctx instanceof KqlBaseParser.TopLevelQueryContext topLeveQueryContext) {
return new MatchAllQueryBuilder();
}

throw new IllegalArgumentException("context should be of type TopLevelQueryContext");
}
}
Loading