Prometheus: Use lezer-promql types instead of hardcoded strings (#53287)

* Prometheus: Use lezer-promql types instead of hardcoded strings

* Update comment line

* Removing more hardcoded values
This commit is contained in:
ismail simsek
2022-08-08 10:14:12 +02:00
committed by GitHub
parent 2fea3f0d9a
commit a40c1e227c
2 changed files with 105 additions and 80 deletions
@@ -1,32 +1,54 @@
import type { Tree, SyntaxNode } from '@lezer/common';
import { parser } from 'lezer-promql';
import type { SyntaxNode, Tree } from '@lezer/common';
import {
AggregateExpr,
AggregateModifier,
EqlRegex,
EqlSingle,
FunctionCallBody,
GroupingLabels,
Identifier,
LabelMatcher,
LabelMatchers,
LabelMatchList,
LabelName,
MatchOp,
MatrixSelector,
MetricIdentifier,
Neq,
NeqRegex,
parser,
PromQL,
StringLiteral,
VectorSelector,
} from 'lezer-promql';
import { NeverCaseError } from './util';
type Direction = 'parent' | 'firstChild' | 'lastChild' | 'nextSibling';
type NodeTypeName =
| '⚠' // this is used as error-name
| 'AggregateExpr'
| 'AggregateModifier'
| 'FunctionCallBody'
| 'GroupingLabels'
| 'Identifier'
| 'LabelMatcher'
| 'LabelMatchers'
| 'LabelMatchList'
| 'LabelName'
| 'MetricIdentifier'
| 'PromQL'
| 'StringLiteral'
| 'VectorSelector'
| 'MatrixSelector'
| 'MatchOp'
| 'EqlSingle'
| 'Neq'
| 'EqlRegex'
| 'NeqRegex';
type Path = Array<[Direction, NodeTypeName]>;
type NodeTypeId =
| 0 // this is used as error-id
| typeof AggregateExpr
| typeof AggregateModifier
| typeof FunctionCallBody
| typeof GroupingLabels
| typeof Identifier
| typeof LabelMatcher
| typeof LabelMatchers
| typeof LabelMatchList
| typeof LabelName
| typeof MetricIdentifier
| typeof PromQL
| typeof StringLiteral
| typeof VectorSelector
| typeof MatrixSelector
| typeof MatchOp
| typeof EqlSingle
| typeof Neq
| typeof EqlRegex
| typeof NeqRegex;
type Path = Array<[Direction, NodeTypeId]>;
function move(node: SyntaxNode, direction: Direction): SyntaxNode | null {
switch (direction) {
@@ -51,7 +73,7 @@ function walk(node: SyntaxNode, path: Path): SyntaxNode | null {
// we could not move in the direction, we stop
return null;
}
if (current.type.name !== expectedType) {
if (current.type.id !== expectedType) {
// the reached node has wrong type, we stop
return null;
}
@@ -134,52 +156,52 @@ export type Situation =
};
type Resolver = {
path: NodeTypeName[];
path: NodeTypeId[];
fun: (node: SyntaxNode, text: string, pos: number) => Situation | null;
};
function isPathMatch(resolverPath: string[], cursorPath: string[]): boolean {
function isPathMatch(resolverPath: NodeTypeId[], cursorPath: number[]): boolean {
return resolverPath.every((item, index) => item === cursorPath[index]);
}
const ERROR_NODE_NAME: NodeTypeName = '⚠'; // this is used as error-name
const ERROR_NODE_NAME: NodeTypeId = 0; // this is used as error-id
const RESOLVERS: Resolver[] = [
{
path: ['LabelMatchers', 'VectorSelector'],
path: [LabelMatchers, VectorSelector],
fun: resolveLabelKeysWithEquals,
},
{
path: ['PromQL'],
path: [PromQL],
fun: resolveTopLevel,
},
{
path: ['FunctionCallBody'],
path: [FunctionCallBody],
fun: resolveInFunction,
},
{
path: ['StringLiteral', 'LabelMatcher'],
path: [StringLiteral, LabelMatcher],
fun: resolveLabelMatcher,
},
{
path: [ERROR_NODE_NAME, 'LabelMatcher'],
path: [ERROR_NODE_NAME, LabelMatcher],
fun: resolveLabelMatcher,
},
{
path: [ERROR_NODE_NAME, 'MatrixSelector'],
path: [ERROR_NODE_NAME, MatrixSelector],
fun: resolveDurations,
},
{
path: ['GroupingLabels'],
path: [GroupingLabels],
fun: resolveLabelsForGrouping,
},
];
const LABEL_OP_MAP = new Map<string, LabelOperator>([
['EqlSingle', '='],
['EqlRegex', '=~'],
['Neq', '!='],
['NeqRegex', '!~'],
const LABEL_OP_MAP = new Map<number, LabelOperator>([
[EqlSingle, '='],
[EqlRegex, '=~'],
[Neq, '!='],
[NeqRegex, '!~'],
]);
function getLabelOp(opNode: SyntaxNode): LabelOperator | null {
@@ -188,21 +210,21 @@ function getLabelOp(opNode: SyntaxNode): LabelOperator | null {
return null;
}
return LABEL_OP_MAP.get(opChild.name) ?? null;
return LABEL_OP_MAP.get(opChild.type.id) ?? null;
}
function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
if (labelMatcherNode.type.name !== 'LabelMatcher') {
if (labelMatcherNode.type.id !== LabelMatcher) {
return null;
}
const nameNode = walk(labelMatcherNode, [['firstChild', 'LabelName']]);
const nameNode = walk(labelMatcherNode, [['firstChild', LabelName]]);
if (nameNode === null) {
return null;
}
const opNode = walk(nameNode, [['nextSibling', 'MatchOp']]);
const opNode = walk(nameNode, [['nextSibling', MatchOp]]);
if (opNode === null) {
return null;
}
@@ -212,7 +234,7 @@ function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
return null;
}
const valueNode = walk(labelMatcherNode, [['lastChild', 'StringLiteral']]);
const valueNode = walk(labelMatcherNode, [['lastChild', StringLiteral]]);
if (valueNode === null) {
return null;
@@ -223,17 +245,18 @@ function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
return { name, value, op };
}
function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] {
if (labelMatchersNode.type.name !== 'LabelMatchers') {
if (labelMatchersNode.type.id !== LabelMatchers) {
return [];
}
let listNode: SyntaxNode | null = walk(labelMatchersNode, [['firstChild', 'LabelMatchList']]);
let listNode: SyntaxNode | null = walk(labelMatchersNode, [['firstChild', LabelMatchList]]);
const labels: Label[] = [];
while (listNode !== null) {
const matcherNode = walk(listNode, [['lastChild', 'LabelMatcher']]);
const matcherNode = walk(listNode, [['lastChild', LabelMatcher]]);
if (matcherNode === null) {
// unexpected, we stop
return [];
@@ -245,7 +268,7 @@ function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] {
}
// there might be more labels
listNode = walk(listNode, [['firstChild', 'LabelMatchList']]);
listNode = walk(listNode, [['firstChild', LabelMatchList]]);
}
// our labels-list is last-first, so we reverse it
@@ -264,16 +287,16 @@ function getNodeChildren(node: SyntaxNode): SyntaxNode[] {
return children;
}
function getNodeInSubtree(node: SyntaxNode, typeName: NodeTypeName): SyntaxNode | null {
function getNodeInSubtree(node: SyntaxNode, typeId: NodeTypeId): SyntaxNode | null {
// first we try the current node
if (node.type.name === typeName) {
if (node.type.id === typeId) {
return node;
}
// then we try the children
const children = getNodeChildren(node);
for (const child of children) {
const n = getNodeInSubtree(child, typeName);
const n = getNodeInSubtree(child, typeId);
if (n !== null) {
return n;
}
@@ -284,23 +307,23 @@ function getNodeInSubtree(node: SyntaxNode, typeName: NodeTypeName): SyntaxNode
function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number): Situation | null {
const aggrExpNode = walk(node, [
['parent', 'AggregateModifier'],
['parent', 'AggregateExpr'],
['parent', AggregateModifier],
['parent', AggregateExpr],
]);
if (aggrExpNode === null) {
return null;
}
const bodyNode = aggrExpNode.getChild('FunctionCallBody');
const bodyNode = aggrExpNode.getChild(FunctionCallBody);
if (bodyNode === null) {
return null;
}
const metricIdNode = getNodeInSubtree(bodyNode, 'MetricIdentifier');
const metricIdNode = getNodeInSubtree(bodyNode, MetricIdentifier);
if (metricIdNode === null) {
return null;
}
const idNode = walk(metricIdNode, [['firstChild', 'Identifier']]);
const idNode = walk(metricIdNode, [['firstChild', Identifier]]);
if (idNode === null) {
return null;
}
@@ -319,12 +342,12 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
// - or an error node (like in `{job=^}`)
const inStringNode = !node.type.isError;
const parent = walk(node, [['parent', 'LabelMatcher']]);
const parent = walk(node, [['parent', LabelMatcher]]);
if (parent === null) {
return null;
}
const labelNameNode = walk(parent, [['firstChild', 'LabelName']]);
const labelNameNode = walk(parent, [['firstChild', LabelName]]);
if (labelNameNode === null) {
return null;
}
@@ -335,7 +358,7 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
// there can be one or many `LabelMatchList` parents, we have
// to go through all of them
const firstListNode = walk(parent, [['parent', 'LabelMatchList']]);
const firstListNode = walk(parent, [['parent', LabelMatchList]]);
if (firstListNode === null) {
return null;
}
@@ -352,14 +375,14 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
return null;
}
const { name } = p.type;
const { id } = p.type;
switch (name) {
case 'LabelMatchList':
switch (id) {
case LabelMatchList:
//we keep looping
listNode = p;
continue;
case 'LabelMatchers':
case LabelMatchers:
// we reached the end, we can stop the loop
labelMatchersNode = p;
continue;
@@ -376,9 +399,9 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
const otherLabels = allLabels.filter((label) => label.name !== labelName);
const metricNameNode = walk(labelMatchersNode, [
['parent', 'VectorSelector'],
['firstChild', 'MetricIdentifier'],
['firstChild', 'Identifier'],
['parent', VectorSelector],
['firstChild', MetricIdentifier],
['firstChild', Identifier],
]);
if (metricNameNode === null) {
@@ -436,7 +459,7 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
// next false positive:
// `something{a="1"^}`
const child = walk(node, [['firstChild', 'LabelMatchList']]);
const child = walk(node, [['firstChild', LabelMatchList]]);
if (child !== null) {
// means the label-matching part contains at least one label already.
//
@@ -452,9 +475,9 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
}
const metricNameNode = walk(node, [
['parent', 'VectorSelector'],
['firstChild', 'MetricIdentifier'],
['firstChild', 'Identifier'],
['parent', VectorSelector],
['firstChild', MetricIdentifier],
['firstChild', Identifier],
]);
const otherLabels = getLabels(node, text);
@@ -511,10 +534,10 @@ export function getSituation(text: string, pos: number): Situation | null {
/*
PromQL
Expr
VectorSelector
LabelMatchers
*/
Expr
VectorSelector
LabelMatchers
*/
const tree = parser.parse(text);
// if the tree contains error, it is very probable that
@@ -527,15 +550,15 @@ LabelMatchers
const cur = maybeErrorNode != null ? maybeErrorNode.cursor : tree.cursor(pos);
const currentNode = cur.node;
const names = [cur.name];
const ids = [cur.type.id];
while (cur.parent()) {
names.push(cur.name);
ids.push(cur.type.id);
}
for (let resolver of RESOLVERS) {
// i do not use a foreach because i want to stop as soon
// as i find something
if (isPathMatch(resolver.path, names)) {
if (isPathMatch(resolver.path, ids)) {
return resolver.fun(currentNode, text, pos);
}
}
@@ -23,6 +23,7 @@ import {
ParenExpr,
parser,
StringLiteral,
VectorSelector,
Without,
} from 'lezer-promql';
@@ -167,7 +168,7 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex
}
function isIntervalVariableError(node: SyntaxNode) {
return node.prevSibling?.name === 'Expr' && node.prevSibling?.firstChild?.name === 'VectorSelector';
return node.prevSibling?.type.id === Expr && node.prevSibling?.firstChild?.type.id === VectorSelector;
}
function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter {
@@ -182,6 +183,7 @@ function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter {
}
const rangeFunctions = ['changes', 'rate', 'irate', 'increase', 'delta'];
/**
* Handle function call which is usually and identifier and its body > arguments.
* @param expr
@@ -346,7 +348,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
// is a factor for a current binary operation. So we have to add it as an operation now.
const leftMostChild = getLeftMostChild(right);
if (leftMostChild?.name === 'NumberLiteral') {
if (leftMostChild?.type.id === NumberLiteral) {
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool));
}