Skip to content

Commit c38ea39

Browse files
authored
feat(webui): Add support for building queries from guided SQL input boxes. (#1304)
1 parent 3351e14 commit c38ea39

File tree

3 files changed

+152
-12
lines changed

3 files changed

+152
-12
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {FormEvent} from "react";
2+
3+
import {
4+
Static,
5+
Type,
6+
} from "@sinclair/typebox";
7+
import {Value} from "@sinclair/typebox/value";
8+
9+
import {buildSearchQuery} from "../../../../sql-parser";
10+
import {handlePrestoQuerySubmit} from "./presto-search-requests";
11+
12+
13+
// eslint-disable-next-line no-warning-comments
14+
// TODO: Replace this file with actual SQL inputs
15+
16+
const TransformEmptyStringSchema = Type.Transform(
17+
Type.Optional(Type.Union([
18+
Type.String(),
19+
Type.Undefined(),
20+
]))
21+
)
22+
.Decode((value) => (
23+
("undefined" === typeof value || "" === value.trim()) ?
24+
// eslint-disable-next-line no-undefined
25+
undefined :
26+
value))
27+
.Encode((value) => value);
28+
29+
const BuildSearchQueryPropsSchema = Type.Object({
30+
/* eslint-disable sort-keys */
31+
selectItemList: Type.String(),
32+
relationList: Type.String(),
33+
booleanExpression: TransformEmptyStringSchema,
34+
sortItemList: TransformEmptyStringSchema,
35+
limitValue: TransformEmptyStringSchema,
36+
/* eslint-enable sort-keys */
37+
});
38+
39+
/**
40+
* Returns input boxes to test `buildSearchQuery`.
41+
*
42+
* @return
43+
*/
44+
const BuildSqlTestingInputs = () => {
45+
return (
46+
<form
47+
onSubmit={(ev: FormEvent<HTMLFormElement>) => {
48+
ev.preventDefault();
49+
const formData = new FormData(ev.target as HTMLFormElement);
50+
const props: Static<typeof BuildSearchQueryPropsSchema> = Value.Parse(
51+
BuildSearchQueryPropsSchema,
52+
Object.fromEntries(formData),
53+
);
54+
55+
const sqlString = buildSearchQuery(props);
56+
console.log(`SQL: ${sqlString}`);
57+
handlePrestoQuerySubmit({queryString: sqlString});
58+
}}
59+
>
60+
<label>select:</label>
61+
<input name={"selectItemList"}/>
62+
<label>from:</label>
63+
<input name={"relationList"}/>
64+
<label>where:</label>
65+
<input name={"booleanExpression"}/>
66+
<label>order:</label>
67+
<input name={"sortItemList"}/>
68+
<label>limit:</label>
69+
<input name={"limitValue"}/>
70+
<button type={"submit"}>Run</button>
71+
</form>
72+
);
73+
};
74+
75+
export {BuildSqlTestingInputs};

components/webui/client/src/pages/SearchPage/SearchControls/index.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
import usePrestoSearchState from "../SearchState/Presto";
66
import {PRESTO_SQL_INTERFACE} from "../SearchState/Presto/typings";
77
import NativeControls from "./NativeControls";
8+
import {BuildSqlTestingInputs} from "./Presto/BuildSqlTestingInputs";
89
import FreeformControls from "./Presto/FreeformControls";
910
import GuidedControls from "./Presto/GuidedControls";
1011

@@ -37,9 +38,12 @@ const SearchControls = () => {
3738
}
3839

3940
return (
40-
<form onSubmit={handleSubmit}>
41-
{controls}
42-
</form>
41+
<>
42+
<form onSubmit={handleSubmit}>
43+
{controls}
44+
</form>
45+
{isPrestoGuided && <BuildSqlTestingInputs/>}
46+
</>
4347
);
4448
};
4549

components/webui/client/src/sql-parser/index.ts

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class SyntaxErrorListener<TSymbol> extends ErrorListener<TSymbol> {
2020
_offendingSymbol: TSymbol,
2121
line: number,
2222
column: number,
23-
msg: string
23+
msg: string,
2424
) {
2525
throw new SyntaxError(`line ${line}:${column}: ${msg}`);
2626
}
@@ -46,24 +46,85 @@ class UpperCaseCharStream extends CharStream {
4646
}
4747
}
4848

49-
5049
/**
51-
* Validate a SQL string for syntax errors.
50+
* Creates a SQL parser for a given input string.
5251
*
53-
* @param sqlString
54-
* @throws {SyntaxError} with line, column, and message details if a syntax error is found.
52+
* @param input The SQL query string to be parsed.
53+
* @return The configured SQL parser instance ready to parse the input.
5554
*/
56-
const validate = (sqlString: string) => {
55+
const buildParser = (input: string): SqlBaseParser => {
5756
const syntaxErrorListener = new SyntaxErrorListener();
58-
const lexer = new SqlBaseLexer(new UpperCaseCharStream(sqlString));
57+
const lexer = new SqlBaseLexer(new UpperCaseCharStream(input));
5958
lexer.removeErrorListeners();
6059
lexer.addErrorListener(syntaxErrorListener);
6160
const parser = new SqlBaseParser(new CommonTokenStream(lexer));
6261
parser.removeErrorListeners();
6362
parser.addErrorListener(syntaxErrorListener);
64-
parser.singleStatement();
63+
64+
return parser;
65+
};
66+
67+
/**
68+
* Validate a SQL string for syntax errors.
69+
*
70+
* @param sqlString
71+
* @throws {SyntaxError} with line, column, and message details if a syntax error is found.
72+
*/
73+
const validate = (sqlString: string) => {
74+
buildParser(sqlString).singleStatement();
75+
};
76+
77+
interface BuildSearchQueryProps {
78+
selectItemList: string;
79+
relationList: string;
80+
booleanExpression?: string | undefined;
81+
sortItemList?: string | undefined;
82+
limitValue?: string | undefined;
83+
}
84+
85+
/**
86+
* Constructs a SQL search query string from a set of structured components.
87+
*
88+
* @param props
89+
* @param props.selectItemList
90+
* @param props.relationList
91+
* @param props.booleanExpression
92+
* @param props.sortItemList
93+
* @param props.limitValue
94+
* @return
95+
* @throws {Error} if the constructed SQL string is not valid.
96+
*/
97+
const buildSearchQuery = ({
98+
selectItemList,
99+
relationList,
100+
booleanExpression,
101+
sortItemList,
102+
limitValue,
103+
}: BuildSearchQueryProps): string => {
104+
let sqlString = `SELECT ${selectItemList} FROM ${relationList}`;
105+
if ("undefined" !== typeof booleanExpression) {
106+
sqlString += ` WHERE ${booleanExpression}`;
107+
}
108+
if ("undefined" !== typeof sortItemList) {
109+
sqlString += ` ORDER BY ${sortItemList}`;
110+
}
111+
if ("undefined" !== typeof limitValue) {
112+
sqlString += ` LIMIT ${limitValue}`;
113+
}
114+
115+
try {
116+
validate(sqlString);
117+
} catch (err: unknown) {
118+
throw new Error(`The constructed SQL is not valid: ${sqlString}`, {cause: err});
119+
}
120+
121+
return sqlString;
65122
};
66123

67124
export {
68-
SyntaxError, validate,
125+
buildSearchQuery,
126+
SyntaxError,
127+
validate,
69128
};
129+
130+
export type {BuildSearchQueryProps};

0 commit comments

Comments
 (0)