more acurate keyword auto complete (#7934)

This commit is contained in:
Vadim Ogievetsky 2019-06-20 21:14:54 -07:00 committed by Fangjin Yang
parent d046d5aa8d
commit 2bc8e7c0e8
5 changed files with 144 additions and 76 deletions

View File

@ -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(/&#124;/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();

View File

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

View File

@ -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'
];

View File

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

View File

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