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