mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki Query Builder: binary expression and numeric literal bugs (#74950)
* Following similar changes made to prometheus in #47198, reverse the order of binary operator parameter array, and fix bugs introduced by importing prometheus lezer constants into loki parser * add unit test asserting buildVisualQueryFromString does not properly parse queries with nested binary operations * fix onOrIgnoring parsing logic which was always stripping out the value of the matcher when a boolean was found
This commit is contained in:
parent
06d89e1929
commit
ac98197132
@ -82,12 +82,12 @@ export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDe
|
|||||||
const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
|
const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
|
||||||
const defaultParams: any[] = [2];
|
const defaultParams: any[] = [2];
|
||||||
if (opDef.comparison) {
|
if (opDef.comparison) {
|
||||||
params.unshift({
|
params.push({
|
||||||
name: 'Bool',
|
name: 'Bool',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'If checked comparison will return 0 or 1 for the value rather than filtering.',
|
description: 'If checked comparison will return 0 or 1 for the value rather than filtering.',
|
||||||
});
|
});
|
||||||
defaultParams.unshift(false);
|
defaultParams.push(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -107,8 +107,7 @@ function getSimpleBinaryRenderer(operator: string) {
|
|||||||
let param = model.params[0];
|
let param = model.params[0];
|
||||||
let bool = '';
|
let bool = '';
|
||||||
if (model.params.length === 2) {
|
if (model.params.length === 2) {
|
||||||
param = model.params[1];
|
bool = model.params[1] ? ' bool' : '';
|
||||||
bool = model.params[0] ? ' bool' : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${innerExpr} ${operator}${bool} ${param}`;
|
return `${innerExpr} ${operator}${bool} ${param}`;
|
||||||
|
@ -11,6 +11,116 @@ describe('buildVisualQueryFromString', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('parses simple binary comparison', () => {
|
||||||
|
expect(buildVisualQueryFromString('count_over_time({app="aggregator"} [$__auto]) == 11')).toEqual({
|
||||||
|
query: {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
label: 'app',
|
||||||
|
op: '=',
|
||||||
|
value: 'aggregator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
id: LokiOperationId.CountOverTime,
|
||||||
|
params: ['$__auto'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: LokiOperationId.EqualTo,
|
||||||
|
// defined in getSimpleBinaryRenderer, the first argument is the bool value, and the second is the comparison operator
|
||||||
|
params: [11, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// This still fails because loki doesn't properly parse the bool operator
|
||||||
|
it('parses simple query with label-values with boolean operator', () => {
|
||||||
|
expect(buildVisualQueryFromString('count_over_time({app="aggregator"} [$__auto]) == bool 12')).toEqual({
|
||||||
|
query: {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
label: 'app',
|
||||||
|
op: '=',
|
||||||
|
value: 'aggregator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
id: LokiOperationId.CountOverTime,
|
||||||
|
params: ['$__auto'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: LokiOperationId.EqualTo,
|
||||||
|
// defined in getSimpleBinaryRenderer, the first argument is the bool value, and the second is the comparison operator
|
||||||
|
params: [12, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses binary operation with query', () => {
|
||||||
|
expect(
|
||||||
|
// There is no capability for "bool" in the query builder for (nested) binary operation with query as of now, it will always be stripped out
|
||||||
|
buildVisualQueryFromString(
|
||||||
|
'max by(stream) (count_over_time({app="aggregator"}[1m])) > bool ignoring(stream) avg(count_over_time({app="aggregator"}[1m]))'
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
query: {
|
||||||
|
binaryQueries: [
|
||||||
|
{
|
||||||
|
// nested binary operation
|
||||||
|
operator: '>',
|
||||||
|
query: {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
label: 'app',
|
||||||
|
op: '=',
|
||||||
|
value: 'aggregator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
id: 'count_over_time',
|
||||||
|
params: ['1m'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'avg',
|
||||||
|
params: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
vectorMatches: 'stream',
|
||||||
|
vectorMatchesType: 'ignoring',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
label: 'app',
|
||||||
|
op: '=',
|
||||||
|
value: 'aggregator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
id: 'count_over_time',
|
||||||
|
params: ['1m'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '__max_by',
|
||||||
|
params: ['stream'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('parses simple query with label-values', () => {
|
it('parses simple query with label-values', () => {
|
||||||
expect(buildVisualQueryFromString('{app="frontend"}')).toEqual(
|
expect(buildVisualQueryFromString('{app="frontend"}')).toEqual(
|
||||||
noErrors({
|
noErrors({
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { SyntaxNode } from '@lezer/common';
|
import { SyntaxNode } from '@lezer/common';
|
||||||
import { BinModifiers, OnOrIgnoring } from '@prometheus-io/lezer-promql';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
And,
|
And,
|
||||||
@ -50,6 +49,8 @@ import {
|
|||||||
VectorAggregationExpr,
|
VectorAggregationExpr,
|
||||||
VectorOp,
|
VectorOp,
|
||||||
Without,
|
Without,
|
||||||
|
BinOpModifier,
|
||||||
|
OnOrIgnoringModifier,
|
||||||
} from '@grafana/lezer-logql';
|
} from '@grafana/lezer-logql';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -565,7 +566,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
|||||||
const visQuery = context.query;
|
const visQuery = context.query;
|
||||||
const left = node.firstChild!;
|
const left = node.firstChild!;
|
||||||
const op = getString(expr, left.nextSibling);
|
const op = getString(expr, left.nextSibling);
|
||||||
const binModifier = getBinaryModifier(expr, node.getChild(BinModifiers));
|
const binModifier = getBinaryModifier(expr, node.getChild(BinOpModifier));
|
||||||
|
|
||||||
const right = node.lastChild!;
|
const right = node.lastChild!;
|
||||||
|
|
||||||
@ -591,7 +592,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
|||||||
// Due to the way binary ops are parsed we can get a binary operation on the right that starts with a number which
|
// 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.
|
// is a factor for a current binary operation. So we have to add it as an operation now.
|
||||||
const leftMostChild = getLeftMostChild(right);
|
const leftMostChild = getLeftMostChild(right);
|
||||||
if (leftMostChild?.name === 'Number') {
|
if (leftMostChild?.type.id === NumberLezer) {
|
||||||
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool));
|
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,15 +625,17 @@ function getBinaryModifier(
|
|||||||
node: SyntaxNode | null
|
node: SyntaxNode | null
|
||||||
):
|
):
|
||||||
| { isBool: true; isMatcher: false }
|
| { isBool: true; isMatcher: false }
|
||||||
| { isBool: false; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
|
| { isBool: boolean; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
|
||||||
| undefined {
|
| undefined {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (node.getChild(Bool)) {
|
const matcher = node.getChild(OnOrIgnoringModifier);
|
||||||
|
const boolMatcher = node.getChild(Bool);
|
||||||
|
|
||||||
|
if (!matcher && boolMatcher) {
|
||||||
return { isBool: true, isMatcher: false };
|
return { isBool: true, isMatcher: false };
|
||||||
} else {
|
} else {
|
||||||
const matcher = node.getChild(OnOrIgnoring);
|
|
||||||
if (!matcher) {
|
if (!matcher) {
|
||||||
// Not sure what this could be, maybe should be an error.
|
// Not sure what this could be, maybe should be an error.
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -640,7 +643,7 @@ function getBinaryModifier(
|
|||||||
const labels = getString(expr, matcher.getChild(GroupingLabels)?.getChild(GroupingLabelList));
|
const labels = getString(expr, matcher.getChild(GroupingLabels)?.getChild(GroupingLabelList));
|
||||||
return {
|
return {
|
||||||
isMatcher: true,
|
isMatcher: true,
|
||||||
isBool: false,
|
isBool: !!boolMatcher,
|
||||||
matches: labels,
|
matches: labels,
|
||||||
matchType: matcher.getChild(On) ? 'on' : 'ignoring',
|
matchType: matcher.getChild(On) ? 'on' : 'ignoring',
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { buildVisualQueryFromString } from './parsing';
|
import { buildVisualQueryFromString } from './parsing';
|
||||||
import { PromVisualQuery } from './types';
|
import { PromOperationId, PromVisualQuery } from './types';
|
||||||
|
|
||||||
describe('buildVisualQueryFromString', () => {
|
describe('buildVisualQueryFromString', () => {
|
||||||
it('creates no errors for empty query', () => {
|
it('creates no errors for empty query', () => {
|
||||||
@ -11,6 +11,50 @@ describe('buildVisualQueryFromString', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('parses simple binary comparison', () => {
|
||||||
|
expect(buildVisualQueryFromString('{app="aggregator"} == 11')).toEqual({
|
||||||
|
query: {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
label: 'app',
|
||||||
|
op: '=',
|
||||||
|
value: 'aggregator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metric: '',
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
id: PromOperationId.EqualTo,
|
||||||
|
params: [11, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// This still fails because loki doesn't properly parse the bool operator
|
||||||
|
it('parses simple query with with boolean operator', () => {
|
||||||
|
expect(buildVisualQueryFromString('{app="aggregator"} == bool 12')).toEqual({
|
||||||
|
query: {
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
label: 'app',
|
||||||
|
op: '=',
|
||||||
|
value: 'aggregator',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metric: '',
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
id: PromOperationId.EqualTo,
|
||||||
|
params: [12, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
it('parses simple query', () => {
|
it('parses simple query', () => {
|
||||||
expect(buildVisualQueryFromString('counters_logins{app="frontend"}')).toEqual(
|
expect(buildVisualQueryFromString('counters_logins{app="frontend"}')).toEqual(
|
||||||
noErrors({
|
noErrors({
|
||||||
|
Loading…
Reference in New Issue
Block a user