WIP
parent
f018f8a355
commit
8e649d390d
@ -0,0 +1,127 @@
|
|||||||
|
/* PrismJS 1.20.0
|
||||||
|
https://prismjs.com/download.html#themes=prism-okaidia&languages=clike+javascript */
|
||||||
|
/**
|
||||||
|
* okaidia theme for JavaScript, CSS and HTML
|
||||||
|
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||||
|
* @author ocodia
|
||||||
|
*/
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #f8f8f2;
|
||||||
|
background: none;
|
||||||
|
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #272822;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #8292a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.boolean,
|
||||||
|
.token.number {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #a6e22e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string,
|
||||||
|
.token.variable {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.function,
|
||||||
|
.token.class-name {
|
||||||
|
color: #e6db74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.keyword {
|
||||||
|
color: #66d9ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important {
|
||||||
|
color: #fd971f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>raqb demo</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<link rel="stylesheet" href="highlight.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="js/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,107 @@
|
|||||||
|
html {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
/* position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0; */
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3fr 3fr 2fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ast {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ast pre {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code, .ast {
|
||||||
|
border-right: 1px solid gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ast, .options, .result {
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result pre {
|
||||||
|
font-size: 16px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
opacity: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea, .code pre {
|
||||||
|
tab-size: 4;
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgb(90, 0, 0);
|
||||||
|
padding: 1em;
|
||||||
|
max-height: 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error strong {
|
||||||
|
border-bottom: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorMessage {
|
||||||
|
font-size: 16px;
|
||||||
|
/* font-weight: bold; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorStack {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optimizerOption {
|
||||||
|
margin: .3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optimizerOption input {
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optimizerOption label {
|
||||||
|
font-size: .9em;
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optimizerOption label .time {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 3.7em;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const budoExpress = require("budo-express");
|
||||||
|
|
||||||
|
budoExpress({
|
||||||
|
port: 3000,
|
||||||
|
expressApp: require("./src/server/app"),
|
||||||
|
basePath: __dirname,
|
||||||
|
entryFiles: "src/index.jsx",
|
||||||
|
staticPath: "dist",
|
||||||
|
bundlePath: "js/bundle.js",
|
||||||
|
livereloadPattern: "**/*.{css,html,js,svg}",
|
||||||
|
browserify: {
|
||||||
|
extensions: [".jsx"],
|
||||||
|
transform: [
|
||||||
|
["babelify", {
|
||||||
|
presets: ["@babel/preset-env", "@babel/preset-react"],
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,244 @@
|
|||||||
|
"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: 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"));
|
@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
|
||||||
|
module.exports = express();
|
@ -0,0 +1,26 @@
|
|||||||
|
// 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: allOf([
|
||||||
|
niceNumbers,
|
||||||
|
lessThan(2)
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
where({
|
||||||
|
number_three: anyOf([ 42, column("number_one") ]),
|
||||||
|
number_four: 1337
|
||||||
|
})
|
||||||
|
]);
|
@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
require("./collapse-where"),
|
||||||
|
require("./conditions-to-expressions"),
|
||||||
|
require("./flatten-not-predicates"),
|
||||||
|
require("./flatten-predicate-lists"),
|
||||||
|
require("./arrayify-predicate-lists"),
|
||||||
|
];
|
Loading…
Reference in New Issue