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:
Karl Power 2026-03-30 14:38:51 +02:00 committed by GitHub
parent 5e5c6a941e
commit a55b151e84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 74 additions and 500 deletions

View file

@ -0,0 +1,6 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/app": patch
---
fix: render clickhouse keywords properly in codemirror

View file

@ -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",

View file

@ -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"
/>

View file

@ -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}

View file

@ -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,
}),
),

View file

@ -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,
}),
),

View file

@ -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)],

View 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 });

View file

@ -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"
},

View file

@ -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);
});

View file

@ -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 });

View file

@ -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