You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
6.1 KiB
JavaScript

"use strict";
process.hrtime = require('browser-hrtime');
const React = require("react");
const ReactDOM = require("react-dom");
const debounce = require("debounce");
const util = require("util");
const { AggregrateValidationError } = require("@validatem/core");
const Editor = require("react-simple-code-editor").default;
const classnames = require("classnames");
const { highlight, languages } = require("prismjs");
require("prismjs/components/prism-sql.js");
require("prismjs/components/prism-plsql.js");
const operations = require("../../src/operations");
const optimizeAST = require("../../src/ast/optimize");
const astToQuery = require("../../src/ast-to-query");
const optimizers = require("../../src/optimizers");
function evaluateCode(code) {
let {
select, onlyColumns, addColumns, alias,
where, column, foreignColumn, table,
value, parameter,
not, anyOf, allOf,
lessThan, moreThan, equals,
expression, unsafeSQL,
} = operations;
let query;
try {
eval(code);
return {
success: true,
ast: query
};
} catch (error) {
return {
success: false,
error: error
};
}
}
function processCode(code, activeOptimizers) {
let result = evaluateCode(code);
if (!result.success) {
return result;
} else {
try {
let { timings, ast } = optimizeAST(result.ast, optimizers.filter((optimizer) => activeOptimizers.has(optimizer.name)));
let query = astToQuery(ast);
return {
success: true,
query: query,
ast: ast,
timings: timings
};
} catch (error) {
return {
success: false,
error: error
};
}
}
}
let sampleCode = `
// Edit me!
/* Available functions:
select, onlyColumns, addColumns, alias,
where, column, foreignColumn, table,
value, parameter,
not, anyOf, allOf,
lessThan, moreThan, equals,
expression, unsafeSQL
*/
let niceNumbers = anyOf([ 1, 2, 3 ]);
query = select("projects", [
where({
number_one: niceNumbers,
number_two: niceNumbers
}),
where({
number_three: anyOf([ 42, column("number_one") ]),
number_four: moreThan(1337)
})
]);
`.trim();
function JSEditor({ onChanged }) {
let [ code, setCode ] = React.useState(sampleCode);
let [ maybeRunCode, setMaybeRunCode ] = React.useState();
React.useEffect(() => {
setMaybeRunCode(() => debounce((code) => {
onChanged(code);
}, 200));
}, [ onChanged ]);
return (
<Editor
value={code}
onValueChange={(value) => {
setCode(value);
maybeRunCode(value);
}}
highlight={(code) => highlight(code, languages.js)}
padding={10}
insertSpaces={false}
style={{
// fontFamily: '"Fira code", "Fira Mono", monospace',
// fontSize: 16,
fontFamily: "monospace",
fontSize: 16,
height: "100%",
tabSize: 4
}}
/>
);
}
function HighlightedCode({ code, language }) {
return <pre dangerouslySetInnerHTML={{ __html: highlight(code, languages[language]) }} />;
}
function HighlightedData({ data }) {
return <HighlightedCode code={util.inspect(data, { depth: null })} language="js" />;
}
function App() {
let [ result, setResult ] = React.useState(null);
let [ code, setCode ] = React.useState(sampleCode);
let [ lastGoodResult, setLastGoodResult ] = React.useState(null);
let [ activeOptimizers, setActiveOptimizers ] = React.useState([ new Set(optimizers.map((optimizer) => optimizer.name)) ]);
React.useEffect(() => {
if (result != null && result.success === true) {
setLastGoodResult(result);
} else if (result != null && result.success === false) {
console.error(result.error);
}
}, [ result ]);
React.useEffect(() => {
if (code != null) {
setResult(processCode(code, activeOptimizers[0]));
}
}, [ code, activeOptimizers ]);
let configurableOptimizers = new Set(optimizers
.filter((optimizer) => !optimizer.category.includes("normalization"))
.map((optimizer) => optimizer.name));
return (<>
<div className="layout">
<div className={classnames("result", { failed: (result != null && !result.success) })}>
{(lastGoodResult != null)
? <>
<HighlightedCode
code={lastGoodResult.query.query}
language="plsql"
/>
<HighlightedData data={lastGoodResult.query.params} />
</>
: "Result goes here..."
}
</div>
<div className="editor">
<div className="code">
<JSEditor onChanged={(code) => {
setCode(code);
}} />
</div>
<div className={classnames("ast", { failed: (result != null && !result.success) })}>
{(lastGoodResult != null)
? <HighlightedData data={result.ast} />
// ? <HighlightedData data={result} />
: "Please wait..."
}
</div>
<div className={classnames("options", { failed: (result != null && !result.success) })}>
<h1>Options</h1>
{optimizers.map((optimizer) => {
let configurable = configurableOptimizers.has(optimizer.name);
return <div className="optimizerOption" key={optimizer.name}>
<input
type="checkbox"
name={optimizer.name}
id={optimizer.name}
checked={activeOptimizers[0].has(optimizer.name)}
disabled={!configurable}
onChange={() => {
if (activeOptimizers[0].has(optimizer.name)) {
activeOptimizers[0].delete(optimizer.name);
} else {
activeOptimizers[0].add(optimizer.name);
}
setActiveOptimizers([ activeOptimizers[0] ]);
}}
/>
<label htmlFor={optimizer.name}>
<span className="time">
{(lastGoodResult != null && lastGoodResult.timings[optimizer.name] != null)
? `${(lastGoodResult.timings[optimizer.name] / 1e6).toFixed()} ms`
: "? ms"
}
</span>
{optimizer.name}
{(configurable)
? null
: " (required)"
}
</label>
</div>;
})}
</div>
</div>
</div>
{(result != null && result.success === false)
? <div className="error">
<strong>Oh no!</strong>
<pre class="errorMessage">{result.error.message}</pre>
{(result.error instanceof AggregrateValidationError)
? null
: <pre class="errorStack">{result.error.stack}</pre>
}
</div>
: null
}
</>);
}
ReactDOM.render(<App />, document.querySelector("#app"));