mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
MySQL: Quote identifiers that include special characters (#61135)
* SQL: toRawSQL required and escape table * Fix autocomplete for MySQL * Change the way we escape for builder * Rework escape ident to be smart instead * Fix A11y for alias * Add first e2e test * Add test for code editor * Add doc * Review comments * Move functions to sqlUtil
This commit is contained in:
@@ -3,15 +3,13 @@ import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorHeader, EditorMode, EditorRow, FlexItem, InlineSelect, Space } from '@grafana/experimental';
|
||||
import { Button, InlineField, InlineSwitch, RadioButtonGroup, Select, Tooltip } from '@grafana/ui';
|
||||
import { Button, InlineSwitch, RadioButtonGroup, Tooltip } from '@grafana/ui';
|
||||
|
||||
import { QueryWithDefaults } from '../defaults';
|
||||
import { SQLQuery, QueryFormat, QueryRowFilter, QUERY_FORMAT_OPTIONS, DB } from '../types';
|
||||
import { defaultToRawSql } from '../utils/sql.utils';
|
||||
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
import { DatasetSelector } from './DatasetSelector';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { TableSelector } from './TableSelector';
|
||||
|
||||
export interface QueryHeaderProps {
|
||||
@@ -43,7 +41,7 @@ export function QueryHeader({
|
||||
const { editorMode } = query;
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const toRawSql = db.toRawSql || defaultToRawSql;
|
||||
const toRawSql = db.toRawSql;
|
||||
|
||||
const onEditorModeChange = useCallback(
|
||||
(newEditorMode: EditorMode) => {
|
||||
@@ -94,28 +92,14 @@ export function QueryHeader({
|
||||
return (
|
||||
<>
|
||||
<EditorHeader>
|
||||
{/* Backward compatibility check. Inline select uses SelectContainer that was added in 8.3 */}
|
||||
<ErrorBoundary
|
||||
fallBackComponent={
|
||||
<InlineField label="Format" labelWidth={15}>
|
||||
<Select
|
||||
placeholder="Select format"
|
||||
value={query.format}
|
||||
onChange={onFormatChange}
|
||||
options={QUERY_FORMAT_OPTIONS}
|
||||
/>
|
||||
</InlineField>
|
||||
}
|
||||
>
|
||||
<InlineSelect
|
||||
label="Format"
|
||||
value={query.format}
|
||||
placeholder="Select format"
|
||||
menuShouldPortal
|
||||
onChange={onFormatChange}
|
||||
options={QUERY_FORMAT_OPTIONS}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<InlineSelect
|
||||
label="Format"
|
||||
value={query.format}
|
||||
placeholder="Select format"
|
||||
menuShouldPortal
|
||||
onChange={onFormatChange}
|
||||
options={QUERY_FORMAT_OPTIONS}
|
||||
/>
|
||||
|
||||
{editorMode === EditorMode.Builder && (
|
||||
<>
|
||||
|
||||
@@ -137,6 +137,7 @@ export function SelectRow({ sql, format, columns, onSqlChange, functions }: Sele
|
||||
<EditorField label="Alias" optional width={15}>
|
||||
<Select
|
||||
value={item.alias ? toOption(item.alias) : null}
|
||||
inputId={`select-alias-${index}-${uniqueId()}`}
|
||||
options={timeSeriesAliasOpts}
|
||||
onChange={onAliasChange(item, index)}
|
||||
isClearable
|
||||
|
||||
@@ -132,7 +132,7 @@ export interface DB {
|
||||
dispose?: (dsID?: string) => void;
|
||||
lookup?: (path?: string) => Promise<Array<{ name: string; completion: string }>>;
|
||||
getEditorLanguageDefinition: () => LanguageDefinition;
|
||||
toRawSql?: (query: SQLQuery) => string;
|
||||
toRawSql: (query: SQLQuery) => string;
|
||||
functions?: () => string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import {
|
||||
QueryEditorExpressionType,
|
||||
QueryEditorFunctionExpression,
|
||||
@@ -7,47 +5,9 @@ import {
|
||||
QueryEditorPropertyExpression,
|
||||
QueryEditorPropertyType,
|
||||
} from '../expressions';
|
||||
import { SQLQuery, SQLExpression } from '../types';
|
||||
import { SQLExpression } from '../types';
|
||||
|
||||
export function defaultToRawSql({ sql, dataset, table }: SQLQuery): string {
|
||||
let rawQuery = '';
|
||||
|
||||
// Return early with empty string if there is no sql column
|
||||
if (!sql || !haveColumns(sql.columns)) {
|
||||
return rawQuery;
|
||||
}
|
||||
|
||||
rawQuery += createSelectClause(sql.columns);
|
||||
|
||||
if (dataset && table) {
|
||||
rawQuery += `FROM ${dataset}.${table} `;
|
||||
}
|
||||
|
||||
if (sql.whereString) {
|
||||
rawQuery += `WHERE ${sql.whereString} `;
|
||||
}
|
||||
|
||||
if (sql.groupBy?.[0]?.property.name) {
|
||||
const groupBy = sql.groupBy.map((g) => g.property.name).filter((g) => !isEmpty(g));
|
||||
rawQuery += `GROUP BY ${groupBy.join(', ')} `;
|
||||
}
|
||||
|
||||
if (sql.orderBy?.property.name) {
|
||||
rawQuery += `ORDER BY ${sql.orderBy.property.name} `;
|
||||
}
|
||||
|
||||
if (sql.orderBy?.property.name && sql.orderByDirection) {
|
||||
rawQuery += `${sql.orderByDirection} `;
|
||||
}
|
||||
|
||||
// Altough LIMIT 0 doesn't make sense, it is still possible to have LIMIT 0
|
||||
if (sql.limit !== undefined && sql.limit >= 0) {
|
||||
rawQuery += `LIMIT ${sql.limit} `;
|
||||
}
|
||||
return rawQuery;
|
||||
}
|
||||
|
||||
function createSelectClause(sqlColumns: NonNullable<SQLExpression['columns']>): string {
|
||||
export function createSelectClause(sqlColumns: NonNullable<SQLExpression['columns']>): string {
|
||||
const columns = sqlColumns.map((c) => {
|
||||
let rawColumn = '';
|
||||
if (c.name && c.alias) {
|
||||
|
||||
@@ -2,8 +2,6 @@ import { useCallback } from 'react';
|
||||
|
||||
import { DB, SQLExpression, SQLQuery } from '../types';
|
||||
|
||||
import { defaultToRawSql } from './sql.utils';
|
||||
|
||||
interface UseSqlChange {
|
||||
db: DB;
|
||||
query: SQLQuery;
|
||||
@@ -13,7 +11,7 @@ interface UseSqlChange {
|
||||
export function useSqlChange({ query, onQueryChange, db }: UseSqlChange) {
|
||||
const onSqlChange = useCallback(
|
||||
(sql: SQLExpression) => {
|
||||
const toRawSql = db.toRawSql || defaultToRawSql;
|
||||
const toRawSql = db.toRawSql;
|
||||
const rawSql = toRawSql({ sql, dataset: query.dataset, table: query.table, refId: query.refId });
|
||||
const newQuery: SQLQuery = { ...query, sql, rawSql };
|
||||
onQueryChange(newQuery);
|
||||
|
||||
Reference in New Issue
Block a user