SQLGenerator.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
  2. import {
  3. QueryEditorArrayExpression,
  4. QueryEditorExpression,
  5. QueryEditorExpressionType,
  6. QueryEditorFunctionExpression,
  7. QueryEditorOperatorExpression,
  8. QueryEditorPropertyExpression,
  9. } from '../expressions';
  10. import { SQLExpression } from '../types';
  11. export default class SQLGenerator {
  12. constructor(private templateSrv: TemplateSrv = getTemplateSrv()) {}
  13. expressionToSqlQuery({
  14. select,
  15. from,
  16. where,
  17. groupBy,
  18. orderBy,
  19. orderByDirection,
  20. limit,
  21. }: SQLExpression): string | undefined {
  22. if (!from || !select?.name || !select?.parameters?.length) {
  23. return undefined;
  24. }
  25. let parts: string[] = [];
  26. this.appendSelect(select, parts);
  27. this.appendFrom(from, parts);
  28. this.appendWhere(where, parts, true, where?.expressions?.length ?? 0);
  29. this.appendGroupBy(groupBy, parts);
  30. this.appendOrderBy(orderBy, orderByDirection, parts);
  31. this.appendLimit(limit, parts);
  32. return parts.join(' ');
  33. }
  34. private appendSelect(select: QueryEditorFunctionExpression | undefined, parts: string[]) {
  35. parts.push('SELECT');
  36. this.appendFunction(select, parts);
  37. }
  38. private appendFrom(from: QueryEditorPropertyExpression | QueryEditorFunctionExpression | undefined, parts: string[]) {
  39. parts.push('FROM');
  40. from?.type === QueryEditorExpressionType.Function
  41. ? this.appendFunction(from, parts)
  42. : parts.push(this.formatValue(from?.property?.name ?? ''));
  43. }
  44. private appendWhere(
  45. filter: QueryEditorExpression | undefined,
  46. parts: string[],
  47. isTopLevelExpression: boolean,
  48. topLevelExpressionsCount: number
  49. ) {
  50. if (!filter) {
  51. return;
  52. }
  53. const hasChildExpressions = 'expressions' in filter && filter.expressions.length > 0;
  54. if (isTopLevelExpression && hasChildExpressions) {
  55. parts.push('WHERE');
  56. }
  57. if (filter.type === QueryEditorExpressionType.And) {
  58. const andParts: string[] = [];
  59. filter.expressions.map((exp) => this.appendWhere(exp, andParts, false, topLevelExpressionsCount));
  60. if (andParts.length === 0) {
  61. return;
  62. }
  63. const andCombined = andParts.join(' AND ');
  64. const wrapInParentheses = !isTopLevelExpression && topLevelExpressionsCount > 1 && andParts.length > 1;
  65. return parts.push(wrapInParentheses ? `(${andCombined})` : andCombined);
  66. }
  67. if (filter.type === QueryEditorExpressionType.Or) {
  68. const orParts: string[] = [];
  69. filter.expressions.map((exp) => this.appendWhere(exp, orParts, false, topLevelExpressionsCount));
  70. if (orParts.length === 0) {
  71. return;
  72. }
  73. const orCombined = orParts.join(' OR ');
  74. const wrapInParentheses = !isTopLevelExpression && topLevelExpressionsCount > 1 && orParts.length > 1;
  75. parts.push(wrapInParentheses ? `(${orCombined})` : orCombined);
  76. return;
  77. }
  78. if (filter.type === QueryEditorExpressionType.Operator) {
  79. return this.appendOperator(filter, parts);
  80. }
  81. }
  82. private appendGroupBy(groupBy: QueryEditorArrayExpression | undefined, parts: string[]) {
  83. const groupByParts: string[] = [];
  84. for (const expression of groupBy?.expressions ?? []) {
  85. if (expression?.type !== QueryEditorExpressionType.GroupBy || !expression.property.name) {
  86. continue;
  87. }
  88. groupByParts.push(this.formatValue(expression.property.name));
  89. }
  90. if (groupByParts.length > 0) {
  91. parts.push(`GROUP BY ${groupByParts.join(', ')}`);
  92. }
  93. }
  94. private appendOrderBy(
  95. orderBy: QueryEditorFunctionExpression | undefined,
  96. orderByDirection: string | undefined,
  97. parts: string[]
  98. ) {
  99. if (orderBy) {
  100. parts.push('ORDER BY');
  101. this.appendFunction(orderBy, parts);
  102. parts.push(orderByDirection ?? 'ASC');
  103. }
  104. }
  105. private appendLimit(limit: number | undefined, parts: string[]) {
  106. limit && parts.push(`LIMIT ${limit}`);
  107. }
  108. private appendOperator(expression: QueryEditorOperatorExpression, parts: string[], prefix?: string) {
  109. const { property, operator } = expression;
  110. if (!property.name || !operator.name || !operator.value) {
  111. return;
  112. }
  113. parts.push(`${this.formatValue(property.name)} ${operator.name} '${operator.value}'`);
  114. }
  115. private appendFunction(select: QueryEditorFunctionExpression | undefined, parts: string[]) {
  116. if (!select?.name) {
  117. return;
  118. }
  119. const params = (select.parameters ?? [])
  120. .map((p) => p.name && this.formatValue(p.name))
  121. .filter(Boolean)
  122. .join(', ');
  123. parts.push(`${select.name}(${params})`);
  124. }
  125. private formatValue(label: string): string {
  126. const specialCharacters = /[/\s\.-]/; // slash, space, dot or dash
  127. const interpolated = this.templateSrv.replace(label, {}, 'raw');
  128. if (specialCharacters.test(interpolated)) {
  129. return `"${label}"`;
  130. }
  131. return label;
  132. }
  133. }