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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
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
|
||||
|
||||
<div class="note caution">
|
||||
|
|
|
@ -9,6 +9,8 @@ coordinator-console/
|
|||
pages/
|
||||
index.html
|
||||
|
||||
lib/sql-function-doc.ts
|
||||
|
||||
.tscache
|
||||
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
|
||||
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..."
|
||||
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 ReactDOMServer from 'react-dom/server';
|
||||
import axios from "axios";
|
||||
import * as classNames from 'classnames';
|
||||
import * as ace from 'brace'
|
||||
import AceEditor from "react-ace";
|
||||
|
@ -24,8 +26,13 @@ import 'brace/mode/sql';
|
|||
import 'brace/mode/hjson';
|
||||
import 'brace/theme/solarized_dark';
|
||||
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 './sql-control.scss'
|
||||
import {AppToaster} from "../singletons/toaster";
|
||||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
|
||||
export interface SqlControlProps extends React.Props<any> {
|
||||
initSql: string | null;
|
||||
|
@ -35,6 +42,7 @@ export interface SqlControlProps extends React.Props<any> {
|
|||
export interface SqlControlState {
|
||||
query: string;
|
||||
autoCompleteOn: boolean;
|
||||
autoCompleteLoading: boolean;
|
||||
}
|
||||
|
||||
export class SqlControl extends React.Component<SqlControlProps, SqlControlState> {
|
||||
|
@ -42,10 +50,113 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
|
|||
super(props, context);
|
||||
this.state = {
|
||||
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 => {
|
||||
this.setState({
|
||||
query: newValue
|
||||
|
@ -58,6 +169,15 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
|
|||
|
||||
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
|
||||
return <div className="sql-control">
|
||||
<AceEditor
|
||||
|
@ -83,9 +203,14 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
|
|||
}}
|
||||
/>
|
||||
<div className="buttons">
|
||||
<Button rightIconName={IconNames.CARET_RIGHT} onClick={() => onRun(query)}>{isRune ? 'Rune' : 'Run'}</Button>
|
||||
<Button rightIconName={IconNames.CARET_RIGHT} onClick={() => onRun(query)}>
|
||||
{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",
|
||||
"lib": ["dom", "es2016"],
|
||||
"jsx": "react",
|
||||
"rootDir": "src",
|
||||
"rootDirs": ["lib","src"],
|
||||
|
||||
"outDir": "build"
|
||||
},
|
||||
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
"src/**/*.tsx",
|
||||
"lib/sql-function-doc.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.test.ts"
|
||||
|
|
Loading…
Reference in New Issue