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 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
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
|
@ -43,65 +77,19 @@ const heading = `/*
|
|||
|
||||
// This file is auto generated and should not be modified
|
||||
|
||||
export interface FunctionDescription {
|
||||
export interface SyntaxDescription {
|
||||
syntax: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/* tslint:disable:quotemark */
|
||||
|
||||
export const SQLFunctionDoc: FunctionDescription[] = `;
|
||||
export const SQL_FUNCTIONS: SyntaxDescription[] = ${JSON.stringify(functionDocs, null, 2)};
|
||||
|
||||
const readDoc = async () => {
|
||||
try {
|
||||
const data = await fs.readFile(readfile, 'utf-8');
|
||||
const sections = data.split("##");
|
||||
export const SQL_DATE_TYPES: SyntaxDescription[] = ${JSON.stringify(dataTypeDocs, null, 2)};
|
||||
`;
|
||||
|
||||
let entries = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
await fs.writeFile(writefile, content, 'utf-8');
|
||||
};
|
||||
|
||||
readDoc();
|
||||
|
|
|
@ -150,10 +150,12 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
|||
case 1: // Datasource
|
||||
const tableSchema = columnTree[selectedTreeIndex].label;
|
||||
if (tableSchema === 'druid') {
|
||||
onQueryStringChange(`SELECT * FROM "${nodeData.label}"
|
||||
onQueryStringChange(`SELECT *
|
||||
FROM "${nodeData.label}"
|
||||
WHERE "__time" >= CURRENT_TIMESTAMP - INTERVAL '1' DAY`);
|
||||
} else {
|
||||
onQueryStringChange(`SELECT * FROM ${tableSchema}.${nodeData.label}`);
|
||||
onQueryStringChange(`SELECT *
|
||||
FROM ${tableSchema}.${nodeData.label}`);
|
||||
}
|
||||
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.
|
||||
*/
|
||||
|
||||
import { IResizeEntry, ITreeNode, ResizeSensor } from '@blueprintjs/core';
|
||||
import { IResizeEntry, ResizeSensor } from '@blueprintjs/core';
|
||||
import ace from 'brace';
|
||||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
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 { ColumnMetadata } from '../../../utils/column-metadata';
|
||||
import { ColumnTreeProps, ColumnTreeState } from '../column-tree/column-tree';
|
||||
|
||||
import { SQL_CONSTANTS, SQL_DYNAMICS, SQL_EXPRESSION_PARTS, SQL_KEYWORDS } from './keywords';
|
||||
|
||||
import './query-input.scss';
|
||||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
|
@ -46,7 +48,6 @@ export interface QueryInputState {
|
|||
}
|
||||
|
||||
export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputState> {
|
||||
|
||||
static getDerivedStateFromProps(props: ColumnTreeProps, state: ColumnTreeState) {
|
||||
const { columnMetadata } = props;
|
||||
|
||||
|
@ -80,33 +81,29 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
|||
|
||||
private replaceDefaultAutoCompleter = () => {
|
||||
if (!langTools) return;
|
||||
/*
|
||||
Please refer to the source code @
|
||||
https://github.com/ajaxorg/ace/blob/9b5b63d1dc7c1b81b58d30c87d14b5905d030ca5/lib/ace/ext/language_tools.js#L41
|
||||
for the implementation of keyword completer
|
||||
*/
|
||||
|
||||
const keywordList = ([] as any[]).concat(
|
||||
SQL_KEYWORDS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
|
||||
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 = {
|
||||
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
|
||||
if (session.$mode.completer) {
|
||||
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);
|
||||
return callback(null, keywordList);
|
||||
}
|
||||
};
|
||||
|
||||
langTools.setCompleters([langTools.snippetCompleter, langTools.textCompleter, keywordCompleter]);
|
||||
}
|
||||
|
||||
private addFunctionAutoCompleter = (): void => {
|
||||
if (!langTools) return;
|
||||
|
||||
const functionList: any[] = SQLFunctionDoc.map((entry: any) => {
|
||||
let funcName: string = entry.syntax.replace(/\(.*\)/, '()');
|
||||
if (!funcName.includes('(')) funcName = funcName.substr(0, 10);
|
||||
const functionList: any[] = SQL_FUNCTIONS.map((entry: SyntaxDescription) => {
|
||||
const funcName: string = entry.syntax.replace(/\(.*\)/, '()');
|
||||
return {
|
||||
value: funcName,
|
||||
score: 80,
|
||||
|
|
|
@ -90,7 +90,7 @@ export class RunButton extends React.PureComponent<RunButtonProps, RunButtonStat
|
|||
return <Menu>
|
||||
<MenuItem
|
||||
icon={IconNames.HELP}
|
||||
text="Docs"
|
||||
text="Query docs"
|
||||
href={runeMode ? DRUID_DOCS_RUNE : DRUID_DOCS_SQL}
|
||||
target="_blank"
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue