mirror of
https://github.com/grafana/grafana.git
synced 2025-09-26 22:54:32 +08:00

* add support for code editor and builder * refactor cloudwatch migration * Add tooltip to editor field (#56) * add tooltip * add old tooltips * Bug bash feedback fixes (#58) * make ASC the default option * update sql preview whenever sql changes * don't allow queries without aggregation * set default value for aggregation * use new input field * cleanup * pr feedback * prevent unnecessary rerenders * use frame error instead of main error * remove not used snapshot * Use dimension filter in schema picker (#63) * use dimension key filter in group by and schema labels * add dimension filter also to code editor * add tests * fix build error * fix strict error * remove debug code * fix annotation editor (#64) * fix annotation editor * fix broken test * revert annotation backend change * PR feedback (#67) * pr feedback * removed dimension filter from group by * add spacing between common fields and rest * do not generate deep link for metric queries (#70) * update docs (#69) Co-authored-by: Erik Sundell <erik.sundell87@gmail.com> * fix lint problem caused by merge conflict Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
158 lines
4.8 KiB
TypeScript
158 lines
4.8 KiB
TypeScript
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
|
import { SQLExpression } from '../types';
|
|
import {
|
|
QueryEditorArrayExpression,
|
|
QueryEditorExpression,
|
|
QueryEditorExpressionType,
|
|
QueryEditorFunctionExpression,
|
|
QueryEditorOperatorExpression,
|
|
QueryEditorPropertyExpression,
|
|
} from '../expressions';
|
|
|
|
export default class SQLGenerator {
|
|
constructor(private templateSrv: TemplateSrv = getTemplateSrv()) {}
|
|
|
|
expressionToSqlQuery({
|
|
select,
|
|
from,
|
|
where,
|
|
groupBy,
|
|
orderBy,
|
|
orderByDirection,
|
|
limit,
|
|
}: SQLExpression): string | undefined {
|
|
if (!from || !select?.name || !select?.parameters?.length) {
|
|
return undefined;
|
|
}
|
|
|
|
let parts: string[] = [];
|
|
this.appendSelect(select, parts);
|
|
this.appendFrom(from, parts);
|
|
this.appendWhere(where, parts, true, where?.expressions?.length ?? 0);
|
|
this.appendGroupBy(groupBy, parts);
|
|
this.appendOrderBy(orderBy, orderByDirection, parts);
|
|
this.appendLimit(limit, parts);
|
|
|
|
return parts.join(' ');
|
|
}
|
|
|
|
private appendSelect(select: QueryEditorFunctionExpression | undefined, parts: string[]) {
|
|
parts.push('SELECT');
|
|
this.appendFunction(select, parts);
|
|
}
|
|
|
|
private appendFrom(from: QueryEditorPropertyExpression | QueryEditorFunctionExpression | undefined, parts: string[]) {
|
|
parts.push('FROM');
|
|
from?.type === QueryEditorExpressionType.Function
|
|
? this.appendFunction(from, parts)
|
|
: parts.push(this.formatValue(from?.property?.name ?? ''));
|
|
}
|
|
|
|
private appendWhere(
|
|
filter: QueryEditorExpression | undefined,
|
|
parts: string[],
|
|
isTopLevelExpression: boolean,
|
|
topLevelExpressionsCount: number
|
|
) {
|
|
if (!filter) {
|
|
return;
|
|
}
|
|
|
|
const hasChildExpressions = 'expressions' in filter && filter.expressions.length > 0;
|
|
if (isTopLevelExpression && hasChildExpressions) {
|
|
parts.push('WHERE');
|
|
}
|
|
|
|
if (filter.type === QueryEditorExpressionType.And) {
|
|
const andParts: string[] = [];
|
|
filter.expressions.map((exp) => this.appendWhere(exp, andParts, false, topLevelExpressionsCount));
|
|
if (andParts.length === 0) {
|
|
return;
|
|
}
|
|
const andCombined = andParts.join(' AND ');
|
|
const wrapInParentheses = !isTopLevelExpression && topLevelExpressionsCount > 1 && andParts.length > 1;
|
|
return parts.push(wrapInParentheses ? `(${andCombined})` : andCombined);
|
|
}
|
|
|
|
if (filter.type === QueryEditorExpressionType.Or) {
|
|
const orParts: string[] = [];
|
|
filter.expressions.map((exp) => this.appendWhere(exp, orParts, false, topLevelExpressionsCount));
|
|
if (orParts.length === 0) {
|
|
return;
|
|
}
|
|
const orCombined = orParts.join(' OR ');
|
|
const wrapInParentheses = !isTopLevelExpression && topLevelExpressionsCount > 1 && orParts.length > 1;
|
|
parts.push(wrapInParentheses ? `(${orCombined})` : orCombined);
|
|
return;
|
|
}
|
|
|
|
if (filter.type === QueryEditorExpressionType.Operator) {
|
|
return this.appendOperator(filter, parts);
|
|
}
|
|
}
|
|
|
|
private appendGroupBy(groupBy: QueryEditorArrayExpression | undefined, parts: string[]) {
|
|
const groupByParts: string[] = [];
|
|
for (const expression of groupBy?.expressions ?? []) {
|
|
if (expression?.type !== QueryEditorExpressionType.GroupBy || !expression.property.name) {
|
|
continue;
|
|
}
|
|
groupByParts.push(this.formatValue(expression.property.name));
|
|
}
|
|
|
|
if (groupByParts.length > 0) {
|
|
parts.push(`GROUP BY ${groupByParts.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
private appendOrderBy(
|
|
orderBy: QueryEditorFunctionExpression | undefined,
|
|
orderByDirection: string | undefined,
|
|
parts: string[]
|
|
) {
|
|
if (orderBy) {
|
|
parts.push('ORDER BY');
|
|
this.appendFunction(orderBy, parts);
|
|
parts.push(orderByDirection ?? 'ASC');
|
|
}
|
|
}
|
|
|
|
private appendLimit(limit: number | undefined, parts: string[]) {
|
|
limit && parts.push(`LIMIT ${limit}`);
|
|
}
|
|
|
|
private appendOperator(expression: QueryEditorOperatorExpression, parts: string[], prefix?: string) {
|
|
const { property, operator } = expression;
|
|
|
|
if (!property.name || !operator.name || !operator.value) {
|
|
return;
|
|
}
|
|
|
|
parts.push(`${this.formatValue(property.name)} ${operator.name} '${operator.value}'`);
|
|
}
|
|
|
|
private appendFunction(select: QueryEditorFunctionExpression | undefined, parts: string[]) {
|
|
if (!select?.name) {
|
|
return;
|
|
}
|
|
|
|
const params = (select.parameters ?? [])
|
|
.map((p) => p.name && this.formatValue(p.name))
|
|
.filter(Boolean)
|
|
.join(', ');
|
|
|
|
parts.push(`${select.name}(${params})`);
|
|
}
|
|
|
|
private formatValue(label: string): string {
|
|
const specialCharacters = /[/\s\.-]/; // slash, space, dot or dash
|
|
|
|
const interpolated = this.templateSrv.replace(label, {}, 'raw');
|
|
if (specialCharacters.test(interpolated)) {
|
|
return `"${label}"`;
|
|
}
|
|
|
|
return label;
|
|
}
|
|
}
|