2022-04-01 18:33:09 +02:00
import { SyntaxNode } from '@lezer/common' ;
2022-04-22 14:33:13 +01:00
2022-09-16 19:05:20 +05:30
import {
And ,
BinOpExpr ,
Bool ,
By ,
ConvOp ,
2023-05-24 15:23:54 +02:00
Decolorize ,
2023-08-23 14:52:19 +02:00
DropLabel ,
DropLabels ,
DropLabelsExpr ,
2022-09-16 19:05:20 +05:30
Filter ,
FilterOp ,
Grouping ,
GroupingLabelList ,
GroupingLabels ,
Identifier ,
Ip ,
IpLabelFilter ,
Json ,
JsonExpressionParser ,
2023-08-23 14:52:19 +02:00
KeepLabel ,
KeepLabels ,
KeepLabelsExpr ,
2023-09-22 11:34:17 +02:00
LabelExtractionExpression ,
2022-09-16 19:05:20 +05:30
LabelFilter ,
LabelFormatMatcher ,
LabelParser ,
LineFilter ,
LineFormatExpr ,
2023-09-22 11:34:17 +02:00
LogfmtExpressionParser ,
LogfmtParser ,
2022-09-16 19:05:20 +05:30
LogRangeExpr ,
Matcher ,
MetricExpr ,
Number as NumberLezer ,
On ,
Or ,
parser ,
2023-09-22 11:34:17 +02:00
ParserFlag ,
2022-09-16 19:05:20 +05:30
Range ,
RangeAggregationExpr ,
RangeOp ,
String ,
UnitFilter ,
Unwrap ,
UnwrapExpr ,
VectorAggregationExpr ,
VectorOp ,
Without ,
2023-09-22 07:17:01 -05:00
BinOpModifier ,
OnOrIgnoringModifier ,
2023-11-29 15:58:32 +01:00
OrFilter ,
2022-09-16 19:05:20 +05:30
} from '@grafana/lezer-logql' ;
2022-04-22 14:33:13 +01:00
2022-12-22 15:31:41 +01:00
import {
QueryBuilderLabelFilter ,
QueryBuilderOperation ,
QueryBuilderOperationParamValue ,
} from '../../prometheus/querybuilder/shared/types' ;
2022-04-22 14:33:13 +01:00
2022-03-31 09:51:49 +01:00
import { binaryScalarDefs } from './binaryScalarOperations' ;
2022-12-22 15:31:41 +01:00
import { checkParamsAreValid , getDefinitionById } from './operations' ;
2023-12-15 17:15:20 +01:00
import {
ErrorId ,
getAllByType ,
getLeftMostChild ,
getString ,
makeBinOp ,
makeError ,
replaceVariables ,
} from './parsingUtils' ;
2022-07-25 15:16:04 +02:00
import { LokiOperationId , LokiVisualQuery , LokiVisualQueryBinary } from './types' ;
2022-03-31 09:51:49 +01:00
interface Context {
query : LokiVisualQuery ;
errors : ParsingError [ ] ;
}
interface ParsingError {
text : string ;
2022-04-06 11:17:49 +02:00
from ? : number ;
to? : number ;
2022-03-31 09:51:49 +01:00
parentType? : string ;
}
2023-09-22 11:34:17 +02:00
interface GetOperationResult {
operation? : QueryBuilderOperation ;
error? : string ;
}
2022-03-31 09:51:49 +01:00
export function buildVisualQueryFromString ( expr : string ) : Context {
const replacedExpr = replaceVariables ( expr ) ;
const tree = parser . parse ( replacedExpr ) ;
const node = tree . topNode ;
// This will be modified in the handleExpression
const visQuery : LokiVisualQuery = {
labels : [ ] ,
operations : [ ] ,
} ;
2022-04-06 11:17:49 +02:00
const context : Context = {
2022-03-31 09:51:49 +01:00
query : visQuery ,
errors : [ ] ,
} ;
2022-04-06 11:17:49 +02:00
try {
handleExpression ( replacedExpr , node , context ) ;
} catch ( err ) {
// Not ideal to log it here, but otherwise we would lose the stack trace.
console . error ( err ) ;
2022-06-15 08:59:29 +01:00
if ( err instanceof Error ) {
context . errors . push ( {
text : err.message ,
} ) ;
}
2022-04-06 11:17:49 +02:00
}
// If we have empty query, we want to reset errors
if ( isEmptyQuery ( context . query ) ) {
context . errors = [ ] ;
}
2022-03-31 09:51:49 +01:00
return context ;
}
export function handleExpression ( expr : string , node : SyntaxNode , context : Context ) {
const visQuery = context . query ;
2022-09-16 19:05:20 +05:30
switch ( node . type . id ) {
case Matcher : {
2022-03-31 09:51:49 +01:00
visQuery . labels . push ( getLabel ( expr , node ) ) ;
2022-09-16 19:05:20 +05:30
const err = node . getChild ( ErrorId ) ;
2022-03-31 09:51:49 +01:00
if ( err ) {
context . errors . push ( makeError ( expr , err ) ) ;
}
break ;
}
2022-09-16 19:05:20 +05:30
case LineFilter : {
2022-04-05 17:07:13 +02:00
const { operation , error } = getLineFilter ( expr , node ) ;
if ( operation ) {
visQuery . operations . push ( operation ) ;
}
// Show error for query patterns not supported in visual query builder
if ( error ) {
context . errors . push ( createNotSupportedError ( expr , node , error ) ) ;
}
2022-03-31 09:51:49 +01:00
break ;
}
2022-09-16 19:05:20 +05:30
case LabelParser : {
2022-03-31 09:51:49 +01:00
visQuery . operations . push ( getLabelParser ( expr , node ) ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case LabelFilter : {
2022-04-05 17:07:13 +02:00
const { operation , error } = getLabelFilter ( expr , node ) ;
if ( operation ) {
visQuery . operations . push ( operation ) ;
}
// Show error for query patterns not supported in visual query builder
if ( error ) {
context . errors . push ( createNotSupportedError ( expr , node , error ) ) ;
}
2022-03-31 09:51:49 +01:00
break ;
}
2022-09-16 19:05:20 +05:30
case JsonExpressionParser : {
2022-07-12 15:42:01 +02:00
visQuery . operations . push ( getJsonExpressionParser ( expr , node ) ) ;
break ;
2022-04-05 17:07:13 +02:00
}
2022-03-31 09:51:49 +01:00
2023-09-22 11:34:17 +02:00
case LogfmtParser :
case LogfmtExpressionParser : {
const { operation , error } = getLogfmtParser ( expr , node ) ;
if ( operation ) {
visQuery . operations . push ( operation ) ;
}
if ( error ) {
context . errors . push ( createNotSupportedError ( expr , node , error ) ) ;
}
break ;
}
2022-09-16 19:05:20 +05:30
case LineFormatExpr : {
2022-03-31 09:51:49 +01:00
visQuery . operations . push ( getLineFormat ( expr , node ) ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case LabelFormatMatcher : {
2022-03-31 09:51:49 +01:00
visQuery . operations . push ( getLabelFormat ( expr , node ) ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case UnwrapExpr : {
2022-05-27 14:00:51 +02:00
const { operation , error } = handleUnwrapExpr ( expr , node , context ) ;
2022-04-05 17:07:13 +02:00
if ( operation ) {
visQuery . operations . push ( operation ) ;
}
// Show error for query patterns not supported in visual query builder
if ( error ) {
context . errors . push ( createNotSupportedError ( expr , node , error ) ) ;
}
2022-04-04 17:44:23 +01:00
break ;
}
2023-05-24 15:23:54 +02:00
case Decolorize : {
visQuery . operations . push ( getDecolorize ( ) ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case RangeAggregationExpr : {
2022-03-31 09:51:49 +01:00
visQuery . operations . push ( handleRangeAggregation ( expr , node , context ) ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case VectorAggregationExpr : {
2022-03-31 09:51:49 +01:00
visQuery . operations . push ( handleVectorAggregation ( expr , node , context ) ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case BinOpExpr : {
2022-03-31 09:51:49 +01:00
handleBinary ( expr , node , context ) ;
break ;
}
2022-09-16 19:05:20 +05:30
case ErrorId : {
2022-03-31 09:51:49 +01:00
if ( isIntervalVariableError ( node ) ) {
break ;
}
context . errors . push ( makeError ( expr , node ) ) ;
break ;
}
2023-08-23 14:52:19 +02:00
case DropLabelsExpr : {
visQuery . operations . push ( handleDropFilter ( expr , node , context ) ) ;
break ;
}
case KeepLabelsExpr : {
visQuery . operations . push ( handleKeepFilter ( expr , node , context ) ) ;
break ;
}
2022-03-31 09:51:49 +01:00
default : {
2022-08-09 10:19:30 +02:00
// Any other nodes we just ignore and go to its children. This should be fine as there are lots of wrapper
2022-03-31 09:51:49 +01:00
// nodes that can be skipped.
// TODO: there are probably cases where we will just skip nodes we don't support and we should be able to
// detect those and report back.
let child = node . firstChild ;
while ( child ) {
handleExpression ( expr , child , context ) ;
child = child . nextSibling ;
}
}
}
}
function getLabel ( expr : string , node : SyntaxNode ) : QueryBuilderLabelFilter {
2022-09-16 19:05:20 +05:30
const labelNode = node . getChild ( Identifier ) ;
2022-03-31 09:51:49 +01:00
const label = getString ( expr , labelNode ) ;
2023-10-13 10:26:32 -05:00
const op = getString ( expr , labelNode ? . nextSibling ) ;
2022-12-06 12:30:03 +01:00
let value = getString ( expr , node . getChild ( String ) ) ;
// `value` is wrapped in double quotes, so we need to remove them. As a value can contain double quotes, we can't use RegEx here.
value = value . substring ( 1 , value . length - 1 ) ;
2022-03-31 09:51:49 +01:00
return {
label ,
op ,
value ,
} ;
}
2023-09-22 11:34:17 +02:00
function getLineFilter ( expr : string , node : SyntaxNode ) : GetOperationResult {
2022-09-16 19:05:20 +05:30
const filter = getString ( expr , node . getChild ( Filter ) ) ;
const filterExpr = handleQuotes ( getString ( expr , node . getChild ( String ) ) ) ;
const ipLineFilter = node . getChild ( FilterOp ) ? . getChild ( Ip ) ;
2022-07-25 15:16:04 +02:00
if ( ipLineFilter ) {
2022-04-05 17:07:13 +02:00
return {
2022-07-25 15:16:04 +02:00
operation : {
id : LokiOperationId.LineFilterIpMatches ,
params : [ filter , filterExpr ] ,
} ,
2022-04-05 17:07:13 +02:00
} ;
}
2023-11-29 15:58:32 +01:00
const params = [ filterExpr ] ;
let orFilter = node . getChild ( OrFilter ) ;
while ( orFilter ) {
params . push ( handleQuotes ( getString ( expr , orFilter . getChild ( String ) ) ) ) ;
orFilter = orFilter . getChild ( OrFilter ) ;
}
2023-02-09 09:03:13 +00:00
const mapFilter : Record < string , LokiOperationId > = {
2022-07-25 15:16:04 +02:00
'|=' : LokiOperationId . LineContains ,
'!=' : LokiOperationId . LineContainsNot ,
'|~' : LokiOperationId . LineMatchesRegex ,
'!~' : LokiOperationId . LineMatchesRegexNot ,
2022-03-31 09:51:49 +01:00
} ;
return {
2022-04-05 17:07:13 +02:00
operation : {
id : mapFilter [ filter ] ,
2023-11-29 15:58:32 +01:00
params ,
2022-04-05 17:07:13 +02:00
} ,
2022-03-31 09:51:49 +01:00
} ;
}
function getLabelParser ( expr : string , node : SyntaxNode ) : QueryBuilderOperation {
const parserNode = node . firstChild ;
const parser = getString ( expr , parserNode ) ;
2022-09-16 19:05:20 +05:30
const string = handleQuotes ( getString ( expr , node . getChild ( String ) ) ) ;
2022-12-22 15:31:41 +01:00
let params : QueryBuilderOperationParamValue [ ] = ! ! string ? [ string ] : [ ] ;
const opDef = getDefinitionById ( parser ) ;
if ( opDef && ! checkParamsAreValid ( opDef , params ) ) {
params = opDef ? . defaultParams || [ ] ;
}
2022-03-31 09:51:49 +01:00
return {
2022-07-12 15:42:01 +02:00
id : parser ,
params ,
} ;
}
function getJsonExpressionParser ( expr : string , node : SyntaxNode ) : QueryBuilderOperation {
2022-09-16 19:05:20 +05:30
const parserNode = node . getChild ( Json ) ;
2022-07-12 15:42:01 +02:00
const parser = getString ( expr , parserNode ) ;
2023-09-22 11:34:17 +02:00
const params = [ . . . getAllByType ( expr , node , LabelExtractionExpression ) ] ;
2022-07-12 15:42:01 +02:00
return {
2022-03-31 09:51:49 +01:00
id : parser ,
params ,
} ;
}
2023-09-22 11:34:17 +02:00
function getLogfmtParser ( expr : string , node : SyntaxNode ) : GetOperationResult {
const flags : string [ ] = [ ] ;
const labels : string [ ] = [ ] ;
let error : string | undefined = undefined ;
const offset = node . from ;
node . toTree ( ) . iterate ( {
enter : ( subNode ) = > {
if ( subNode . type . id === ParserFlag ) {
flags . push ( expr . substring ( subNode . from + offset , subNode . to + offset ) ) ;
} else if ( subNode . type . id === LabelExtractionExpression ) {
labels . push ( expr . substring ( subNode . from + offset , subNode . to + offset ) ) ;
} else if ( subNode . type . id === ErrorId ) {
error = ` Unexpected string " ${ expr . substring ( subNode . from + offset , subNode . to + offset ) } " ` ;
}
} ,
} ) ;
const operation = {
id : LokiOperationId.Logfmt ,
params : [ flags . includes ( '--strict' ) , flags . includes ( '--keep-empty' ) , . . . labels ] ,
} ;
return {
operation ,
error ,
} ;
}
function getLabelFilter ( expr : string , node : SyntaxNode ) : GetOperationResult {
2022-04-05 17:07:13 +02:00
// Check for nodes not supported in visual builder and return error
2022-09-16 19:05:20 +05:30
if ( node . getChild ( Or ) || node . getChild ( And ) || node . getChild ( 'Comma' ) ) {
2022-04-05 17:07:13 +02:00
return {
error : 'Label filter with comma, "and", "or" not supported in query builder' ,
} ;
}
2022-09-16 19:05:20 +05:30
if ( node . firstChild ! . type . id === IpLabelFilter ) {
2022-07-25 15:16:04 +02:00
const ipLabelFilter = node . firstChild ;
2022-09-16 19:05:20 +05:30
const label = ipLabelFilter ? . getChild ( Identifier ) ;
2022-07-25 15:16:04 +02:00
const op = label ? . nextSibling ;
2022-09-16 19:05:20 +05:30
const value = ipLabelFilter ? . getChild ( String ) ;
2022-07-25 15:16:04 +02:00
const valueString = handleQuotes ( getString ( expr , value ) ) ;
2022-04-05 17:07:13 +02:00
return {
2022-07-25 15:16:04 +02:00
operation : {
id : LokiOperationId.LabelFilterIpMatches ,
params : [ getString ( expr , label ) , getString ( expr , op ) , valueString ] ,
} ,
2022-04-05 17:07:13 +02:00
} ;
}
2022-03-31 09:51:49 +01:00
2022-08-03 12:50:43 +02:00
const id = LokiOperationId . LabelFilter ;
2022-09-16 19:05:20 +05:30
if ( node . firstChild ! . type . id === UnitFilter ) {
2022-03-31 09:51:49 +01:00
const filter = node . firstChild ! . firstChild ;
const label = filter ! . firstChild ;
const op = label ! . nextSibling ;
const value = op ! . nextSibling ;
const valueString = handleQuotes ( getString ( expr , value ) ) ;
return {
2022-04-05 17:07:13 +02:00
operation : {
id ,
params : [ getString ( expr , label ) , getString ( expr , op ) , valueString ] ,
} ,
2022-03-31 09:51:49 +01:00
} ;
}
2022-04-05 17:07:13 +02:00
// In this case it is Matcher or NumberFilter
const filter = node . firstChild ;
const label = filter ! . firstChild ;
const op = label ! . nextSibling ;
const value = op ! . nextSibling ;
const params = [ getString ( expr , label ) , getString ( expr , op ) , handleQuotes ( getString ( expr , value ) ) ] ;
2022-03-31 09:51:49 +01:00
2022-04-05 17:07:13 +02:00
// Special case of pipe filtering - no errors
if ( params . join ( '' ) === ` __error__= ` ) {
2022-03-31 09:51:49 +01:00
return {
2022-04-05 17:07:13 +02:00
operation : {
2022-08-03 12:50:43 +02:00
id : LokiOperationId.LabelFilterNoErrors ,
2022-03-31 09:51:49 +01:00
params : [ ] ,
2022-04-05 17:07:13 +02:00
} ,
} ;
}
2022-03-31 09:51:49 +01:00
2022-04-05 17:07:13 +02:00
return {
operation : {
2022-03-31 09:51:49 +01:00
id ,
params ,
2022-04-05 17:07:13 +02:00
} ,
} ;
2022-03-31 09:51:49 +01:00
}
function getLineFormat ( expr : string , node : SyntaxNode ) : QueryBuilderOperation {
2022-08-03 12:50:43 +02:00
const id = LokiOperationId . LineFormat ;
2022-09-16 19:05:20 +05:30
const string = handleQuotes ( getString ( expr , node . getChild ( String ) ) ) ;
2022-03-31 09:51:49 +01:00
return {
id ,
params : [ string ] ,
} ;
}
function getLabelFormat ( expr : string , node : SyntaxNode ) : QueryBuilderOperation {
2022-08-03 12:50:43 +02:00
const id = LokiOperationId . LabelFormat ;
2022-09-16 19:05:20 +05:30
const renameTo = node . getChild ( Identifier ) ;
2022-07-22 16:59:25 +02:00
const op = renameTo ! . nextSibling ;
const originalLabel = op ! . nextSibling ;
2022-03-31 09:51:49 +01:00
return {
id ,
2022-07-22 16:59:25 +02:00
params : [ getString ( expr , originalLabel ) , handleQuotes ( getString ( expr , renameTo ) ) ] ,
2022-03-31 09:51:49 +01:00
} ;
}
2023-05-24 15:23:54 +02:00
function getDecolorize ( ) : QueryBuilderOperation {
const id = LokiOperationId . Decolorize ;
return {
id ,
params : [ ] ,
} ;
}
2023-09-22 11:34:17 +02:00
function handleUnwrapExpr ( expr : string , node : SyntaxNode , context : Context ) : GetOperationResult {
2022-09-16 19:05:20 +05:30
const unwrapExprChild = node . getChild ( UnwrapExpr ) ;
const labelFilterChild = node . getChild ( LabelFilter ) ;
const unwrapChild = node . getChild ( Unwrap ) ;
2022-05-27 14:00:51 +02:00
if ( unwrapExprChild ) {
handleExpression ( expr , unwrapExprChild , context ) ;
}
if ( labelFilterChild ) {
handleExpression ( expr , labelFilterChild , context ) ;
}
if ( unwrapChild ) {
2022-09-16 19:05:20 +05:30
if ( unwrapChild . nextSibling ? . type . id === ConvOp ) {
2022-07-25 12:51:28 +02:00
const convOp = unwrapChild . nextSibling ;
const identifier = convOp . nextSibling ;
2022-05-27 14:00:51 +02:00
return {
2022-07-25 12:51:28 +02:00
operation : {
2022-08-03 12:50:43 +02:00
id : LokiOperationId.Unwrap ,
2022-07-25 12:51:28 +02:00
params : [ getString ( expr , identifier ) , getString ( expr , convOp ) ] ,
} ,
2022-05-27 14:00:51 +02:00
} ;
}
2022-04-05 17:07:13 +02:00
return {
2022-05-27 14:00:51 +02:00
operation : {
2022-08-03 12:50:43 +02:00
id : LokiOperationId.Unwrap ,
2022-07-25 12:51:28 +02:00
params : [ getString ( expr , unwrapChild ? . nextSibling ) , '' ] ,
2022-05-27 14:00:51 +02:00
} ,
2022-04-05 17:07:13 +02:00
} ;
}
2022-05-27 14:00:51 +02:00
return { } ;
2022-04-04 17:44:23 +01:00
}
2023-05-25 14:22:16 +02:00
2022-03-31 09:51:49 +01:00
function handleRangeAggregation ( expr : string , node : SyntaxNode , context : Context ) {
2022-09-16 19:05:20 +05:30
const nameNode = node . getChild ( RangeOp ) ;
2022-03-31 09:51:49 +01:00
const funcName = getString ( expr , nameNode ) ;
2022-09-16 19:05:20 +05:30
const number = node . getChild ( NumberLezer ) ;
const logExpr = node . getChild ( LogRangeExpr ) ;
2022-03-31 09:51:49 +01:00
const params = number !== null && number !== undefined ? [ getString ( expr , number ) ] : [ ] ;
2023-01-18 15:54:18 +01:00
const range = logExpr ? . getChild ( Range ) ;
const rangeValue = range ? getString ( expr , range ) : null ;
2024-01-08 17:17:19 +01:00
const grouping = node . getChild ( Grouping ) ;
2022-03-31 09:51:49 +01:00
2023-01-18 15:54:18 +01:00
if ( rangeValue ) {
params . unshift ( rangeValue . substring ( 1 , rangeValue . length - 1 ) ) ;
2022-03-31 09:51:49 +01:00
}
2024-01-08 17:17:19 +01:00
if ( grouping ) {
params . push ( . . . getAllByType ( expr , grouping , Identifier ) ) ;
}
2022-03-31 09:51:49 +01:00
const op = {
id : funcName ,
params ,
} ;
if ( logExpr ) {
handleExpression ( expr , logExpr , context ) ;
}
return op ;
}
function handleVectorAggregation ( expr : string , node : SyntaxNode , context : Context ) {
2022-09-16 19:05:20 +05:30
const nameNode = node . getChild ( VectorOp ) ;
2022-03-31 09:51:49 +01:00
let funcName = getString ( expr , nameNode ) ;
2022-09-16 19:05:20 +05:30
const grouping = node . getChild ( Grouping ) ;
2022-06-11 21:36:44 +02:00
const params = [ ] ;
2022-09-16 19:05:20 +05:30
const numberNode = node . getChild ( NumberLezer ) ;
2022-06-11 21:36:44 +02:00
if ( numberNode ) {
params . push ( Number ( getString ( expr , numberNode ) ) ) ;
}
2022-04-04 17:11:15 +01:00
if ( grouping ) {
2022-09-16 19:05:20 +05:30
const byModifier = grouping . getChild ( By ) ;
2022-04-04 17:11:15 +01:00
if ( byModifier && funcName ) {
funcName = ` __ ${ funcName } _by ` ;
}
2022-09-16 19:05:20 +05:30
const withoutModifier = grouping . getChild ( Without ) ;
2022-04-04 17:11:15 +01:00
if ( withoutModifier ) {
funcName = ` __ ${ funcName } _without ` ;
}
2022-09-16 19:05:20 +05:30
params . push ( . . . getAllByType ( expr , grouping , Identifier ) ) ;
2022-04-04 17:11:15 +01:00
}
2022-09-16 19:05:20 +05:30
const metricExpr = node . getChild ( MetricExpr ) ;
2022-06-11 21:36:44 +02:00
const op : QueryBuilderOperation = { id : funcName , params } ;
2022-03-31 09:51:49 +01:00
if ( metricExpr ) {
2023-09-25 13:28:51 -05:00
// A vector aggregation expression with a child of metric expression with a child of binary expression is ambiguous after being parsed into a visual query
if ( metricExpr . firstChild ? . type . id === BinOpExpr ) {
context . errors . push ( {
text : 'Query parsing is ambiguous.' ,
from : metricExpr . firstChild . from ,
to : metricExpr.firstChild?.to ,
} ) ;
}
2022-03-31 09:51:49 +01:00
handleExpression ( expr , metricExpr , context ) ;
}
return op ;
}
2023-02-09 09:03:13 +00:00
const operatorToOpName = binaryScalarDefs . reduce < Record < string , { id : string ; comparison ? : boolean } > > ( ( acc , def ) = > {
2022-03-31 09:51:49 +01:00
acc [ def . sign ] = {
id : def.id ,
comparison : def.comparison ,
} ;
return acc ;
2023-02-09 09:03:13 +00:00
} , { } ) ;
2022-03-31 09:51:49 +01:00
/ * *
* Right now binary expressions can be represented in 2 way in visual query . As additional operation in case it is
* just operation with scalar or it creates a binaryQuery when it ' s 2 queries .
* @param expr
* @param node
* @param context
* /
function handleBinary ( expr : string , node : SyntaxNode , context : Context ) {
const visQuery = context . query ;
const left = node . firstChild ! ;
const op = getString ( expr , left . nextSibling ) ;
2023-09-22 07:17:01 -05:00
const binModifier = getBinaryModifier ( expr , node . getChild ( BinOpModifier ) ) ;
2022-03-31 09:51:49 +01:00
const right = node . lastChild ! ;
const opDef = operatorToOpName [ op ] ;
2022-04-01 18:33:09 +02:00
const leftNumber = getLastChildWithSelector ( left , 'MetricExpr.LiteralExpr.Number' ) ;
const rightNumber = getLastChildWithSelector ( right , 'MetricExpr.LiteralExpr.Number' ) ;
2022-03-31 09:51:49 +01:00
2022-09-16 19:05:20 +05:30
const rightBinary = right . getChild ( BinOpExpr ) ;
2022-03-31 09:51:49 +01:00
if ( leftNumber ) {
// TODO: this should be already handled in case parent is binary expression as it has to be added to parent
// if query starts with a number that isn't handled now.
} else {
// If this is binary we don't really know if there is a query or just chained scalars. So
// we have to traverse a bit deeper to know
handleExpression ( expr , left , context ) ;
}
if ( rightNumber ) {
visQuery . operations . push ( makeBinOp ( opDef , expr , right , ! ! binModifier ? . isBool ) ) ;
} else if ( rightBinary ) {
// Due to the way binary ops are parsed we can get a binary operation on the right that starts with a number which
// is a factor for a current binary operation. So we have to add it as an operation now.
const leftMostChild = getLeftMostChild ( right ) ;
2023-09-22 07:17:01 -05:00
if ( leftMostChild ? . type . id === NumberLezer ) {
2022-03-31 09:51:49 +01:00
visQuery . operations . push ( makeBinOp ( opDef , expr , leftMostChild , ! ! binModifier ? . isBool ) ) ;
}
// If we added the first number literal as operation here we still can continue and handle the rest as the first
// number will be just skipped.
handleExpression ( expr , right , context ) ;
} else {
visQuery . binaryQueries = visQuery . binaryQueries || [ ] ;
const binQuery : LokiVisualQueryBinary = {
operator : op ,
query : {
labels : [ ] ,
operations : [ ] ,
} ,
} ;
if ( binModifier ? . isMatcher ) {
binQuery . vectorMatchesType = binModifier . matchType ;
binQuery . vectorMatches = binModifier . matches ;
}
visQuery . binaryQueries . push ( binQuery ) ;
handleExpression ( expr , right , {
query : binQuery.query ,
errors : context.errors ,
} ) ;
}
}
function getBinaryModifier (
expr : string ,
node : SyntaxNode | null
) :
| { isBool : true ; isMatcher : false }
2023-09-22 07:17:01 -05:00
| { isBool : boolean ; isMatcher : true ; matches : string ; matchType : 'ignoring' | 'on' }
2022-03-31 09:51:49 +01:00
| undefined {
if ( ! node ) {
return undefined ;
}
2023-09-22 07:17:01 -05:00
const matcher = node . getChild ( OnOrIgnoringModifier ) ;
const boolMatcher = node . getChild ( Bool ) ;
if ( ! matcher && boolMatcher ) {
2022-03-31 09:51:49 +01:00
return { isBool : true , isMatcher : false } ;
} else {
if ( ! matcher ) {
// Not sure what this could be, maybe should be an error.
return undefined ;
}
2022-09-16 19:05:20 +05:30
const labels = getString ( expr , matcher . getChild ( GroupingLabels ) ? . getChild ( GroupingLabelList ) ) ;
2022-03-31 09:51:49 +01:00
return {
isMatcher : true ,
2023-09-22 07:17:01 -05:00
isBool : ! ! boolMatcher ,
2022-03-31 09:51:49 +01:00
matches : labels ,
2022-09-16 19:05:20 +05:30
matchType : matcher.getChild ( On ) ? 'on' : 'ignoring' ,
2022-03-31 09:51:49 +01:00
} ;
}
}
function isIntervalVariableError ( node : SyntaxNode ) {
2022-09-16 19:05:20 +05:30
return node ? . parent ? . type . id === Range ;
2022-03-31 09:51:49 +01:00
}
2023-06-26 16:45:33 +02:00
export function handleQuotes ( string : string ) {
2022-03-31 09:51:49 +01:00
if ( string [ 0 ] === ` " ` && string [ string . length - 1 ] === ` " ` ) {
2023-06-06 11:07:21 +02:00
return string
. substring ( 1 , string . length - 1 )
. replace ( /\\"/g , '"' )
. replace ( /\\\\/g , '\\' ) ;
2022-03-31 09:51:49 +01:00
}
return string . replace ( /`/g , '' ) ;
}
/ * *
2022-04-01 18:33:09 +02:00
* Simple helper to traverse the syntax tree . Instead of node . getChild ( 'foo' ) ? . getChild ( 'bar' ) ? . getChild ( 'baz' ) you
* can write getChildWithSelector ( node , 'foo.bar.baz' )
* @param node
* @param selector
2022-03-31 09:51:49 +01:00
* /
2022-04-01 18:33:09 +02:00
function getLastChildWithSelector ( node : SyntaxNode , selector : string ) {
let child : SyntaxNode | null = node ;
const children = selector . split ( '.' ) ;
for ( const s of children ) {
child = child . getChild ( s ) ;
if ( ! child ) {
return null ;
2022-03-31 09:51:49 +01:00
}
2022-04-01 18:33:09 +02:00
}
return child ;
2022-03-31 09:51:49 +01:00
}
2022-04-05 17:07:13 +02:00
/ * *
* Helper function to enrich error text with information that visual query builder doesn ' t support that logQL
* @param expr
* @param node
* @param error
* /
function createNotSupportedError ( expr : string , node : SyntaxNode , error : string ) {
const err = makeError ( expr , node ) ;
err . text = ` ${ error } : ${ err . text } ` ;
return err ;
}
2022-04-06 11:17:49 +02:00
function isEmptyQuery ( query : LokiVisualQuery ) {
if ( query . labels . length === 0 && query . operations . length === 0 ) {
return true ;
}
return false ;
}
2023-05-25 14:22:16 +02:00
2023-08-23 14:52:19 +02:00
function handleDropFilter ( expr : string , node : SyntaxNode , context : Context ) : QueryBuilderOperation {
const labels : string [ ] = [ ] ;
let exploringNode = node . getChild ( DropLabels ) ;
while ( exploringNode ) {
const label = getString ( expr , exploringNode . getChild ( DropLabel ) ) ;
if ( label ) {
labels . push ( label ) ;
}
exploringNode = exploringNode ? . getChild ( DropLabels ) ;
}
labels . reverse ( ) ;
return {
id : LokiOperationId.Drop ,
params : labels ,
} ;
}
function handleKeepFilter ( expr : string , node : SyntaxNode , context : Context ) : QueryBuilderOperation {
const labels : string [ ] = [ ] ;
let exploringNode = node . getChild ( KeepLabels ) ;
while ( exploringNode ) {
const label = getString ( expr , exploringNode . getChild ( KeepLabel ) ) ;
if ( label ) {
labels . push ( label ) ;
}
exploringNode = exploringNode ? . getChild ( KeepLabels ) ;
}
labels . reverse ( ) ;
return {
id : LokiOperationId.Keep ,
params : labels ,
} ;
}