prometheus/web/ui/mantine-ui/src/promql/utils.ts
Julius Volz 87a22500e1
Some checks failed
CI / Go tests (push) Has been cancelled
CI / More Go tests (push) Has been cancelled
CI / Go tests with previous Go version (push) Has been cancelled
CI / UI tests (push) Has been cancelled
CI / Go tests on Windows (push) Has been cancelled
CI / Mixins tests (push) Has been cancelled
CI / Build Prometheus for common architectures (0) (push) Has been cancelled
CI / Build Prometheus for common architectures (1) (push) Has been cancelled
CI / Build Prometheus for common architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (0) (push) Has been cancelled
CI / Build Prometheus for all architectures (1) (push) Has been cancelled
CI / Build Prometheus for all architectures (10) (push) Has been cancelled
CI / Build Prometheus for all architectures (11) (push) Has been cancelled
CI / Build Prometheus for all architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (3) (push) Has been cancelled
CI / Build Prometheus for all architectures (4) (push) Has been cancelled
CI / Build Prometheus for all architectures (5) (push) Has been cancelled
CI / Build Prometheus for all architectures (6) (push) Has been cancelled
CI / Build Prometheus for all architectures (7) (push) Has been cancelled
CI / Build Prometheus for all architectures (8) (push) Has been cancelled
CI / Build Prometheus for all architectures (9) (push) Has been cancelled
CI / Check generated parser (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / fuzzing (push) Has been cancelled
CI / codeql (push) Has been cancelled
CI / Report status of build Prometheus for all architectures (push) Has been cancelled
CI / Publish main branch artifacts (push) Has been cancelled
CI / Publish release artefacts (push) Has been cancelled
CI / Publish UI on npm Registry (push) Has been cancelled
Add PromQL logic code and labels explorer from PromLens, add testing deps
Signed-off-by: Julius Volz <julius.volz@gmail.com>
2024-09-02 13:45:36 +02:00

242 lines
7.2 KiB
TypeScript

import ASTNode, { binaryOperatorType, nodeType, valueType, Call, compOperatorTypes, setOperatorTypes } from './ast';
import { functionArgNames } from './functionMeta';
export const getNonParenNodeType = (n: ASTNode) => {
let cur: ASTNode;
for (cur = n; cur.type === 'parenExpr'; cur = cur.expr) {}
return cur.type;
};
export const isComparisonOperator = (op: binaryOperatorType) => {
return compOperatorTypes.includes(op);
};
export const isSetOperator = (op: binaryOperatorType) => {
return setOperatorTypes.includes(op);
};
const binOpPrecedence = {
[binaryOperatorType.add]: 3,
[binaryOperatorType.sub]: 3,
[binaryOperatorType.mul]: 2,
[binaryOperatorType.div]: 2,
[binaryOperatorType.mod]: 2,
[binaryOperatorType.pow]: 1,
[binaryOperatorType.eql]: 4,
[binaryOperatorType.neq]: 4,
[binaryOperatorType.gtr]: 4,
[binaryOperatorType.lss]: 4,
[binaryOperatorType.gte]: 4,
[binaryOperatorType.lte]: 4,
[binaryOperatorType.and]: 5,
[binaryOperatorType.or]: 6,
[binaryOperatorType.unless]: 5,
[binaryOperatorType.atan2]: 2,
};
export const maybeParenthesizeBinopChild = (op: binaryOperatorType, child: ASTNode): ASTNode => {
if (child.type !== nodeType.binaryExpr) {
return child;
}
if (binOpPrecedence[op] > binOpPrecedence[child.op]) {
return child;
}
// TODO: Parens aren't necessary for left-associativity within same precedence,
// or right-associativity between two power operators.
return {
type: nodeType.parenExpr,
expr: child,
};
};
export const getNodeChildren = (node: ASTNode): ASTNode[] => {
switch (node.type) {
case nodeType.aggregation:
return node.param === null ? [node.expr] : [node.param, node.expr];
case nodeType.subquery:
return [node.expr];
case nodeType.parenExpr:
return [node.expr];
case nodeType.call:
return node.args;
case nodeType.matrixSelector:
case nodeType.vectorSelector:
case nodeType.numberLiteral:
case nodeType.stringLiteral:
return [];
case nodeType.placeholder:
return node.children;
case nodeType.unaryExpr:
return [node.expr];
case nodeType.binaryExpr:
return [node.lhs, node.rhs];
default:
throw new Error('unsupported node type');
}
};
export const getNodeChild = (node: ASTNode, idx: number) => {
switch (node.type) {
case nodeType.aggregation:
return node.param === null || idx === 1 ? node.expr : node.param;
case nodeType.subquery:
return node.expr;
case nodeType.parenExpr:
return node.expr;
case nodeType.call:
return node.args[idx];
case nodeType.unaryExpr:
return node.expr;
case nodeType.binaryExpr:
return idx === 0 ? node.lhs : node.rhs;
default:
throw new Error('unsupported node type');
}
};
export const containsPlaceholders = (node: ASTNode): boolean =>
node.type === nodeType.placeholder || getNodeChildren(node).some((n) => containsPlaceholders(n));
export const nodeValueType = (node: ASTNode): valueType | null => {
switch (node.type) {
case nodeType.aggregation:
return valueType.vector;
case nodeType.binaryExpr:
const childTypes = [nodeValueType(node.lhs), nodeValueType(node.rhs)];
if (childTypes.includes(null)) {
// One of the children is or a has a placeholder and thus an undefined type.
return null;
}
if (childTypes.includes(valueType.vector)) {
return valueType.vector;
}
return valueType.scalar;
case nodeType.call:
return node.func.returnType;
case nodeType.matrixSelector:
return valueType.matrix;
case nodeType.numberLiteral:
return valueType.scalar;
case nodeType.parenExpr:
return nodeValueType(node.expr);
case nodeType.placeholder:
return null;
case nodeType.stringLiteral:
return valueType.string;
case nodeType.subquery:
return valueType.matrix;
case nodeType.unaryExpr:
return nodeValueType(node.expr);
case nodeType.vectorSelector:
return valueType.vector;
default:
throw new Error('invalid node type');
}
};
export const childDescription = (node: ASTNode, idx: number): string => {
switch (node.type) {
case nodeType.aggregation:
if (aggregatorsWithParam.includes(node.op) && idx === 0) {
switch (node.op) {
case 'topk':
case 'bottomk':
case 'limitk':
return 'k';
case 'quantile':
return 'quantile';
case 'count_values':
return 'target label name';
case 'limit_ratio':
return 'ratio';
}
}
return 'vector to aggregate';
case nodeType.binaryExpr:
return idx === 0 ? 'left-hand side' : 'right-hand side';
case nodeType.call:
if (functionArgNames.hasOwnProperty(node.func.name)) {
const argNames = functionArgNames[node.func.name];
return argNames[Math.min(functionArgNames[node.func.name].length - 1, idx)];
}
return 'argument';
case nodeType.parenExpr:
return 'expression';
case nodeType.placeholder:
return 'argument';
case nodeType.subquery:
return 'subquery to execute';
case nodeType.unaryExpr:
return 'expression';
default:
throw new Error('invalid node type');
}
};
export const aggregatorsWithParam = ['topk', 'bottomk', 'quantile', 'count_values', 'limitk', 'limit_ratio'];
export const anyValueType = [valueType.scalar, valueType.string, valueType.matrix, valueType.vector];
export const allowedChildValueTypes = (node: ASTNode, idx: number): valueType[] => {
switch (node.type) {
case nodeType.aggregation:
if (aggregatorsWithParam.includes(node.op) && idx === 0) {
if (node.op === 'count_values') {
return [valueType.string];
}
return [valueType.scalar];
}
return [valueType.vector];
case nodeType.binaryExpr:
// TODO: Do deeper constraint checking here.
// - Set ops only between vectors.
// - Bools only for filter ops.
// - Advanced: check cardinality.
return [valueType.scalar, valueType.vector];
case nodeType.call:
return [node.func.argTypes[Math.min(idx, node.func.argTypes.length - 1)]];
case nodeType.parenExpr:
return anyValueType;
case nodeType.placeholder:
return anyValueType;
case nodeType.subquery:
return [valueType.vector];
case nodeType.unaryExpr:
return anyValueType;
default:
throw new Error('invalid node type');
}
};
export const canAddVarArg = (node: Call): boolean => {
if (node.func.variadic === -1) {
return true;
}
// TODO: Only works for 1 vararg, but PromQL only has functions with either 1 (not 2, 3, ...) or unlimited (-1) varargs in practice, so this is fine for now.
return node.args.length < node.func.argTypes.length;
};
export const canRemoveVarArg = (node: Call): boolean => {
return node.func.variadic !== 0 && node.args.length >= node.func.argTypes.length;
};
export const humanizedValueType: Record<valueType, string> = {
[valueType.none]: 'none',
[valueType.string]: 'string',
[valueType.scalar]: 'number (scalar)',
[valueType.vector]: 'instant vector',
[valueType.matrix]: 'range vector',
};
export const escapeString = (str: string) => {
return str.replace(/([\\"])/g, '\\$1');
};