mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Core code editor/builder components (#52421)
* migrate experimental to core grafana - update refs Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
parent
feb9960f96
commit
de956fc3d8
@ -5362,15 +5362,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
|
||||||
],
|
|
||||||
"public/app/features/playlist/PlaylistNewPage.test.tsx:5381": [
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/playlist/PlaylistSrv.test.ts:5381": [
|
"public/app/features/playlist/PlaylistSrv.test.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
@ -5530,11 +5522,39 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
[0, 0, 0, "Do not use any type assertions.", "7"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "8"]
|
[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": [
|
"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.", "0"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
[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": [
|
"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.", "0"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||||
|
@ -254,7 +254,6 @@
|
|||||||
"@grafana/aws-sdk": "0.0.37",
|
"@grafana/aws-sdk": "0.0.37",
|
||||||
"@grafana/data": "workspace:*",
|
"@grafana/data": "workspace:*",
|
||||||
"@grafana/e2e-selectors": "workspace:*",
|
"@grafana/e2e-selectors": "workspace:*",
|
||||||
"@grafana/experimental": "^0.0.2-canary.32",
|
|
||||||
"@grafana/google-sdk": "0.0.3",
|
"@grafana/google-sdk": "0.0.3",
|
||||||
"@grafana/lezer-logql": "^0.0.14",
|
"@grafana/lezer-logql": "^0.0.14",
|
||||||
"@grafana/runtime": "workspace:*",
|
"@grafana/runtime": "workspace:*",
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { useTheme2, stylesFactory } from '../../themes';
|
||||||
|
import { Button, ButtonProps } from '../Button';
|
||||||
|
|
||||||
|
interface AccessoryButtonProps extends ButtonProps {}
|
||||||
|
|
||||||
|
export const AccessoryButton: React.FC<AccessoryButtonProps> = ({ className, ...props }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getButtonStyles(theme);
|
||||||
|
|
||||||
|
return <Button {...props} className={cx(className, styles.button)} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getButtonStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
button: css({
|
||||||
|
paddingLeft: theme.spacing(3 / 2),
|
||||||
|
paddingRight: theme.spacing(3 / 2),
|
||||||
|
}),
|
||||||
|
}));
|
@ -0,0 +1,79 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { stylesFactory, useTheme2 } from '../../themes';
|
||||||
|
import { ReactUtils } from '../../utils';
|
||||||
|
import { Field } from '../Forms/Field';
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { PopoverContent, Tooltip } from '../Tooltip';
|
||||||
|
|
||||||
|
import { Space } from './Space';
|
||||||
|
|
||||||
|
interface EditorFieldProps extends ComponentProps<typeof Field> {
|
||||||
|
label: string;
|
||||||
|
children: React.ReactElement;
|
||||||
|
width?: number | string;
|
||||||
|
optional?: boolean;
|
||||||
|
tooltip?: PopoverContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorField: React.FC<EditorFieldProps> = (props) => {
|
||||||
|
const { label, optional, tooltip, children, width, ...fieldProps } = props;
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme, width);
|
||||||
|
|
||||||
|
// Null check for backward compatibility
|
||||||
|
const childInputId = fieldProps?.htmlFor || ReactUtils?.getChildId(children);
|
||||||
|
|
||||||
|
const labelEl = (
|
||||||
|
<>
|
||||||
|
<label className={styles.label} htmlFor={childInputId}>
|
||||||
|
{label}
|
||||||
|
{optional && <span className={styles.optional}> - optional</span>}
|
||||||
|
{tooltip && (
|
||||||
|
<Tooltip placement="top" content={tooltip} theme="info">
|
||||||
|
<Icon name="info-circle" size="sm" className={styles.icon} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<Space v={0.5} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Field className={styles.field} label={labelEl} {...fieldProps}>
|
||||||
|
{children}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme2, width?: number | string) => {
|
||||||
|
return {
|
||||||
|
root: css({
|
||||||
|
minWidth: theme.spacing(width ?? 0),
|
||||||
|
}),
|
||||||
|
label: css({
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
}),
|
||||||
|
optional: css({
|
||||||
|
fontStyle: 'italic',
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
}),
|
||||||
|
field: css({
|
||||||
|
marginBottom: 0, // GrafanaUI/Field has a bottom margin which we must remove
|
||||||
|
}),
|
||||||
|
icon: css({
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
':hover': {
|
||||||
|
color: theme.colors.text.primary,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
|
interface EditorFieldGroupProps {}
|
||||||
|
|
||||||
|
export const EditorFieldGroup: React.FC<EditorFieldGroupProps> = ({ children }) => {
|
||||||
|
return <Stack gap={1}>{children}</Stack>;
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { stylesFactory, useTheme2 } from '../../themes';
|
||||||
|
|
||||||
|
interface EditorHeaderProps {}
|
||||||
|
|
||||||
|
export const EditorHeader: React.FC<EditorHeaderProps> = ({ children }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(3),
|
||||||
|
minHeight: theme.spacing(4),
|
||||||
|
}),
|
||||||
|
}));
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Button } from '../Button';
|
||||||
|
|
||||||
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
|
interface EditorListProps<T> {
|
||||||
|
items: Array<Partial<T>>;
|
||||||
|
renderItem: (
|
||||||
|
item: Partial<T>,
|
||||||
|
onChangeItem: (item: Partial<T>) => void,
|
||||||
|
onDeleteItem: () => void
|
||||||
|
) => React.ReactElement;
|
||||||
|
onChange: (items: Array<Partial<T>>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditorList<T>({ items, renderItem, onChange }: EditorListProps<T>) {
|
||||||
|
const onAddItem = () => {
|
||||||
|
const newItems = [...items, {}];
|
||||||
|
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeItem = (itemIndex: number, newItem: Partial<T>) => {
|
||||||
|
const newItems = [...items];
|
||||||
|
newItems[itemIndex] = newItem;
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteItem = (itemIndex: number) => {
|
||||||
|
const newItems = [...items];
|
||||||
|
newItems.splice(itemIndex, 1);
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
{renderItem(
|
||||||
|
item,
|
||||||
|
(newItem) => onChangeItem(index, newItem),
|
||||||
|
() => onDeleteItem(index)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button onClick={onAddItem} variant="secondary" size="md" icon="plus" aria-label="Add" type="button" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
30
packages/grafana-ui/src/components/QueryEditor/EditorRow.tsx
Normal file
30
packages/grafana-ui/src/components/QueryEditor/EditorRow.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { useStyles2 } from '../../themes';
|
||||||
|
|
||||||
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
|
interface EditorRowProps {}
|
||||||
|
|
||||||
|
export const EditorRow: React.FC<EditorRowProps> = ({ children }) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Stack gap={2}>{children}</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
root: css({
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
backgroundColor: theme.colors.background.secondary,
|
||||||
|
borderRadius: theme.shape.borderRadius(1),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
|
interface EditorRowsProps {}
|
||||||
|
|
||||||
|
export const EditorRows: React.FC<EditorRowsProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Stack gap={0.5} direction="column">
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
import { Switch } from '../Switch/Switch';
|
||||||
|
|
||||||
|
// Wrapper component around <Switch /> that properly aligns it in <EditorField />
|
||||||
|
export const EditorSwitch: React.FC<ComponentProps<typeof Switch>> = (props) => {
|
||||||
|
const styles = getStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.switch}>
|
||||||
|
<Switch {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = () => {
|
||||||
|
return {
|
||||||
|
switch: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: 30,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
10
packages/grafana-ui/src/components/QueryEditor/FlexItem.tsx
Normal file
10
packages/grafana-ui/src/components/QueryEditor/FlexItem.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface FlexItemProps {
|
||||||
|
grow?: number;
|
||||||
|
shrink?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FlexItem: React.FC<FlexItemProps> = ({ grow, shrink }) => {
|
||||||
|
return <div style={{ display: 'block', flexGrow: grow, flexShrink: shrink }} />;
|
||||||
|
};
|
@ -0,0 +1,89 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { GroupBase } from 'react-select';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { stylesFactory, useTheme2 } from '../../themes';
|
||||||
|
import { Select } from '../Select/Select';
|
||||||
|
import { SelectContainerProps, SelectContainer as BaseSelectContainer } from '../Select/SelectContainer';
|
||||||
|
import { SelectCommonProps } from '../Select/types';
|
||||||
|
|
||||||
|
interface InlineSelectProps<T> extends SelectCommonProps<T> {
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InlineSelect<T>({ label: labelProp, ...props }: InlineSelectProps<T>) {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const [id] = useState(() => Math.random().toString(16).slice(2));
|
||||||
|
const styles = getSelectStyles(theme);
|
||||||
|
const components = {
|
||||||
|
SelectContainer,
|
||||||
|
ValueContainer,
|
||||||
|
SingleValue: ValueContainer,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
{labelProp && (
|
||||||
|
<label className={styles.label} htmlFor={id}>
|
||||||
|
{labelProp}
|
||||||
|
{':'}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Select openMenuOnFocus inputId={id} {...props} components={components} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
|
||||||
|
props: SelectContainerProps<Option, isMulti, Group>
|
||||||
|
) => {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getSelectStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseSelectContainer {...props} className={cx(props.className, styles.container)}>
|
||||||
|
{children}
|
||||||
|
</BaseSelectContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
|
||||||
|
props: SelectContainerProps<Option, isMulti, Group>
|
||||||
|
) => {
|
||||||
|
const { className, children } = props;
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getSelectStyles(theme);
|
||||||
|
|
||||||
|
return <div className={cx(className, styles.valueContainer)}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
fontSize: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
}),
|
||||||
|
|
||||||
|
label: css({
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}),
|
||||||
|
|
||||||
|
container: css({
|
||||||
|
background: 'none',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
}),
|
||||||
|
|
||||||
|
valueContainer: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 'initial',
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
fontSize: 12,
|
||||||
|
}),
|
||||||
|
}));
|
@ -0,0 +1,54 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { stylesFactory, useTheme2 } from '../../themes';
|
||||||
|
|
||||||
|
interface InputGroupProps {}
|
||||||
|
|
||||||
|
export const InputGroup: React.FC<InputGroupProps> = ({ children }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = useStyles(theme);
|
||||||
|
|
||||||
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
|
||||||
|
// Style the direct children of the component
|
||||||
|
'> *': {
|
||||||
|
'&:not(:first-child)': {
|
||||||
|
// Negative margin hides the double-border on adjacent selects
|
||||||
|
marginLeft: -1,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:first-child': {
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:last-child': {
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:not(:first-child):not(:last-child)': {
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
40
packages/grafana-ui/src/components/QueryEditor/Space.tsx
Normal file
40
packages/grafana-ui/src/components/QueryEditor/Space.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { stylesFactory, useTheme2 } from '../../themes';
|
||||||
|
|
||||||
|
export interface SpaceProps {
|
||||||
|
v?: number;
|
||||||
|
h?: number;
|
||||||
|
layout?: 'block' | 'inline';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Space = (props: SpaceProps) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme, props);
|
||||||
|
|
||||||
|
return <span className={cx(styles.wrapper)} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
Space.defaultProps = {
|
||||||
|
v: 0,
|
||||||
|
h: 0,
|
||||||
|
layout: 'block',
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme2, props: SpaceProps) => ({
|
||||||
|
wrapper: css([
|
||||||
|
{
|
||||||
|
paddingRight: theme.spacing(props.h ?? 0),
|
||||||
|
paddingBottom: theme.spacing(props.v ?? 0),
|
||||||
|
},
|
||||||
|
props.layout === 'inline' && {
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
props.layout === 'block' && {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}));
|
30
packages/grafana-ui/src/components/QueryEditor/Stack.tsx
Normal file
30
packages/grafana-ui/src/components/QueryEditor/Stack.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
import { stylesFactory, useTheme2 } from '../../themes';
|
||||||
|
|
||||||
|
interface StackProps {
|
||||||
|
direction?: CSSProperties['flexDirection'];
|
||||||
|
alignItems?: CSSProperties['alignItems'];
|
||||||
|
wrap?: boolean;
|
||||||
|
gap?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Stack: React.FC<StackProps> = ({ children, ...props }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = useStyles(theme, props);
|
||||||
|
|
||||||
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = stylesFactory((theme: GrafanaTheme2, props: StackProps) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: props.direction ?? 'row',
|
||||||
|
flexWrap: props.wrap ?? true ? 'wrap' : undefined,
|
||||||
|
alignItems: props.alignItems,
|
||||||
|
gap: theme.spacing(props.gap ?? 2),
|
||||||
|
}),
|
||||||
|
}));
|
13
packages/grafana-ui/src/components/QueryEditor/index.ts
Normal file
13
packages/grafana-ui/src/components/QueryEditor/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export { AccessoryButton } from './AccessoryButton';
|
||||||
|
export { EditorFieldGroup } from './EditorFieldGroup';
|
||||||
|
export { EditorHeader } from './EditorHeader';
|
||||||
|
export { EditorField } from './EditorField';
|
||||||
|
export { EditorRow } from './EditorRow';
|
||||||
|
export { EditorList } from './EditorList';
|
||||||
|
export { EditorRows } from './EditorRows';
|
||||||
|
export { EditorSwitch } from './EditorSwitch';
|
||||||
|
export { FlexItem } from './FlexItem';
|
||||||
|
export { Stack } from './Stack';
|
||||||
|
export { InlineSelect } from './InlineSelect';
|
||||||
|
export { InputGroup } from './InputGroup';
|
||||||
|
export { Space } from './Space';
|
@ -265,3 +265,4 @@ export * from './PanelChrome/types';
|
|||||||
export { EmotionPerfTest } from './ThemeDemos/EmotionPerfTest';
|
export { EmotionPerfTest } from './ThemeDemos/EmotionPerfTest';
|
||||||
export { Label as BrowserLabel } from './BrowserLabel/Label';
|
export { Label as BrowserLabel } from './BrowserLabel/Label';
|
||||||
export { PanelContainer } from './PanelContainer/PanelContainer';
|
export { PanelContainer } from './PanelContainer/PanelContainer';
|
||||||
|
export * from './QueryEditor';
|
||||||
|
@ -5,8 +5,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Card, FilterInput, Icon, Pagination, Select, Stack, TagList, useStyles2 } from '@grafana/ui';
|
||||||
import { Card, FilterInput, Icon, Pagination, Select, TagList, useStyles2 } from '@grafana/ui';
|
|
||||||
import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants';
|
import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants';
|
||||||
import { getQueryParamValue } from 'app/core/utils/query';
|
import { getQueryParamValue } from 'app/core/utils/query';
|
||||||
import { FolderState } from 'app/types';
|
import { FolderState } from 'app/types';
|
||||||
|
@ -2,8 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import React, { FormEvent } from 'react';
|
import React, { FormEvent } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Label, Tooltip, Input, Icon, useStyles2, Stack } from '@grafana/ui';
|
||||||
import { Label, Tooltip, Input, Icon, useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -4,8 +4,7 @@ import React, { useCallback } from 'react';
|
|||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { useStyles2, Field, Input, InputControl, Label, Tooltip, Icon, Stack } from '@grafana/ui';
|
||||||
import { useStyles2, Field, Input, InputControl, Label, Tooltip, Icon } from '@grafana/ui';
|
|
||||||
import { FolderPickerFilter } from 'app/core/components/Select/FolderPicker';
|
import { FolderPickerFilter } from 'app/core/components/Select/FolderPicker';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { DashboardSearchHit } from 'app/features/search/types';
|
import { DashboardSearchHit } from 'app/features/search/types';
|
||||||
|
@ -3,8 +3,7 @@ import { isEmpty } from 'lodash';
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data/src';
|
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Stack, useStyles2 } from '@grafana/ui';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler';
|
import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler';
|
||||||
import { RuleFormType } from '../../../types/rule-form';
|
import { RuleFormType } from '../../../types/rule-form';
|
||||||
|
@ -3,9 +3,8 @@ import { debounce } from 'lodash';
|
|||||||
import React, { FormEvent, useState } from 'react';
|
import React, { FormEvent, useState } from 'react';
|
||||||
|
|
||||||
import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data';
|
import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
|
||||||
import { DataSourcePicker } from '@grafana/runtime';
|
import { DataSourcePicker } from '@grafana/runtime';
|
||||||
import { Button, Field, Icon, Input, Label, RadioButtonGroup, Tooltip, useStyles } from '@grafana/ui';
|
import { Button, Field, Icon, Input, Label, RadioButtonGroup, Stack, Tooltip, useStyles } from '@grafana/ui';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
|
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
|
@ -3,8 +3,7 @@ import { debounce, uniqueId } from 'lodash';
|
|||||||
import React, { FormEvent, useState } from 'react';
|
import React, { FormEvent, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Label, Icon, Input, Tooltip, RadioButtonGroup, useStyles2, Button, Field, Stack } from '@grafana/ui';
|
||||||
import { Label, Icon, Input, Tooltip, RadioButtonGroup, useStyles2, Button, Field } from '@grafana/ui';
|
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { SilenceState } from 'app/plugins/datasource/alertmanager/types';
|
import { SilenceState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
|
@ -3,8 +3,7 @@ import React, { FC, useMemo } from 'react';
|
|||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { GrafanaTheme2, dateMath } from '@grafana/data';
|
import { GrafanaTheme2, dateMath } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Icon, useStyles2, Link, Button, Stack } from '@grafana/ui';
|
||||||
import { Icon, useStyles2, Link, Button } from '@grafana/ui';
|
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Button, Checkbox, Form, Stack, TextArea } from '@grafana/ui';
|
||||||
import { Button, Checkbox, Form, TextArea } from '@grafana/ui';
|
|
||||||
import { DashboardModel } from 'app/features/dashboard/state';
|
import { DashboardModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
import { SaveDashboardData, SaveDashboardOptions } from '../types';
|
import { SaveDashboardData, SaveDashboardOptions } from '../types';
|
||||||
|
@ -3,8 +3,7 @@ import { saveAs } from 'file-saver';
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Button, ClipboardButton, HorizontalGroup, Stack, stylesFactory, TextArea, useTheme } from '@grafana/ui';
|
||||||
import { Button, ClipboardButton, HorizontalGroup, stylesFactory, TextArea, useTheme } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SaveDashboardFormProps } from '../types';
|
import { SaveDashboardFormProps } from '../types';
|
||||||
|
|
||||||
|
@ -3,8 +3,7 @@ import React, { ChangeEvent, FC } from 'react';
|
|||||||
import { useToggle } from 'react-use';
|
import { useToggle } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Button, Icon, InlineField, Stack, TextArea, useStyles2 } from '@grafana/ui';
|
||||||
import { Button, Icon, InlineField, TextArea, useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { ExpressionQuery } from '../types';
|
import { ExpressionQuery } from '../types';
|
||||||
|
|
||||||
|
@ -4,9 +4,8 @@ import { Subscription } from 'rxjs';
|
|||||||
|
|
||||||
import { DataFrame } from '@grafana/data';
|
import { DataFrame } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { Stack } from '@grafana/experimental';
|
|
||||||
import { config, RefreshEvent } from '@grafana/runtime';
|
import { config, RefreshEvent } from '@grafana/runtime';
|
||||||
import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
|
import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder, Stack } from '@grafana/ui';
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
||||||
import { PanelModel } from 'app/features/dashboard/state';
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { locationUtil } from '@grafana/data';
|
import { locationUtil } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { Button, LinkButton, Input, Switch, RadioButtonGroup, Form, Field, InputControl, FieldSet } from '@grafana/ui';
|
import {
|
||||||
|
Button,
|
||||||
|
LinkButton,
|
||||||
|
Input,
|
||||||
|
Switch,
|
||||||
|
RadioButtonGroup,
|
||||||
|
Form,
|
||||||
|
Field,
|
||||||
|
InputControl,
|
||||||
|
FieldSet,
|
||||||
|
Stack,
|
||||||
|
} from '@grafana/ui';
|
||||||
import { getConfig } from 'app/core/config';
|
import { getConfig } from 'app/core/config';
|
||||||
import { OrgRole, useDispatch } from 'app/types';
|
import { OrgRole, useDispatch } from 'app/types';
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { QueryEditorProps } from '@grafana/data';
|
import { QueryEditorProps } from '@grafana/data';
|
||||||
import { EditorMode, Space } from '@grafana/experimental';
|
import { Space } from '@grafana/ui';
|
||||||
|
|
||||||
import { SqlDatasource } from '../datasource/SqlDatasource';
|
import { SqlDatasource } from '../datasource/SqlDatasource';
|
||||||
import { applyQueryDefaults } from '../defaults';
|
import { applyQueryDefaults } from '../defaults';
|
||||||
import { SQLQuery, QueryRowFilter, SQLOptions } from '../types';
|
import { SQLQuery, QueryRowFilter, SQLOptions, EditorMode } from '../types';
|
||||||
import { haveColumns } from '../utils/sql.utils';
|
import { haveColumns } from '../utils/sql.utils';
|
||||||
|
|
||||||
import { QueryHeader } from './QueryHeader';
|
import { QueryHeader } from './QueryHeader';
|
||||||
|
@ -2,11 +2,23 @@ import React, { useCallback, useState } from 'react';
|
|||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorHeader, EditorMode, EditorRow, FlexItem, InlineSelect, Space } from '@grafana/experimental';
|
import {
|
||||||
import { Button, InlineField, InlineSwitch, RadioButtonGroup, Select, Tooltip } from '@grafana/ui';
|
Button,
|
||||||
|
EditorField,
|
||||||
|
EditorHeader,
|
||||||
|
EditorRow,
|
||||||
|
FlexItem,
|
||||||
|
InlineField,
|
||||||
|
InlineSelect,
|
||||||
|
InlineSwitch,
|
||||||
|
RadioButtonGroup,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Tooltip,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { QueryWithDefaults } from '../defaults';
|
import { QueryWithDefaults } from '../defaults';
|
||||||
import { SQLQuery, QueryFormat, QueryRowFilter, QUERY_FORMAT_OPTIONS, DB } from '../types';
|
import { SQLQuery, QueryFormat, QueryRowFilter, QUERY_FORMAT_OPTIONS, DB, EditorMode } from '../types';
|
||||||
import { defaultToRawSql } from '../utils/sql.utils';
|
import { defaultToRawSql } from '../utils/sql.utils';
|
||||||
|
|
||||||
import { ConfirmModal } from './ConfirmModal';
|
import { ConfirmModal } from './ConfirmModal';
|
||||||
|
2
public/app/features/plugins/sql/components/index.ts
Normal file
2
public/app/features/plugins/sql/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './query-editor-raw';
|
||||||
|
export * from './visual-query-builder';
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { LanguageCompletionProvider, SQLEditor } from '@grafana/experimental';
|
import { LanguageCompletionProvider, SQLQuery } from '../../types';
|
||||||
|
|
||||||
import { SQLQuery } from '../../types';
|
|
||||||
import { formatSQL } from '../../utils/formatSQL';
|
import { formatSQL } from '../../utils/formatSQL';
|
||||||
|
|
||||||
|
import { SQLEditor } from './SQLEditor';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
query: SQLQuery;
|
query: SQLQuery;
|
||||||
onChange: (value: SQLQuery, processQuery: boolean) => void;
|
onChange: (value: SQLQuery, processQuery: boolean) => void;
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
## SQLEditor
|
||||||
|
|
||||||
|
### Core concepts
|
||||||
|
|
||||||
|
- `SuggestionKind` - a descriptive string representing a type of a suggestion, i.e. `SelectKeyword`, `Tables`, `LogicalOperators` etc.
|
||||||
|
- `LinkedToken` - linked list element representing each individual token with a query. Allows traversing the query back and forth. Used by `StatementPositionResolver`(see below)
|
||||||
|
- `StatementPosition` - a desctiptive string representing cursor/token position within the query. Each statement position is defined together with `StatementPositionResolver` that, given some position context, returns a boolean value indicating whether or not we are in a given `StatementPosition` position.
|
||||||
|
```ts
|
||||||
|
export type StatementPositionResolver = (
|
||||||
|
currentToken: LinkedToken | null,
|
||||||
|
previousKeyword: LinkedToken | null,
|
||||||
|
previousNonWhiteSpace: LinkedToken | null,
|
||||||
|
previousIsSlash: Boolean // To be removed as it's CloudWatch specific
|
||||||
|
) => Boolean;
|
||||||
|
```
|
||||||
|
- `SuggestionKind` and `StatementPosition` are glued together via suggestions kind registry (language specific!). This registry contains items of `SuggestionKindRegistyItem` type of the following interface:
|
||||||
|
```ts
|
||||||
|
export interface SuggestionKindRegistyItem extends RegistryItem {
|
||||||
|
id: StatementPosition;
|
||||||
|
kind: SuggestionKind[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This item defines what kinds of suggestions should be provided in a given statement position
|
||||||
|
- Registries. There are couple of different registries used that drive the autocomplete mechanism.
|
||||||
|
- **Language specific**: functions registry, operators registry, suggestion kinds registries and statement position resolvers registires. Those registires contain SQL defaults as well as allow extension per language type.
|
||||||
|
- **Instance specific**: Registry of `SuggestionsRegistyItem` items that glue particular `SuggestionKind` with an async function that provides completion items for it.
|
||||||
|
```ts
|
||||||
|
export interface SuggestionsRegistyItem extends RegistryItem {
|
||||||
|
id: SuggestionKind;
|
||||||
|
suggestions: (position: PositionContext, m: typeof monacoTypes) => Promise<CustomSuggestion[]>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Think about instance-specific registry as having i.e. mixed data source with multiple query editors for the same type of data source and you wish to provide only table suggestions that are valid for particular query row.
|
||||||
|
|
||||||
|
### SQLEditor component
|
||||||
|
|
||||||
|
Goals
|
||||||
|
|
||||||
|
- [ ] Allow providing suggestions for standard-ish SQL syntax (THIS PR)
|
||||||
|
- [ ] Allow providing custom SQL dialects and suggestions for them (TODO - CloudWatch implementation sets a good base for how to provide custom dialect definition)
|
||||||
|
|
||||||
|
`SQLEditor` component builds on top of `CodeEditor` component, but we may want to base it on `ReactMonacoEditor` component instead to be less prone to `CodeEditor` API changes and have full controll over the Monaco API. For now the `CodeEditor` is good enough for a simplification.
|
||||||
|
|
||||||
|
`SQLEditor` API:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface SQLEditorProps {
|
||||||
|
query: string;
|
||||||
|
onChange: (q: string) => void;
|
||||||
|
language?: LanguageDefinition;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The important part is the `LanguageDefinition` interface which provides way to customize the completion both on a language and instance level:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface LanguageDefinition extends monacoTypes.languages.ILanguageExtensionPoint {
|
||||||
|
// TODO: Will allow providing a custom language definition.
|
||||||
|
loadLanguage?: (module: any) => Promise<void>;
|
||||||
|
// Provides API for customizing the autocomplete
|
||||||
|
completionProvider?: (m: Monaco) => SQLCompletionItemProvider;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `completionProvider` function is the core of the autocomplete customization. `SQLEditor` comes with standard SQL completion items, but this function allows:
|
||||||
|
|
||||||
|
- providing dynamic suggestions: tables, columns
|
||||||
|
- providing custom `StatementPositionResolvers` that are specific for a given dialect or not implemented yet for standard SQL
|
||||||
|
- providing custom `SuggestionKind` and resolvers for this kind of suggestions.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
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;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows dialect specific operators to be added to the completion list.
|
||||||
|
* @alpha
|
||||||
|
*/
|
||||||
|
supportedOperators?: () => Array<{
|
||||||
|
id: string;
|
||||||
|
operator: string;
|
||||||
|
type: OperatorType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows adding macros that are available in the dialect datasource.
|
||||||
|
* @alpha
|
||||||
|
*/
|
||||||
|
supportedMacros?: () => Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: MacroType;
|
||||||
|
args: Array<string>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows custom suggestion kinds to be defined and correlate them with <Custom>StatementPosition.
|
||||||
|
* @alpha
|
||||||
|
*/
|
||||||
|
customSuggestionKinds?: () => CustomSuggestionKind[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows custom statement placement definition.
|
||||||
|
* @alpha
|
||||||
|
*/
|
||||||
|
customStatementPlacement?: () => CustomStatementPlacement[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. I.e. BigQuery requires the table name to be fully qualified name: <project>.<dataset>.<table>
|
||||||
|
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[]>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,405 @@
|
|||||||
|
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)!;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { SQLEditor, LanguageDefinition } from './SQLEditor';
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { SelectableValue, toOption } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
import { AccessoryButton, EditorList, InputGroup } from '@grafana/experimental';
|
import { AccessoryButton, EditorList, InputGroup, Select } from '@grafana/ui';
|
||||||
import { Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { QueryEditorGroupByExpression } from '../../expressions';
|
import { QueryEditorGroupByExpression } from '../../expressions';
|
||||||
import { SQLExpression } from '../../types';
|
import { SQLExpression } from '../../types';
|
||||||
|
@ -2,8 +2,7 @@ import { uniqueId } from 'lodash';
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { SelectableValue, toOption } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
import { EditorField, InputGroup, Space } from '@grafana/experimental';
|
import { EditorField, Input, InputGroup, RadioButtonGroup, Select, Space } from '@grafana/ui';
|
||||||
import { Input, RadioButtonGroup, Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SQLExpression } from '../../types';
|
import { SQLExpression } from '../../types';
|
||||||
import { setPropertyField } from '../../utils/sql.utils';
|
import { setPropertyField } from '../../utils/sql.utils';
|
||||||
|
@ -3,8 +3,7 @@ import { uniqueId } from 'lodash';
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { SelectableValue, toOption } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
import { EditorField, Stack } from '@grafana/experimental';
|
import { Button, EditorField, Select, Stack, useStyles2 } from '@grafana/ui';
|
||||||
import { Button, Select, useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { QueryEditorExpressionType, QueryEditorFunctionExpression } from '../../expressions';
|
import { QueryEditorExpressionType, QueryEditorFunctionExpression } from '../../expressions';
|
||||||
import { SQLExpression } from '../../types';
|
import { SQLExpression } from '../../types';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { EditorField, EditorRow, EditorRows } from '@grafana/experimental';
|
import { EditorRows, EditorRow, EditorField } from '@grafana/ui';
|
||||||
|
|
||||||
import { DB, QueryEditorProps, QueryRowFilter } from '../../types';
|
import { DB, QueryEditorProps, QueryRowFilter } from '../../types';
|
||||||
import { QueryToolbox } from '../query-editor-raw/QueryToolbox';
|
import { QueryToolbox } from '../query-editor-raw/QueryToolbox';
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export { GroupByRow } from './GroupByRow';
|
@ -1,4 +1,4 @@
|
|||||||
import { OperatorType } from '@grafana/experimental';
|
import { OperatorType } from './types';
|
||||||
|
|
||||||
export const AGGREGATE_FNS = [
|
export const AGGREGATE_FNS = [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { EditorMode } from '@grafana/experimental';
|
import { EditorMode, QueryFormat, SQLQuery } from './types';
|
||||||
|
|
||||||
import { QueryFormat, SQLQuery } from './types';
|
|
||||||
import { createFunctionField, setGroupByField } from './utils/sql.utils';
|
import { createFunctionField, setGroupByField } from './utils/sql.utils';
|
||||||
|
|
||||||
export function applyQueryDefaults(q?: SQLQuery): SQLQuery {
|
export function applyQueryDefaults(q?: SQLQuery): SQLQuery {
|
||||||
|
22
public/app/features/plugins/sql/index.ts
Normal file
22
public/app/features/plugins/sql/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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';
|
26
public/app/features/plugins/sql/mocks/Monaco.ts
Normal file
26
public/app/features/plugins/sql/mocks/Monaco.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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 };
|
21
public/app/features/plugins/sql/mocks/TextModel.ts
Normal file
21
public/app/features/plugins/sql/mocks/TextModel.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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 };
|
@ -0,0 +1,214 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,229 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,269 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
import { TestQueryModel } from '../../test-utils/types';
|
||||||
|
|
||||||
|
export const singleLineEmptyQuery: TestQueryModel = {
|
||||||
|
query: '',
|
||||||
|
tokens: [],
|
||||||
|
};
|
@ -0,0 +1,196 @@
|
|||||||
|
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[][],
|
||||||
|
};
|
@ -0,0 +1,209 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,250 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,385 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,415 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
9
public/app/features/plugins/sql/mocks/testData.ts
Normal file
9
public/app/features/plugins/sql/mocks/testData.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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';
|
24
public/app/features/plugins/sql/standardSql/definition.ts
Normal file
24
public/app/features/plugins/sql/standardSql/definition.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
@ -0,0 +1,273 @@
|
|||||||
|
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",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,34 @@
|
|||||||
|
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);
|
||||||
|
};
|
@ -0,0 +1,184 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,31 @@
|
|||||||
|
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;
|
||||||
|
}
|
880
public/app/features/plugins/sql/standardSql/language.ts
Normal file
880
public/app/features/plugins/sql/standardSql/language.ts
Normal file
@ -0,0 +1,880 @@
|
|||||||
|
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' }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
67
public/app/features/plugins/sql/standardSql/macros.ts
Normal file
67
public/app/features/plugins/sql/standardSql/macros.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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.',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,425 @@
|
|||||||
|
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(')');
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
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')
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
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],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
52
public/app/features/plugins/sql/standardSql/types.ts
Normal file
52
public/app/features/plugins/sql/standardSql/types.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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[];
|
||||||
|
}
|
11
public/app/features/plugins/sql/test-utils/index.ts
Normal file
11
public/app/features/plugins/sql/test-utils/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as testData from '../mocks/testData';
|
||||||
|
|
||||||
|
import { testStatementPosition } from './statementPosition';
|
||||||
|
import { TestQueryModel } from './types';
|
||||||
|
|
||||||
|
export const SQLEditorTestUtils = {
|
||||||
|
testData,
|
||||||
|
testStatementPosition,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TestQueryModel };
|
@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
21
public/app/features/plugins/sql/test-utils/testQueries.txt
Normal file
21
public/app/features/plugins/sql/test-utils/testQueries.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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;
|
11
public/app/features/plugins/sql/test-utils/types.ts
Normal file
11
public/app/features/plugins/sql/test-utils/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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 };
|
||||||
|
}
|
@ -9,7 +9,7 @@ import {
|
|||||||
TimeRange,
|
TimeRange,
|
||||||
toOption as toOptionFromData,
|
toOption as toOptionFromData,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { CompletionItemKind, EditorMode, LanguageCompletionProvider } from '@grafana/experimental';
|
import { Monaco, monacoTypes } from '@grafana/ui';
|
||||||
|
|
||||||
import { QueryWithDefaults } from './defaults';
|
import { QueryWithDefaults } from './defaults';
|
||||||
import {
|
import {
|
||||||
@ -17,6 +17,8 @@ import {
|
|||||||
QueryEditorGroupByExpression,
|
QueryEditorGroupByExpression,
|
||||||
QueryEditorPropertyExpression,
|
QueryEditorPropertyExpression,
|
||||||
} from './expressions';
|
} from './expressions';
|
||||||
|
import { StatementPositionResolver, SuggestionsResolver } from './standardSql/types';
|
||||||
|
import { LinkedToken } from './utils/LinkedToken';
|
||||||
|
|
||||||
export interface SqlQueryForInterpolation {
|
export interface SqlQueryForInterpolation {
|
||||||
dataset?: string;
|
dataset?: string;
|
||||||
@ -49,6 +51,11 @@ export enum QueryFormat {
|
|||||||
Table = 'table',
|
Table = 'table',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EditorMode {
|
||||||
|
Builder = 'builder',
|
||||||
|
Code = 'code',
|
||||||
|
}
|
||||||
|
|
||||||
export interface SQLQuery extends DataQuery {
|
export interface SQLQuery extends DataQuery {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
format?: QueryFormat;
|
format?: QueryFormat;
|
||||||
@ -174,3 +181,250 @@ export interface MetaDefinition {
|
|||||||
completion?: string;
|
completion?: string;
|
||||||
kind: CompletionItemKind;
|
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,
|
||||||
|
}
|
||||||
|
176
public/app/features/plugins/sql/utils/LinkedToken.ts
Normal file
176
public/app/features/plugins/sql/utils/LinkedToken.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
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;
|
||||||
|
}
|
4
public/app/features/plugins/sql/utils/commands.ts
Normal file
4
public/app/features/plugins/sql/utils/commands.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const TRIGGER_SUGGEST = {
|
||||||
|
id: 'editor.action.triggerSuggest',
|
||||||
|
title: '',
|
||||||
|
};
|
12
public/app/features/plugins/sql/utils/debugger.ts
Normal file
12
public/app/features/plugins/sql/utils/debugger.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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 };
|
@ -0,0 +1,31 @@
|
|||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
22
public/app/features/plugins/sql/utils/getSuggestionKind.ts
Normal file
22
public/app/features/plugins/sql/utils/getSuggestionKind.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
56
public/app/features/plugins/sql/utils/linkedTokenBuilder.ts
Normal file
56
public/app/features/plugins/sql/utils/linkedTokenBuilder.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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;
|
||||||
|
}
|
19
public/app/features/plugins/sql/utils/toCompletionItem.ts
Normal file
19
public/app/features/plugins/sql/utils/toCompletionItem.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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;
|
||||||
|
};
|
57
public/app/features/plugins/sql/utils/tokenUtils.ts
Normal file
57
public/app/features/plugins/sql/utils/tokenUtils.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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;
|
||||||
|
};
|
14
public/app/features/plugins/sql/utils/types.ts
Normal file
14
public/app/features/plugins/sql/utils/types.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Card, Stack } from '@grafana/ui';
|
||||||
import { Card } from '@grafana/ui';
|
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
@ -2,8 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Button, Stack, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||||
import { Button, ToolbarButton, useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SceneObjectBase } from '../core/SceneObjectBase';
|
import { SceneObjectBase } from '../core/SceneObjectBase';
|
||||||
import { SceneObject, SceneLayoutChildState, SceneComponentProps, SceneLayout } from '../core/types';
|
import { SceneObject, SceneLayoutChildState, SceneComponentProps, SceneLayout } from '../core/types';
|
||||||
|
@ -11,8 +11,7 @@ import {
|
|||||||
LabelsToFieldsMode,
|
LabelsToFieldsMode,
|
||||||
LabelsToFieldsOptions,
|
LabelsToFieldsOptions,
|
||||||
} from '@grafana/data/src/transformations/transformers/labelsToFields';
|
} from '@grafana/data/src/transformations/transformers/labelsToFields';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { InlineField, InlineFieldRow, RadioButtonGroup, Select, FilterPill, Stack } from '@grafana/ui';
|
||||||
import { InlineField, InlineFieldRow, RadioButtonGroup, Select, FilterPill } from '@grafana/ui';
|
|
||||||
|
|
||||||
const modes: Array<SelectableValue<LabelsToFieldsMode>> = [
|
const modes: Array<SelectableValue<LabelsToFieldsMode>> = [
|
||||||
{ value: LabelsToFieldsMode.Columns, label: 'Columns' },
|
{ value: LabelsToFieldsMode.Columns, label: 'Columns' },
|
||||||
|
@ -2,9 +2,8 @@ import React, { useState } from 'react';
|
|||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
|
|
||||||
import { QueryEditorProps, toOption } from '@grafana/data';
|
import { QueryEditorProps, toOption } from '@grafana/data';
|
||||||
import { EditorField, EditorRows } from '@grafana/experimental';
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { Input } from '@grafana/ui';
|
import { EditorField, EditorRows, Input } from '@grafana/ui';
|
||||||
|
|
||||||
import { INPUT_WIDTH } from '../constants';
|
import { INPUT_WIDTH } from '../constants';
|
||||||
import CloudMonitoringDatasource from '../datasource';
|
import CloudMonitoringDatasource from '../datasource';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField } from '@grafana/experimental';
|
import { EditorField, Select } from '@grafana/ui';
|
||||||
import { Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { getAggregationOptionsByMetric } from '../../functions';
|
import { getAggregationOptionsByMetric } from '../../functions';
|
||||||
import { MetricDescriptor, MetricKind, ValueTypes } from '../../types';
|
import { MetricDescriptor, MetricKind, ValueTypes } from '../../types';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { FunctionComponent, useState } from 'react';
|
import React, { FunctionComponent, useState } from 'react';
|
||||||
|
|
||||||
import { EditorRow, EditorField } from '@grafana/experimental';
|
import { EditorField, EditorRow, Input } from '@grafana/ui';
|
||||||
import { Input } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SELECT_WIDTH } from '../../constants';
|
import { SELECT_WIDTH } from '../../constants';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorRow, EditorField, EditorFieldGroup, Stack } from '@grafana/experimental';
|
import { EditorRow, EditorFieldGroup, EditorField, Stack } from '@grafana/ui';
|
||||||
|
|
||||||
import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../../constants';
|
import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../../constants';
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorRow, HorizontalGroup, Switch } from '@grafana/ui';
|
||||||
import { HorizontalGroup, Switch } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { GRAPH_PERIODS, SELECT_WIDTH } from '../../constants';
|
import { GRAPH_PERIODS, SELECT_WIDTH } from '../../constants';
|
||||||
import { PeriodSelect } from '../index';
|
import { PeriodSelect } from '../index';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { FunctionComponent, useMemo } from 'react';
|
import React, { FunctionComponent, useMemo } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorFieldGroup, EditorRow, MultiSelect } from '@grafana/ui';
|
||||||
import { MultiSelect } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SYSTEM_LABELS } from '../../constants';
|
import { SYSTEM_LABELS } from '../../constants';
|
||||||
import { labelsToGroupedOptions } from '../../functions';
|
import { labelsToGroupedOptions } from '../../functions';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { FunctionComponent, useMemo } from 'react';
|
import React, { FunctionComponent, useMemo } from 'react';
|
||||||
|
|
||||||
import { SelectableValue, toOption } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
import { AccessoryButton, EditorRow, EditorField, EditorList } from '@grafana/experimental';
|
import { AccessoryButton, EditorField, EditorList, EditorRow, HorizontalGroup, Select } from '@grafana/ui';
|
||||||
import { HorizontalGroup, Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { labelsToGroupedOptions, stringArrayToFilters } from '../../functions';
|
import { labelsToGroupedOptions, stringArrayToFilters } from '../../functions';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorRows } from '@grafana/experimental';
|
import { EditorRows } from '@grafana/ui';
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
import { getAlignmentPickerData } from '../../functions';
|
import { getAlignmentPickerData } from '../../functions';
|
||||||
|
@ -3,8 +3,7 @@ import { startCase, uniqBy } from 'lodash';
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import { EditorRow, EditorField, EditorFieldGroup } from '@grafana/experimental';
|
import { EditorField, EditorFieldGroup, EditorRow, getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
import { getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
import { MetricDescriptor } from '../../types';
|
import { MetricDescriptor } from '../../types';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { FunctionComponent, useMemo } from 'react';
|
import React, { FunctionComponent, useMemo } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorRow, RadioButtonGroup } from '@grafana/ui';
|
||||||
import { RadioButtonGroup } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { getAlignmentPickerData } from '../../functions';
|
import { getAlignmentPickerData } from '../../functions';
|
||||||
import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../../types';
|
import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../../types';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||||
import { Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { EditorHeader, FlexItem, InlineSelect } from '@grafana/experimental';
|
import { EditorHeader, FlexItem, InlineSelect, RadioButtonGroup } from '@grafana/ui';
|
||||||
import { RadioButtonGroup } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { QUERY_TYPES } from '../../constants';
|
import { QUERY_TYPES } from '../../constants';
|
||||||
import { EditorMode, CloudMonitoringQuery, QueryType, SLOQuery, MetricQuery } from '../../types';
|
import { EditorMode, CloudMonitoringQuery, QueryType, SLOQuery, MetricQuery } from '../../types';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||||
import { Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
import { SLOQuery } from '../../types';
|
import { SLOQuery } from '../../types';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorFieldGroup, EditorRow, Stack } from '@grafana/experimental';
|
import { EditorRow, EditorFieldGroup, EditorField, Stack } from '@grafana/ui';
|
||||||
|
|
||||||
import { ALIGNMENT_PERIODS } from '../../constants';
|
import { ALIGNMENT_PERIODS } from '../../constants';
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { EditorField, EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||||
import { Select } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SELECTORS } from '../../constants';
|
import { SELECTORS } from '../../constants';
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user