mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
fix: render clickhouse keywords in codemirror (#2008)
## Summary - A newer version of the `sql-formatter` dependency has built-in support for ClickHouse SQL. This PR removes the custom ClickHouse SQL dialect code, and uses the built-in dialect. - Uses this new dialect from `sql-formatter` to create a ClickHouse dialect for CodeMirror, based on the default CodeMirror SQL one. - Uses the custom CodeMirror dialect for rendering SQL in CodeMirror. - Uses dynamic color theme for CodeMirror instances. ### Screenshots or video | Before | After | | :----- | :---- | | <img width="759" height="501" alt="before_search" src="https://github.com/user-attachments/assets/1ee6568c-6cf6-41d1-b3fc-86a60b8fd47e" /> | <img width="759" height="501" alt="after_search" src="https://github.com/user-attachments/assets/df7e3bc4-545b-44d3-8f71-fdaaa8dca112" /> | | <img width="599" height="693" alt="raw_sql_before" src="https://github.com/user-attachments/assets/546c77ee-3772-484b-8fc7-c1a42b66e974" /> | <img width="599" height="693" alt="raw_sql_after" src="https://github.com/user-attachments/assets/8f962b0f-25b4-4137-a5c1-9d8164c8b6cb" /> | | <img width="469" height="343" alt="ch_before" src="https://github.com/user-attachments/assets/6e5122e2-8820-4916-abc5-4aa37a6d1867" /> | <img width="469" height="343" alt="ch_after" src="https://github.com/user-attachments/assets/5d4c0e72-3266-4c28-ae67-38bba31516a5" /> | ### How to test locally or on Vercel 1. Check the SQL rendering anywhere the `SQLPreview` or `ChartSQLPreview` components are used, e.g. the search page, under charts, in expanded row of ClickHouse dashboard `Slowest Queries` logs table. 2. Check the rendering and autocompletion of the SQL editor e.g. in raw SQL charts. ### References - Linear Issue: Closes HDX-3841 - Related PRs:
This commit is contained in:
parent
5e5c6a941e
commit
a55b151e84
12 changed files with 74 additions and 500 deletions
6
.changeset/moody-feet-study.md
Normal file
6
.changeset/moody-feet-study.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: render clickhouse keywords properly in codemirror
|
||||
|
|
@ -88,6 +88,7 @@
|
|||
"rrweb": "2.0.0-alpha.8",
|
||||
"sass": "^1.54.8",
|
||||
"serialize-query-params": "^2.0.2",
|
||||
"sql-formatter": "^15.7.2",
|
||||
"sqlstring": "^2.3.3",
|
||||
"store2": "^2.14.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
useQueryStates,
|
||||
} from 'nuqs';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { format as formatSql } from '@hyperdx/common-utils/dist/sqlFormatter';
|
||||
import {
|
||||
ChartConfigWithDateRange,
|
||||
|
|
@ -28,6 +27,7 @@ import {
|
|||
Tabs,
|
||||
Text,
|
||||
Tooltip,
|
||||
useMantineColorScheme,
|
||||
} from '@mantine/core';
|
||||
import { IconRefresh } from '@tabler/icons-react';
|
||||
import ReactCodeMirror from '@uiw/react-codemirror';
|
||||
|
|
@ -43,6 +43,7 @@ import { DBSqlRowTable } from './components/DBRowTable';
|
|||
import DBTableChart from './components/DBTableChart';
|
||||
import OnboardingModal from './components/OnboardingModal';
|
||||
import { useDashboardRefresh } from './hooks/useDashboardRefresh';
|
||||
import { clickhouseSql } from './utils/codeMirror';
|
||||
import { useConnections } from './connection';
|
||||
import { parseTimeQuery, useNewTimeQuery } from './timeQuery';
|
||||
import { usePrevious } from './utils';
|
||||
|
|
@ -487,6 +488,7 @@ function InsertsTab({
|
|||
}
|
||||
|
||||
function ClickhousePage() {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const { data: connections } = useConnections();
|
||||
const [_connection, setConnection] = useQueryState('connection');
|
||||
const [latencyFilter, setLatencyFilter] = useQueryStates({
|
||||
|
|
@ -790,10 +792,10 @@ function ClickhousePage() {
|
|||
renderRowDetails={row => {
|
||||
return (
|
||||
<ReactCodeMirror
|
||||
extensions={[sql()]}
|
||||
extensions={[clickhouseSql()]}
|
||||
editable={false}
|
||||
value={formatSql(row.query)}
|
||||
theme="dark"
|
||||
theme={colorScheme === 'dark' ? 'dark' : 'light'}
|
||||
lang="sql"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { format } from '@hyperdx/common-utils/dist/sqlFormatter';
|
||||
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
|
||||
import { Button, Paper, Text } from '@mantine/core';
|
||||
import { Button, Paper, Text, useMantineColorScheme } from '@mantine/core';
|
||||
import { IconCheck, IconCopy } from '@tabler/icons-react';
|
||||
import CodeMirror, { EditorView } from '@uiw/react-codemirror';
|
||||
|
||||
import { useRenderedSqlChartConfig } from '@/hooks/useChartConfig';
|
||||
import { clickhouseSql } from '@/utils/codeMirror';
|
||||
|
||||
function tryFormat(data?: string) {
|
||||
try {
|
||||
|
|
@ -64,13 +64,14 @@ export function SQLPreview({
|
|||
enableLineWrapping?: boolean;
|
||||
}) {
|
||||
const displayed = formatData ? tryFormat(data) : data;
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
||||
return (
|
||||
<div className="position-relative">
|
||||
<CodeMirror
|
||||
indentWithTab={false}
|
||||
value={displayed}
|
||||
theme="dark"
|
||||
theme={colorScheme === 'dark' ? 'dark' : 'light'}
|
||||
basicSetup={{
|
||||
lineNumbers: false,
|
||||
foldGutter: false,
|
||||
|
|
@ -78,7 +79,7 @@ export function SQLPreview({
|
|||
highlightActiveLineGutter: false,
|
||||
}}
|
||||
extensions={[
|
||||
sql(),
|
||||
clickhouseSql(),
|
||||
...(enableLineWrapping ? [EditorView.lineWrapping] : []),
|
||||
]}
|
||||
editable={false}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useController, UseControllerProps } from 'react-hook-form';
|
||||
import { acceptCompletion } from '@codemirror/autocomplete';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { TableConnection } from '@hyperdx/common-utils/dist/core/metadata';
|
||||
import { Paper, useMantineColorScheme } from '@mantine/core';
|
||||
import CodeMirror, {
|
||||
|
|
@ -12,6 +11,7 @@ import CodeMirror, {
|
|||
} from '@uiw/react-codemirror';
|
||||
|
||||
import { useMultipleAllFields } from '@/hooks/useMetadata';
|
||||
import { clickhouseSql } from '@/utils/codeMirror';
|
||||
|
||||
import {
|
||||
createCodeMirrorSqlDialect,
|
||||
|
|
@ -100,7 +100,7 @@ export default function SQLEditor({
|
|||
createCodeMirrorStyleTheme(),
|
||||
// eslint-disable-next-line react-hooks/refs
|
||||
compartmentRef.current.of(
|
||||
sql({
|
||||
clickhouseSql({
|
||||
upperCaseKeywords: true,
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
Completion,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import {
|
||||
Field,
|
||||
TableConnectionChoice,
|
||||
|
|
@ -34,6 +33,7 @@ import CodeMirror, {
|
|||
import InputLanguageSwitch from '@/components/SearchInput/InputLanguageSwitch';
|
||||
import { useMultipleAllFields } from '@/hooks/useMetadata';
|
||||
import { useQueryHistory } from '@/utils';
|
||||
import { clickhouseSql } from '@/utils/codeMirror';
|
||||
|
||||
import { KEYWORDS_FOR_WHERE_OR_ORDER_BY } from './constants';
|
||||
import {
|
||||
|
|
@ -244,7 +244,7 @@ export default function SQLInlineEditor({
|
|||
|
||||
// eslint-disable-next-line react-hooks/refs
|
||||
compartmentRef.current.of(
|
||||
sql({
|
||||
clickhouseSql({
|
||||
upperCaseKeywords: true,
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ import {
|
|||
Completion,
|
||||
CompletionContext,
|
||||
} from '@codemirror/autocomplete';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { EditorView } from '@uiw/react-codemirror';
|
||||
|
||||
import { clickhouseSql } from '@/utils/codeMirror';
|
||||
|
||||
import {
|
||||
AGGREGATE_FUNCTIONS,
|
||||
ALL_KEYWORDS,
|
||||
|
|
@ -96,7 +97,7 @@ export const createCodeMirrorSqlDialect = ({
|
|||
|
||||
return [
|
||||
// SQL language for syntax highlighting (completions are overridden below)
|
||||
sql({ upperCaseKeywords: true }),
|
||||
clickhouseSql({ upperCaseKeywords: true }),
|
||||
// Override built-in SQL completions with our custom source
|
||||
autocompletion({
|
||||
override: [createIdentifierCompletionSource(completions)],
|
||||
|
|
|
|||
27
packages/app/src/utils/codeMirror.ts
Normal file
27
packages/app/src/utils/codeMirror.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { clickhouse } from 'sql-formatter';
|
||||
import { SQLConfig, SQLDialect } from '@codemirror/lang-sql';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
|
||||
const { tokenizerOptions } = clickhouse;
|
||||
|
||||
const allKeywords = [
|
||||
...tokenizerOptions.reservedKeywords,
|
||||
...tokenizerOptions.reservedClauses,
|
||||
...tokenizerOptions.reservedSelect,
|
||||
...tokenizerOptions.reservedSetOperations,
|
||||
...tokenizerOptions.reservedJoins,
|
||||
...(tokenizerOptions.reservedKeywordPhrases ?? []),
|
||||
];
|
||||
|
||||
const clickhouseDialect = SQLDialect.define({
|
||||
keywords: allKeywords.join(' ').toLowerCase(),
|
||||
types: tokenizerOptions.reservedDataTypes.join(' ').toLowerCase(),
|
||||
builtin: tokenizerOptions.reservedFunctionNames.join(' ').toLowerCase(),
|
||||
backslashEscapes: true,
|
||||
doubleDollarQuotedStrings: true,
|
||||
operatorChars: '*+-%<>!=&|~^/?:',
|
||||
identifierQuotes: '`"',
|
||||
});
|
||||
|
||||
export const clickhouseSql = (config?: SQLConfig) =>
|
||||
sql({ ...config, dialect: clickhouseDialect });
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
"lodash": "^4.17.23",
|
||||
"node-sql-parser": "^5.3.5",
|
||||
"object-hash": "^3.0.0",
|
||||
"sql-formatter": "^15.4.11",
|
||||
"sql-formatter": "^15.7.2",
|
||||
"sqlstring": "^2.3.3",
|
||||
"zod": "3.25"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@ describe('sqlFormatter(clickhouse)', () => {
|
|||
"SELECT countIf((ServiceName = 'hdx-oss-dev-api')),toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS `__hdx_time_bucket` FROM default.otel_logs WHERE (TimestampTime >= fromUnixTimestamp64Milli(1741887731578) AND TimestampTime <= fromUnixTimestamp64Milli(1742492531585)) AND ((ServiceName = 'hdx-oss-dev-api')) GROUP BY toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS `__hdx_time_bucket` ORDER BY toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS `__hdx_time_bucket`";
|
||||
const expected = `SELECT
|
||||
countIf ((ServiceName = 'hdx-oss-dev-api')),
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
FROM
|
||||
default.otel_logs
|
||||
WHERE
|
||||
(
|
||||
TimestampTime >= fromUnixTimestamp64Milli (1741887731578)
|
||||
AND TimestampTime <= fromUnixTimestamp64Milli (1742492531585)
|
||||
TimestampTime >= fromUnixTimestamp64Milli(1741887731578)
|
||||
AND TimestampTime <= fromUnixTimestamp64Milli(1742492531585)
|
||||
)
|
||||
AND ((ServiceName = 'hdx-oss-dev-api'))
|
||||
GROUP BY
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
ORDER BY
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\``;
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\``;
|
||||
expect(format(input)).toBe(expected);
|
||||
});
|
||||
|
||||
|
|
@ -30,22 +30,22 @@ ORDER BY
|
|||
ResourceAttributes['telemetry.sdk.language'] = 'nodejs'
|
||||
),
|
||||
ResourceAttributes['telemetry.sdk.language'],
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
FROM
|
||||
default.otel_logs
|
||||
WHERE
|
||||
(
|
||||
TimestampTime >= fromUnixTimestamp64Milli (1741887731578)
|
||||
AND TimestampTime <= fromUnixTimestamp64Milli (1742492531585)
|
||||
TimestampTime >= fromUnixTimestamp64Milli(1741887731578)
|
||||
AND TimestampTime <= fromUnixTimestamp64Milli(1742492531585)
|
||||
)
|
||||
AND (
|
||||
ResourceAttributes['telemetry.sdk.language'] = 'nodejs'
|
||||
)
|
||||
GROUP BY
|
||||
ResourceAttributes['telemetry.sdk.language'],
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
ORDER BY
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\``;
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\``;
|
||||
expect(format(input)).toBe(expected);
|
||||
});
|
||||
|
||||
|
|
@ -59,13 +59,13 @@ ORDER BY
|
|||
)
|
||||
),
|
||||
ResourceAttributes['telemetry.sdk.language'],
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
FROM
|
||||
default.otel_logs
|
||||
WHERE
|
||||
(
|
||||
TimestampTime >= fromUnixTimestamp64Milli (1741887731578)
|
||||
AND TimestampTime <= fromUnixTimestamp64Milli (1742492531585)
|
||||
TimestampTime >= fromUnixTimestamp64Milli(1741887731578)
|
||||
AND TimestampTime <= fromUnixTimestamp64Milli(1742492531585)
|
||||
)
|
||||
AND (
|
||||
(
|
||||
|
|
@ -74,9 +74,9 @@ WHERE
|
|||
)
|
||||
GROUP BY
|
||||
ResourceAttributes['telemetry.sdk.language'],
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\`
|
||||
ORDER BY
|
||||
toStartOfInterval (toDateTime (TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\``;
|
||||
toStartOfInterval(toDateTime(TimestampTime), INTERVAL 6 hour) AS \`__hdx_time_bucket\``;
|
||||
expect(format(input)).toBe(expected);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,461 +1,4 @@
|
|||
// custom dialect ref: https://github.com/sql-formatter-org/sql-formatter/blob/master/docs/dialect.md#custom-dialect-configuration-experimental
|
||||
// Dialect source: https://github.com/sql-formatter-org/sql-formatter/blob/master/src/languages/sql/sql.formatter.ts
|
||||
import { DialectOptions, expandPhrases, formatDialect } from 'sql-formatter';
|
||||
|
||||
// source : https://github.com/sql-formatter-org/sql-formatter/blob/master/src/languages/sql/sql.functions.ts
|
||||
export const functions: string[] = [
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_9_set_function_specification
|
||||
'GROUPING',
|
||||
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_10_window_function
|
||||
'RANK',
|
||||
'DENSE_RANK',
|
||||
'PERCENT_RANK',
|
||||
'CUME_DIST',
|
||||
'ROW_NUMBER',
|
||||
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_27_numeric_value_function
|
||||
'POSITION',
|
||||
'OCCURRENCES_REGEX',
|
||||
'POSITION_REGEX',
|
||||
'EXTRACT',
|
||||
'CHAR_LENGTH',
|
||||
'CHARACTER_LENGTH',
|
||||
'OCTET_LENGTH',
|
||||
'CARDINALITY',
|
||||
'ABS',
|
||||
'MOD',
|
||||
'LN',
|
||||
'EXP',
|
||||
'POWER',
|
||||
'SQRT',
|
||||
'FLOOR',
|
||||
'CEIL',
|
||||
'CEILING',
|
||||
'WIDTH_BUCKET',
|
||||
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_29_string_value_function
|
||||
'SUBSTRING',
|
||||
'SUBSTRING_REGEX',
|
||||
'UPPER',
|
||||
'LOWER',
|
||||
'CONVERT',
|
||||
'TRANSLATE',
|
||||
'TRANSLATE_REGEX',
|
||||
'TRIM',
|
||||
'OVERLAY',
|
||||
'NORMALIZE',
|
||||
'SPECIFICTYPE',
|
||||
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_31_datetime_value_function
|
||||
'CURRENT_DATE',
|
||||
'CURRENT_TIME',
|
||||
'LOCALTIME',
|
||||
'CURRENT_TIMESTAMP',
|
||||
'LOCALTIMESTAMP',
|
||||
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_38_multiset_value_function
|
||||
// SET serves multiple roles: a SET() function and a SET keyword e.g. in UPDATE table SET ...
|
||||
// multiset
|
||||
// 'SET', (disabled for now)
|
||||
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_10_9_aggregate_function
|
||||
'COUNT',
|
||||
'AVG',
|
||||
'MAX',
|
||||
'MIN',
|
||||
'SUM',
|
||||
// 'EVERY',
|
||||
// 'ANY',
|
||||
// 'SOME',
|
||||
'STDDEV_POP',
|
||||
'STDDEV_SAMP',
|
||||
'VAR_SAMP',
|
||||
'VAR_POP',
|
||||
'COLLECT',
|
||||
'FUSION',
|
||||
'INTERSECTION',
|
||||
'COVAR_POP',
|
||||
'COVAR_SAMP',
|
||||
'CORR',
|
||||
'REGR_SLOPE',
|
||||
'REGR_INTERCEPT',
|
||||
'REGR_COUNT',
|
||||
'REGR_R2',
|
||||
'REGR_AVGX',
|
||||
'REGR_AVGY',
|
||||
'REGR_SXX',
|
||||
'REGR_SYY',
|
||||
'REGR_SXY',
|
||||
'PERCENTILE_CONT',
|
||||
'PERCENTILE_DISC',
|
||||
|
||||
// CAST is a pretty complex case, involving multiple forms:
|
||||
// - CAST(col AS int)
|
||||
// - CAST(...) WITH ...
|
||||
// - CAST FROM int
|
||||
// - CREATE CAST(mycol AS int) WITH ...
|
||||
'CAST',
|
||||
|
||||
// Shorthand functions to use in place of CASE expression
|
||||
'COALESCE',
|
||||
'NULLIF',
|
||||
|
||||
// Non-standard functions that have widespread support
|
||||
'ROUND',
|
||||
'SIN',
|
||||
'COS',
|
||||
'TAN',
|
||||
'ASIN',
|
||||
'ACOS',
|
||||
'ATAN',
|
||||
];
|
||||
|
||||
// source: https://github.com/sql-formatter-org/sql-formatter/blob/master/src/languages/sql/sql.keywords.ts
|
||||
export const keywords: string[] = [
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#reserved-word
|
||||
'ALL',
|
||||
'ALLOCATE',
|
||||
'ALTER',
|
||||
'ANY', // <- moved over from functions
|
||||
'ARE',
|
||||
'AS',
|
||||
'ASC', // Not reserved in SQL-2008, but commonly reserved in most dialects
|
||||
'ASENSITIVE',
|
||||
'ASYMMETRIC',
|
||||
'AT',
|
||||
'ATOMIC',
|
||||
'AUTHORIZATION',
|
||||
'BEGIN',
|
||||
'BETWEEN',
|
||||
'BOTH',
|
||||
'BY',
|
||||
'CALL',
|
||||
'CALLED',
|
||||
'CASCADED',
|
||||
'CAST',
|
||||
'CHECK',
|
||||
'CLOSE',
|
||||
'COALESCE',
|
||||
'COLLATE',
|
||||
'COLUMN',
|
||||
'COMMIT',
|
||||
'CONDITION',
|
||||
'CONNECT',
|
||||
'CONSTRAINT',
|
||||
'CORRESPONDING',
|
||||
'CREATE',
|
||||
'CROSS',
|
||||
'CUBE',
|
||||
'CURRENT',
|
||||
'CURRENT_CATALOG',
|
||||
'CURRENT_DEFAULT_TRANSFORM_GROUP',
|
||||
'CURRENT_PATH',
|
||||
'CURRENT_ROLE',
|
||||
'CURRENT_SCHEMA',
|
||||
'CURRENT_TRANSFORM_GROUP_FOR_TYPE',
|
||||
'CURRENT_USER',
|
||||
'CURSOR',
|
||||
'CYCLE',
|
||||
'DEALLOCATE',
|
||||
'DAY',
|
||||
'DECLARE',
|
||||
'DEFAULT',
|
||||
'DELETE',
|
||||
'DEREF',
|
||||
'DESC', // Not reserved in SQL-2008, but commonly reserved in most dialects
|
||||
'DESCRIBE',
|
||||
'DETERMINISTIC',
|
||||
'DISCONNECT',
|
||||
'DISTINCT',
|
||||
'DROP',
|
||||
'DYNAMIC',
|
||||
'EACH',
|
||||
'ELEMENT',
|
||||
'END-EXEC',
|
||||
'ESCAPE',
|
||||
'EVERY', // <- moved over from functions
|
||||
'EXCEPT',
|
||||
'EXEC',
|
||||
'EXECUTE',
|
||||
'EXISTS',
|
||||
'EXTERNAL',
|
||||
'FALSE',
|
||||
'FETCH',
|
||||
'FILTER',
|
||||
'FOR',
|
||||
'FOREIGN',
|
||||
'FREE',
|
||||
'FROM',
|
||||
'FULL',
|
||||
'FUNCTION',
|
||||
'GET',
|
||||
'GLOBAL',
|
||||
'GRANT',
|
||||
'GROUP',
|
||||
'HAVING',
|
||||
'HOLD',
|
||||
'HOUR',
|
||||
'IDENTITY',
|
||||
'IN',
|
||||
'INDICATOR',
|
||||
'INNER',
|
||||
'INOUT',
|
||||
'INSENSITIVE',
|
||||
'INSERT',
|
||||
'INTERSECT',
|
||||
'INTO',
|
||||
'IS',
|
||||
'LANGUAGE',
|
||||
'LARGE',
|
||||
'LATERAL',
|
||||
'LEADING',
|
||||
'LEFT',
|
||||
'LIKE',
|
||||
'LIKE_REGEX',
|
||||
'LOCAL',
|
||||
'MATCH',
|
||||
'MEMBER',
|
||||
'MERGE',
|
||||
'METHOD',
|
||||
'MINUTE',
|
||||
'MODIFIES',
|
||||
'MODULE',
|
||||
'MONTH',
|
||||
'NATURAL',
|
||||
'NEW',
|
||||
'NO',
|
||||
'NONE',
|
||||
'NOT',
|
||||
'NULL',
|
||||
'NULLIF',
|
||||
'OF',
|
||||
'OLD',
|
||||
'ON',
|
||||
'ONLY',
|
||||
'OPEN',
|
||||
'ORDER',
|
||||
'OUT',
|
||||
'OUTER',
|
||||
'OVER',
|
||||
'OVERLAPS',
|
||||
'PARAMETER',
|
||||
'PARTITION',
|
||||
'PRECISION',
|
||||
'PREPARE',
|
||||
'PRIMARY',
|
||||
'PROCEDURE',
|
||||
'RANGE',
|
||||
'READS',
|
||||
'REAL',
|
||||
'RECURSIVE',
|
||||
'REF',
|
||||
'REFERENCES',
|
||||
'REFERENCING',
|
||||
'RELEASE',
|
||||
'RESULT',
|
||||
'RETURN',
|
||||
'RETURNS',
|
||||
'REVOKE',
|
||||
'RIGHT',
|
||||
'ROLLBACK',
|
||||
'ROLLUP',
|
||||
'ROW',
|
||||
'ROWS',
|
||||
'SAVEPOINT',
|
||||
'SCOPE',
|
||||
'SCROLL',
|
||||
'SEARCH',
|
||||
'SECOND',
|
||||
'SELECT',
|
||||
'SENSITIVE',
|
||||
'SESSION_USER',
|
||||
'SET',
|
||||
'SIMILAR',
|
||||
'SOME', // <- moved over from functions
|
||||
'SPECIFIC',
|
||||
'SQL',
|
||||
'SQLEXCEPTION',
|
||||
'SQLSTATE',
|
||||
'SQLWARNING',
|
||||
'START',
|
||||
'STATIC',
|
||||
'SUBMULTISET',
|
||||
'SYMMETRIC',
|
||||
'SYSTEM',
|
||||
'SYSTEM_USER',
|
||||
'TABLE',
|
||||
'TABLESAMPLE',
|
||||
'THEN',
|
||||
'TIMEZONE_HOUR',
|
||||
'TIMEZONE_MINUTE',
|
||||
'TO',
|
||||
'TRAILING',
|
||||
'TRANSLATION',
|
||||
'TREAT',
|
||||
'TRIGGER',
|
||||
'TRUE',
|
||||
'UESCAPE',
|
||||
'UNION',
|
||||
'UNIQUE',
|
||||
'UNKNOWN',
|
||||
'UNNEST',
|
||||
'UPDATE',
|
||||
'USER',
|
||||
'USING',
|
||||
'VALUE',
|
||||
'VALUES',
|
||||
'WHENEVER',
|
||||
'WINDOW',
|
||||
'WITHIN',
|
||||
'WITHOUT',
|
||||
'YEAR',
|
||||
];
|
||||
// source: https://github.com/sql-formatter-org/sql-formatter/blob/master/src/languages/sql/sql.keywords.ts
|
||||
export const dataTypes: string[] = [
|
||||
// https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_1_data_type
|
||||
'ARRAY',
|
||||
'BIGINT',
|
||||
'BINARY LARGE OBJECT',
|
||||
'BINARY VARYING',
|
||||
'BINARY',
|
||||
'BLOB',
|
||||
'BOOLEAN',
|
||||
'CHAR LARGE OBJECT',
|
||||
'CHAR VARYING',
|
||||
'CHAR',
|
||||
'CHARACTER LARGE OBJECT',
|
||||
'CHARACTER VARYING',
|
||||
'CHARACTER',
|
||||
'CLOB',
|
||||
'DATE',
|
||||
'DEC',
|
||||
'DECIMAL',
|
||||
'DOUBLE',
|
||||
'FLOAT',
|
||||
'INT',
|
||||
'INTEGER',
|
||||
'INTERVAL',
|
||||
'MULTISET',
|
||||
'NATIONAL CHAR VARYING',
|
||||
'NATIONAL CHAR',
|
||||
'NATIONAL CHARACTER LARGE OBJECT',
|
||||
'NATIONAL CHARACTER VARYING',
|
||||
'NATIONAL CHARACTER',
|
||||
'NCHAR LARGE OBJECT',
|
||||
'NCHAR VARYING',
|
||||
'NCHAR',
|
||||
'NCLOB',
|
||||
'NUMERIC',
|
||||
'SMALLINT',
|
||||
'TIME',
|
||||
'TIMESTAMP',
|
||||
'VARBINARY',
|
||||
'VARCHAR',
|
||||
];
|
||||
|
||||
const reservedSelect = expandPhrases(['SELECT [ALL | DISTINCT]']);
|
||||
|
||||
const reservedClauses = expandPhrases([
|
||||
// queries
|
||||
'WITH [RECURSIVE]',
|
||||
'FROM',
|
||||
'WHERE',
|
||||
'GROUP BY [ALL | DISTINCT]',
|
||||
'HAVING',
|
||||
'WINDOW',
|
||||
'PARTITION BY',
|
||||
'ORDER BY',
|
||||
'LIMIT',
|
||||
'OFFSET',
|
||||
'FETCH {FIRST | NEXT}',
|
||||
// Data manipulation
|
||||
// - insert:
|
||||
'INSERT INTO',
|
||||
'VALUES',
|
||||
// - update:
|
||||
'SET',
|
||||
]);
|
||||
|
||||
const standardOnelineClauses = expandPhrases([
|
||||
'CREATE [GLOBAL TEMPORARY | LOCAL TEMPORARY] TABLE',
|
||||
]);
|
||||
|
||||
const tabularOnelineClauses = expandPhrases([
|
||||
// - create:
|
||||
'CREATE [RECURSIVE] VIEW',
|
||||
// - update:
|
||||
'UPDATE',
|
||||
'WHERE CURRENT OF',
|
||||
// - delete:
|
||||
'DELETE FROM',
|
||||
// - drop table:
|
||||
'DROP TABLE',
|
||||
// - alter table:
|
||||
'ALTER TABLE',
|
||||
'ADD COLUMN',
|
||||
'DROP [COLUMN]',
|
||||
'RENAME COLUMN',
|
||||
'RENAME TO',
|
||||
'ALTER [COLUMN]',
|
||||
'{SET | DROP} DEFAULT', // for alter column
|
||||
'ADD SCOPE', // for alter column
|
||||
'DROP SCOPE {CASCADE | RESTRICT}', // for alter column
|
||||
'RESTART WITH', // for alter column
|
||||
// - truncate:
|
||||
'TRUNCATE TABLE',
|
||||
// other
|
||||
'SET SCHEMA',
|
||||
]);
|
||||
|
||||
const reservedSetOperations = expandPhrases([
|
||||
'UNION [ALL | DISTINCT]',
|
||||
'EXCEPT [ALL | DISTINCT]',
|
||||
'INTERSECT [ALL | DISTINCT]',
|
||||
]);
|
||||
|
||||
const reservedJoins = expandPhrases([
|
||||
'JOIN',
|
||||
'{LEFT | RIGHT | FULL} [OUTER] JOIN',
|
||||
'{INNER | CROSS} JOIN',
|
||||
'NATURAL [INNER] JOIN',
|
||||
'NATURAL {LEFT | RIGHT | FULL} [OUTER] JOIN',
|
||||
]);
|
||||
|
||||
const reservedPhrases = expandPhrases([
|
||||
'ON {UPDATE | DELETE} [SET NULL | SET DEFAULT]',
|
||||
'{ROWS | RANGE} BETWEEN',
|
||||
]);
|
||||
|
||||
const clickhouse: DialectOptions = {
|
||||
name: 'clickhouse',
|
||||
tokenizerOptions: {
|
||||
reservedSelect,
|
||||
reservedClauses: [
|
||||
...reservedClauses,
|
||||
...standardOnelineClauses,
|
||||
...tabularOnelineClauses,
|
||||
],
|
||||
reservedSetOperations,
|
||||
reservedJoins,
|
||||
reservedPhrases,
|
||||
reservedKeywords: keywords,
|
||||
reservedDataTypes: dataTypes,
|
||||
reservedFunctionNames: functions,
|
||||
stringTypes: [
|
||||
{ quote: "''-qq-bs", prefixes: ['N', 'U&'] },
|
||||
{ quote: "''-raw", prefixes: ['X'], requirePrefix: true },
|
||||
],
|
||||
identTypes: [`""-qq`, '``'],
|
||||
extraParens: ['[]'],
|
||||
paramTypes: { positional: true },
|
||||
operators: ['||', '%'],
|
||||
},
|
||||
formatOptions: {
|
||||
onelineClauses: [...standardOnelineClauses, ...tabularOnelineClauses],
|
||||
tabularOnelineClauses,
|
||||
},
|
||||
};
|
||||
import { clickhouse, formatDialect } from 'sql-formatter';
|
||||
|
||||
export function format(query) {
|
||||
return formatDialect(query, { dialect: clickhouse });
|
||||
|
|
|
|||
19
yarn.lock
19
yarn.lock
|
|
@ -4314,6 +4314,7 @@ __metadata:
|
|||
sass: "npm:^1.54.8"
|
||||
serialize-query-params: "npm:^2.0.2"
|
||||
serve: "npm:^14.0.0"
|
||||
sql-formatter: "npm:^15.7.2"
|
||||
sqlstring: "npm:^2.3.3"
|
||||
store2: "npm:^2.14.3"
|
||||
storybook: "npm:^10.2.10"
|
||||
|
|
@ -4362,7 +4363,7 @@ __metadata:
|
|||
node-sql-parser: "npm:^5.3.5"
|
||||
nodemon: "npm:^2.0.20"
|
||||
object-hash: "npm:^3.0.0"
|
||||
sql-formatter: "npm:^15.4.11"
|
||||
sql-formatter: "npm:^15.7.2"
|
||||
sqlstring: "npm:^2.3.3"
|
||||
ts-jest: "npm:^29.4.5"
|
||||
tsup: "npm:^8.4.0"
|
||||
|
|
@ -16421,13 +16422,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stdin@npm:=8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "get-stdin@npm:8.0.0"
|
||||
checksum: 10c0/b71b72b83928221052f713b3b6247ebf1ceaeb4ef76937778557537fd51ad3f586c9e6a7476865022d9394b39b74eed1dc7514052fa74d80625276253571b76f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "get-stream@npm:6.0.1"
|
||||
|
|
@ -25542,16 +25536,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sql-formatter@npm:^15.4.11":
|
||||
version: 15.4.11
|
||||
resolution: "sql-formatter@npm:15.4.11"
|
||||
"sql-formatter@npm:^15.7.2":
|
||||
version: 15.7.2
|
||||
resolution: "sql-formatter@npm:15.7.2"
|
||||
dependencies:
|
||||
argparse: "npm:^2.0.1"
|
||||
get-stdin: "npm:=8.0.0"
|
||||
nearley: "npm:^2.20.1"
|
||||
bin:
|
||||
sql-formatter: bin/sql-formatter-cli.cjs
|
||||
checksum: 10c0/a61f252d6d71face6693a41e5b6a689c913f9bf88a37c3410bdf24a0858da552cbac336a9dc2a254e876fdc2f77026465b55425b1c9f15c4afa2f6218c22d80f
|
||||
checksum: 10c0/276681392df604aa59af49eb901a831db0b3ada7817a2372fb427500e77f61068aae63b586006b0521700e74ab76b2e7a7ebd20f67ca3f0b1518f5f0c7a7bd1f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue