Skip to content

Commit

Permalink
[KQL Query] Create the ANTLR parser (elastic#114927)
Browse files Browse the repository at this point in the history
  • Loading branch information
afoucret committed Oct 18, 2024
1 parent d9c930d commit 3e4281d
Show file tree
Hide file tree
Showing 19 changed files with 3,033 additions and 0 deletions.
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
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
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 {

}
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> {
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

0 comments on commit 3e4281d

Please sign in to comment.