|
|
|
"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"));
|