123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
- import {
- QueryEditorArrayExpression,
- QueryEditorExpression,
- QueryEditorExpressionType,
- QueryEditorFunctionExpression,
- QueryEditorOperatorExpression,
- QueryEditorPropertyExpression,
- } from '../expressions';
- import { SQLExpression } from '../types';
- 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;
- }
- }
|