mirror of https://github.com/apache/druid.git
Add SQL auto complete in druid console (#7244)
* Add SQL auto complete in druid console * Add comment in sql.md to alert user to change create-sql-function-doc if sql.md format gets changed
This commit is contained in:
parent
892d1d35d6
commit
5406aaa49d
|
@ -22,6 +22,13 @@ title: "SQL"
|
||||||
~ under the License.
|
~ under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The format of the tables that describe the functions and operators
|
||||||
|
should not be changed without updating the script create-sql-function-doc
|
||||||
|
in web-console/script/create-sql-function-doc, because the script detects
|
||||||
|
patterns in this markdown file and parse it to TypeScript file for web console
|
||||||
|
-->
|
||||||
|
|
||||||
# SQL
|
# SQL
|
||||||
|
|
||||||
<div class="note caution">
|
<div class="note caution">
|
||||||
|
|
|
@ -9,6 +9,8 @@ coordinator-console/
|
||||||
pages/
|
pages/
|
||||||
index.html
|
index.html
|
||||||
|
|
||||||
|
lib/sql-function-doc.ts
|
||||||
|
|
||||||
.tscache
|
.tscache
|
||||||
tscommand-*.tmp.txt
|
tscommand-*.tmp.txt
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,9 @@ echo "Copying blueprint assets in..."
|
||||||
sed 's|url("assets|url("/assets|g' "./node_modules/@blueprintjs/core/dist/blueprint.css" > lib/blueprint.css
|
sed 's|url("assets|url("/assets|g' "./node_modules/@blueprintjs/core/dist/blueprint.css" > lib/blueprint.css
|
||||||
cp -r "./node_modules/@blueprintjs/core/dist/assets" .
|
cp -r "./node_modules/@blueprintjs/core/dist/assets" .
|
||||||
|
|
||||||
|
echo "Adding SQL function doc..."
|
||||||
|
PATH="./target/node:$PATH" ./script/create-sql-function-doc
|
||||||
|
|
||||||
echo "Transpiling ReactTable CSS..."
|
echo "Transpiling ReactTable CSS..."
|
||||||
PATH="./target/node:$PATH" ./node_modules/.bin/stylus lib/react-table.styl -o lib/react-table.css
|
PATH="./target/node:$PATH" ./node_modules/.bin/stylus lib/react-table.styl -o lib/react-table.css
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
readfile='../docs/content/querying/sql.md'
|
||||||
|
writefile='lib/sql-function-doc.ts'
|
||||||
|
|
||||||
|
> "$writefile"
|
||||||
|
|
||||||
|
echo -e "// This file is auto generated and should not be modified\n" > "$writefile"
|
||||||
|
echo -e 'export const SQLFunctionDoc: any[] = [' >> "$writefile"
|
||||||
|
|
||||||
|
isFunction=false
|
||||||
|
|
||||||
|
while read -r line; do
|
||||||
|
if [[ $line =~ ^###.*functions$ ]]; then
|
||||||
|
isFunction=true
|
||||||
|
elif [[ $line =~ ^## ]] ; then
|
||||||
|
isFunction=false
|
||||||
|
elif [[ $isFunction == true ]]; then
|
||||||
|
if [[ $line =~ \|\`.*\`\|.*\| ]]; then
|
||||||
|
syntax=$(echo $line | grep -o '|`.*`|')
|
||||||
|
syntax=${syntax:2:${#syntax}-4}
|
||||||
|
syntax=${syntax//\\/}
|
||||||
|
description=$(echo $line | grep -o '`|.*.|')
|
||||||
|
description=${description//\"/\'}
|
||||||
|
description=${description:2:${#description}-4}
|
||||||
|
echo -e " {" >> "$writefile"
|
||||||
|
echo -e " syntax: \"$syntax\"," >> "$writefile"
|
||||||
|
echo -e " description: \"$description\"" >> "$writefile"
|
||||||
|
echo -e " }," >> "$writefile"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$readfile"
|
||||||
|
|
||||||
|
echo -e ']' >> "$writefile"
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
.sql-control {
|
||||||
|
|
||||||
|
.ace_scroller {
|
||||||
|
background-color: #232C35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_gutter-layer {
|
||||||
|
background-color: #27313c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
|
||||||
|
button{
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-complete-checkbox {
|
||||||
|
margin:10px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_tooltip {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #333D47;
|
||||||
|
color: #C1CCD5;
|
||||||
|
width: 500px;
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
white-space: initial;
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-doc-name {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import * as ReactDOMServer from 'react-dom/server';
|
||||||
|
import axios from "axios";
|
||||||
import * as classNames from 'classnames';
|
import * as classNames from 'classnames';
|
||||||
import * as ace from 'brace'
|
import * as ace from 'brace'
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
|
@ -24,8 +26,13 @@ import 'brace/mode/sql';
|
||||||
import 'brace/mode/hjson';
|
import 'brace/mode/hjson';
|
||||||
import 'brace/theme/solarized_dark';
|
import 'brace/theme/solarized_dark';
|
||||||
import 'brace/ext/language_tools';
|
import 'brace/ext/language_tools';
|
||||||
import { Intent, Button } from "@blueprintjs/core";
|
import {Intent, Button, Popover, Checkbox, Classes, Position} from "@blueprintjs/core";
|
||||||
|
import { SQLFunctionDoc } from "../../lib/sql-function-doc";
|
||||||
import { IconNames } from './filler';
|
import { IconNames } from './filler';
|
||||||
|
import './sql-control.scss'
|
||||||
|
import {AppToaster} from "../singletons/toaster";
|
||||||
|
|
||||||
|
const langTools = ace.acequire('ace/ext/language_tools');
|
||||||
|
|
||||||
export interface SqlControlProps extends React.Props<any> {
|
export interface SqlControlProps extends React.Props<any> {
|
||||||
initSql: string | null;
|
initSql: string | null;
|
||||||
|
@ -35,6 +42,7 @@ export interface SqlControlProps extends React.Props<any> {
|
||||||
export interface SqlControlState {
|
export interface SqlControlState {
|
||||||
query: string;
|
query: string;
|
||||||
autoCompleteOn: boolean;
|
autoCompleteOn: boolean;
|
||||||
|
autoCompleteLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SqlControl extends React.Component<SqlControlProps, SqlControlState> {
|
export class SqlControl extends React.Component<SqlControlProps, SqlControlState> {
|
||||||
|
@ -42,10 +50,113 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
query: props.initSql || '',
|
query: props.initSql || '',
|
||||||
autoCompleteOn: true
|
autoCompleteOn: true,
|
||||||
|
autoCompleteLoading: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private addDatasourceAutoCompleter = async (): Promise<any> =>{
|
||||||
|
const datasourceResp = await axios.post("/druid/v2/sql", { query: `SELECT datasource FROM sys.segments GROUP BY 1`})
|
||||||
|
const datasourceList: any[] = datasourceResp.data.map((d: any) => {
|
||||||
|
const datasourceName: string = d.datasource;
|
||||||
|
return {
|
||||||
|
value: datasourceName,
|
||||||
|
score: 50,
|
||||||
|
meta: "datasource"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const completer = {
|
||||||
|
getCompletions: (editor:any , session: any, pos: any, prefix: any, callback: any) => {
|
||||||
|
callback(null, datasourceList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
langTools.addCompleter(completer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addColumnNameAutoCompleter = async (): Promise<any> => {
|
||||||
|
const columnNameResp = await axios.post("/druid/v2/sql", {query: `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'druid'`})
|
||||||
|
const columnNameList: any[] = columnNameResp.data.map((d: any) => {
|
||||||
|
const columnName: string = d.COLUMN_NAME;
|
||||||
|
return {
|
||||||
|
value: columnName,
|
||||||
|
score: 50,
|
||||||
|
meta: "column"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const completer = {
|
||||||
|
getCompletions: (editor:any , session: any, pos: any, prefix: any, callback: any) => {
|
||||||
|
callback(null, columnNameList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
langTools.addCompleter(completer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addFunctionAutoCompleter = (): void => {
|
||||||
|
const functionList: any[]= SQLFunctionDoc.map((entry: any) => {
|
||||||
|
let funcName: string = entry.syntax.replace(/\(.*\)/,"()");
|
||||||
|
if (!funcName.includes("(")) funcName = funcName.substr(0,10);
|
||||||
|
return {
|
||||||
|
value: funcName,
|
||||||
|
score: 80,
|
||||||
|
meta: "function",
|
||||||
|
syntax: entry.syntax,
|
||||||
|
description: entry.description,
|
||||||
|
completer: {
|
||||||
|
insertMatch: (editor:any, data:any) => {
|
||||||
|
editor.completer.insertMatch({value: data.caption});
|
||||||
|
const pos = editor.getCursorPosition();
|
||||||
|
editor.gotoLine(pos.row+1, pos.column-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const completer = {
|
||||||
|
getCompletions: (editor:any , session: any, pos: any, prefix: any, callback: any) => {
|
||||||
|
callback(null, functionList);
|
||||||
|
},
|
||||||
|
getDocTooltip: (item: any) => {
|
||||||
|
if (item.meta === "function") {
|
||||||
|
const functionName = item.caption.slice(0,-2);
|
||||||
|
item.docHTML = ReactDOMServer.renderToStaticMarkup((
|
||||||
|
<div className={"function-doc"}>
|
||||||
|
<div className={"function-doc-name"}><b>{functionName}</b></div>
|
||||||
|
<hr/>
|
||||||
|
<div><b>Syntax:</b></div>
|
||||||
|
<div>{item.syntax}</div>
|
||||||
|
<br/>
|
||||||
|
<div><b>Description:</b></div>
|
||||||
|
<div>{item.description}</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
langTools.addCompleter(completer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addCompleters = async () => {
|
||||||
|
try {
|
||||||
|
this.addFunctionAutoCompleter();
|
||||||
|
await this.addDatasourceAutoCompleter();
|
||||||
|
await this.addColumnNameAutoCompleter();
|
||||||
|
} catch (e) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: "Failed to load SQL auto completer",
|
||||||
|
intent: Intent.DANGER
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.addCompleters();
|
||||||
|
}
|
||||||
|
|
||||||
private handleChange = (newValue: string): void => {
|
private handleChange = (newValue: string): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
query: newValue
|
query: newValue
|
||||||
|
@ -58,6 +169,15 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
|
||||||
|
|
||||||
const isRune = query.trim().startsWith('{');
|
const isRune = query.trim().startsWith('{');
|
||||||
|
|
||||||
|
const autoCompletePopover = <div className={"auto-complete-checkbox"}>
|
||||||
|
<Checkbox
|
||||||
|
checked={isRune ? false : autoCompleteOn}
|
||||||
|
disabled={isRune}
|
||||||
|
label={"Auto complete"}
|
||||||
|
onChange={() => this.setState({autoCompleteOn: !autoCompleteOn})}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
|
||||||
// Set the key in the AceEditor to force a rebind and prevent an error that happens otherwise
|
// Set the key in the AceEditor to force a rebind and prevent an error that happens otherwise
|
||||||
return <div className="sql-control">
|
return <div className="sql-control">
|
||||||
<AceEditor
|
<AceEditor
|
||||||
|
@ -83,9 +203,14 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
<Button rightIconName={IconNames.CARET_RIGHT} onClick={() => onRun(query)}>{isRune ? 'Rune' : 'Run'}</Button>
|
<Button rightIconName={IconNames.CARET_RIGHT} onClick={() => onRun(query)}>
|
||||||
</div>
|
{isRune ? 'Rune' : 'Run'}
|
||||||
|
</Button>
|
||||||
|
<Popover position={Position.BOTTOM_LEFT} content={autoCompletePopover} inline>
|
||||||
|
<Button className={`${Classes.MINIMAL} pt-icon-more`}/>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,15 @@
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"lib": ["dom", "es2016"],
|
"lib": ["dom", "es2016"],
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"rootDir": "src",
|
"rootDirs": ["lib","src"],
|
||||||
|
|
||||||
"outDir": "build"
|
"outDir": "build"
|
||||||
},
|
},
|
||||||
|
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx"
|
"src/**/*.tsx",
|
||||||
|
"lib/sql-function-doc.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"**/*.test.ts"
|
"**/*.test.ts"
|
||||||
|
|
Loading…
Reference in New Issue