diff --git a/docs/content/querying/sql.md b/docs/content/querying/sql.md index 883904ced99e..a03a712e1345 100644 --- a/docs/content/querying/sql.md +++ b/docs/content/querying/sql.md @@ -22,6 +22,13 @@ title: "SQL" ~ under the License. --> + + # SQL
diff --git a/web-console/.gitignore b/web-console/.gitignore index 1e0bebd4e2ed..7228ec055320 100644 --- a/web-console/.gitignore +++ b/web-console/.gitignore @@ -9,6 +9,8 @@ coordinator-console/ pages/ index.html +lib/sql-function-doc.ts + .tscache tscommand-*.tmp.txt diff --git a/web-console/script/build b/web-console/script/build index 75371226c92d..cbc9092b32e4 100755 --- a/web-console/script/build +++ b/web-console/script/build @@ -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 diff --git a/web-console/script/create-sql-function-doc b/web-console/script/create-sql-function-doc new file mode 100755 index 000000000000..146a39e147a3 --- /dev/null +++ b/web-console/script/create-sql-function-doc @@ -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" \ No newline at end of file diff --git a/web-console/src/components/sql-control.scss b/web-console/src/components/sql-control.scss new file mode 100644 index 000000000000..9e55c5a76157 --- /dev/null +++ b/web-console/src/components/sql-control.scss @@ -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; + } +} \ No newline at end of file diff --git a/web-console/src/components/sql-control.tsx b/web-console/src/components/sql-control.tsx index 96dd780a449b..a2d21b577670 100644 --- a/web-console/src/components/sql-control.tsx +++ b/web-console/src/components/sql-control.tsx @@ -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 { initSql: string | null; @@ -35,6 +42,7 @@ export interface SqlControlProps extends React.Props { export interface SqlControlState { query: string; autoCompleteOn: boolean; + autoCompleteLoading: boolean; } export class SqlControl extends React.Component { @@ -42,8 +50,111 @@ export class SqlControl extends React.Component =>{ + 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 => { + 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(( +
+
{functionName}
+
+
Syntax:
+
{item.syntax}
+
+
Description:
+
{item.description}
+
+ )) + } + } + }; + 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 => { @@ -58,6 +169,15 @@ export class SqlControl extends React.Component + this.setState({autoCompleteOn: !autoCompleteOn})} + /> +
; + // Set the key in the AceEditor to force a rebind and prevent an error that happens otherwise return
- + + +
-
+ ; } } diff --git a/web-console/tsconfig.json b/web-console/tsconfig.json index 56fdba07dfb5..a445cfaa71c2 100644 --- a/web-console/tsconfig.json +++ b/web-console/tsconfig.json @@ -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"