ref experimental query editor (#53061)

* ref experimental query editor
This commit is contained in:
Scott Lepper 2022-08-02 11:44:06 -04:00 committed by GitHub
parent 904d21dc93
commit fe7add0bc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 37 additions and 5868 deletions

View File

@ -5377,39 +5377,11 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Do not use any type assertions.", "8"]
],
"public/app/features/plugins/sql/components/query-editor-raw/SQLEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Do not use any type assertions.", "5"]
],
"public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/features/plugins/sql/mocks/Monaco.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/plugins/sql/mocks/queries/singleLineFullQuery.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/plugins/sql/standardSql/definition.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/plugins/sql/test-utils/statementPosition.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
"public/app/features/plugins/sql/utils/debugger.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/features/plugins/tests/datasource_srv.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],

View File

@ -254,6 +254,7 @@
"@grafana/aws-sdk": "0.0.37",
"@grafana/data": "workspace:*",
"@grafana/e2e-selectors": "workspace:*",
"@grafana/experimental": "^0.0.2-canary.36",
"@grafana/google-sdk": "0.0.3",
"@grafana/lezer-logql": "0.0.14",
"@grafana/runtime": "workspace:*",

View File

@ -1,2 +1 @@
export * from './query-editor-raw';
export * from './visual-query-builder';

View File

@ -1,10 +1,10 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { SQLEditor } from '@grafana/experimental';
import { LanguageCompletionProvider, SQLQuery } from '../../types';
import { formatSQL } from '../../utils/formatSQL';
import { SQLEditor } from './SQLEditor';
type Props = {
query: SQLQuery;
onChange: (value: SQLQuery, processQuery: boolean) => void;

View File

@ -1,405 +0,0 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { v4 } from 'uuid';
import { Registry } from '@grafana/data';
import { CodeEditor, Monaco, monacoTypes } from '@grafana/ui';
import standardSQLLanguageDefinition from '../../standardSql/definition';
import { getStandardSuggestions } from '../../standardSql/getStandardSuggestions';
import { getStatementPosition } from '../../standardSql/getStatementPosition';
import {
initFunctionsRegistry,
initMacrosRegistry,
initOperatorsRegistry,
initStandardSuggestions,
} from '../../standardSql/standardSuggestionsRegistry';
import { initStatementPositionResolvers } from '../../standardSql/statementPositionResolversRegistry';
import { initSuggestionsKindRegistry, SuggestionKindRegistryItem } from '../../standardSql/suggestionsKindRegistry';
import {
FunctionsRegistryItem,
MacrosRegistryItem,
OperatorsRegistryItem,
SQLMonarchLanguage,
StatementPositionResolversRegistryItem,
SuggestionsRegistryItem,
} from '../../standardSql/types';
import {
CompletionItemKind,
CompletionItemPriority,
CustomSuggestion,
PositionContext,
SQLCompletionItemProvider,
StatementPosition,
SuggestionKind,
} from '../../types';
import { LinkedToken } from '../../utils/LinkedToken';
import { TRIGGER_SUGGEST } from '../../utils/commands';
import { sqlEditorLog } from '../../utils/debugger';
import { getSuggestionKinds } from '../../utils/getSuggestionKind';
import { linkedTokenBuilder } from '../../utils/linkedTokenBuilder';
import { getTableToken } from '../../utils/tokenUtils';
const STANDARD_SQL_LANGUAGE = 'sql';
export interface LanguageDefinition extends monacoTypes.languages.ILanguageExtensionPoint {
loader?: (module: any) => Promise<{
language: SQLMonarchLanguage;
conf: monacoTypes.languages.LanguageConfiguration;
}>;
// Provides API for customizing the autocomplete
completionProvider?: (m: Monaco) => SQLCompletionItemProvider;
// Function that returns a formatted query
formatter?: (q: string) => string;
}
interface SQLEditorProps {
query: string;
/**
* Use for inspecting the query as it changes. I.e. for validation.
*/
onChange?: (q: string, processQuery: boolean) => void;
language?: LanguageDefinition;
children?: (props: { formatQuery: () => void }) => React.ReactNode;
width?: number;
height?: number;
}
const defaultTableNameParser = (t: LinkedToken) => t.value;
interface LanguageRegistries {
functions: Registry<FunctionsRegistryItem>;
operators: Registry<OperatorsRegistryItem>;
suggestionKinds: Registry<SuggestionKindRegistryItem>;
positionResolvers: Registry<StatementPositionResolversRegistryItem>;
macros: Registry<MacrosRegistryItem>;
}
const LANGUAGES_CACHE = new Map<string, LanguageRegistries>();
const INSTANCE_CACHE = new Map<string, Registry<SuggestionsRegistryItem>>();
export const SQLEditor: React.FC<SQLEditorProps> = ({
children,
onChange,
query,
language = { id: STANDARD_SQL_LANGUAGE },
width,
height,
}) => {
const monacoRef = useRef<monacoTypes.editor.IStandaloneCodeEditor>(null);
const langUid = useRef<string>();
// create unique language id for each SQLEditor instance
const id = useMemo(() => {
const uid = v4();
const id = `${language.id}-${uid}`;
langUid.current = id;
return id;
}, [language.id]);
useEffect(() => {
return () => {
INSTANCE_CACHE.delete(langUid.current!);
sqlEditorLog(`Removing instance cache ${langUid.current}`, false, INSTANCE_CACHE);
};
}, []);
const formatQuery = useCallback(() => {
if (monacoRef.current) {
monacoRef.current.getAction('editor.action.formatDocument').run();
}
}, []);
return (
<div style={{ width }}>
<CodeEditor
height={height || '240px'}
// -2px to compensate for borders width
width={width ? `${width - 2}px` : undefined}
language={id}
value={query}
onBlur={(v) => onChange && onChange(v, false)}
showMiniMap={false}
showLineNumbers={true}
// Using onEditorDidMount instead of onBeforeEditorMount to support Grafana < 8.2.x
onEditorDidMount={(editor, m) => {
// TODO - says its read only but this worked in experimental
// monacoRef.current = editor;
editor.onDidChangeModelContent((e) => {
const text = editor.getValue();
if (onChange) {
onChange(text, false);
}
});
editor.addCommand(m.KeyMod.CtrlCmd | m.KeyCode.Enter, () => {
const text = editor.getValue();
if (onChange) {
onChange(text, true);
}
});
registerLanguageAndSuggestions(m, language, id);
}}
/>
{children && children({ formatQuery })}
</div>
);
};
// There's three ways to define Monaco language:
// 1. Leave language.id empty or set it to 'sql'. This will load a standard sql language definition, including syntax highlighting and tokenization for
// common Grafana entities such as macros and template variables
// 2. Provide a custom language and load it via the async LanguageDefinition.loader callback
// 3. Specify a language.id that exists in the Monaco language registry. See available languages here: https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages
// If a custom language is specified, its LanguageDefinition will be merged with the LanguageDefinition for standard SQL. This allows the consumer to only
// override parts of the LanguageDefinition, such as for example the completion item provider.
const resolveLanguage = (monaco: Monaco, languageDefinitionProp: LanguageDefinition): LanguageDefinition => {
if (languageDefinitionProp?.id !== STANDARD_SQL_LANGUAGE && !languageDefinitionProp.loader) {
sqlEditorLog(`Loading language '${languageDefinitionProp?.id}' from Monaco registry`, false);
const allLangs = monaco.languages.getLanguages();
const custom = allLangs.find(({ id }) => id === languageDefinitionProp?.id);
if (!custom) {
throw Error(`Unknown Monaco language ${languageDefinitionProp?.id}`);
}
return custom;
}
return {
...standardSQLLanguageDefinition,
...languageDefinitionProp,
};
};
export const registerLanguageAndSuggestions = async (monaco: Monaco, l: LanguageDefinition, lid: string) => {
const languageDefinition = resolveLanguage(monaco, l);
const { language, conf } = await languageDefinition.loader!(monaco);
monaco.languages.register({ id: lid });
monaco.languages.setMonarchTokensProvider(lid, { ...language });
monaco.languages.setLanguageConfiguration(lid, { ...conf });
if (languageDefinition.formatter) {
monaco.languages.registerDocumentFormattingEditProvider(lid, {
provideDocumentFormattingEdits: (model) => {
var formatted = l.formatter!(model.getValue());
return [
{
range: model.getFullModelRange(),
text: formatted,
},
];
},
});
}
if (languageDefinition.completionProvider) {
const customProvider = l.completionProvider!(monaco);
extendStandardRegistries(l.id, lid, customProvider);
const languageSuggestionsRegistries = LANGUAGES_CACHE.get(l.id)!;
const instanceSuggestionsRegistry = INSTANCE_CACHE.get(lid)!;
const completionProvider: monacoTypes.languages.CompletionItemProvider['provideCompletionItems'] = async (
model,
position,
context,
token
) => {
const currentToken = linkedTokenBuilder(monaco, model, position, 'sql');
const statementPosition = getStatementPosition(currentToken, languageSuggestionsRegistries.positionResolvers);
const kind = getSuggestionKinds(statementPosition, languageSuggestionsRegistries.suggestionKinds);
sqlEditorLog('Statement position', false, statementPosition);
sqlEditorLog('Suggestion kinds', false, kind);
const ctx: PositionContext = {
position,
currentToken,
statementPosition,
kind,
range: monaco.Range.fromPositions(position),
};
// // Completely custom suggestions - hope this won't we needed
// let ci;
// if (customProvider.provideCompletionItems) {
// ci = customProvider.provideCompletionItems(model, position, context, token, ctx);
// }
const stdSuggestions = await getStandardSuggestions(monaco, currentToken, kind, ctx, instanceSuggestionsRegistry);
return {
// ...ci,
suggestions: stdSuggestions,
};
};
monaco.languages.registerCompletionItemProvider(lid, {
...customProvider,
provideCompletionItems: completionProvider,
});
}
};
function extendStandardRegistries(id: string, lid: string, customProvider: SQLCompletionItemProvider) {
if (!LANGUAGES_CACHE.has(id)) {
initializeLanguageRegistries(id);
}
const languageRegistries = LANGUAGES_CACHE.get(id)!;
if (!INSTANCE_CACHE.has(lid)) {
INSTANCE_CACHE.set(
lid,
new Registry(
initStandardSuggestions(languageRegistries.functions, languageRegistries.operators, languageRegistries.macros)
)
);
}
const instanceSuggestionsRegistry = INSTANCE_CACHE.get(lid)!;
if (customProvider.supportedFunctions) {
for (const func of customProvider.supportedFunctions()) {
const exists = languageRegistries.functions.getIfExists(func.id);
if (!exists) {
languageRegistries.functions.register(func);
}
}
}
if (customProvider.supportedOperators) {
for (const op of customProvider.supportedOperators()) {
const exists = languageRegistries.operators.getIfExists(op.id);
if (!exists) {
languageRegistries.operators.register({ ...op, name: op.id });
}
}
}
if (customProvider.supportedMacros) {
for (const macro of customProvider.supportedMacros()) {
const exists = languageRegistries.macros.getIfExists(macro.id);
if (!exists) {
languageRegistries.macros.register({ ...macro, name: macro.id });
}
}
}
if (customProvider.customStatementPlacement) {
for (const placement of customProvider.customStatementPlacement()) {
const exists = languageRegistries.positionResolvers.getIfExists(placement.id);
if (!exists) {
languageRegistries.positionResolvers.register({
...placement,
id: placement.id as StatementPosition,
name: placement.id,
});
languageRegistries.suggestionKinds.register({
id: placement.id as StatementPosition,
name: placement.id,
kind: [],
});
} else {
// Allow extension to the built-in placement resolvers
const origResolve = exists.resolve;
exists.resolve = (...args) => {
const ext = placement.resolve(...args);
if (placement.overrideDefault) {
return ext;
}
const orig = origResolve(...args);
return orig || ext;
};
}
}
}
if (customProvider.customSuggestionKinds) {
for (const kind of customProvider.customSuggestionKinds()) {
kind.applyTo?.forEach((applyTo) => {
const exists = languageRegistries.suggestionKinds.getIfExists(applyTo);
if (exists) {
// avoid duplicates
if (exists.kind.indexOf(kind.id as SuggestionKind) === -1) {
exists.kind.push(kind.id as SuggestionKind);
}
}
});
if (kind.overrideDefault) {
const stbBehaviour = instanceSuggestionsRegistry.get(kind.id);
if (stbBehaviour !== undefined) {
stbBehaviour.suggestions = kind.suggestionsResolver;
continue;
}
}
instanceSuggestionsRegistry.register({
id: kind.id as SuggestionKind,
name: kind.id,
suggestions: kind.suggestionsResolver,
});
}
}
if (customProvider.tables) {
const stbBehaviour = instanceSuggestionsRegistry.get(SuggestionKind.Tables);
const s = stbBehaviour!.suggestions;
stbBehaviour!.suggestions = async (ctx, m) => {
const o = await s(ctx, m);
const oo = (await customProvider.tables!.resolve!()).map((x) => ({
label: x.name,
insertText: x.completion ?? x.name,
command: TRIGGER_SUGGEST,
kind: CompletionItemKind.Field,
sortText: CompletionItemPriority.High,
}));
return [...o, ...oo];
};
}
if (customProvider.columns) {
const stbBehaviour = instanceSuggestionsRegistry.get(SuggestionKind.Columns);
const s = stbBehaviour!.suggestions;
stbBehaviour!.suggestions = async (ctx, m) => {
const o = await s(ctx, m);
const tableToken = getTableToken(ctx.currentToken);
let table = '';
const tableNameParser = customProvider.tables?.parseName ?? defaultTableNameParser;
if (tableToken && tableToken.value) {
table = tableNameParser(tableToken).trim();
}
let oo: CustomSuggestion[] = [];
if (table) {
const columns = await customProvider.columns?.resolve!(table);
oo = columns
? columns.map<CustomSuggestion>((x) => ({
label: x.name,
insertText: x.completion ?? x.name,
kind: CompletionItemKind.Field,
sortText: CompletionItemPriority.High,
detail: x.type,
documentation: x.description,
}))
: [];
}
return [...o, ...oo];
};
}
}
/**
* Initializes language specific registries that are treated as singletons
*/
function initializeLanguageRegistries(id: string) {
if (!LANGUAGES_CACHE.has(id)) {
LANGUAGES_CACHE.set(id, {
functions: new Registry(initFunctionsRegistry),
operators: new Registry(initOperatorsRegistry),
suggestionKinds: new Registry(initSuggestionsKindRegistry),
positionResolvers: new Registry(initStatementPositionResolvers),
macros: new Registry(initMacrosRegistry),
});
}
return LANGUAGES_CACHE.get(id)!;
}

View File

@ -1 +0,0 @@
export { SQLEditor, LanguageDefinition } from './SQLEditor';

View File

@ -1,22 +1,2 @@
export { SQLEditorTestUtils, TestQueryModel } from './test-utils';
export { LinkedToken } from './utils/LinkedToken';
export { language as grafanaStandardSQLLanguage, conf as grafanaStandardSQLLanguageConf } from './standardSql/language';
export { SQLMonarchLanguage } from './standardSql/types';
export {
TableDefinition,
ColumnDefinition,
StatementPlacementProvider,
SuggestionKindProvider,
LanguageCompletionProvider,
OperatorType,
MacroType,
TokenType,
StatementPosition,
SuggestionKind,
CompletionItemKind,
CompletionItemPriority,
CompletionItemInsertTextRule,
} from './types';
export * from './components';
export * from './types';

View File

@ -1,26 +0,0 @@
import { monacoTypes } from '@grafana/ui';
// Stub for the Monaco instance. Only implements the parts that are used in cloudwatch sql
const getMonacoMock: (
testData: Map<string, Array<Array<Pick<monacoTypes.Token, 'language' | 'offset' | 'type'>>>>
) => any = (testData) => ({
editor: {
tokenize: (value: string, languageId: string) => testData.get(value),
},
Range: {
containsPosition: (range: monacoTypes.IRange, position: monacoTypes.IPosition) => {
return (
position.lineNumber >= range.startLineNumber &&
position.lineNumber <= range.endLineNumber &&
position.column >= range.startColumn &&
position.column <= range.endColumn
);
},
},
languages: {
CompletionItemKind: { Snippet: 2, Function: 1, Keyword: 3 },
CompletionItemInsertTextRule: { InsertAsSnippet: 2 },
},
});
export { getMonacoMock };

View File

@ -1,21 +0,0 @@
import { monacoTypes } from '@grafana/ui';
// Stub for monacoTypes.editor.ITextModel
function TextModel(value: string) {
return {
getValue: function (eol?: monacoTypes.editor.EndOfLinePreference, preserveBOM?: boolean): string {
return value;
},
getValueInRange: function (range: monacoTypes.IRange, eol?: monacoTypes.editor.EndOfLinePreference): string {
const lines = value.split('\n');
const line = lines[range.startLineNumber - 1];
return line.trim().slice(range.startColumn === 0 ? 0 : range.startColumn - 1, range.endColumn - 1);
},
getLineLength: function (lineNumber: number): number {
const lines = value.split('\n');
return lines[lineNumber - 1].trim().length;
},
};
}
export { TextModel };

View File

@ -1,214 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const multiLineFullQuery: TestQueryModel = {
query: `SELECT column1,
FROM table1
WHERE column1 = "value1"
GROUP BY column1 ORDER BY column1 DESC
LIMIT 10;`,
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 14,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 15,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 4,
type: 'white.sql',
language: 'sql',
},
{
offset: 5,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 11,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 13,
type: 'white.sql',
language: 'sql',
},
{
offset: 14,
type: 'operator.sql',
language: 'sql',
},
{
offset: 15,
type: 'white.sql',
language: 'sql',
},
{
offset: 16,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 17,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 23,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 24,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 8,
type: 'white.sql',
language: 'sql',
},
{
offset: 9,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 16,
type: 'white.sql',
language: 'sql',
},
{
offset: 17,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 25,
type: 'white.sql',
language: 'sql',
},
{
offset: 26,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 33,
type: 'white.sql',
language: 'sql',
},
{
offset: 34,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 38,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'number.sql',
language: 'sql',
},
{
offset: 8,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,229 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const multiLineFullQueryWithAggregation: TestQueryModel = {
query: `SELECT count(column1),
FROM table1
WHERE column1 = "value1"
GROUP BY column1 ORDER BY column1 DESC
LIMIT 10;`,
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 12,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 13,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 20,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 21,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 4,
type: 'white.sql',
language: 'sql',
},
{
offset: 5,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 11,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 13,
type: 'white.sql',
language: 'sql',
},
{
offset: 14,
type: 'operator.sql',
language: 'sql',
},
{
offset: 15,
type: 'white.sql',
language: 'sql',
},
{
offset: 16,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 17,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 23,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 24,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 8,
type: 'white.sql',
language: 'sql',
},
{
offset: 9,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 16,
type: 'white.sql',
language: 'sql',
},
{
offset: 17,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 25,
type: 'white.sql',
language: 'sql',
},
{
offset: 26,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 33,
type: 'white.sql',
language: 'sql',
},
{
offset: 34,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 38,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'number.sql',
language: 'sql',
},
{
offset: 8,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,269 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const multiLineMultipleColumns: TestQueryModel = {
query: `SELECT count(column1), column2
FROM table1
WHERE column1 = "value1"
GROUP BY column1 ORDER BY column1, avg(column2) DESC
LIMIT 10;`,
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 12,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 13,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 20,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 21,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 30,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 4,
type: 'white.sql',
language: 'sql',
},
{
offset: 5,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 11,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 13,
type: 'white.sql',
language: 'sql',
},
{
offset: 14,
type: 'operator.sql',
language: 'sql',
},
{
offset: 15,
type: 'white.sql',
language: 'sql',
},
{
offset: 16,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 17,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 23,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 24,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 8,
type: 'white.sql',
language: 'sql',
},
{
offset: 9,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 16,
type: 'white.sql',
language: 'sql',
},
{
offset: 17,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 25,
type: 'white.sql',
language: 'sql',
},
{
offset: 26,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 33,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 34,
type: 'white.sql',
language: 'sql',
},
{
offset: 35,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 38,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 39,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 46,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 47,
type: 'white.sql',
language: 'sql',
},
{
offset: 48,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 52,
type: 'white.sql',
language: 'sql',
},
],
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 5,
type: 'white.sql',
language: 'sql',
},
{
offset: 6,
type: 'number.sql',
language: 'sql',
},
{
offset: 8,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,6 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const singleLineEmptyQuery: TestQueryModel = {
query: '',
tokens: [],
};

View File

@ -1,196 +0,0 @@
import { monacoTypes } from '@grafana/ui';
import { TestQueryModel } from '../../test-utils/types';
export const singleLineFullQuery: TestQueryModel = {
query: `SELECT column1, FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10`,
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 14,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 15,
type: 'white.sql',
language: 'sql',
},
{
offset: 16,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 20,
type: 'white.sql',
language: 'sql',
},
{
offset: 21,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 27,
type: 'white.sql',
language: 'sql',
},
{
offset: 28,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 33,
type: 'white.sql',
language: 'sql',
},
{
offset: 34,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 41,
type: 'white.sql',
language: 'sql',
},
{
offset: 42,
type: 'operator.sql',
language: 'sql',
},
{
offset: 43,
type: 'white.sql',
language: 'sql',
},
{
offset: 44,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 45,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 51,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 52,
type: 'white.sql',
language: 'sql',
},
{
offset: 53,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 58,
type: 'white.sql',
language: 'sql',
},
{
offset: 59,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 61,
type: 'white.sql',
language: 'sql',
},
{
offset: 62,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 69,
type: 'white.sql',
language: 'sql',
},
{
offset: 70,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 75,
type: 'white.sql',
language: 'sql',
},
{
offset: 76,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 78,
type: 'white.sql',
language: 'sql',
},
{
offset: 79,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 86,
type: 'white.sql',
language: 'sql',
},
{
offset: 87,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 91,
type: 'white.sql',
language: 'sql',
},
{
offset: 92,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 97,
type: 'white.sql',
language: 'sql',
},
{
offset: 98,
type: 'number.sql',
language: 'sql',
},
{
offset: 100,
type: 'delimiter.sql',
language: 'sql',
},
],
] as monacoTypes.Token[][],
};

View File

@ -1,209 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const singleLineFullQueryWithAggregation: TestQueryModel = {
query: 'SELECT count(column1), FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10;',
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 12,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 13,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 20,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 21,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 27,
type: 'white.sql',
language: 'sql',
},
{
offset: 28,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 34,
type: 'white.sql',
language: 'sql',
},
{
offset: 35,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 40,
type: 'white.sql',
language: 'sql',
},
{
offset: 41,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 48,
type: 'white.sql',
language: 'sql',
},
{
offset: 49,
type: 'operator.sql',
language: 'sql',
},
{
offset: 50,
type: 'white.sql',
language: 'sql',
},
{
offset: 51,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 52,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 58,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 59,
type: 'white.sql',
language: 'sql',
},
{
offset: 60,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 65,
type: 'white.sql',
language: 'sql',
},
{
offset: 66,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 68,
type: 'white.sql',
language: 'sql',
},
{
offset: 69,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 76,
type: 'white.sql',
language: 'sql',
},
{
offset: 77,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 82,
type: 'white.sql',
language: 'sql',
},
{
offset: 83,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 85,
type: 'white.sql',
language: 'sql',
},
{
offset: 86,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 93,
type: 'white.sql',
language: 'sql',
},
{
offset: 94,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 98,
type: 'white.sql',
language: 'sql',
},
{
offset: 99,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 104,
type: 'white.sql',
language: 'sql',
},
{
offset: 105,
type: 'number.sql',
language: 'sql',
},
{
offset: 107,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,250 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const singleLineMultipleColumns: TestQueryModel = {
query:
'SELECT count(column1), column2 FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1, avg(column2) DESC LIMIT 10;',
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 12,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 13,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 20,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 21,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 30,
type: 'white.sql',
language: 'sql',
},
{
offset: 31,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 35,
type: 'white.sql',
language: 'sql',
},
{
offset: 36,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 42,
type: 'white.sql',
language: 'sql',
},
{
offset: 43,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 48,
type: 'white.sql',
language: 'sql',
},
{
offset: 49,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 56,
type: 'white.sql',
language: 'sql',
},
{
offset: 57,
type: 'operator.sql',
language: 'sql',
},
{
offset: 58,
type: 'white.sql',
language: 'sql',
},
{
offset: 59,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 60,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 66,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 67,
type: 'white.sql',
language: 'sql',
},
{
offset: 68,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 73,
type: 'white.sql',
language: 'sql',
},
{
offset: 74,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 76,
type: 'white.sql',
language: 'sql',
},
{
offset: 77,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 84,
type: 'white.sql',
language: 'sql',
},
{
offset: 85,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 90,
type: 'white.sql',
language: 'sql',
},
{
offset: 91,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 93,
type: 'white.sql',
language: 'sql',
},
{
offset: 94,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 101,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 102,
type: 'white.sql',
language: 'sql',
},
{
offset: 103,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 106,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 107,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 114,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 115,
type: 'white.sql',
language: 'sql',
},
{
offset: 116,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 120,
type: 'white.sql',
language: 'sql',
},
{
offset: 121,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 126,
type: 'white.sql',
language: 'sql',
},
{
offset: 127,
type: 'number.sql',
language: 'sql',
},
{
offset: 129,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,385 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const singleLineTwoQueries: TestQueryModel = {
query:
'SELECT column1, FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10; SELECT column2, FROM table2 WHERE column2 = "value2" GROUP BY column1 ORDER BY column2 DESC LIMIT 10;',
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 14,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 15,
type: 'white.sql',
language: 'sql',
},
{
offset: 16,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 20,
type: 'white.sql',
language: 'sql',
},
{
offset: 21,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 27,
type: 'white.sql',
language: 'sql',
},
{
offset: 28,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 33,
type: 'white.sql',
language: 'sql',
},
{
offset: 34,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 41,
type: 'white.sql',
language: 'sql',
},
{
offset: 42,
type: 'operator.sql',
language: 'sql',
},
{
offset: 43,
type: 'white.sql',
language: 'sql',
},
{
offset: 44,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 45,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 51,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 52,
type: 'white.sql',
language: 'sql',
},
{
offset: 53,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 58,
type: 'white.sql',
language: 'sql',
},
{
offset: 59,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 61,
type: 'white.sql',
language: 'sql',
},
{
offset: 62,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 69,
type: 'white.sql',
language: 'sql',
},
{
offset: 70,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 75,
type: 'white.sql',
language: 'sql',
},
{
offset: 76,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 78,
type: 'white.sql',
language: 'sql',
},
{
offset: 79,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 86,
type: 'white.sql',
language: 'sql',
},
{
offset: 87,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 91,
type: 'white.sql',
language: 'sql',
},
{
offset: 92,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 97,
type: 'white.sql',
language: 'sql',
},
{
offset: 98,
type: 'number.sql',
language: 'sql',
},
{
offset: 100,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 101,
type: 'white.sql',
language: 'sql',
},
{
offset: 102,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 108,
type: 'white.sql',
language: 'sql',
},
{
offset: 109,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 116,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 117,
type: 'white.sql',
language: 'sql',
},
{
offset: 118,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 122,
type: 'white.sql',
language: 'sql',
},
{
offset: 123,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 129,
type: 'white.sql',
language: 'sql',
},
{
offset: 130,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 135,
type: 'white.sql',
language: 'sql',
},
{
offset: 136,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 143,
type: 'white.sql',
language: 'sql',
},
{
offset: 144,
type: 'operator.sql',
language: 'sql',
},
{
offset: 145,
type: 'white.sql',
language: 'sql',
},
{
offset: 146,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 147,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 153,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 154,
type: 'white.sql',
language: 'sql',
},
{
offset: 155,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 160,
type: 'white.sql',
language: 'sql',
},
{
offset: 161,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 163,
type: 'white.sql',
language: 'sql',
},
{
offset: 164,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 171,
type: 'white.sql',
language: 'sql',
},
{
offset: 172,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 177,
type: 'white.sql',
language: 'sql',
},
{
offset: 178,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 180,
type: 'white.sql',
language: 'sql',
},
{
offset: 181,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 188,
type: 'white.sql',
language: 'sql',
},
{
offset: 189,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 193,
type: 'white.sql',
language: 'sql',
},
{
offset: 194,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 199,
type: 'white.sql',
language: 'sql',
},
{
offset: 200,
type: 'number.sql',
language: 'sql',
},
{
offset: 202,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,415 +0,0 @@
import { TestQueryModel } from '../../test-utils/types';
export const singleLineTwoQueriesWithAggregation: TestQueryModel = {
query:
'SELECT count(column1), FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10; SELECT count(column2), FROM table2 WHERE column2 = "value2" GROUP BY column1 ORDER BY column2 DESC LIMIT 10;',
tokens: [
[
{
offset: 0,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 6,
type: 'white.sql',
language: 'sql',
},
{
offset: 7,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 12,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 13,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 20,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 21,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 22,
type: 'white.sql',
language: 'sql',
},
{
offset: 23,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 27,
type: 'white.sql',
language: 'sql',
},
{
offset: 28,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 34,
type: 'white.sql',
language: 'sql',
},
{
offset: 35,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 40,
type: 'white.sql',
language: 'sql',
},
{
offset: 41,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 48,
type: 'white.sql',
language: 'sql',
},
{
offset: 49,
type: 'operator.sql',
language: 'sql',
},
{
offset: 50,
type: 'white.sql',
language: 'sql',
},
{
offset: 51,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 52,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 58,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 59,
type: 'white.sql',
language: 'sql',
},
{
offset: 60,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 65,
type: 'white.sql',
language: 'sql',
},
{
offset: 66,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 68,
type: 'white.sql',
language: 'sql',
},
{
offset: 69,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 76,
type: 'white.sql',
language: 'sql',
},
{
offset: 77,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 82,
type: 'white.sql',
language: 'sql',
},
{
offset: 83,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 85,
type: 'white.sql',
language: 'sql',
},
{
offset: 86,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 93,
type: 'white.sql',
language: 'sql',
},
{
offset: 94,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 98,
type: 'white.sql',
language: 'sql',
},
{
offset: 99,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 104,
type: 'white.sql',
language: 'sql',
},
{
offset: 105,
type: 'number.sql',
language: 'sql',
},
{
offset: 107,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 108,
type: 'white.sql',
language: 'sql',
},
{
offset: 109,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 115,
type: 'white.sql',
language: 'sql',
},
{
offset: 116,
type: 'predefined.sql',
language: 'sql',
},
{
offset: 121,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 122,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 129,
type: 'delimiter.parenthesis.sql',
language: 'sql',
},
{
offset: 130,
type: 'delimiter.sql',
language: 'sql',
},
{
offset: 131,
type: 'white.sql',
language: 'sql',
},
{
offset: 132,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 136,
type: 'white.sql',
language: 'sql',
},
{
offset: 137,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 143,
type: 'white.sql',
language: 'sql',
},
{
offset: 144,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 149,
type: 'white.sql',
language: 'sql',
},
{
offset: 150,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 157,
type: 'white.sql',
language: 'sql',
},
{
offset: 158,
type: 'operator.sql',
language: 'sql',
},
{
offset: 159,
type: 'white.sql',
language: 'sql',
},
{
offset: 160,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 161,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 167,
type: 'identifier.quote.sql',
language: 'sql',
},
{
offset: 168,
type: 'white.sql',
language: 'sql',
},
{
offset: 169,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 174,
type: 'white.sql',
language: 'sql',
},
{
offset: 175,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 177,
type: 'white.sql',
language: 'sql',
},
{
offset: 178,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 185,
type: 'white.sql',
language: 'sql',
},
{
offset: 186,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 191,
type: 'white.sql',
language: 'sql',
},
{
offset: 192,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 194,
type: 'white.sql',
language: 'sql',
},
{
offset: 195,
type: 'identifier.sql',
language: 'sql',
},
{
offset: 202,
type: 'white.sql',
language: 'sql',
},
{
offset: 203,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 207,
type: 'white.sql',
language: 'sql',
},
{
offset: 208,
type: 'keyword.sql',
language: 'sql',
},
{
offset: 213,
type: 'white.sql',
language: 'sql',
},
{
offset: 214,
type: 'number.sql',
language: 'sql',
},
{
offset: 216,
type: 'delimiter.sql',
language: 'sql',
},
],
],
};

View File

@ -1,9 +0,0 @@
export { singleLineFullQuery } from './queries/singleLineFullQuery';
export { singleLineFullQueryWithAggregation } from './queries/singleLineFullQueryWithAggregation';
export { multiLineFullQuery } from './queries/multiLineFullQuery';
export { multiLineFullQueryWithAggregation } from './queries/multiLineFullQueryWithAggregation';
export { singleLineEmptyQuery } from './queries/singleLineEmptyQuery';
export { singleLineTwoQueries } from './queries/singleLineTwoQueries';
export { singleLineTwoQueriesWithAggregation } from './queries/singleLineTwoQueriesWithAggregation';
export { singleLineMultipleColumns } from './queries/singleLineMultipleColumns';
export { multiLineMultipleColumns } from './queries/multiLineMultipleColumns';

View File

@ -1,24 +0,0 @@
import { monacoTypes } from '@grafana/ui';
import { SQLMonarchLanguage } from './types';
export type LanguageDefinition = {
id: string;
extensions: string[];
aliases: string[];
mimetypes: string[];
loader: (monaco: any) => Promise<{
language: SQLMonarchLanguage;
conf: monacoTypes.languages.LanguageConfiguration;
}>;
};
const standardSQLLanguageDefinition: LanguageDefinition = {
id: 'standardSql',
extensions: ['.sql'],
aliases: ['sql'],
mimetypes: [],
loader: () => import('./language'),
};
export default standardSQLLanguageDefinition;

View File

@ -1,273 +0,0 @@
import { Registry } from '@grafana/data';
import { monacoTypes } from '@grafana/ui';
import { getMonacoMock } from '../mocks/Monaco';
import { TextModel } from '../mocks/TextModel';
import { singleLineFullQuery } from '../mocks/testData';
import { OperatorType, SuggestionKind, CustomSuggestion, PositionContext, MacroType } from '../types';
import { linkedTokenBuilder } from '../utils/linkedTokenBuilder';
import { getStandardSuggestions } from './getStandardSuggestions';
import { initStandardSuggestions } from './standardSuggestionsRegistry';
import { FunctionsRegistryItem, MacrosRegistryItem, OperatorsRegistryItem, SuggestionsRegistryItem } from './types';
describe('getStandardSuggestions', () => {
const mockQueries = new Map<string, Array<Array<Pick<monacoTypes.Token, 'language' | 'offset' | 'type'>>>>();
const cases = [{ query: singleLineFullQuery, position: { line: 1, column: 0 } }];
cases.forEach((c) => mockQueries.set(c.query.query, c.query.tokens));
const MonacoMock = getMonacoMock(mockQueries);
const token = linkedTokenBuilder(MonacoMock, TextModel(singleLineFullQuery.query) as monacoTypes.editor.ITextModel, {
lineNumber: 1,
column: 0,
});
const posContextMock = {};
it('calls the resolvers', async () => {
const suggestionMock: CustomSuggestion = { label: 'customSuggest' };
const resolveFunctionSpy = jest.fn().mockReturnValue([suggestionMock]);
const kind = 'customSuggestionItemKind' as SuggestionKind;
const suggestionsRegistry = new Registry<SuggestionsRegistryItem>(() => {
return [
{
id: kind,
name: 'customSuggestionItemKind',
suggestions: resolveFunctionSpy,
},
];
});
const result = await getStandardSuggestions(
MonacoMock,
token,
[kind],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(resolveFunctionSpy).toBeCalledTimes(1);
expect(resolveFunctionSpy).toBeCalledWith({ range: token!.range }, MonacoMock);
expect(result).toHaveLength(1);
expect(result[0].label).toEqual(suggestionMock.label);
});
it('suggests custom functions with arguments from the registry', async () => {
const customFunction = {
name: 'customFunction',
id: 'customFunction',
};
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => [customFunction]),
new Registry<OperatorsRegistryItem>(() => []),
new Registry<MacrosRegistryItem>(() => [])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.FunctionsWithArguments],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(1);
expect(result[0].label).toEqual(customFunction.name);
});
it('suggests custom functions without arguments from the registry', async () => {
const customFunction = {
name: 'customFunction',
id: 'customFunction',
};
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => [customFunction]),
new Registry<OperatorsRegistryItem>(() => []),
new Registry<MacrosRegistryItem>(() => [])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.FunctionsWithoutArguments],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(1);
expect(result[0].label).toEqual(customFunction.name);
});
it('suggests custom logical operators from the registry', async () => {
const customLogicalOperator = {
type: OperatorType.Logical,
name: 'customOperator',
id: 'customOperator',
operator: '½',
};
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => []),
new Registry<OperatorsRegistryItem>(() => [customLogicalOperator]),
new Registry<MacrosRegistryItem>(() => [])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.LogicalOperators],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(1);
expect(result[0].label).toEqual(customLogicalOperator.operator);
});
it('suggests custom comparison operators from the registry', async () => {
const customComparisonOperator = {
type: OperatorType.Comparison,
name: 'customOperator',
id: 'customOperator',
operator: '§',
};
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => []),
new Registry<OperatorsRegistryItem>(() => [customComparisonOperator]),
new Registry<MacrosRegistryItem>(() => [])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.ComparisonOperators],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(5);
expect(result[0].label).toEqual(customComparisonOperator.operator);
});
it('does not suggest logical operators when asked for comparison operators', async () => {
const customLogicalOperator = {
type: OperatorType.Logical,
name: 'customOperator',
id: 'customOperator',
operator: '§',
};
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => []),
new Registry<OperatorsRegistryItem>(() => [customLogicalOperator]),
new Registry<MacrosRegistryItem>(() => [])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.ComparisonOperators],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(4);
});
it('suggests $__time(dateColumn) macro when in column position', async () => {
const customMacro: MacrosRegistryItem = {
name: '$__time',
id: '$__time',
text: '$__time',
type: MacroType.Value,
};
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => []),
new Registry<OperatorsRegistryItem>(() => []),
new Registry<MacrosRegistryItem>(() => [customMacro])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.SelectMacro],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(1);
expect(result[0].label).toEqual('$__time');
});
it('suggests SELECT and SELECT FROM from the standard registry', async () => {
const suggestionsRegistry = new Registry(
initStandardSuggestions(
new Registry<FunctionsRegistryItem>(() => []),
new Registry<OperatorsRegistryItem>(() => []),
new Registry<MacrosRegistryItem>(() => [])
)
);
const result = await getStandardSuggestions(
MonacoMock,
token,
[SuggestionKind.SelectKeyword],
posContextMock as PositionContext,
suggestionsRegistry
);
expect(result).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
Array [
Object {
"command": Object {
"id": "editor.action.triggerSuggest",
"title": "",
},
"insertText": "SELECT $0",
"insertTextRules": 4,
"kind": 27,
"label": "SELECT <column>",
"range": Object {
"endColumn": 7,
"endLineNumber": 1,
"startColumn": 0,
"startLineNumber": 1,
},
"sortText": "g",
},
Object {
"command": Object {
"id": "editor.action.triggerSuggest",
"title": "",
},
"insertText": "SELECT $2 FROM $1",
"insertTextRules": 4,
"kind": 27,
"label": "SELECT <column> FROM <table>",
"range": Object {
"endColumn": 7,
"endLineNumber": 1,
"startColumn": 0,
"startLineNumber": 1,
},
"sortText": "g",
},
]
`);
});
});

View File

@ -1,34 +0,0 @@
import { Registry } from '@grafana/data';
import { Monaco, monacoTypes } from '@grafana/ui';
import { PositionContext, SuggestionKind } from '../types';
import { LinkedToken } from '../utils/LinkedToken';
import { toCompletionItem } from '../utils/toCompletionItem';
import { SuggestionsRegistryItem } from './types';
// Given standard and custom registered suggestions and kinds of suggestion expected, return a list of completion items
export const getStandardSuggestions = async (
monaco: Monaco,
currentToken: LinkedToken | null,
suggestionKinds: SuggestionKind[],
positionContext: PositionContext,
suggestionsRegistry: Registry<SuggestionsRegistryItem>
): Promise<monacoTypes.languages.CompletionItem[]> => {
let suggestions: monacoTypes.languages.CompletionItem[] = [];
const invalidRangeToken = currentToken?.isWhiteSpace() || currentToken?.isParenthesis();
const range =
invalidRangeToken || !currentToken?.range
? monaco.Range.fromPositions(positionContext.position)
: currentToken?.range;
// iterating over Set to deduplicate
for (const suggestion of [...new Set(suggestionKinds)]) {
const registeredSuggestions = suggestionsRegistry.getIfExists(suggestion);
if (registeredSuggestions) {
const su = await registeredSuggestions.suggestions({ ...positionContext, range }, monaco);
suggestions = [...suggestions, ...su.map((s) => toCompletionItem(s.label, range, { kind: s.kind, ...s }))];
}
}
return Promise.resolve(suggestions);
};

View File

@ -1,184 +0,0 @@
import {
multiLineFullQuery,
multiLineFullQueryWithAggregation,
multiLineMultipleColumns,
singleLineEmptyQuery,
singleLineFullQuery,
singleLineFullQueryWithAggregation,
singleLineMultipleColumns,
singleLineTwoQueries,
singleLineTwoQueriesWithAggregation,
} from '../mocks/testData';
import { testStatementPosition } from '../test-utils/statementPosition';
import { StatementPosition } from '../types';
import { initStatementPositionResolvers } from './statementPositionResolversRegistry';
const templateSrvMock = { replace: jest.fn(), getVariables: () => [], getAdhocFilters: jest.fn() };
jest.mock('@grafana/runtime', () => ({
...(jest.requireActual('@grafana/runtime') as unknown as object),
getTemplateSrv: () => templateSrvMock,
}));
describe('statementPosition', () => {
testStatementPosition(
StatementPosition.SelectKeyword,
[
{ query: singleLineEmptyQuery, position: { line: 1, column: 0 } },
{ query: singleLineFullQuery, position: { line: 1, column: 0 } },
{ query: multiLineFullQuery, position: { line: 1, column: 0 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 103 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterSelectKeyword,
[
{ query: singleLineFullQuery, position: { line: 1, column: 7 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 109 } },
{ query: multiLineFullQuery, position: { line: 1, column: 7 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterSelectArguments,
[
{ query: singleLineFullQuery, position: { line: 1, column: 16 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 16 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 118 } },
{ query: multiLineFullQuery, position: { line: 1, column: 16 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterSelectFuncFirstArgument,
[
{ query: singleLineFullQueryWithAggregation, position: { line: 1, column: 14 } },
{ query: multiLineFullQueryWithAggregation, position: { line: 1, column: 14 } },
{ query: singleLineTwoQueriesWithAggregation, position: { line: 1, column: 128 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.FromKeyword,
[
{ query: singleLineFullQuery, position: { line: 1, column: 17 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 119 } },
{ query: multiLineFullQuery, position: { line: 2, column: 0 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterFromKeyword,
[
{ query: singleLineFullQuery, position: { line: 1, column: 21 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 123 } },
{ query: multiLineFullQuery, position: { line: 2, column: 5 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterFrom,
[
{ query: singleLineFullQuery, position: { line: 1, column: 28 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 130 } },
{ query: multiLineFullQuery, position: { line: 2, column: 12 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.WhereKeyword,
[
{ query: singleLineFullQuery, position: { line: 1, column: 34 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 136 } },
{ query: multiLineFullQuery, position: { line: 4, column: 6 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.WhereComparisonOperator,
[
{ query: singleLineFullQuery, position: { line: 1, column: 43 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 145 } },
{ query: multiLineFullQuery, position: { line: 4, column: 15 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.WhereValue,
[
{ query: singleLineFullQuery, position: { line: 1, column: 44 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 146 } },
{ query: multiLineFullQuery, position: { line: 4, column: 16 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterWhereValue,
[
{ query: singleLineFullQuery, position: { line: 1, column: 53 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 155 } },
{ query: multiLineFullQuery, position: { line: 4, column: 25 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterGroupByKeywords,
[
{ query: singleLineFullQuery, position: { line: 1, column: 63 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 167 } },
{ query: multiLineFullQuery, position: { line: 5, column: 11 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterGroupBy,
[
{ query: singleLineFullQuery, position: { line: 1, column: 71 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 173 } },
{ query: multiLineFullQuery, position: { line: 5, column: 18 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterOrderByKeywords,
[
{ query: singleLineFullQuery, position: { line: 1, column: 80 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 181 } },
{ query: multiLineFullQuery, position: { line: 5, column: 26 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterOrderByFunction,
[
{ query: singleLineMultipleColumns, position: { line: 1, column: 108 } },
{ query: multiLineMultipleColumns, position: { line: 5, column: 40 } },
],
initStatementPositionResolvers
);
testStatementPosition(
StatementPosition.AfterOrderByDirection,
[
{ query: singleLineFullQuery, position: { line: 1, column: 92 } },
{ query: singleLineTwoQueries, position: { line: 1, column: 196 } },
{ query: multiLineFullQuery, position: { line: 5, column: 39 } },
],
initStatementPositionResolvers
);
});

View File

@ -1,31 +0,0 @@
import { Registry } from '@grafana/data';
import { StatementPosition, TokenType } from '../types';
import { LinkedToken } from '../utils/LinkedToken';
import { StatementPositionResolversRegistryItem } from './types';
// Given current cursor position in the SQL editor, returns the statement position.
export function getStatementPosition(
currentToken: LinkedToken | null,
statementPositionResolversRegistry: Registry<StatementPositionResolversRegistryItem>
): StatementPosition[] {
const previousNonWhiteSpace = currentToken?.getPreviousNonWhiteSpaceToken();
const previousKeyword = currentToken?.getPreviousKeyword();
const previousIsSlash = currentToken?.getPreviousNonWhiteSpaceToken()?.is(TokenType.Operator, '/');
const resolvers = statementPositionResolversRegistry.list();
const positions = [];
for (const resolver of resolvers) {
if (
resolver.resolve(currentToken, previousKeyword ?? null, previousNonWhiteSpace ?? null, Boolean(previousIsSlash))
) {
positions.push(resolver.id);
}
}
if (positions.length === 0) {
return [StatementPosition.Unknown];
}
return positions;
}

View File

@ -1,880 +0,0 @@
import { monacoTypes } from '@grafana/ui';
import { SQLMonarchLanguage } from './types';
// STD basic SQL
export const SELECT = 'select';
export const FROM = 'from';
export const WHERE = 'where';
export const GROUP = 'group';
export const ORDER = 'order';
export const BY = 'by';
export const DESC = 'desc';
export const ASC = 'asc';
export const LIMIT = 'limit';
export const WITH = 'with';
export const AS = 'as';
export const SCHEMA = 'schema';
export const STD_STATS = ['AVG', 'COUNT', 'MAX', 'MIN', 'SUM'];
export const AND = 'AND';
export const OR = 'OR';
export const LOGICAL_OPERATORS = [AND, OR];
export const EQUALS = '=';
export const NOT_EQUALS = '!=';
export const COMPARISON_OPERATORS = [EQUALS, NOT_EQUALS];
export const STD_OPERATORS = [...COMPARISON_OPERATORS];
export const conf: monacoTypes.languages.LanguageConfiguration = {
comments: {
lineComment: '--',
blockComment: ['/*', '*/'],
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')'],
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
],
};
// based on https://github.com/microsoft/monaco-editor/blob/main/src/basic-languages/sql/sql.ts
export const language: SQLMonarchLanguage = {
defaultToken: '',
tokenPostfix: '.sql',
ignoreCase: true,
brackets: [
{ open: '[', close: ']', token: 'delimiter.square' },
{ open: '(', close: ')', token: 'delimiter.parenthesis' },
],
keywords: [
'ABORT',
'ABSOLUTE',
'ACTION',
'ADA',
'ADD',
'AFTER',
'ALL',
'ALLOCATE',
'ALTER',
'ALWAYS',
'ANALYZE',
'AND',
'ANY',
'ARE',
'AS',
'ASC',
'ASSERTION',
'AT',
'ATTACH',
'AUTHORIZATION',
'AUTOINCREMENT',
'AVG',
'BACKUP',
'BEFORE',
'BEGIN',
'BETWEEN',
'BIT',
'BIT_LENGTH',
'BOTH',
'BREAK',
'BROWSE',
'BULK',
'BY',
'CASCADE',
'CASCADED',
'CASE',
'CAST',
'CATALOG',
'CHAR',
'CHARACTER',
'CHARACTER_LENGTH',
'CHAR_LENGTH',
'CHECK',
'CHECKPOINT',
'CLOSE',
'CLUSTERED',
'COALESCE',
'COLLATE',
'COLLATION',
'COLUMN',
'COMMIT',
'COMPUTE',
'CONFLICT',
'CONNECT',
'CONNECTION',
'CONSTRAINT',
'CONSTRAINTS',
'CONTAINS',
'CONTAINSTABLE',
'CONTINUE',
'CONVERT',
'CORRESPONDING',
'COUNT',
'CREATE',
'CROSS',
'CURRENT',
'CURRENT_DATE',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_USER',
'CURSOR',
'DATABASE',
'DATE',
'DAY',
'DBCC',
'DEALLOCATE',
'DEC',
'DECIMAL',
'DECLARE',
'DEFAULT',
'DEFERRABLE',
'DEFERRED',
'DELETE',
'DENY',
'DESC',
'DESCRIBE',
'DESCRIPTOR',
'DETACH',
'DIAGNOSTICS',
'DISCONNECT',
'DISK',
'DISTINCT',
'DISTRIBUTED',
'DO',
'DOMAIN',
'DOUBLE',
'DROP',
'DUMP',
'EACH',
'ELSE',
'END',
'END-EXEC',
'ERRLVL',
'ESCAPE',
'EXCEPT',
'EXCEPTION',
'EXCLUDE',
'EXCLUSIVE',
'EXEC',
'EXECUTE',
'EXISTS',
'EXIT',
'EXPLAIN',
'EXTERNAL',
'EXTRACT',
'FAIL',
'FALSE',
'FETCH',
'FILE',
'FILLFACTOR',
'FILTER',
'FIRST',
'FLOAT',
'FOLLOWING',
'FOR',
'FOREIGN',
'FORTRAN',
'FOUND',
'FREETEXT',
'FREETEXTTABLE',
'FROM',
'FULL',
'FUNCTION',
'GENERATED',
'GET',
'GLOB',
'GLOBAL',
'GO',
'GOTO',
'GRANT',
'GROUP',
'GROUPS',
'HAVING',
'HOLDLOCK',
'HOUR',
'IDENTITY',
'IDENTITYCOL',
'IDENTITY_INSERT',
'IF',
'IGNORE',
'IMMEDIATE',
'IN',
'INCLUDE',
'INDEX',
'INDEXED',
'INDICATOR',
'INITIALLY',
'INNER',
'INPUT',
'INSENSITIVE',
'INSERT',
'INSTEAD',
'INT',
'INTEGER',
'INTERSECT',
'INTERVAL',
'INTO',
'IS',
'ISNULL',
'ISOLATION',
'JOIN',
'KEY',
'KILL',
'LANGUAGE',
'LAST',
'LEADING',
'LEFT',
'LEVEL',
'LIKE',
'LIMIT',
'LINENO',
'LOAD',
'LOCAL',
'LOWER',
'MATCH',
'MATERIALIZED',
'MAX',
'MERGE',
'MIN',
'MINUTE',
'MODULE',
'MONTH',
'NAMES',
'NATIONAL',
'NATURAL',
'NCHAR',
'NEXT',
'NO',
'NOCHECK',
'NONCLUSTERED',
'NONE',
'NOT',
'NOTHING',
'NOTNULL',
'NULL',
'NULLIF',
'NULLS',
'NUMERIC',
'OCTET_LENGTH',
'OF',
'OFF',
'OFFSET',
'OFFSETS',
'ON',
'ONLY',
'OPEN',
'OPENDATASOURCE',
'OPENQUERY',
'OPENROWSET',
'OPENXML',
'OPTION',
'OR',
'ORDER',
'OTHERS',
'OUTER',
'OUTPUT',
'OVER',
'OVERLAPS',
'PAD',
'PARTIAL',
'PARTITION',
'PASCAL',
'PERCENT',
'PIVOT',
'PLAN',
'POSITION',
'PRAGMA',
'PRECEDING',
'PRECISION',
'PREPARE',
'PRESERVE',
'PRIMARY',
'PRINT',
'PRIOR',
'PRIVILEGES',
'PROC',
'PROCEDURE',
'PUBLIC',
'QUERY',
'RAISE',
'RAISERROR',
'RANGE',
'READ',
'READTEXT',
'REAL',
'RECONFIGURE',
'RECURSIVE',
'REFERENCES',
'REGEXP',
'REINDEX',
'RELATIVE',
'RELEASE',
'RENAME',
'REPLACE',
'REPLICATION',
'RESTORE',
'RESTRICT',
'RETURN',
'RETURNING',
'REVERT',
'REVOKE',
'RIGHT',
'ROLLBACK',
'ROW',
'ROWCOUNT',
'ROWGUIDCOL',
'ROWS',
'RULE',
'SAVE',
'SAVEPOINT',
'SCHEMA',
'SCROLL',
'SECOND',
'SECTION',
'SECURITYAUDIT',
'SELECT',
'SEMANTICKEYPHRASETABLE',
'SEMANTICSIMILARITYDETAILSTABLE',
'SEMANTICSIMILARITYTABLE',
'SESSION',
'SESSION_USER',
'SET',
'SETUSER',
'SHUTDOWN',
'SIZE',
'SMALLINT',
'SOME',
'SPACE',
'SQL',
'SQLCA',
'SQLCODE',
'SQLERROR',
'SQLSTATE',
'SQLWARNING',
'STATISTICS',
'SUBSTRING',
'SUM',
'SYSTEM_USER',
'TABLE',
'TABLESAMPLE',
'TEMP',
'TEMPORARY',
'TEXTSIZE',
'THEN',
'TIES',
'TIME',
'TIMESTAMP',
'TIMEZONE_HOUR',
'TIMEZONE_MINUTE',
'TO',
'TOP',
'TRAILING',
'TRAN',
'TRANSACTION',
'TRANSLATE',
'TRANSLATION',
'TRIGGER',
'TRIM',
'TRUE',
'TRUNCATE',
'TRY_CONVERT',
'TSEQUAL',
'UNBOUNDED',
'UNION',
'UNIQUE',
'UNKNOWN',
'UNPIVOT',
'UPDATE',
'UPDATETEXT',
'UPPER',
'USAGE',
'USE',
'USER',
'USING',
'VACUUM',
'VALUE',
'VALUES',
'VARCHAR',
'VARYING',
'VIEW',
'VIRTUAL',
'WAITFOR',
'WHEN',
'WHENEVER',
'WHERE',
'WHILE',
'WINDOW',
'WITH',
'WITHIN GROUP',
'WITHOUT',
'WORK',
'WRITE',
'WRITETEXT',
'YEAR',
'ZONE',
],
operators: [
// Set
'EXCEPT',
'INTERSECT',
'UNION',
// Join
'APPLY',
'CROSS',
'FULL',
'INNER',
'JOIN',
'LEFT',
'OUTER',
'RIGHT',
// Predicates
'CONTAINS',
'FREETEXT',
'IS',
'NULL',
// Pivoting
'PIVOT',
'UNPIVOT',
// Merging
'MATCHED',
],
logicalOperators: ['ALL', 'AND', 'ANY', 'BETWEEN', 'EXISTS', 'IN', 'LIKE', 'NOT', 'OR', 'SOME'],
comparisonOperators: ['<>', '>', '<', '>=', '<=', '=', '!=', '&', '~', '^', '%'],
builtinFunctions: [
// Aggregate
'AVG',
'CHECKSUM_AGG',
'COUNT',
'COUNT_BIG',
'GROUPING',
'GROUPING_ID',
'MAX',
'MIN',
'SUM',
'STDEV',
'STDEVP',
'VAR',
'VARP',
// Analytic
'CUME_DIST',
'FIRST_VALUE',
'LAG',
'LAST_VALUE',
'LEAD',
'PERCENTILE_CONT',
'PERCENTILE_DISC',
'PERCENT_RANK',
// Collation
'COLLATE',
'COLLATIONPROPERTY',
'TERTIARY_WEIGHTS',
// Azure
'FEDERATION_FILTERING_VALUE',
// Conversion
'CAST',
'CONVERT',
'PARSE',
'TRY_CAST',
'TRY_CONVERT',
'TRY_PARSE',
// Cryptographic
'ASYMKEY_ID',
'ASYMKEYPROPERTY',
'CERTPROPERTY',
'CERT_ID',
'CRYPT_GEN_RANDOM',
'DECRYPTBYASYMKEY',
'DECRYPTBYCERT',
'DECRYPTBYKEY',
'DECRYPTBYKEYAUTOASYMKEY',
'DECRYPTBYKEYAUTOCERT',
'DECRYPTBYPASSPHRASE',
'ENCRYPTBYASYMKEY',
'ENCRYPTBYCERT',
'ENCRYPTBYKEY',
'ENCRYPTBYPASSPHRASE',
'HASHBYTES',
'IS_OBJECTSIGNED',
'KEY_GUID',
'KEY_ID',
'KEY_NAME',
'SIGNBYASYMKEY',
'SIGNBYCERT',
'SYMKEYPROPERTY',
'VERIFYSIGNEDBYCERT',
'VERIFYSIGNEDBYASYMKEY',
// Cursor
'CURSOR_STATUS',
// Datatype
'DATALENGTH',
'IDENT_CURRENT',
'IDENT_INCR',
'IDENT_SEED',
'IDENTITY',
'SQL_VARIANT_PROPERTY',
// Datetime
'CURRENT_TIMESTAMP',
'DATEADD',
'DATEDIFF',
'DATEFROMPARTS',
'DATENAME',
'DATEPART',
'DATETIME2FROMPARTS',
'DATETIMEFROMPARTS',
'DATETIMEOFFSETFROMPARTS',
'DAY',
'EOMONTH',
'GETDATE',
'GETUTCDATE',
'ISDATE',
'MONTH',
'SMALLDATETIMEFROMPARTS',
'SWITCHOFFSET',
'SYSDATETIME',
'SYSDATETIMEOFFSET',
'SYSUTCDATETIME',
'TIMEFROMPARTS',
'TODATETIMEOFFSET',
'YEAR',
// Logical
'CHOOSE',
'COALESCE',
'IIF',
'NULLIF',
// Mathematical
'ABS',
'ACOS',
'ASIN',
'ATAN',
'ATN2',
'CEILING',
'COS',
'COT',
'DEGREES',
'EXP',
'FLOOR',
'LOG',
'LOG10',
'PI',
'POWER',
'RADIANS',
'RAND',
'ROUND',
'SIGN',
'SIN',
'SQRT',
'SQUARE',
'TAN',
// Metadata
'APP_NAME',
'APPLOCK_MODE',
'APPLOCK_TEST',
'ASSEMBLYPROPERTY',
'COL_LENGTH',
'COL_NAME',
'COLUMNPROPERTY',
'DATABASE_PRINCIPAL_ID',
'DATABASEPROPERTYEX',
'DB_ID',
'DB_NAME',
'FILE_ID',
'FILE_IDEX',
'FILE_NAME',
'FILEGROUP_ID',
'FILEGROUP_NAME',
'FILEGROUPPROPERTY',
'FILEPROPERTY',
'FULLTEXTCATALOGPROPERTY',
'FULLTEXTSERVICEPROPERTY',
'INDEX_COL',
'INDEXKEY_PROPERTY',
'INDEXPROPERTY',
'OBJECT_DEFINITION',
'OBJECT_ID',
'OBJECT_NAME',
'OBJECT_SCHEMA_NAME',
'OBJECTPROPERTY',
'OBJECTPROPERTYEX',
'ORIGINAL_DB_NAME',
'PARSENAME',
'SCHEMA_ID',
'SCHEMA_NAME',
'SCOPE_IDENTITY',
'SERVERPROPERTY',
'STATS_DATE',
'TYPE_ID',
'TYPE_NAME',
'TYPEPROPERTY',
// Ranking
'DENSE_RANK',
'NTILE',
'RANK',
'ROW_NUMBER',
// Replication
'PUBLISHINGSERVERNAME',
// Rowset
'OPENDATASOURCE',
'OPENQUERY',
'OPENROWSET',
'OPENXML',
// Security
'CERTENCODED',
'CERTPRIVATEKEY',
'CURRENT_USER',
'HAS_DBACCESS',
'HAS_PERMS_BY_NAME',
'IS_MEMBER',
'IS_ROLEMEMBER',
'IS_SRVROLEMEMBER',
'LOGINPROPERTY',
'ORIGINAL_LOGIN',
'PERMISSIONS',
'PWDENCRYPT',
'PWDCOMPARE',
'SESSION_USER',
'SESSIONPROPERTY',
'SUSER_ID',
'SUSER_NAME',
'SUSER_SID',
'SUSER_SNAME',
'SYSTEM_USER',
'USER',
'USER_ID',
'USER_NAME',
// String
'ASCII',
'CHAR',
'CHARINDEX',
'CONCAT',
'DIFFERENCE',
'FORMAT',
'LEFT',
'LEN',
'LOWER',
'LTRIM',
'NCHAR',
'PATINDEX',
'QUOTENAME',
'REPLACE',
'REPLICATE',
'REVERSE',
'RIGHT',
'RTRIM',
'SOUNDEX',
'SPACE',
'STR',
'STUFF',
'SUBSTRING',
'UNICODE',
'UPPER',
// System
'BINARY_CHECKSUM',
'CHECKSUM',
'CONNECTIONPROPERTY',
'CONTEXT_INFO',
'CURRENT_REQUEST_ID',
'ERROR_LINE',
'ERROR_NUMBER',
'ERROR_MESSAGE',
'ERROR_PROCEDURE',
'ERROR_SEVERITY',
'ERROR_STATE',
'FORMATMESSAGE',
'GETANSINULL',
'GET_FILESTREAM_TRANSACTION_CONTEXT',
'HOST_ID',
'HOST_NAME',
'ISNULL',
'ISNUMERIC',
'MIN_ACTIVE_ROWVERSION',
'NEWID',
'NEWSEQUENTIALID',
'ROWCOUNT_BIG',
'XACT_STATE',
// TextImage
'TEXTPTR',
'TEXTVALID',
// Trigger
'COLUMNS_UPDATED',
'EVENTDATA',
'TRIGGER_NESTLEVEL',
'UPDATE',
// ChangeTracking
'CHANGETABLE',
'CHANGE_TRACKING_CONTEXT',
'CHANGE_TRACKING_CURRENT_VERSION',
'CHANGE_TRACKING_IS_COLUMN_IN_MASK',
'CHANGE_TRACKING_MIN_VALID_VERSION',
// FullTextSearch
'CONTAINSTABLE',
'FREETEXTTABLE',
// SemanticTextSearch
'SEMANTICKEYPHRASETABLE',
'SEMANTICSIMILARITYDETAILSTABLE',
'SEMANTICSIMILARITYTABLE',
// FileStream
'FILETABLEROOTPATH',
'GETFILENAMESPACEPATH',
'GETPATHLOCATOR',
'PATHNAME',
// ServiceBroker
'GET_TRANSMISSION_STATUS',
],
builtinVariables: [
// Configuration
'@@DATEFIRST',
'@@DBTS',
'@@LANGID',
'@@LANGUAGE',
'@@LOCK_TIMEOUT',
'@@MAX_CONNECTIONS',
'@@MAX_PRECISION',
'@@NESTLEVEL',
'@@OPTIONS',
'@@REMSERVER',
'@@SERVERNAME',
'@@SERVICENAME',
'@@SPID',
'@@TEXTSIZE',
'@@VERSION',
// Cursor
'@@CURSOR_ROWS',
'@@FETCH_STATUS',
// Datetime
'@@DATEFIRST',
// Metadata
'@@PROCID',
// System
'@@ERROR',
'@@IDENTITY',
'@@ROWCOUNT',
'@@TRANCOUNT',
// Stats
'@@CONNECTIONS',
'@@CPU_BUSY',
'@@IDLE',
'@@IO_BUSY',
'@@PACKET_ERRORS',
'@@PACK_RECEIVED',
'@@PACK_SENT',
'@@TIMETICKS',
'@@TOTAL_ERRORS',
'@@TOTAL_READ',
'@@TOTAL_WRITE',
],
pseudoColumns: ['$ACTION', '$IDENTITY', '$ROWGUID', '$PARTITION'],
tokenizer: {
root: [
{ include: '@templateVariables' },
{ include: '@macros' },
{ include: '@comments' },
{ include: '@whitespace' },
{ include: '@pseudoColumns' },
{ include: '@numbers' },
{ include: '@strings' },
{ include: '@complexIdentifiers' },
{ include: '@scopes' },
[/[;,.]/, 'delimiter'],
[/[()]/, '@brackets'],
[
/[\w@#$|<|>|=|!|%|&|+|\|-|*|/|~|^]+/,
{
cases: {
'@operators': 'operator',
'@comparisonOperators': 'operator',
'@logicalOperators': 'operator',
'@builtinVariables': 'predefined',
'@builtinFunctions': 'predefined',
'@keywords': 'keyword',
'@default': 'identifier',
},
},
],
],
templateVariables: [[/\$[a-zA-Z0-9]+/, 'variable']],
macros: [[/\$__[a-zA-Z0-9-_]+/, 'type']],
whitespace: [[/\s+/, 'white']],
comments: [
[/--+.*/, 'comment'],
[/\/\*/, { token: 'comment.quote', next: '@comment' }],
],
comment: [
[/[^*/]+/, 'comment'],
// Not supporting nested comments, as nested comments seem to not be standard?
// i.e. http://stackoverflow.com/questions/728172/are-there-multiline-comment-delimiters-in-sql-that-are-vendor-agnostic
// [/\/\*/, { token: 'comment.quote', next: '@push' }], // nested comment not allowed :-(
[/\*\//, { token: 'comment.quote', next: '@pop' }],
[/./, 'comment'],
],
pseudoColumns: [
[
/[$][A-Za-z_][\w@#$]*/,
{
cases: {
'@pseudoColumns': 'predefined',
'@default': 'identifier',
},
},
],
],
numbers: [
[/0[xX][0-9a-fA-F]*/, 'number'],
[/[$][+-]*\d*(\.\d*)?/, 'number'],
[/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, 'number'],
],
strings: [
[/N'/, { token: 'string', next: '@string' }],
[/'/, { token: 'string', next: '@string' }],
],
string: [
[/[^']+/, 'string'],
[/''/, 'string'],
[/'/, { token: 'string', next: '@pop' }],
],
complexIdentifiers: [
[/\[/, { token: 'identifier.quote', next: '@bracketedIdentifier' }],
[/"/, { token: 'identifier.quote', next: '@quotedIdentifier' }],
],
bracketedIdentifier: [
[/[^\]]+/, 'identifier'],
[/]]/, 'identifier'],
[/]/, { token: 'identifier.quote', next: '@pop' }],
],
quotedIdentifier: [
[/[^"]+/, 'identifier'],
[/""/, 'identifier'],
[/"/, { token: 'identifier.quote', next: '@pop' }],
],
scopes: [
[/BEGIN\s+(DISTRIBUTED\s+)?TRAN(SACTION)?\b/i, 'keyword'],
[/BEGIN\s+TRY\b/i, { token: 'keyword.try' }],
[/END\s+TRY\b/i, { token: 'keyword.try' }],
[/BEGIN\s+CATCH\b/i, { token: 'keyword.catch' }],
[/END\s+CATCH\b/i, { token: 'keyword.catch' }],
[/(BEGIN|CASE)\b/i, { token: 'keyword.block' }],
[/END\b/i, { token: 'keyword.block' }],
[/WHEN\b/i, { token: 'keyword.choice' }],
[/THEN\b/i, { token: 'keyword.choice' }],
],
},
};

View File

@ -1,67 +0,0 @@
import { MacrosRegistryItem } from './types';
const COLUMN = 'column',
RELATIVE_TIME_STRING = "'5m'";
export enum MacroType {
Value,
Filter,
Group,
Column,
Table,
}
export const MACROS: MacrosRegistryItem[] = [
{
id: '$__timeFilter(dateColumn)',
name: '$__timeFilter(dateColumn)',
text: '$__timeFilter',
args: [COLUMN],
type: MacroType.Filter,
description:
'Will be replaced by a time range filter using the specified column name. For example, dateColumn BETWEEN FROM_UNIXTIME(1494410783) AND FROM_UNIXTIME(1494410983)',
},
{
id: '$__timeFrom()',
name: '$__timeFrom()',
text: '$__timeFrom',
args: [],
type: MacroType.Filter,
description:
'Will be replaced by the start of the currently active time selection. For example, FROM_UNIXTIME(1494410783)',
},
{
id: '$__timeTo()',
name: '$__timeTo()',
text: '$__timeTo',
args: [],
type: MacroType.Filter,
description:
'Will be replaced by the end of the currently active time selection. For example, FROM_UNIXTIME(1494410983)',
},
{
id: "$__timeGroup(dateColumn, '5m')",
name: "$__timeGroup(dateColumn, '5m')",
text: '$__timeGroup',
args: [COLUMN, RELATIVE_TIME_STRING],
type: MacroType.Value,
description:
'Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),*',
},
{
id: '$__table',
name: '$__table',
text: '$__table',
args: [],
type: MacroType.Table,
description: 'Will be replaced by the query table.',
},
{
id: '$__column',
name: '$__column',
text: '$__column',
args: [],
type: MacroType.Column,
description: 'Will be replaced by the query column.',
},
];

View File

@ -1,425 +0,0 @@
import { Registry } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import {
CompletionItemInsertTextRule,
CompletionItemKind,
CompletionItemPriority,
MacroType,
OperatorType,
SuggestionKind,
} from '../types';
import { TRIGGER_SUGGEST } from '../utils/commands';
import { ASC, DESC, LOGICAL_OPERATORS, STD_OPERATORS, STD_STATS } from './language';
import { MACROS } from './macros';
import { FunctionsRegistryItem, MacrosRegistryItem, OperatorsRegistryItem, SuggestionsRegistryItem } from './types';
/**
* This registry glues particular SuggestionKind with an async function that provides completion items for it.
* To add a new suggestion kind, SQLEditor should be configured with a provider that implements customSuggestionKinds.
*/
export const initStandardSuggestions =
(
functions: Registry<FunctionsRegistryItem>,
operators: Registry<OperatorsRegistryItem>,
macros: Registry<MacrosRegistryItem>
) =>
(): SuggestionsRegistryItem[] =>
[
{
id: SuggestionKind.SelectKeyword,
name: SuggestionKind.SelectKeyword,
suggestions: (_, m) =>
Promise.resolve([
{
label: `SELECT <column>`,
insertText: `SELECT $0`,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Snippet,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
},
{
label: `SELECT <column> FROM <table>`,
insertText: `SELECT $2 FROM $1`,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Snippet,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
},
]),
},
{
id: SuggestionKind.TemplateVariables,
name: SuggestionKind.TemplateVariables,
suggestions: (_, m) => {
const templateSrv = getTemplateSrv();
if (!templateSrv) {
return Promise.resolve([]);
}
return Promise.resolve(
templateSrv.getVariables().map((variable) => {
const label = `\$${variable.name}`;
const val = templateSrv.replace(label);
return {
label,
detail: `(Template Variable) ${val}`,
kind: CompletionItemKind.Snippet,
documentation: `(Template Variable) ${val}`,
insertText: `\\$${variable.name} `,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
command: TRIGGER_SUGGEST,
};
})
);
},
},
{
id: SuggestionKind.SelectMacro,
name: SuggestionKind.SelectMacro,
suggestions: (_, m) =>
Promise.resolve([
...macros
.list()
.filter((m) => m.type === MacroType.Value || m.type === MacroType.Column)
.map(createMacroSuggestionItem),
]),
},
{
id: SuggestionKind.TableMacro,
name: SuggestionKind.TableMacro,
suggestions: (_, m) =>
Promise.resolve([
...macros
.list()
.filter((m) => m.type === MacroType.Table)
.map(createMacroSuggestionItem),
]),
},
{
id: SuggestionKind.GroupMacro,
name: SuggestionKind.GroupMacro,
suggestions: (_, m) =>
Promise.resolve([
...macros
.list()
.filter((m) => m.type === MacroType.Group)
.map(createMacroSuggestionItem),
]),
},
{
id: SuggestionKind.FilterMacro,
name: SuggestionKind.FilterMacro,
suggestions: (_, m) =>
Promise.resolve([
...macros
.list()
.filter((m) => m.type === MacroType.Filter)
.map(createMacroSuggestionItem),
]),
},
{
id: SuggestionKind.WithKeyword,
name: SuggestionKind.WithKeyword,
suggestions: (_, m) =>
Promise.resolve([
{
label: `WITH <alias> AS ( ... )`,
insertText: `WITH $1 AS ( $2 )`,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Snippet,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
},
]),
},
{
id: SuggestionKind.FunctionsWithArguments,
name: SuggestionKind.FunctionsWithArguments,
suggestions: (_, m) =>
Promise.resolve([
...functions.list().map((f) => ({
label: f.name,
insertText: `${f.name}($0)`,
documentation: f.description,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Function,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumHigh,
})),
]),
},
{
id: SuggestionKind.FunctionsWithoutArguments,
name: SuggestionKind.FunctionsWithoutArguments,
suggestions: (_, m) =>
Promise.resolve([
...functions.list().map((f) => ({
label: f.name,
insertText: `${f.name}()`,
documentation: f.description,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Function,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumHigh,
})),
]),
},
{
id: SuggestionKind.FromKeyword,
name: SuggestionKind.FromKeyword,
suggestions: (_, m) =>
Promise.resolve([
{
label: 'FROM',
insertText: `FROM $0`,
command: TRIGGER_SUGGEST,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Keyword,
},
]),
},
{
id: SuggestionKind.Tables,
name: SuggestionKind.Tables,
suggestions: (_, m) => Promise.resolve([]),
},
{
id: SuggestionKind.Columns,
name: SuggestionKind.Columns,
suggestions: (_, m) => Promise.resolve([]),
},
{
id: SuggestionKind.LogicalOperators,
name: SuggestionKind.LogicalOperators,
suggestions: (_, m) =>
Promise.resolve(
operators
.list()
.filter((o) => o.type === OperatorType.Logical)
.map((o) => ({
label: o.operator,
insertText: `${o.operator} `,
documentation: o.description,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumHigh,
kind: CompletionItemKind.Operator,
}))
),
},
{
id: SuggestionKind.WhereKeyword,
name: SuggestionKind.WhereKeyword,
suggestions: (_, m) =>
Promise.resolve([
{
label: 'WHERE',
insertText: `WHERE `,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumHigh,
kind: CompletionItemKind.Keyword,
},
]),
},
{
id: SuggestionKind.ComparisonOperators,
name: SuggestionKind.ComparisonOperators,
suggestions: (_, m) =>
Promise.resolve([
...operators
.list()
.filter((o) => o.type === OperatorType.Comparison)
.map((o) => ({
label: o.operator,
insertText: `${o.operator} `,
documentation: o.description,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumHigh,
kind: CompletionItemKind.Operator,
})),
{
label: 'IN (...)',
insertText: `IN ( $0 )`,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
kind: CompletionItemKind.Operator,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
},
{
label: 'NOT IN (...)',
insertText: `NOT IN ( $0 )`,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
kind: CompletionItemKind.Operator,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
},
{
label: 'IS',
insertText: `IS`,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
kind: CompletionItemKind.Operator,
},
{
label: 'IS NOT',
insertText: `IS NOT`,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
kind: CompletionItemKind.Operator,
},
]),
},
{
id: SuggestionKind.GroupByKeywords,
name: SuggestionKind.GroupByKeywords,
suggestions: (_, m) =>
Promise.resolve([
{
label: 'GROUP BY',
insertText: `GROUP BY `,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumHigh,
kind: CompletionItemKind.Keyword,
},
]),
},
{
id: SuggestionKind.OrderByKeywords,
name: SuggestionKind.OrderByKeywords,
suggestions: (_, m) =>
Promise.resolve([
{
label: 'ORDER BY',
insertText: `ORDER BY `,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.Medium,
kind: CompletionItemKind.Keyword,
},
{
label: 'ORDER BY(ascending)',
insertText: `ORDER BY $1 ASC `,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumLow,
kind: CompletionItemKind.Snippet,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
},
{
label: 'ORDER BY(descending)',
insertText: `ORDER BY $1 DESC`,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumLow,
kind: CompletionItemKind.Snippet,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
},
]),
},
{
id: SuggestionKind.LimitKeyword,
name: SuggestionKind.LimitKeyword,
suggestions: (_, m) =>
Promise.resolve([
{
label: 'LIMIT',
insertText: `LIMIT `,
command: TRIGGER_SUGGEST,
sortText: CompletionItemPriority.MediumLow,
kind: CompletionItemKind.Keyword,
},
]),
},
{
id: SuggestionKind.SortOrderDirectionKeyword,
name: SuggestionKind.SortOrderDirectionKeyword,
suggestions: (_, m) =>
Promise.resolve(
[ASC, DESC].map((o) => ({
label: o,
insertText: `${o} `,
command: TRIGGER_SUGGEST,
kind: CompletionItemKind.Keyword,
}))
),
},
{
id: SuggestionKind.NotKeyword,
name: SuggestionKind.NotKeyword,
suggestions: () =>
Promise.resolve([
{
label: 'NOT',
insertText: 'NOT',
command: TRIGGER_SUGGEST,
kind: CompletionItemKind.Keyword,
sortText: CompletionItemPriority.High,
},
]),
},
{
id: SuggestionKind.BoolValues,
name: SuggestionKind.BoolValues,
suggestions: () =>
Promise.resolve(
['TRUE', 'FALSE'].map((o) => ({
label: o,
insertText: `${o}`,
command: TRIGGER_SUGGEST,
kind: CompletionItemKind.Keyword,
sortText: CompletionItemPriority.Medium,
}))
),
},
{
id: SuggestionKind.NullValue,
name: SuggestionKind.NullValue,
suggestions: () =>
Promise.resolve(
['NULL'].map((o) => ({
label: o,
insertText: `${o}`,
command: TRIGGER_SUGGEST,
kind: CompletionItemKind.Keyword,
sortText: CompletionItemPriority.Low,
}))
),
},
];
export const initFunctionsRegistry = (): FunctionsRegistryItem[] => [
...STD_STATS.map((s) => ({
id: s,
name: s,
})),
];
export const initMacrosRegistry = (): MacrosRegistryItem[] => [...MACROS];
export const initOperatorsRegistry = (): OperatorsRegistryItem[] => [
...STD_OPERATORS.map((o) => ({
id: o,
name: o,
operator: o,
type: OperatorType.Comparison,
})),
...LOGICAL_OPERATORS.map((o) => ({ id: o, name: o.toUpperCase(), operator: o, type: OperatorType.Logical })),
];
function createMacroSuggestionItem(m: MacrosRegistryItem) {
return {
label: m.name,
insertText: `${'\\' + m.text}${argsString(m.args)} `,
insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,
kind: CompletionItemKind.Snippet,
documentation: m.description,
command: TRIGGER_SUGGEST,
};
}
function argsString(args?: string[]): string {
if (!args) {
return '()';
}
return '('.concat(args.map((t, i) => `\${${i}:${t}}`).join(', ')).concat(')');
}

View File

@ -1,244 +0,0 @@
import { StatementPosition, TokenType } from '../types';
import { AND, AS, ASC, BY, DESC, FROM, GROUP, ORDER, SELECT, WHERE, WITH } from './language';
import { StatementPositionResolversRegistryItem } from './types';
export function initStatementPositionResolvers(): StatementPositionResolversRegistryItem[] {
return [
{
id: StatementPosition.SelectKeyword,
name: StatementPosition.SelectKeyword,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
currentToken === null ||
(currentToken.isWhiteSpace() && currentToken.previous === null) ||
currentToken.is(TokenType.Keyword, SELECT) ||
(currentToken.is(TokenType.Keyword, SELECT) && currentToken.previous === null) ||
previousIsSlash ||
(currentToken.isIdentifier() && (previousIsSlash || currentToken?.previous === null)) ||
(currentToken.isIdentifier() && SELECT.startsWith(currentToken.value.toLowerCase()))
),
},
{
id: StatementPosition.WithKeyword,
name: StatementPosition.WithKeyword,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
currentToken === null ||
(currentToken.isWhiteSpace() && currentToken.previous === null) ||
(currentToken.is(TokenType.Keyword, WITH) && currentToken.previous === null) ||
(currentToken.isIdentifier() && WITH.toLowerCase().startsWith(currentToken.value.toLowerCase()))
),
},
{
id: StatementPosition.AfterSelectKeyword,
name: StatementPosition.AfterSelectKeyword,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(previousNonWhiteSpace?.value.toLowerCase() === SELECT),
},
{
id: StatementPosition.AfterSelectArguments,
name: StatementPosition.AfterSelectArguments,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(previousKeyword?.value.toLowerCase() === SELECT && previousNonWhiteSpace?.value === ',');
},
},
{
id: StatementPosition.AfterSelectFuncFirstArgument,
name: StatementPosition.AfterSelectFuncFirstArgument,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
(previousKeyword?.value.toLowerCase() === SELECT || previousKeyword?.value.toLowerCase() === AS) &&
(previousNonWhiteSpace?.is(TokenType.Parenthesis, '(') || currentToken?.is(TokenType.Parenthesis, '()'))
);
},
},
{
id: StatementPosition.AfterWhereFunctionArgument,
name: StatementPosition.AfterWhereFunctionArgument,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
previousKeyword?.is(TokenType.Keyword, WHERE) &&
(previousNonWhiteSpace?.is(TokenType.Parenthesis, '(') || currentToken?.is(TokenType.Parenthesis, '()'))
);
},
},
{
id: StatementPosition.AfterGroupBy,
name: StatementPosition.AfterGroupBy,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
previousKeyword?.is(TokenType.Keyword, BY) &&
previousKeyword?.getPreviousKeyword()?.is(TokenType.Keyword, GROUP) &&
(previousNonWhiteSpace?.isIdentifier() ||
previousNonWhiteSpace?.isDoubleQuotedString() ||
previousNonWhiteSpace?.is(TokenType.Parenthesis, ')') ||
previousNonWhiteSpace?.is(TokenType.Parenthesis, '()'))
),
},
{
id: StatementPosition.SelectAlias,
name: StatementPosition.SelectAlias,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
if (previousNonWhiteSpace?.value === ',' && previousKeyword?.value.toLowerCase() === AS) {
return true;
}
return false;
},
},
{
id: StatementPosition.FromKeyword,
name: StatementPosition.FromKeyword,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
(previousKeyword?.value.toLowerCase() === SELECT && previousNonWhiteSpace?.value !== ',') ||
((currentToken?.isKeyword() || currentToken?.isIdentifier()) &&
FROM.toLowerCase().startsWith(currentToken.value.toLowerCase()))
);
},
},
{
id: StatementPosition.AfterFromKeyword,
name: StatementPosition.AfterFromKeyword,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(previousNonWhiteSpace?.value.toLowerCase() === FROM),
},
{
id: StatementPosition.AfterFrom,
name: StatementPosition.AfterFrom,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
(previousKeyword?.value.toLowerCase() === FROM && previousNonWhiteSpace?.isDoubleQuotedString()) ||
(previousKeyword?.value.toLowerCase() === FROM && previousNonWhiteSpace?.isIdentifier()) ||
(previousKeyword?.value.toLowerCase() === FROM && previousNonWhiteSpace?.isVariable())
),
},
{
id: StatementPosition.AfterTable,
name: StatementPosition.AfterTable,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
previousKeyword?.value.toLowerCase() === FROM &&
(previousNonWhiteSpace?.isVariable() || previousNonWhiteSpace?.value !== '')
);
},
},
{
id: StatementPosition.WhereKeyword,
name: StatementPosition.WhereKeyword,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
previousKeyword?.value.toLowerCase() === WHERE &&
(previousNonWhiteSpace?.isKeyword() ||
previousNonWhiteSpace?.is(TokenType.Parenthesis, '(') ||
previousNonWhiteSpace?.is(TokenType.Operator, AND))
),
},
{
id: StatementPosition.WhereComparisonOperator,
name: StatementPosition.WhereComparisonOperator,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
previousKeyword?.value.toLowerCase() === WHERE &&
!previousNonWhiteSpace?.getPreviousNonWhiteSpaceToken()?.isOperator() &&
!currentToken?.is(TokenType.Delimiter, '.') &&
!currentToken?.isParenthesis() &&
(previousNonWhiteSpace?.isIdentifier() || previousNonWhiteSpace?.isDoubleQuotedString())
),
},
{
id: StatementPosition.WhereValue,
name: StatementPosition.WhereValue,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(previousKeyword?.value.toLowerCase() === WHERE && previousNonWhiteSpace?.isOperator()),
},
{
id: StatementPosition.AfterWhereValue,
name: StatementPosition.AfterWhereValue,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
previousKeyword?.value.toLowerCase() === WHERE &&
(previousNonWhiteSpace?.is(TokenType.Operator, 'and') ||
previousNonWhiteSpace?.is(TokenType.Operator, 'or') ||
previousNonWhiteSpace?.isString() ||
previousNonWhiteSpace?.isNumber() ||
previousNonWhiteSpace?.is(TokenType.Parenthesis, ')') ||
previousNonWhiteSpace?.is(TokenType.Parenthesis, '()') ||
previousNonWhiteSpace?.isTemplateVariable() ||
(previousNonWhiteSpace?.is(TokenType.IdentifierQuote) &&
previousNonWhiteSpace.getPreviousNonWhiteSpaceToken()?.is(TokenType.Identifier) &&
previousNonWhiteSpace
?.getPreviousNonWhiteSpaceToken()
?.getPreviousNonWhiteSpaceToken()
?.is(TokenType.IdentifierQuote)))
);
},
},
{
id: StatementPosition.AfterGroupByKeywords,
name: StatementPosition.AfterGroupByKeywords,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
previousKeyword?.is(TokenType.Keyword, BY) &&
previousKeyword?.getPreviousKeyword()?.is(TokenType.Keyword, GROUP) &&
(previousNonWhiteSpace?.is(TokenType.Keyword, BY) || previousNonWhiteSpace?.is(TokenType.Delimiter, ','))
),
},
{
id: StatementPosition.AfterGroupByFunctionArgument,
name: StatementPosition.AfterGroupByFunctionArgument,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
previousKeyword?.is(TokenType.Keyword, BY) &&
previousKeyword?.getPreviousKeyword()?.is(TokenType.Keyword, GROUP) &&
(previousNonWhiteSpace?.is(TokenType.Parenthesis, '(') || currentToken?.is(TokenType.Parenthesis, '()'))
);
},
},
{
id: StatementPosition.AfterOrderByKeywords,
name: StatementPosition.AfterOrderByKeywords,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
previousNonWhiteSpace?.is(TokenType.Keyword, BY) &&
previousNonWhiteSpace?.getPreviousKeyword()?.is(TokenType.Keyword, ORDER)
),
},
{
id: StatementPosition.AfterOrderByFunction,
name: StatementPosition.AfterOrderByFunction,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(
previousKeyword?.is(TokenType.Keyword, BY) &&
previousKeyword?.getPreviousKeyword()?.is(TokenType.Keyword, ORDER) &&
previousNonWhiteSpace?.is(TokenType.Parenthesis) &&
previousNonWhiteSpace?.getPreviousNonWhiteSpaceToken()?.is(TokenType.Function)
),
},
{
id: StatementPosition.AfterOrderByDirection,
name: StatementPosition.AfterOrderByDirection,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) =>
Boolean(previousKeyword?.is(TokenType.Keyword, DESC) || previousKeyword?.is(TokenType.Keyword, ASC)),
},
{
id: StatementPosition.AfterIsOperator,
name: StatementPosition.AfterIsOperator,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(previousNonWhiteSpace?.is(TokenType.Operator, 'IS'));
},
},
{
id: StatementPosition.AfterIsNotOperator,
name: StatementPosition.AfterIsNotOperator,
resolve: (currentToken, previousKeyword, previousNonWhiteSpace, previousIsSlash) => {
return Boolean(
previousNonWhiteSpace?.is(TokenType.Operator, 'NOT') &&
previousNonWhiteSpace.getPreviousNonWhiteSpaceToken()?.is(TokenType.Operator, 'IS')
);
},
},
];
}

View File

@ -1,146 +0,0 @@
import { RegistryItem } from '@grafana/data';
import { StatementPosition, SuggestionKind } from '../types';
export interface SuggestionKindRegistryItem extends RegistryItem {
id: StatementPosition;
kind: SuggestionKind[];
}
// Registry of possible suggestions for the given statement position
export const initSuggestionsKindRegistry = (): SuggestionKindRegistryItem[] => {
return [
{
id: StatementPosition.SelectKeyword,
name: StatementPosition.SelectKeyword,
kind: [SuggestionKind.SelectKeyword],
},
{
id: StatementPosition.WithKeyword,
name: StatementPosition.WithKeyword,
kind: [SuggestionKind.WithKeyword],
},
{
id: StatementPosition.AfterSelectKeyword,
name: StatementPosition.AfterSelectKeyword,
kind: [SuggestionKind.FunctionsWithArguments, SuggestionKind.Columns, SuggestionKind.SelectMacro],
},
{
id: StatementPosition.AfterSelectFuncFirstArgument,
name: StatementPosition.AfterSelectFuncFirstArgument,
kind: [SuggestionKind.Columns],
},
{
id: StatementPosition.AfterGroupByFunctionArgument,
name: StatementPosition.AfterGroupByFunctionArgument,
kind: [SuggestionKind.Columns],
},
{
id: StatementPosition.AfterWhereFunctionArgument,
name: StatementPosition.AfterWhereFunctionArgument,
kind: [SuggestionKind.Columns],
},
{
id: StatementPosition.AfterSelectArguments,
name: StatementPosition.AfterSelectArguments,
kind: [SuggestionKind.Columns],
},
{
id: StatementPosition.AfterFromKeyword,
name: StatementPosition.AfterFromKeyword,
kind: [SuggestionKind.Tables, SuggestionKind.TableMacro],
},
{
id: StatementPosition.SelectAlias,
name: StatementPosition.SelectAlias,
kind: [SuggestionKind.Columns, SuggestionKind.FunctionsWithArguments],
},
{
id: StatementPosition.FromKeyword,
name: StatementPosition.FromKeyword,
kind: [SuggestionKind.FromKeyword],
},
{
id: StatementPosition.AfterFrom,
name: StatementPosition.AfterFrom,
kind: [
SuggestionKind.WhereKeyword,
SuggestionKind.GroupByKeywords,
SuggestionKind.OrderByKeywords,
SuggestionKind.LimitKeyword,
],
},
{
id: StatementPosition.AfterTable,
name: StatementPosition.AfterTable,
kind: [
SuggestionKind.WhereKeyword,
SuggestionKind.GroupByKeywords,
SuggestionKind.OrderByKeywords,
SuggestionKind.LimitKeyword,
],
},
{
id: StatementPosition.WhereKeyword,
name: StatementPosition.WhereKeyword,
kind: [SuggestionKind.Columns, SuggestionKind.FilterMacro, SuggestionKind.TemplateVariables],
},
{
id: StatementPosition.WhereComparisonOperator,
name: StatementPosition.WhereComparisonOperator,
kind: [SuggestionKind.ComparisonOperators],
},
{
id: StatementPosition.WhereValue,
name: StatementPosition.WhereValue,
kind: [SuggestionKind.Columns, SuggestionKind.FilterMacro, SuggestionKind.TemplateVariables],
},
{
id: StatementPosition.AfterWhereValue,
name: StatementPosition.AfterWhereValue,
kind: [
SuggestionKind.LogicalOperators,
SuggestionKind.GroupByKeywords,
SuggestionKind.OrderByKeywords,
SuggestionKind.LimitKeyword,
SuggestionKind.Columns,
SuggestionKind.TemplateVariables,
],
},
{
id: StatementPosition.AfterGroupByKeywords,
name: StatementPosition.AfterGroupByKeywords,
kind: [SuggestionKind.GroupMacro],
},
{
id: StatementPosition.AfterGroupBy,
name: StatementPosition.AfterGroupBy,
kind: [SuggestionKind.OrderByKeywords, SuggestionKind.LimitKeyword],
},
{
id: StatementPosition.AfterOrderByKeywords,
name: StatementPosition.AfterOrderByKeywords,
kind: [SuggestionKind.Columns],
},
{
id: StatementPosition.AfterOrderByFunction,
name: StatementPosition.AfterOrderByFunction,
kind: [SuggestionKind.SortOrderDirectionKeyword, SuggestionKind.LimitKeyword],
},
{
id: StatementPosition.AfterOrderByDirection,
name: StatementPosition.AfterOrderByDirection,
kind: [SuggestionKind.LimitKeyword],
},
{
id: StatementPosition.AfterIsOperator,
name: StatementPosition.AfterOrderByDirection,
kind: [SuggestionKind.NotKeyword, SuggestionKind.NullValue, SuggestionKind.BoolValues],
},
{
id: StatementPosition.AfterIsNotOperator,
name: StatementPosition.AfterOrderByDirection,
kind: [SuggestionKind.NullValue, SuggestionKind.BoolValues],
},
];
};

View File

@ -1,52 +0,0 @@
import { RegistryItem } from '@grafana/data';
import { monacoTypes } from '@grafana/ui';
import {
CustomSuggestion,
MacroType,
OperatorType,
PositionContext,
StatementPosition,
SuggestionKind,
} from '../types';
import { LinkedToken } from '../utils/LinkedToken';
export interface SuggestionsRegistryItem extends RegistryItem {
id: SuggestionKind;
suggestions: (position: PositionContext, m: typeof monacoTypes) => Promise<CustomSuggestion[]>;
}
export interface MacrosRegistryItem extends RegistryItem {
type: MacroType;
text: string;
args?: string[];
}
export interface FunctionsRegistryItem extends RegistryItem {}
export interface OperatorsRegistryItem extends RegistryItem {
operator: string;
type: OperatorType;
}
export type StatementPositionResolver = (
currentToken: LinkedToken | null,
previousKeyword: LinkedToken | null,
previousNonWhiteSpace: LinkedToken | null,
previousIsSlash: Boolean
) => Boolean;
export interface StatementPositionResolversRegistryItem extends RegistryItem {
id: StatementPosition;
resolve: StatementPositionResolver;
}
export type SuggestionsResolver = <T extends PositionContext = PositionContext>(
positionContext: T
) => Promise<CustomSuggestion[]>;
export interface SQLMonarchLanguage extends monacoTypes.languages.IMonarchLanguage {
keywords?: string[];
builtinFunctions?: string[];
logicalOperators?: string[];
comparisonOperators?: string[];
}

View File

@ -1,11 +0,0 @@
import * as testData from '../mocks/testData';
import { testStatementPosition } from './statementPosition';
import { TestQueryModel } from './types';
export const SQLEditorTestUtils = {
testData,
testStatementPosition,
};
export { TestQueryModel };

View File

@ -1,63 +0,0 @@
import { Registry } from '@grafana/data';
import { monacoTypes } from '@grafana/ui';
import { getMonacoMock } from '../mocks/Monaco';
import { TextModel } from '../mocks/TextModel';
import { getStatementPosition } from '../standardSql/getStatementPosition';
import { StatementPositionResolversRegistryItem } from '../standardSql/types';
import { CustomStatementPlacement, StatementPosition } from '../types';
import { linkedTokenBuilder } from '../utils/linkedTokenBuilder';
import { StatementPositionResolverTestCase } from './types';
function assertPosition(
query: string,
position: monacoTypes.IPosition,
expected: StatementPosition | string,
monacoMock: any,
resolversRegistry: Registry<StatementPositionResolversRegistryItem>
) {
const testModel = TextModel(query);
const current = linkedTokenBuilder(monacoMock, testModel as monacoTypes.editor.ITextModel, position);
const statementPosition = getStatementPosition(current, resolversRegistry);
expect(statementPosition).toContain(expected);
}
export const testStatementPosition = (
expected: StatementPosition | string,
cases: StatementPositionResolverTestCase[],
resolvers: () => CustomStatementPlacement[]
) => {
describe(`${expected}`, () => {
let MonacoMock: any;
let statementPositionResolversRegistry: Registry<StatementPositionResolversRegistryItem>;
beforeEach(() => {
const mockQueries = new Map<string, Array<Array<Pick<monacoTypes.Token, 'language' | 'offset' | 'type'>>>>();
cases.forEach((c) => mockQueries.set(c.query.query, c.query.tokens));
MonacoMock = getMonacoMock(mockQueries);
statementPositionResolversRegistry = new Registry(() => {
return resolvers().map((r) => ({
id: r.id as StatementPosition,
name: r.name || r.id,
resolve: r.resolve,
}));
});
});
// using forEach here rather than test.each as been struggling to get the arguments intepolated in test name string
cases.forEach((c) => {
test(`${c.query.query}`, () => {
assertPosition(
c.query.query,
{ lineNumber: c.position.line, column: c.position.column },
expected,
MonacoMock,
statementPositionResolversRegistry
);
});
});
});
};

View File

@ -1,21 +0,0 @@
SELECT column1, FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10
SELECT column1, FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10; SELECT column2, FROM table2 WHERE column2 = "value2" GROUP BY column1 ORDER BY column2 DESC LIMIT 10;
SELECT count(column1), FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10;
SELECT count(column1), FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1 DESC LIMIT 10; SELECT count(column2), FROM table2 WHERE column2 = "value2" GROUP BY column1 ORDER BY column2 DESC LIMIT 10;
SELECT column1,
FROM table1
WHERE column1 = "value1"
GROUP BY column1 ORDER BY column1 DESC
LIMIT 10;
SELECT count(column1), column2 FROM table1 WHERE column1 = "value1" GROUP BY column1 ORDER BY column1, avg(column2) DESC LIMIT 10;
SELECT count(column1), column2
FROM table1
WHERE column1 = "value1"
GROUP BY column1 ORDER BY column1, avg(column2) DESC
LIMIT 10;

View File

@ -1,11 +0,0 @@
import { monacoTypes } from '@grafana/ui';
export interface TestQueryModel {
query: string;
tokens: Array<Array<Pick<monacoTypes.Token, 'language' | 'offset' | 'type'>>>;
}
export interface StatementPositionResolverTestCase {
query: TestQueryModel;
position: { line: number; column: number };
}

View File

@ -9,7 +9,7 @@ import {
TimeRange,
toOption as toOptionFromData,
} from '@grafana/data';
import { Monaco, monacoTypes } from '@grafana/ui';
import { CompletionItemKind, LanguageCompletionProvider } from '@grafana/experimental';
import { QueryWithDefaults } from './defaults';
import {
@ -17,8 +17,6 @@ import {
QueryEditorGroupByExpression,
QueryEditorPropertyExpression,
} from './expressions';
import { StatementPositionResolver, SuggestionsResolver } from './standardSql/types';
import { LinkedToken } from './utils/LinkedToken';
export interface SqlQueryForInterpolation {
dataset?: string;
@ -182,249 +180,17 @@ export interface MetaDefinition {
kind: CompletionItemKind;
}
/**
* Provides a context for suggestions resolver
* @alpha
*/
export interface PositionContext {
position: monacoTypes.IPosition;
kind: SuggestionKind[];
statementPosition: StatementPosition[];
currentToken: LinkedToken | null;
range: monacoTypes.IRange;
}
export type CustomSuggestion = Partial<monacoTypes.languages.CompletionItem> & { label: string };
export interface CustomSuggestionKind {
id: string;
suggestionsResolver: SuggestionsResolver;
applyTo?: Array<StatementPosition | string>;
overrideDefault?: boolean;
}
export interface CustomStatementPlacement {
id: string;
name?: string;
resolve: StatementPositionResolver;
overrideDefault?: boolean;
}
export type StatementPlacementProvider = () => CustomStatementPlacement[];
export type SuggestionKindProvider = () => CustomSuggestionKind[];
export interface ColumnDefinition {
name: string;
type?: string;
description?: string;
// Text used for automplete. If not provided name is used.
completion?: string;
}
export interface TableDefinition {
name: string;
// Text used for automplete. If not provided name is used.
completion?: string;
}
export interface SQLCompletionItemProvider
extends Omit<monacoTypes.languages.CompletionItemProvider, 'provideCompletionItems'> {
/**
* Allows dialect specific functions to be added to the completion list.
* @alpha
*/
supportedFunctions?: () => Array<{
id: string;
name: string;
description?: string;
}>;
/**
* Allows dialect specific operators to be added to the completion list.
* @alpha
*/
supportedOperators?: () => Array<{
id: string;
operator: string;
type: OperatorType;
description?: string;
}>;
supportedMacros?: () => Array<{
id: string;
text: string;
type: MacroType;
args: string[];
description?: string;
}>;
/**
* Allows custom suggestion kinds to be defined and correlate them with <Custom>StatementPosition.
* @alpha
*/
customSuggestionKinds?: SuggestionKindProvider;
/**
* Allows custom statement placement definition.
* @alpha
*/
customStatementPlacement?: StatementPlacementProvider;
/**
* Allows providing a custom function for resolving db tables.
* It's up to the consumer to decide whether the columns are resolved via API calls or preloaded in the query editor(i.e. full db schema is preloades loaded).
* @alpha
*/
tables?: {
resolve: () => Promise<TableDefinition[]>;
// Allows providing a custom function for calculating the table name from the query. If not specified a default implemnentation is used.
parseName?: (t: LinkedToken) => string;
};
/**
* Allows providing a custom function for resolving table.
* It's up to the consumer to decide whether the columns are resolved via API calls or preloaded in the query editor(i.e. full db schema is preloades loaded).
* @alpha
*/
columns?: {
resolve: (table: string) => Promise<ColumnDefinition[]>;
};
/**
* TODO: Not sure whether or not we need this. Would like to avoid this kind of flexibility.
* @alpha
*/
provideCompletionItems?: (
model: monacoTypes.editor.ITextModel,
position: monacoTypes.Position,
context: monacoTypes.languages.CompletionContext,
token: monacoTypes.CancellationToken,
positionContext: PositionContext // Decorates original provideCompletionItems function with our custom statement position context
) => monacoTypes.languages.CompletionList;
}
export type LanguageCompletionProvider = (m: Monaco) => SQLCompletionItemProvider;
export enum OperatorType {
Comparison,
Logical,
}
export enum MacroType {
Value,
Filter,
Group,
Column,
Table,
}
export enum TokenType {
Parenthesis = 'delimiter.parenthesis.sql',
Whitespace = 'white.sql',
Keyword = 'keyword.sql',
Delimiter = 'delimiter.sql',
Operator = 'operator.sql',
Identifier = 'identifier.sql',
IdentifierQuote = 'identifier.quote.sql',
Type = 'type.sql',
Function = 'predefined.sql',
Number = 'number.sql',
String = 'string.sql',
Variable = 'variable.sql',
}
export enum StatementPosition {
Unknown = 'unknown',
SelectKeyword = 'selectKeyword',
WithKeyword = 'withKeyword',
AfterSelectKeyword = 'afterSelectKeyword',
AfterSelectArguments = 'afterSelectArguments',
AfterSelectFuncFirstArgument = 'afterSelectFuncFirstArgument',
SelectAlias = 'selectAlias',
AfterFromKeyword = 'afterFromKeyword',
AfterTable = 'afterTable',
SchemaFuncFirstArgument = 'schemaFuncFirstArgument',
SchemaFuncExtraArgument = 'schemaFuncExtraArgument',
FromKeyword = 'fromKeyword',
AfterFrom = 'afterFrom',
WhereKeyword = 'whereKeyword',
WhereComparisonOperator = 'whereComparisonOperator',
WhereValue = 'whereValue',
AfterWhereFunctionArgument = 'afterWhereFunctionArgument',
AfterGroupByFunctionArgument = 'afterGroupByFunctionArgument',
AfterWhereValue = 'afterWhereValue',
AfterGroupByKeywords = 'afterGroupByKeywords',
AfterGroupBy = 'afterGroupBy',
AfterOrderByKeywords = 'afterOrderByKeywords',
AfterOrderByFunction = 'afterOrderByFunction',
AfterOrderByDirection = 'afterOrderByDirection',
AfterIsOperator = 'afterIsOperator',
AfterIsNotOperator = 'afterIsNotOperator',
}
export enum SuggestionKind {
Tables = 'tables',
Columns = 'columns',
SelectKeyword = 'selectKeyword',
WithKeyword = 'withKeyword',
FunctionsWithArguments = 'functionsWithArguments',
FromKeyword = 'fromKeyword',
WhereKeyword = 'whereKeyword',
GroupByKeywords = 'groupByKeywords',
OrderByKeywords = 'orderByKeywords',
FunctionsWithoutArguments = 'functionsWithoutArguments',
LimitKeyword = 'limitKeyword',
SortOrderDirectionKeyword = 'sortOrderDirectionKeyword',
ComparisonOperators = 'comparisonOperators',
LogicalOperators = 'logicalOperators',
SelectMacro = 'selectMacro',
TableMacro = 'tableMacro',
FilterMacro = 'filterMacro',
GroupMacro = 'groupMacro',
BoolValues = 'boolValues',
NullValue = 'nullValue',
NotKeyword = 'notKeyword',
TemplateVariables = 'templateVariables',
}
// TODO: export from grafana/ui
export enum CompletionItemPriority {
High = 'a',
MediumHigh = 'd',
Medium = 'g',
MediumLow = 'k',
Low = 'q',
}
export enum CompletionItemKind {
Method = 0,
Function = 1,
Constructor = 2,
Field = 3,
Variable = 4,
Class = 5,
Struct = 6,
Interface = 7,
Module = 8,
Property = 9,
Event = 10,
Operator = 11,
Unit = 12,
Value = 13,
Constant = 14,
Enum = 15,
EnumMember = 16,
Keyword = 17,
Text = 18,
Color = 19,
File = 20,
Reference = 21,
Customcolor = 22,
Folder = 23,
TypeParameter = 24,
User = 25,
Issue = 26,
Snippet = 27,
}
export enum CompletionItemInsertTextRule {
KeepWhitespace = 1,
InsertAsSnippet = 4,
}
export {
CompletionItemKind,
LanguageCompletionProvider,
LinkedToken,
ColumnDefinition,
CompletionItemPriority,
StatementPlacementProvider,
SuggestionKindProvider,
TableDefinition,
TokenType,
OperatorType,
StatementPosition,
PositionContext,
} from '@grafana/experimental';

View File

@ -1,176 +0,0 @@
import { getTemplateSrv } from '@grafana/runtime';
import { monacoTypes } from '@grafana/ui';
import { TokenType } from '../types';
export class LinkedToken {
constructor(
public type: string,
public value: string,
public range: monacoTypes.IRange,
public previous: LinkedToken | null,
public next: LinkedToken | null
) {}
isKeyword(): boolean {
return this.type === TokenType.Keyword;
}
isWhiteSpace(): boolean {
return this.type === TokenType.Whitespace;
}
isParenthesis(): boolean {
return this.type === TokenType.Parenthesis;
}
isIdentifier(): boolean {
return this.type === TokenType.Identifier;
}
isString(): boolean {
return this.type === TokenType.String;
}
isNumber(): boolean {
return this.type === TokenType.Number;
}
isDoubleQuotedString(): boolean {
return this.type === TokenType.Type;
}
isVariable(): boolean {
return this.type === TokenType.Variable;
}
isFunction(): boolean {
return this.type === TokenType.Function;
}
isOperator(): boolean {
return this.type === TokenType.Operator;
}
isTemplateVariable(): boolean {
const variables = getTemplateSrv()?.getVariables();
return variables.find((v) => '$' + v.name === this.value) !== undefined;
}
is(type: TokenType, value?: string | number | boolean): boolean {
const isType = this.type === type;
return value !== undefined ? isType && compareTokenWithValue(type, this, value) : isType;
}
getPreviousNonWhiteSpaceToken(): LinkedToken | null {
let curr = this.previous;
while (curr != null) {
if (!curr.isWhiteSpace()) {
return curr;
}
curr = curr.previous;
}
return null;
}
getPreviousOfType(type: TokenType, value?: string): LinkedToken | null {
let curr = this.previous;
while (curr != null) {
const isType = curr.type === type;
if (value !== undefined ? isType && compareTokenWithValue(type, curr, value) : isType) {
return curr;
}
curr = curr.previous;
}
return null;
}
getPreviousUntil(type: TokenType, ignoreTypes: TokenType[], value?: string): LinkedToken[] | null {
let tokens: LinkedToken[] = [];
let curr = this.previous;
while (curr != null) {
if (ignoreTypes.some((t) => t === curr?.type)) {
curr = curr.previous;
continue;
}
const isType = curr.type === type;
if (value !== undefined ? isType && compareTokenWithValue(type, curr, value) : isType) {
return tokens;
}
if (!curr.isWhiteSpace()) {
tokens.push(curr);
}
curr = curr.previous;
}
return tokens;
}
getNextUntil(type: TokenType, ignoreTypes: TokenType[], value?: string): LinkedToken[] | null {
let tokens: LinkedToken[] = [];
let curr = this.next;
while (curr != null) {
if (ignoreTypes.some((t) => t === curr?.type)) {
curr = curr.next;
continue;
}
const isType = curr.type === type;
if (value !== undefined ? isType && compareTokenWithValue(type, curr, value) : isType) {
return tokens;
}
if (!curr.isWhiteSpace()) {
tokens.push(curr);
}
curr = curr.next;
}
return tokens;
}
getPreviousKeyword(): LinkedToken | null {
let curr = this.previous;
while (curr != null) {
if (curr.isKeyword()) {
return curr;
}
curr = curr.previous;
}
return null;
}
getNextNonWhiteSpaceToken(): LinkedToken | null {
let curr = this.next;
while (curr != null) {
if (!curr.isWhiteSpace()) {
return curr;
}
curr = curr.next;
}
return null;
}
getNextOfType(type: TokenType, value?: string): LinkedToken | null {
let curr = this.next;
while (curr != null) {
const isType = curr.type === type;
if (value !== undefined ? isType && compareTokenWithValue(type, curr, value) : isType) {
return curr;
}
curr = curr.next;
}
return null;
}
}
function compareTokenWithValue(type: TokenType, token: LinkedToken, value: string | number | boolean) {
return type === TokenType.Keyword || type === TokenType.Operator
? token.value.toLowerCase() === value.toString().toLowerCase()
: token.value === value;
}

View File

@ -1,4 +0,0 @@
export const TRIGGER_SUGGEST = {
id: 'editor.action.triggerSuggest',
title: '',
};

View File

@ -1,12 +0,0 @@
import { attachDebugger, createLogger } from '@grafana/ui';
let sqlEditorLogger = { logger: () => {} };
let sqlEditorLog: (...t: any[]) => void = () => {};
if (attachDebugger) {
sqlEditorLogger = createLogger('SQLEditor');
sqlEditorLog = sqlEditorLogger.logger;
attachDebugger('sqleditor', undefined, sqlEditorLogger as any);
}
export { sqlEditorLog, sqlEditorLogger };

View File

@ -1,31 +0,0 @@
import { Registry } from '@grafana/data';
import { SuggestionKindRegistryItem } from '../standardSql/suggestionsKindRegistry';
import { StatementPosition, SuggestionKind } from '../types';
import { getSuggestionKinds } from './getSuggestionKind';
describe('getSuggestionKind', () => {
const registry = new Registry((): SuggestionKindRegistryItem[] => {
return [
{
id: StatementPosition.SelectKeyword,
name: StatementPosition.SelectKeyword,
kind: [SuggestionKind.SelectKeyword],
},
{
id: StatementPosition.AfterSelectArguments,
name: StatementPosition.AfterSelectArguments,
kind: [SuggestionKind.Columns],
},
];
});
it('should return select kind when given select keyword as position', () => {
const pos = [StatementPosition.SelectKeyword];
expect([SuggestionKind.SelectKeyword]).toEqual(getSuggestionKinds(pos, registry));
});
it('should return column kind when given AfterSelectArguments as position', () => {
const pos = [StatementPosition.AfterSelectArguments];
expect([SuggestionKind.Columns]).toEqual(getSuggestionKinds(pos, registry));
});
});

View File

@ -1,22 +0,0 @@
import { Registry } from '@grafana/data';
import { SuggestionKindRegistryItem } from '../standardSql/suggestionsKindRegistry';
import { StatementPosition, SuggestionKind } from '../types';
/**
* Given statement positions, returns list of suggestion kinds that apply to those positions.
*/
export function getSuggestionKinds(
statementPosition: StatementPosition[],
suggestionsKindRegistry: Registry<SuggestionKindRegistryItem>
): SuggestionKind[] {
let result: SuggestionKind[] = [];
for (let i = 0; i < statementPosition.length; i++) {
const exists = suggestionsKindRegistry.getIfExists(statementPosition[i]);
if (exists) {
result = result.concat(exists.kind);
}
}
return result;
}

View File

@ -1,72 +0,0 @@
import { monacoTypes } from '@grafana/ui';
import { getMonacoMock } from '../mocks/Monaco';
import { TextModel } from '../mocks/TextModel';
import { multiLineFullQuery, singleLineFullQuery } from '../mocks/testData';
import { DESC, LIMIT, SELECT } from '../standardSql/language';
import { TokenType } from '../types';
import { linkedTokenBuilder } from './linkedTokenBuilder';
describe('linkedTokenBuilder', () => {
describe('singleLineFullQuery', () => {
const testModel = TextModel(singleLineFullQuery.query);
const queriesMock = new Map();
queriesMock.set(singleLineFullQuery.query, singleLineFullQuery.tokens);
const MonacoMock = getMonacoMock(queriesMock);
it('should add correct references to next LinkedToken', () => {
const position: monacoTypes.IPosition = { lineNumber: 1, column: 0 };
const current = linkedTokenBuilder(MonacoMock, testModel as monacoTypes.editor.ITextModel, position);
expect(current?.is(TokenType.Keyword, SELECT)).toBeTruthy();
expect(current?.getNextNonWhiteSpaceToken()?.is(TokenType.Identifier, 'column1')).toBeTruthy();
});
it('should add correct references to previous LinkedToken', () => {
const position: monacoTypes.IPosition = { lineNumber: 1, column: singleLineFullQuery.query.length };
const current = linkedTokenBuilder(MonacoMock, testModel as monacoTypes.editor.ITextModel, position);
expect(current?.is(TokenType.Number, '10')).toBeTruthy();
expect(current?.getPreviousNonWhiteSpaceToken()?.is(TokenType.Keyword, 'LIMIT')).toBeTruthy();
expect(
current?.getPreviousNonWhiteSpaceToken()?.getPreviousNonWhiteSpaceToken()?.is(TokenType.Keyword, DESC)
).toBeTruthy();
});
});
describe('multiLineFullQuery', () => {
const testModel = TextModel(multiLineFullQuery.query);
const queriesMock = new Map();
queriesMock.set(multiLineFullQuery.query, multiLineFullQuery.tokens);
const MonacoMock = getMonacoMock(queriesMock);
it('should add LinkedToken with whitespace in case empty lines', () => {
const position: monacoTypes.IPosition = { lineNumber: 3, column: 0 };
const current = linkedTokenBuilder(MonacoMock, testModel as monacoTypes.editor.ITextModel, position);
expect(current).not.toBeNull();
expect(current?.isWhiteSpace()).toBeTruthy();
});
it('should add correct references to next LinkedToken', () => {
const position: monacoTypes.IPosition = { lineNumber: 1, column: 0 };
const current = linkedTokenBuilder(MonacoMock, testModel as monacoTypes.editor.ITextModel, position);
expect(current?.is(TokenType.Keyword, SELECT)).toBeTruthy();
expect(current?.getNextNonWhiteSpaceToken()?.is(TokenType.Identifier, 'column1')).toBeTruthy();
});
it('should add correct references to previous LinkedToken even when references spans over multiple lines', () => {
const position: monacoTypes.IPosition = { lineNumber: 6, column: 7 };
const current = linkedTokenBuilder(MonacoMock, testModel as monacoTypes.editor.ITextModel, position);
expect(current?.is(TokenType.Number, '10')).toBeTruthy();
expect(current?.getPreviousNonWhiteSpaceToken()?.is(TokenType.Keyword, LIMIT)).toBeTruthy();
expect(
current?.getPreviousNonWhiteSpaceToken()?.getPreviousNonWhiteSpaceToken()?.is(TokenType.Keyword, DESC)
).toBeTruthy();
});
});
});

View File

@ -1,56 +0,0 @@
import type { monacoTypes } from '@grafana/ui';
import { TokenType } from '../types';
import { LinkedToken } from './LinkedToken';
import { Monaco } from './types';
export function linkedTokenBuilder(
monaco: Monaco,
model: monacoTypes.editor.ITextModel,
position: monacoTypes.IPosition,
languageId = 'sql'
) {
let current: LinkedToken | null = null;
let previous: LinkedToken | null = null;
const tokensPerLine = monaco.editor.tokenize(model.getValue() ?? '', languageId);
for (let lineIndex = 0; lineIndex < tokensPerLine.length; lineIndex++) {
const tokens = tokensPerLine[lineIndex];
// In case position is first column in new line, add empty whitespace token so that links are not broken
if (!tokens.length && previous) {
const token: monacoTypes.Token = {
offset: 0,
type: TokenType.Whitespace,
language: languageId,
_tokenBrand: undefined,
};
tokens.push(token);
}
for (let columnIndex = 0; columnIndex < tokens.length; columnIndex++) {
const token = tokens[columnIndex];
let endColumn =
tokens.length > columnIndex + 1 ? tokens[columnIndex + 1].offset + 1 : model.getLineLength(lineIndex + 1) + 1;
const range: monacoTypes.IRange = {
startLineNumber: lineIndex + 1,
startColumn: token.offset === 0 ? 0 : token.offset + 1,
endLineNumber: lineIndex + 1,
endColumn,
};
const value = model.getValueInRange(range);
const sqlToken: LinkedToken = new LinkedToken(token.type, value, range, previous, null);
if (monaco.Range.containsPosition(range, position)) {
current = sqlToken;
}
if (previous) {
previous.next = sqlToken;
}
previous = sqlToken;
}
}
return current;
}

View File

@ -1,19 +0,0 @@
import { monacoTypes } from '@grafana/ui';
import { CompletionItemKind, CompletionItemPriority } from '../types';
export const toCompletionItem = (
value: string,
range: monacoTypes.IRange,
rest: Partial<monacoTypes.languages.CompletionItem> = {}
) => {
const item: monacoTypes.languages.CompletionItem = {
label: value,
insertText: value,
kind: CompletionItemKind.Field,
sortText: CompletionItemPriority.Medium,
range,
...rest,
};
return item;
};

View File

@ -1,57 +0,0 @@
import { FROM, SCHEMA, SELECT } from '../standardSql/language';
import { TokenType } from '../types';
import { LinkedToken } from './LinkedToken';
export const getSelectToken = (currentToken: LinkedToken | null) =>
currentToken?.getPreviousOfType(TokenType.Keyword, SELECT) ?? null;
export const getSelectStatisticToken = (currentToken: LinkedToken | null) => {
const assumedStatisticToken = getSelectToken(currentToken)?.getNextNonWhiteSpaceToken();
return assumedStatisticToken?.isVariable() || assumedStatisticToken?.isFunction() ? assumedStatisticToken : null;
};
export const getMetricNameToken = (currentToken: LinkedToken | null) => {
// statistic function is followed by `(` and then an argument
const assumedMetricNameToken = getSelectStatisticToken(currentToken)?.next?.next;
return assumedMetricNameToken?.isVariable() || assumedMetricNameToken?.isIdentifier() ? assumedMetricNameToken : null;
};
export const getFromKeywordToken = (currentToken: LinkedToken | null) => {
const selectToken = getSelectToken(currentToken);
return selectToken?.getNextOfType(TokenType.Keyword, FROM);
};
export const getNamespaceToken = (currentToken: LinkedToken | null) => {
const fromToken = getFromKeywordToken(currentToken);
const nextNonWhiteSpace = fromToken?.getNextNonWhiteSpaceToken();
if (
nextNonWhiteSpace?.isDoubleQuotedString() ||
(nextNonWhiteSpace?.isVariable() && nextNonWhiteSpace?.value.toUpperCase() !== SCHEMA)
) {
// schema is not used
return nextNonWhiteSpace;
} else if (nextNonWhiteSpace?.isKeyword() && nextNonWhiteSpace.next?.is(TokenType.Parenthesis, '(')) {
// schema is specified
const assumedNamespaceToken = nextNonWhiteSpace.next?.next;
if (assumedNamespaceToken?.isDoubleQuotedString() || assumedNamespaceToken?.isVariable()) {
return assumedNamespaceToken;
}
}
return null;
};
export const getTableToken = (currentToken: LinkedToken | null) => {
const fromToken = getFromKeywordToken(currentToken);
const nextNonWhiteSpace = fromToken?.getNextNonWhiteSpaceToken();
if (nextNonWhiteSpace?.isVariable()) {
// TODO: resolve column from variable?
return null;
} else if (nextNonWhiteSpace?.isKeyword() && nextNonWhiteSpace.next?.is(TokenType.Parenthesis, '(')) {
return null;
} else {
return nextNonWhiteSpace;
}
return null;
};

View File

@ -1,14 +0,0 @@
import { monacoTypes } from '@grafana/ui';
export interface Editor {
tokenize: (value: string, languageId: string) => monacoTypes.Token[][];
}
export interface Range {
containsPosition: (range: monacoTypes.IRange, position: monacoTypes.IPosition) => boolean;
}
export interface Monaco {
editor: Editor;
Range: Range;
}

View File

@ -1,4 +1,3 @@
import { LinkedToken } from 'app/features/plugins/sql';
import { AGGREGATE_FNS, OPERATORS } from 'app/features/plugins/sql/constants';
import {
ColumnDefinition,
@ -6,6 +5,7 @@ import {
CompletionItemPriority,
DB,
LanguageCompletionProvider,
LinkedToken,
SQLQuery,
StatementPlacementProvider,
SuggestionKindProvider,

View File

@ -1,4 +1,3 @@
import { LinkedToken } from 'app/features/plugins/sql';
import { AGGREGATE_FNS, OPERATORS } from 'app/features/plugins/sql/constants';
import {
Aggregate,
@ -7,6 +6,7 @@ import {
CompletionItemPriority,
DB,
LanguageCompletionProvider,
LinkedToken,
MetaDefinition,
PositionContext,
SQLQuery,

View File

@ -3799,6 +3799,20 @@ __metadata:
languageName: node
linkType: hard
"@grafana/experimental@npm:^0.0.2-canary.36":
version: 0.0.2-canary.36
resolution: "@grafana/experimental@npm:0.0.2-canary.36"
dependencies:
"@types/uuid": ^8.3.3
uuid: ^8.3.2
peerDependencies:
"@emotion/css": 11.1.3
react: 17.0.1
react-select: 5.2.1
checksum: 2aea6c2cbed323f7212c82df1b529897296c5c2938345dc0e7866cc4576c763f731f4f89803e6344ec7a3eb3940e0e8bdc4a01739b67833a5513913434130aba
languageName: node
linkType: hard
"@grafana/google-sdk@npm:0.0.3":
version: 0.0.3
resolution: "@grafana/google-sdk@npm:0.0.3"
@ -10983,7 +10997,7 @@ __metadata:
languageName: node
linkType: hard
"@types/uuid@npm:8.3.4":
"@types/uuid@npm:8.3.4, @types/uuid@npm:^8.3.3":
version: 8.3.4
resolution: "@types/uuid@npm:8.3.4"
checksum: 6f11f3ff70f30210edaa8071422d405e9c1d4e53abbe50fdce365150d3c698fe7bbff65c1e71ae080cbfb8fded860dbb5e174da96fdbbdfcaa3fb3daa474d20f
@ -20233,6 +20247,7 @@ __metadata:
"@grafana/e2e": "workspace:*"
"@grafana/e2e-selectors": "workspace:*"
"@grafana/eslint-config": 4.0.0
"@grafana/experimental": ^0.0.2-canary.36
"@grafana/google-sdk": 0.0.3
"@grafana/lezer-logql": 0.0.14
"@grafana/runtime": "workspace:*"