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