mirror of https://github.com/apache/druid.git
more acurate keyword auto complete (#7934)
This commit is contained in:
parent
d046d5aa8d
commit
2bc8e7c0e8
|
@ -23,7 +23,41 @@ const fs = require('fs-extra');
|
||||||
const readfile = '../docs/content/querying/sql.md';
|
const readfile = '../docs/content/querying/sql.md';
|
||||||
const writefile = 'lib/sql-function-doc.ts';
|
const writefile = 'lib/sql-function-doc.ts';
|
||||||
|
|
||||||
const heading = `/*
|
const readDoc = async () => {
|
||||||
|
const data = await fs.readFile(readfile, 'utf-8');
|
||||||
|
const lines = data.split('\n');
|
||||||
|
|
||||||
|
const functionDocs = [];
|
||||||
|
const dataTypeDocs = [];
|
||||||
|
for (let line of lines) {
|
||||||
|
const functionMatch = line.match(/^\|`(.+\(.*\))`\|(.+)\|$/);
|
||||||
|
if (functionMatch) {
|
||||||
|
functionDocs.push({
|
||||||
|
syntax: functionMatch[1],
|
||||||
|
description: functionMatch[2]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataTypeMatch = line.match(/^\|([A-Z]+)\|([A-Z]+)\|(.*)\|(.*)\|$/);
|
||||||
|
if (dataTypeMatch) {
|
||||||
|
dataTypeDocs.push({
|
||||||
|
syntax: dataTypeMatch[1],
|
||||||
|
description: dataTypeMatch[4] || `Druid runtime type: ${dataTypeMatch[2]}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there are at least 10 functions for sanity
|
||||||
|
if (functionDocs.length < 10) {
|
||||||
|
throw new Error(`Did not find enough function entries did the structure of '${readfile}' change? (found ${functionDocs.length})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there are at least 5 data types for sanity
|
||||||
|
if (dataTypeDocs.length < 10) {
|
||||||
|
throw new Error(`Did not find enough data type entries did the structure of '${readfile}' change? (found ${dataTypeDocs.length})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `/*
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
* or more contributor license agreements. See the NOTICE file
|
* or more contributor license agreements. See the NOTICE file
|
||||||
* distributed with this work for additional information
|
* distributed with this work for additional information
|
||||||
|
@ -43,65 +77,19 @@ const heading = `/*
|
||||||
|
|
||||||
// This file is auto generated and should not be modified
|
// This file is auto generated and should not be modified
|
||||||
|
|
||||||
export interface FunctionDescription {
|
export interface SyntaxDescription {
|
||||||
syntax: string;
|
syntax: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tslint:disable:quotemark */
|
/* tslint:disable:quotemark */
|
||||||
|
|
||||||
export const SQLFunctionDoc: FunctionDescription[] = `;
|
export const SQL_FUNCTIONS: SyntaxDescription[] = ${JSON.stringify(functionDocs, null, 2)};
|
||||||
|
|
||||||
const readDoc = async () => {
|
export const SQL_DATE_TYPES: SyntaxDescription[] = ${JSON.stringify(dataTypeDocs, null, 2)};
|
||||||
try {
|
`;
|
||||||
const data = await fs.readFile(readfile, 'utf-8');
|
|
||||||
const sections = data.split("##");
|
|
||||||
|
|
||||||
let entries = [];
|
await fs.writeFile(writefile, content, 'utf-8');
|
||||||
sections.forEach((section) => {
|
};
|
||||||
if (!/^#.*function/.test(section)) return;
|
|
||||||
|
|
||||||
entries = entries.concat(
|
|
||||||
section.split('\n').map(line => {
|
|
||||||
if (line.startsWith('|`')) {
|
|
||||||
const rawSyntax = line.match(/\|`(.*)`\|/);
|
|
||||||
if (rawSyntax == null) return null;
|
|
||||||
const syntax = rawSyntax[1]
|
|
||||||
.replace(/\\/g,'')
|
|
||||||
.replace(/|/g,'|');
|
|
||||||
|
|
||||||
// Must have an uppercase letter
|
|
||||||
if (!/[A-Z]/.test(syntax)) return null;
|
|
||||||
|
|
||||||
const rawDescription = line.match(/`\|(.*)\|/);
|
|
||||||
if (rawDescription == null) return null;
|
|
||||||
const description = rawDescription[1];
|
|
||||||
|
|
||||||
return {
|
|
||||||
syntax: syntax,
|
|
||||||
description: description
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}).filter(Boolean)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure there are at least 10 functions for sanity
|
|
||||||
if (entries.length < 10) {
|
|
||||||
throw new Error(`Did not find any entries did the structure of '${readfile}' change?`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = heading + JSON.stringify(entries, null, 2) + ';\n';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fs.writeFile(writefile, content, 'utf-8');
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Error when writing to ${writefile}: `, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Error when reading ${readfile}: `, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readDoc();
|
readDoc();
|
||||||
|
|
|
@ -150,10 +150,12 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
||||||
case 1: // Datasource
|
case 1: // Datasource
|
||||||
const tableSchema = columnTree[selectedTreeIndex].label;
|
const tableSchema = columnTree[selectedTreeIndex].label;
|
||||||
if (tableSchema === 'druid') {
|
if (tableSchema === 'druid') {
|
||||||
onQueryStringChange(`SELECT * FROM "${nodeData.label}"
|
onQueryStringChange(`SELECT *
|
||||||
|
FROM "${nodeData.label}"
|
||||||
WHERE "__time" >= CURRENT_TIMESTAMP - INTERVAL '1' DAY`);
|
WHERE "__time" >= CURRENT_TIMESTAMP - INTERVAL '1' DAY`);
|
||||||
} else {
|
} else {
|
||||||
onQueryStringChange(`SELECT * FROM ${tableSchema}.${nodeData.label}`);
|
onQueryStringChange(`SELECT *
|
||||||
|
FROM ${tableSchema}.${nodeData.label}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Hand picked from https://druid.apache.org/docs/latest/querying/sql.html
|
||||||
|
|
||||||
|
export const SQL_KEYWORDS: string[] = [
|
||||||
|
'EXPLAIN PLAN FOR',
|
||||||
|
'WITH',
|
||||||
|
'AS',
|
||||||
|
'SELECT',
|
||||||
|
'ALL',
|
||||||
|
'DISTINCT',
|
||||||
|
'FROM',
|
||||||
|
'WHERE',
|
||||||
|
'GROUP BY',
|
||||||
|
'HAVING',
|
||||||
|
'ORDER BY',
|
||||||
|
'ASC',
|
||||||
|
'DESC',
|
||||||
|
'LIMIT',
|
||||||
|
'UNION ALL'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SQL_EXPRESSION_PARTS: string[] = [
|
||||||
|
'FILTER',
|
||||||
|
'END',
|
||||||
|
'ELSE',
|
||||||
|
'WHEN',
|
||||||
|
'CASE',
|
||||||
|
'OR',
|
||||||
|
'AND',
|
||||||
|
'NOT',
|
||||||
|
'IN',
|
||||||
|
'IS',
|
||||||
|
'TO',
|
||||||
|
'BETWEEN',
|
||||||
|
'LIKE',
|
||||||
|
'ESCAPE',
|
||||||
|
'BOTH',
|
||||||
|
'LEADING',
|
||||||
|
'TRAILING',
|
||||||
|
'EPOCH',
|
||||||
|
'SECOND',
|
||||||
|
'MINUTE',
|
||||||
|
'HOUR',
|
||||||
|
'DAY',
|
||||||
|
'DOW',
|
||||||
|
'DOY',
|
||||||
|
'WEEK',
|
||||||
|
'MONTH',
|
||||||
|
'QUARTER',
|
||||||
|
'YEAR',
|
||||||
|
'TIMESTAMP',
|
||||||
|
'INTERVAL'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SQL_CONSTANTS: string[] = [
|
||||||
|
'NULL',
|
||||||
|
'FALSE',
|
||||||
|
'TRUE'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SQL_DYNAMICS: string[] = [
|
||||||
|
'CURRENT_TIMESTAMP',
|
||||||
|
'CURRENT_DATE'
|
||||||
|
];
|
|
@ -16,17 +16,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IResizeEntry, ITreeNode, ResizeSensor } from '@blueprintjs/core';
|
import { IResizeEntry, ResizeSensor } from '@blueprintjs/core';
|
||||||
import ace from 'brace';
|
import ace from 'brace';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AceEditor from 'react-ace';
|
import AceEditor from 'react-ace';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from 'react-dom/server';
|
||||||
|
|
||||||
import { SQLFunctionDoc } from '../../../../lib/sql-function-doc';
|
import { SQL_DATE_TYPES, SQL_FUNCTIONS, SyntaxDescription } from '../../../../lib/sql-function-doc';
|
||||||
import { uniq } from '../../../utils';
|
import { uniq } from '../../../utils';
|
||||||
import { ColumnMetadata } from '../../../utils/column-metadata';
|
import { ColumnMetadata } from '../../../utils/column-metadata';
|
||||||
import { ColumnTreeProps, ColumnTreeState } from '../column-tree/column-tree';
|
import { ColumnTreeProps, ColumnTreeState } from '../column-tree/column-tree';
|
||||||
|
|
||||||
|
import { SQL_CONSTANTS, SQL_DYNAMICS, SQL_EXPRESSION_PARTS, SQL_KEYWORDS } from './keywords';
|
||||||
|
|
||||||
import './query-input.scss';
|
import './query-input.scss';
|
||||||
|
|
||||||
const langTools = ace.acequire('ace/ext/language_tools');
|
const langTools = ace.acequire('ace/ext/language_tools');
|
||||||
|
@ -46,7 +48,6 @@ export interface QueryInputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputState> {
|
export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputState> {
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: ColumnTreeProps, state: ColumnTreeState) {
|
static getDerivedStateFromProps(props: ColumnTreeProps, state: ColumnTreeState) {
|
||||||
const { columnMetadata } = props;
|
const { columnMetadata } = props;
|
||||||
|
|
||||||
|
@ -80,33 +81,29 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
||||||
|
|
||||||
private replaceDefaultAutoCompleter = () => {
|
private replaceDefaultAutoCompleter = () => {
|
||||||
if (!langTools) return;
|
if (!langTools) return;
|
||||||
/*
|
|
||||||
Please refer to the source code @
|
const keywordList = ([] as any[]).concat(
|
||||||
https://github.com/ajaxorg/ace/blob/9b5b63d1dc7c1b81b58d30c87d14b5905d030ca5/lib/ace/ext/language_tools.js#L41
|
SQL_KEYWORDS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
|
||||||
for the implementation of keyword completer
|
SQL_EXPRESSION_PARTS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
|
||||||
*/
|
SQL_CONSTANTS.map(v => ({ name: v, value: v, score: 0, meta: 'constant' })),
|
||||||
|
SQL_DYNAMICS.map(v => ({ name: v, value: v, score: 0, meta: 'dynamic' })),
|
||||||
|
SQL_DATE_TYPES.map(v => ({ name: v.syntax, value: v.syntax, score: 0, meta: 'keyword' }))
|
||||||
|
);
|
||||||
|
|
||||||
const keywordCompleter = {
|
const keywordCompleter = {
|
||||||
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
|
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
|
||||||
if (session.$mode.completer) {
|
return callback(null, keywordList);
|
||||||
return session.$mode.completer.getCompletions(editor, session, pos, prefix, callback);
|
|
||||||
}
|
|
||||||
const state = editor.session.getState(pos.row);
|
|
||||||
let keywordCompletions = session.$mode.getCompletions(state, session, pos, prefix);
|
|
||||||
keywordCompletions = keywordCompletions.map((d: any) => {
|
|
||||||
return Object.assign(d, {name: d.name.toUpperCase(), value: d.value.toUpperCase()});
|
|
||||||
});
|
|
||||||
return callback(null, keywordCompletions);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
langTools.setCompleters([langTools.snippetCompleter, langTools.textCompleter, keywordCompleter]);
|
langTools.setCompleters([langTools.snippetCompleter, langTools.textCompleter, keywordCompleter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private addFunctionAutoCompleter = (): void => {
|
private addFunctionAutoCompleter = (): void => {
|
||||||
if (!langTools) return;
|
if (!langTools) return;
|
||||||
|
|
||||||
const functionList: any[] = SQLFunctionDoc.map((entry: any) => {
|
const functionList: any[] = SQL_FUNCTIONS.map((entry: SyntaxDescription) => {
|
||||||
let funcName: string = entry.syntax.replace(/\(.*\)/, '()');
|
const funcName: string = entry.syntax.replace(/\(.*\)/, '()');
|
||||||
if (!funcName.includes('(')) funcName = funcName.substr(0, 10);
|
|
||||||
return {
|
return {
|
||||||
value: funcName,
|
value: funcName,
|
||||||
score: 80,
|
score: 80,
|
||||||
|
|
|
@ -90,7 +90,7 @@ export class RunButton extends React.PureComponent<RunButtonProps, RunButtonStat
|
||||||
return <Menu>
|
return <Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.HELP}
|
icon={IconNames.HELP}
|
||||||
text="Docs"
|
text="Query docs"
|
||||||
href={runeMode ? DRUID_DOCS_RUNE : DRUID_DOCS_SQL}
|
href={runeMode ? DRUID_DOCS_RUNE : DRUID_DOCS_SQL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue