master
Sven Slootweg 4 years ago
parent f018f8a355
commit 8e649d390d

@ -1,5 +1,5 @@
{
"extends": "@joepie91/eslint-config",
"extends": "@joepie91/eslint-config/react",
"parserOptions": {
"ecmaVersion": 2020
}

@ -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();

@ -9,15 +9,7 @@ let { select, onlyColumns, where, withRelations, withDerived, column, through, i
const astToQuery = require("../src/ast-to-query");
const optimizeAST = require("../src/ast/optimize");
const measureTime = require("../src/measure-time");
let optimizers = [
require("../src/optimizers/collapse-where"),
require("../src/optimizers/conditions-to-expressions"),
require("../src/optimizers/flatten-not-predicates"),
require("../src/optimizers/flatten-predicate-lists"),
require("../src/optimizers/arrayify-predicate-lists"),
];
const optimizers = require("../src/optimizers");
function withOwner() {
return withRelations({ owner: "owner_id" });
@ -44,6 +36,17 @@ try {
// condition: equals("bar")
// });
// 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", [
@ -53,9 +56,24 @@ try {
}),
where({
number_three: anyOf([ 42, column("number_one") ]),
number_four: 1337
number_four: null
})
]);
// FIXME: Test duplicate fields, in different WHEREs, eg. number_three
// 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
// })
// ]);
// query = select("projects", [

@ -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
})
]);

@ -32,22 +32,37 @@
"as-expression": "^1.0.0",
"assure-array": "^1.0.0",
"astw": "^2.2.0",
"browser-hrtime": "^1.1.6",
"chalk": "^4.1.0",
"classnames": "^2.2.6",
"debounce": "^1.2.0",
"debug": "^4.1.1",
"default-value": "^1.0.0",
"estree-assign-parent": "^1.0.0",
"flatten": "^1.0.3",
"map-obj": "^4.1.0",
"match-value": "^1.1.0",
"prismjs": "^1.20.0",
"scope-analyzer": "^2.0.5",
"split-filter": "^1.1.3",
"split-filter-n": "^1.1.2",
"syncpipe": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.10.4",
"@babel/preset-env": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@joepie91/eslint-config": "^1.1.0",
"babelify": "^10.0.0",
"benchmark": "^2.1.4",
"budo-express": "^1.0.2",
"eslint": "^7.3.1",
"nodemon": "^2.0.4"
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.0.5",
"express": "^4.17.1",
"nodemon": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-simple-code-editor": "^0.11.0"
}
}

@ -28,7 +28,8 @@ function createTimings(optimizers) {
let timings = {};
for (let optimizer of optimizers) {
timings[optimizer.name] = 0n;
// timings[optimizer.name] = 0n;
timings[optimizer.name] = 0;
}
return timings;
@ -165,7 +166,8 @@ module.exports = function optimizeTree(ast, optimizers) {
return handle(ast);
});
let timeSpentInOptimizers = Object.values(timings).reduce((sum, n) => sum + n, 0n);
// let timeSpentInOptimizers = Object.values(timings).reduce((sum, n) => sum + n, 0n);
let timeSpentInOptimizers = Object.values(timings).reduce((sum, n) => sum + n, 0);
if (rootNode !== RemoveNode) {
return {

@ -1,9 +1,16 @@
"use strict";
function hrtimeToNanoseconds(time) {
// If the numbers here become big enough to cause loss of precision, we probably have bigger issues than numeric precision...
return (time[0] * 1e9) + time[1];
}
module.exports = function measureTime(func) {
let startTime = process.hrtime.bigint();
// let startTime = process.hrtime.bigint();
let startTime = hrtimeToNanoseconds(process.hrtime());
let result = func();
let endTime = process.hrtime.bigint();
// let endTime = process.hrtime.bigint();
let endTime = hrtimeToNanoseconds(process.hrtime());
return {
value: result,

@ -11,7 +11,7 @@ module.exports = function (operations) {
const isExpression = require("../validators/operations/is-expression")(operations);
const isWhereObject = require("../validators/operations/is-where-object")(operations);
return function (_predicates) {
return function where(_predicates) {
let [ predicates ] = validateArguments(arguments, {
predicates: [ required, either([
[ isObjectType("sqlExpression") ],

@ -76,7 +76,6 @@ function createHandler(type) {
return function arrayifyPredicateList(node) {
// FIXME: Also detect non-parameterizable cases like raw SQL!
let tracker = createExpressionTracker();
console.log(node);
for (let item of node.items) {
// Only regular expressions can be arrayified, not {all,any}OfExpressions, which will get visited by this optimizer later on anyway

@ -28,7 +28,7 @@ module.exports = {
if (listOperation != null) {
return listOperation(node.items.map((item) => convertNode(item)));
} else if (typeOf(node) === "notCondition") {
return operations.notExpression(convertNode(node.condition));
return operations.not(convertNode(node.condition));
} else if (typeOf(node) === "condition") {
return operations.expression({
left: rootNode.left,

@ -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"),
];

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save