diff --git a/src/lib/components/Editor/clickhouse.ts b/src/lib/components/Editor/clickhouse.ts new file mode 100644 index 0000000..cb4cb19 --- /dev/null +++ b/src/lib/components/Editor/clickhouse.ts @@ -0,0 +1,332 @@ +const operators = [ + '=', + '>', + '<', + '!', + '~', + '?', + ':', + '==', + '<=', + '>=', + '!=', + '&&', + '||', + '++', + '--', + '+', + '-', + '*', + '/', + '&', + '|', + '^', + '%', + '>>', + '<<', + '>>>', + '+=', + '-=', + '*=', + '/=', + '&=', + '|=', + '^=', + '%=', + '>>=', + '<<=', + '>>>=' +]; + +const keywords = [ + 'SELECT', + 'FROM', + 'WHERE', + 'GROUP BY', + 'ORDER BY', + 'LIMIT', + 'OFFSET', + 'HAVING', + 'PREWHERE', + 'UNION ALL', + 'JOIN', + 'LEFT JOIN', + 'RIGHT JOIN', + 'INNER JOIN', + 'FULL JOIN', + 'CROSS JOIN', + 'ON', + 'USING', + 'WITH', + 'DISTINCT', + 'ANY', + 'ALL', + 'DESCRIBE', + 'DESC', + 'ASC', + 'AS', + 'IN', + 'BETWEEN', + 'LIKE', + 'ILIKE', + 'NOT', + 'OR', + 'AND', + 'SHOW TABLES', + 'SHOW CREATE TABLE', + 'SHOW DICTIONARIES', + 'SHOW DATABASES' +]; + +const functions = [ + // Arithmetic Functions + 'plus', + 'minus', + 'multiply', + 'divide', + 'intDiv', + 'intDivOrZero', + 'modulo', + 'negate', + 'abs', + 'gcd', + 'lcm', + // Array Functions + 'empty', + 'notEmpty', + 'length', + 'emptyArrayUInt8', + 'emptyArrayUInt16', + 'emptyArrayUInt32', + 'emptyArrayUInt64', + 'emptyArrayInt8', + 'emptyArrayInt16', + 'emptyArrayInt32', + 'emptyArrayInt64', + 'emptyArrayFloat32', + 'emptyArrayFloat64', + 'emptyArrayDate', + 'emptyArrayDateTime', + 'emptyArrayString', + 'range', + 'array', + 'arrayConcat', + 'arrayElement', + // Comparison Functions + 'equals', + 'notEquals', + 'less', + 'greater', + 'lessOrEquals', + 'greaterOrEquals', + 'notIn', + // Date/Time Functions + 'toYear', + 'toQuarter', + 'toMonth', + 'toDayOfYear', + 'toDayOfMonth', + 'toDayOfWeek', + 'toHour', + 'toMinute', + 'toSecond', + 'toUnixTimestamp', + 'toStartOfYear', + 'toStartOfQuarter', + 'toStartOfMonth', + 'toMonday', + 'toStartOfDay', + 'toStartOfHour', + 'toStartOfMinute', + 'toStartOfSecond', + 'toStartOfFiveMinute', + 'toStartOfTenMinutes', + 'toStartOfFifteenMinutes', + 'toStartOfInterval', + 'toTime', + 'toRelativeYearNum', + 'toRelativeQuarterNum', + 'toRelativeMonthNum', + 'toRelativeDayNum', + 'toRelativeHourNum', + 'toRelativeMinuteNum', + 'toRelativeSecondNum', + 'now', + 'today', + 'yesterday', + 'date_trunc', + // String Functions + 'empty', + 'notEmpty', + 'length', + 'lengthUTF8', + 'lower', + 'upper', + 'lowerUTF8', + 'upperUTF8', + 'reverse', + 'reverseUTF8', + 'concat', + 'substring', + 'substringUTF8', + 'appendTrailingCharIfAbsent', + 'convertCharset', + 'base64Encode', + 'base64Decode', + 'tryBase64Decode', + 'endsWith', + 'startsWith', + 'trim', + 'trimLeft', + 'trimRight', + 'trimBoth', + // JSON Functions + 'JSONExtract', + 'JSONExtractString', + 'JSONExtractInt', + 'JSONExtractUInt', + 'JSONExtractFloat', + 'JSONExtractBool', + // Aggregate Functions + 'count', + 'min', + 'max', + 'sum', + 'avg', + 'any', + 'stddevPop', + 'stddevSamp', + 'varPop', + 'varSamp', + 'covarPop', + 'covarSamp', + 'anyHeavy', + 'anyLast', + 'argMin', + 'argMax', + 'avgWeighted', + 'corr', + 'topK', + 'topKWeighted', + 'groupArray', + 'groupUniqArray', + 'groupArrayInsertAt', + 'groupArrayMovingAvg', + 'groupArrayMovingSum', + 'groupBitAnd', + 'groupBitOr', + 'groupBitXor', + 'groupBitmap', + 'groupBitmapAnd', + 'groupBitmapOr', + 'groupBitmapXor', + 'quantile', + 'quantileDeterministic', + 'quantileTiming', + 'quantileTimingWeighted', + 'quantileExact', + 'quantileExactWeighted', + 'quantileTDigest', + 'quantileTDigestWeighted', + // Bitmap Functions + 'bitmapBuild', + 'bitmapToArray', + 'bitmapSubsetInRange', + 'bitmapContains', + 'bitmapHasAny', + 'bitmapHasAll', + // Conditional Functions + 'if', + 'multiIf', + 'ifNull', + 'nullIf', + 'coalesce', + 'assumeNotNull', + 'isNull', + 'isNotNull', + // Type Conversion Functions + 'toUInt8', + 'toUInt16', + 'toUInt32', + 'toUInt64', + 'toInt8', + 'toInt16', + 'toInt32', + 'toInt64', + 'toFloat32', + 'toFloat64', + 'toDate', + 'toDateTime', + 'toString', + 'cast', + 'accurate cast', + 'toFixedString', + 'toStringCutToZero', + // Hash Functions + 'halfMD5', + 'MD5', + 'sipHash64', + 'sipHash128', + 'cityHash64', + 'intHash32', + 'intHash64', + 'SHA1', + 'SHA224', + 'SHA256', + 'URLHash', + 'hex', + 'unhex', + 'bitmapHasAny', + 'bitmapHasAll' +]; + +const types = [ + 'Int8', + 'Int16', + 'Int32', + 'Int64', + 'Int128', + 'Int256', + 'UInt8', + 'UInt16', + 'UInt32', + 'UInt64', + 'UInt128', + 'UInt256', + 'Float32', + 'Float64', + 'Decimal', + 'Decimal32', + 'Decimal64', + 'Decimal128', + 'Decimal256', + 'Boolean', + 'String', + 'FixedString', + 'UUID', + 'Date', + 'Date32', + 'DateTime', + 'DateTime64', + 'Enum', + 'Enum8', + 'Enum16', + 'LowCardinality', + 'Array', + 'AggregateFunction', + 'SimpleAggregateFunction', + 'Tuple', + 'Nested', + 'Map', + 'IPv4', + 'IPv6', + 'Point', + 'Ring', + 'Polygon', + 'MultiPolygon', + 'JSON', + 'Nothing', + 'Nullable' +]; + +export { operators, keywords, functions, types }; diff --git a/src/lib/components/Editor/language.ts b/src/lib/components/Editor/language.ts index 7bb5b1f..a20c368 100644 --- a/src/lib/components/Editor/language.ts +++ b/src/lib/components/Editor/language.ts @@ -1,382 +1,94 @@ import * as monaco from 'monaco-editor'; -// shared keywords and functions -const keywords = [ - 'SELECT', - 'FROM', - 'WHERE', - 'GROUP BY', - 'ORDER BY', - 'LIMIT', - 'OFFSET', - 'HAVING', - 'PREWHERE', - 'UNION ALL', - 'JOIN', - 'LEFT JOIN', - 'RIGHT JOIN', - 'INNER JOIN', - 'FULL JOIN', - 'CROSS JOIN', - 'ON', - 'USING', - 'WITH', - 'DISTINCT', - 'ANY', - 'ALL', - 'DESCRIBE', - 'DESC', - 'ASC', - 'AS', - 'IN', - 'BETWEEN', - 'LIKE', - 'ILIKE', - 'NOT', - 'OR', - 'AND', - 'SHOW TABLES', - 'SHOW CREATE TABLE', - 'SHOW DICTIONARIES', - 'SHOW DATABASES' -]; +export function setupLanguage( + id: string, + keywords: string[] = [], + functions: string[] = [], + tables: string[] = [], + types: string[] = [], + operators: string[] = [] +) { + monaco.languages.register({ id }); + monaco.languages.setMonarchTokensProvider('clickhouse', { + ignoreCase: true, + keywords: keywords.map((k) => k.split(' ')[0]), + functions: functions, + tables: tables, + typeKeywords: types, + operators: operators, -const functions = [ - // Arithmetic Functions - 'plus', - 'minus', - 'multiply', - 'divide', - 'intDiv', - 'intDivOrZero', - 'modulo', - 'negate', - 'abs', - 'gcd', - 'lcm', - // Array Functions - 'empty', - 'notEmpty', - 'length', - 'emptyArrayUInt8', - 'emptyArrayUInt16', - 'emptyArrayUInt32', - 'emptyArrayUInt64', - 'emptyArrayInt8', - 'emptyArrayInt16', - 'emptyArrayInt32', - 'emptyArrayInt64', - 'emptyArrayFloat32', - 'emptyArrayFloat64', - 'emptyArrayDate', - 'emptyArrayDateTime', - 'emptyArrayString', - 'range', - 'array', - 'arrayConcat', - 'arrayElement', - // Comparison Functions - 'equals', - 'notEquals', - 'less', - 'greater', - 'lessOrEquals', - 'greaterOrEquals', - 'notIn', - // Date/Time Functions - 'toYear', - 'toQuarter', - 'toMonth', - 'toDayOfYear', - 'toDayOfMonth', - 'toDayOfWeek', - 'toHour', - 'toMinute', - 'toSecond', - 'toUnixTimestamp', - 'toStartOfYear', - 'toStartOfQuarter', - 'toStartOfMonth', - 'toMonday', - 'toStartOfDay', - 'toStartOfHour', - 'toStartOfMinute', - 'toStartOfSecond', - 'toStartOfFiveMinute', - 'toStartOfTenMinutes', - 'toStartOfFifteenMinutes', - 'toStartOfInterval', - 'toTime', - 'toRelativeYearNum', - 'toRelativeQuarterNum', - 'toRelativeMonthNum', - 'toRelativeDayNum', - 'toRelativeHourNum', - 'toRelativeMinuteNum', - 'toRelativeSecondNum', - 'now', - 'today', - 'yesterday', - 'date_trunc', - // String Functions - 'empty', - 'notEmpty', - 'length', - 'lengthUTF8', - 'lower', - 'upper', - 'lowerUTF8', - 'upperUTF8', - 'reverse', - 'reverseUTF8', - 'concat', - 'substring', - 'substringUTF8', - 'appendTrailingCharIfAbsent', - 'convertCharset', - 'base64Encode', - 'base64Decode', - 'tryBase64Decode', - 'endsWith', - 'startsWith', - 'trim', - 'trimLeft', - 'trimRight', - 'trimBoth', - // JSON Functions - 'JSONExtract', - 'JSONExtractString', - 'JSONExtractInt', - 'JSONExtractUInt', - 'JSONExtractFloat', - 'JSONExtractBool', - // Aggregate Functions - 'count', - 'min', - 'max', - 'sum', - 'avg', - 'any', - 'stddevPop', - 'stddevSamp', - 'varPop', - 'varSamp', - 'covarPop', - 'covarSamp', - 'anyHeavy', - 'anyLast', - 'argMin', - 'argMax', - 'avgWeighted', - 'corr', - 'topK', - 'topKWeighted', - 'groupArray', - 'groupUniqArray', - 'groupArrayInsertAt', - 'groupArrayMovingAvg', - 'groupArrayMovingSum', - 'groupBitAnd', - 'groupBitOr', - 'groupBitXor', - 'groupBitmap', - 'groupBitmapAnd', - 'groupBitmapOr', - 'groupBitmapXor', - 'quantile', - 'quantileDeterministic', - 'quantileTiming', - 'quantileTimingWeighted', - 'quantileExact', - 'quantileExactWeighted', - 'quantileTDigest', - 'quantileTDigestWeighted', - // Bitmap Functions - 'bitmapBuild', - 'bitmapToArray', - 'bitmapSubsetInRange', - 'bitmapContains', - 'bitmapHasAny', - 'bitmapHasAll', - // Conditional Functions - 'if', - 'multiIf', - 'ifNull', - 'nullIf', - 'coalesce', - 'assumeNotNull', - 'isNull', - 'isNotNull', - // Type Conversion Functions - 'toUInt8', - 'toUInt16', - 'toUInt32', - 'toUInt64', - 'toInt8', - 'toInt16', - 'toInt32', - 'toInt64', - 'toFloat32', - 'toFloat64', - 'toDate', - 'toDateTime', - 'toString', - 'cast', - 'accurate cast', - 'toFixedString', - 'toStringCutToZero', - // Hash Functions - 'halfMD5', - 'MD5', - 'sipHash64', - 'sipHash128', - 'cityHash64', - 'intHash32', - 'intHash64', - 'SHA1', - 'SHA224', - 'SHA256', - 'URLHash', - 'hex', - 'unhex', - 'bitmapHasAny', - 'bitmapHasAll' -]; + symbols: /[=> k.split(' ')[0]), // Handle multi-word keywords - functions: functions, - - typeKeywords: [ - 'Int8', - 'Int16', - 'Int32', - 'Int64', - 'UInt8', - 'UInt16', - 'UInt32', - 'UInt64', - 'Float32', - 'Float64', - 'String', - 'Date', - 'DateTime', - 'UUID', - 'Array' - ], - - operators: [ - '=', - '>', - '<', - '!', - '~', - '?', - ':', - '==', - '<=', - '>=', - '!=', - '&&', - '||', - '++', - '--', - '+', - '-', - '*', - '/', - '&', - '|', - '^', - '%', - '>>', - '<<', - '>>>', - '+=', - '-=', - '*=', - '/=', - '&=', - '|=', - '^=', - '%=', - '>>=', - '<<=', - '>>>=' - ], - - symbols: /[=> { - const word = model.getWordUntilPosition(position); - const range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: word.startColumn, - endColumn: word.endColumn - }; + const sql = { + provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => { + const word = model.getWordUntilPosition(position); + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; - const allItems = [...keywords, ...functions]; - const uniqueItems = [...new Set(allItems)]; + const allItems = [...keywords, ...functions, ...tables]; + const uniqueItems = [...new Set(allItems)]; - return { - suggestions: uniqueItems.map((item) => ({ - label: item, - kind: keywords.includes(item) - ? monaco.languages.CompletionItemKind.Keyword - : monaco.languages.CompletionItemKind.Function, - insertText: item, - range: range - })) - }; - } -}; + return { + suggestions: uniqueItems.map((item) => ({ + label: item, + kind: keywords.includes(item) + ? monaco.languages.CompletionItemKind.Keyword + : functions.includes(item) + ? monaco.languages.CompletionItemKind.Function + : monaco.languages.CompletionItemKind.Class, + insertText: item, + range: range + })) + }; + } + }; -monaco.languages.registerCompletionItemProvider('clickhouse', sql); + monaco.languages.registerCompletionItemProvider('clickhouse', sql); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index dd8896b..fb55a26 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -28,6 +28,8 @@ import debounce from 'p-debounce'; import { format } from 'sql-formatter'; import { tick, type ComponentProps } from 'svelte'; + import { setupLanguage } from '$lib/components/Editor/language'; + import { keywords, functions, operators, types } from '$lib/components/Editor/clickhouse'; let response = $state.raw(); let loading = $state(false); @@ -60,7 +62,21 @@ let history = $state.raw([]); let queries = $state.raw([]); - $effect(() => void engine.getSchema().then((t) => (tables = t))); + $effect( + () => + void engine.getSchema().then((t) => { + tables = t; + + setupLanguage( + 'clickhouse', + keywords, + functions, + tables.map((t) => t.name), + types, + operators + ); + }) + ); $effect(() => void historyRepository.getAll().then((entries) => (history = entries))); $effect(() => void queryRepository.getAll().then((q) => (queries = q)));