2022-10-14 13:11:33 +00:00
|
|
|
import {
|
|
|
|
addParentPointers,
|
|
|
|
collectNodesMatching,
|
|
|
|
ParseTree,
|
|
|
|
renderToText,
|
2022-12-14 19:04:20 +00:00
|
|
|
replaceNodesMatching,
|
2022-10-28 14:17:40 +00:00
|
|
|
} from "$sb/lib/tree.ts";
|
2022-10-14 13:11:33 +00:00
|
|
|
|
|
|
|
export const queryRegex =
|
|
|
|
/(<!--\s*#query\s+(.+?)-->)(.+?)(<!--\s*\/query\s*-->)/gs;
|
|
|
|
|
|
|
|
export const directiveStartRegex = /<!--\s*#([\w\-]+)\s+(.+?)-->/s;
|
|
|
|
|
|
|
|
export const directiveEndRegex = /<!--\s*\/([\w\-]+)\s*-->/s;
|
|
|
|
|
|
|
|
export type QueryFilter = {
|
|
|
|
op: string;
|
|
|
|
prop: string;
|
|
|
|
value: any;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type ParsedQuery = {
|
|
|
|
table: string;
|
|
|
|
orderBy?: string;
|
|
|
|
orderDesc?: boolean;
|
|
|
|
limit?: number;
|
|
|
|
filter: QueryFilter[];
|
|
|
|
select?: string[];
|
|
|
|
render?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function applyQuery<T>(parsedQuery: ParsedQuery, records: T[]): T[] {
|
|
|
|
let resultRecords: any[] = [];
|
|
|
|
if (parsedQuery.filter.length === 0) {
|
|
|
|
resultRecords = records.slice();
|
|
|
|
} else {
|
|
|
|
recordLoop:
|
|
|
|
for (const record of records) {
|
|
|
|
const recordAny: any = record;
|
2022-10-15 17:02:56 +00:00
|
|
|
for (const { op, prop, value } of parsedQuery.filter) {
|
2022-10-14 13:11:33 +00:00
|
|
|
switch (op) {
|
|
|
|
case "=": {
|
|
|
|
const recordPropVal = recordAny[prop];
|
|
|
|
if (Array.isArray(recordPropVal) && !Array.isArray(value)) {
|
|
|
|
// Record property is an array, and value is a scalar: find the value in the array
|
|
|
|
if (!recordPropVal.includes(value)) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
} else if (Array.isArray(recordPropVal) && Array.isArray(value)) {
|
|
|
|
// Record property is an array, and value is an array: find the value in the array
|
|
|
|
if (!recordPropVal.some((v) => value.includes(v))) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
} else if (!(recordPropVal == value)) {
|
|
|
|
// Both are scalars: exact value
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "!=":
|
|
|
|
if (!(recordAny[prop] != value)) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "<":
|
|
|
|
if (!(recordAny[prop] < value)) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "<=":
|
|
|
|
if (!(recordAny[prop] <= value)) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ">":
|
|
|
|
if (!(recordAny[prop] > value)) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ">=":
|
|
|
|
if (!(recordAny[prop] >= value)) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "=~":
|
|
|
|
// TODO: Cache regexps somehow
|
|
|
|
if (!new RegExp(value).exec(recordAny[prop])) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "!=~":
|
|
|
|
if (new RegExp(value).exec(recordAny[prop])) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "in":
|
|
|
|
if (!value.includes(recordAny[prop])) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resultRecords.push(recordAny);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now the sorting
|
|
|
|
if (parsedQuery.orderBy) {
|
|
|
|
resultRecords = resultRecords.sort((a: any, b: any) => {
|
|
|
|
const orderBy = parsedQuery.orderBy!;
|
|
|
|
const orderDesc = parsedQuery.orderDesc!;
|
|
|
|
if (a[orderBy] === b[orderBy]) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a[orderBy] < b[orderBy]) {
|
|
|
|
return orderDesc ? 1 : -1;
|
|
|
|
} else {
|
|
|
|
return orderDesc ? -1 : 1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (parsedQuery.limit) {
|
|
|
|
resultRecords = resultRecords.slice(0, parsedQuery.limit);
|
|
|
|
}
|
|
|
|
if (parsedQuery.select) {
|
|
|
|
resultRecords = resultRecords.map((rec) => {
|
2022-10-15 17:02:56 +00:00
|
|
|
const newRec: any = {};
|
|
|
|
for (const k of parsedQuery.select!) {
|
2022-10-14 13:11:33 +00:00
|
|
|
newRec[k] = rec[k];
|
|
|
|
}
|
|
|
|
return newRec;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return resultRecords;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function removeQueries(pt: ParseTree) {
|
2022-12-14 19:04:20 +00:00
|
|
|
replaceNodesMatching(pt, (t) => {
|
|
|
|
if (t.type !== "Directive") {
|
|
|
|
return;
|
2022-10-14 13:11:33 +00:00
|
|
|
}
|
2022-12-14 19:04:20 +00:00
|
|
|
const renderedText = renderToText(t);
|
|
|
|
return {
|
|
|
|
from: t.from,
|
|
|
|
to: t.to,
|
2022-10-14 13:11:33 +00:00
|
|
|
text: new Array(renderedText.length + 1).join(" "),
|
2022-12-14 19:04:20 +00:00
|
|
|
};
|
2022-10-14 13:11:33 +00:00
|
|
|
});
|
|
|
|
}
|