123 lines
3.7 KiB
TypeScript
123 lines
3.7 KiB
TypeScript
import {
|
|
collectNodesOfType,
|
|
findNodeOfType,
|
|
ParseTree,
|
|
replaceNodesMatching,
|
|
} from "$sb/lib/tree.ts";
|
|
|
|
// @ts-ignore auto generated
|
|
import { ParsedQuery, QueryFilter } from "$sb/lib/query.ts";
|
|
|
|
export function parseQuery(queryTree: ParseTree): ParsedQuery {
|
|
// const n = lezerToParseTree(query, parser.parse(query).topNode);
|
|
// Clean the tree a bit
|
|
replaceNodesMatching(queryTree, (n) => {
|
|
if (!n.type) {
|
|
const trimmed = n.text!.trim();
|
|
if (!trimmed) {
|
|
return null;
|
|
}
|
|
n.text = trimmed;
|
|
}
|
|
});
|
|
|
|
// console.log("Parsed", JSON.stringify(n, null, 2));
|
|
const queryNode = queryTree.children![0];
|
|
const parsedQuery: ParsedQuery = {
|
|
table: queryNode.children![0].children![0].text!,
|
|
filter: [],
|
|
ordering: [],
|
|
};
|
|
|
|
const orderByNodes = collectNodesOfType(queryNode, "OrderClause");
|
|
for (const orderByNode of orderByNodes) {
|
|
const nameNode = findNodeOfType(orderByNode, "Name");
|
|
const orderBy = nameNode!.children![0].text!;
|
|
const orderNode = findNodeOfType(orderByNode, "OrderDirection");
|
|
const orderDesc = orderNode
|
|
? orderNode.children![0].text! === "desc"
|
|
: false;
|
|
parsedQuery.ordering.push({ orderBy, orderDesc });
|
|
}
|
|
/**
|
|
* @deprecated due to PR #387
|
|
* We'll take the first ordering and send that as the deprecated
|
|
* fields orderBy and orderDesc. This way it will be backward
|
|
* Plugs using the old ParsedQuery.
|
|
* Remove this block completely when ParsedQuery no longer have
|
|
* those two fields
|
|
*/
|
|
if (parsedQuery.ordering.length > 0) {
|
|
parsedQuery.orderBy = parsedQuery.ordering[0].orderBy;
|
|
parsedQuery.orderDesc = parsedQuery.ordering[0].orderDesc;
|
|
}
|
|
/** @end-deprecation due to PR #387 */
|
|
|
|
const limitNode = findNodeOfType(queryNode, "LimitClause");
|
|
if (limitNode) {
|
|
const nameNode = findNodeOfType(limitNode, "Number");
|
|
parsedQuery.limit = valueNodeToVal(nameNode!);
|
|
}
|
|
|
|
const filterNodes = collectNodesOfType(queryNode, "FilterExpr");
|
|
for (const filterNode of filterNodes) {
|
|
let val: any = undefined;
|
|
const valNode = filterNode.children![2].children![0];
|
|
val = valueNodeToVal(valNode);
|
|
const f: QueryFilter = {
|
|
prop: filterNode.children![0].children![0].text!,
|
|
op: filterNode.children![1].text!,
|
|
value: val,
|
|
};
|
|
parsedQuery.filter.push(f);
|
|
}
|
|
const selectNode = findNodeOfType(queryNode, "SelectClause");
|
|
if (selectNode) {
|
|
parsedQuery.select = [];
|
|
collectNodesOfType(selectNode, "Name").forEach((t) => {
|
|
parsedQuery.select!.push(t.children![0].text!);
|
|
});
|
|
}
|
|
|
|
const renderNode = findNodeOfType(queryNode, "RenderClause");
|
|
if (renderNode) {
|
|
let renderNameNode = findNodeOfType(renderNode, "PageRef");
|
|
if (!renderNameNode) {
|
|
renderNameNode = findNodeOfType(renderNode, "String");
|
|
}
|
|
parsedQuery.render = valueNodeToVal(renderNameNode!);
|
|
}
|
|
|
|
return parsedQuery;
|
|
}
|
|
|
|
export function valueNodeToVal(valNode: ParseTree): any {
|
|
switch (valNode.type) {
|
|
case "Number":
|
|
return +valNode.children![0].text!;
|
|
case "Bool":
|
|
return valNode.children![0].text! === "true";
|
|
case "Null":
|
|
return null;
|
|
case "Name":
|
|
return valNode.children![0].text!;
|
|
case "Regex": {
|
|
const val = valNode.children![0].text!;
|
|
return val.substring(1, val.length - 1);
|
|
}
|
|
case "String": {
|
|
const stringVal = valNode.children![0].text!;
|
|
return stringVal.substring(1, stringVal.length - 1);
|
|
}
|
|
case "PageRef": {
|
|
const pageRefVal = valNode.children![0].text!;
|
|
return pageRefVal.substring(2, pageRefVal.length - 2);
|
|
}
|
|
case "List": {
|
|
return collectNodesOfType(valNode, "Value").map((t) =>
|
|
valueNodeToVal(t.children![0])
|
|
);
|
|
}
|
|
}
|
|
}
|