1
0
silverbullet/plug-api/lib/query.ts

158 lines
4.4 KiB
TypeScript
Raw Normal View History

2022-12-15 11:59:38 +00:00
import { ParseTree, renderToText, replaceNodesMatching } 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 QueryOrdering = {
orderBy: string;
orderDesc: boolean;
};
2022-10-14 13:11:33 +00:00
export type ParsedQuery = {
table: string;
limit?: number;
ordering: QueryOrdering[];
/** @deprecated Please use ordering.
* Deprecated due to PR #387
* Currently holds ordering[0] if exists
*/
2022-10-14 13:11:33 +00:00
orderBy?: string;
/** @deprecated Please use ordering.
* Deprecated due to PR #387
* Currently holds ordering[0] if exists
*/
2022-10-14 13:11:33 +00:00
orderDesc?: boolean;
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);
}
}
if (parsedQuery.ordering.length > 0) {
resultRecords = resultRecords.sort((a: any, b: any) => {
for (const { orderBy, orderDesc } of parsedQuery.ordering) {
if (a[orderBy] < b[orderBy] || a[orderBy] === undefined) {
return orderDesc ? 1 : -1;
}
if (a[orderBy] > b[orderBy] || b[orderBy] === undefined) {
return orderDesc ? -1 : 1;
}
// Consider them equal. This way helps with comparing arrays (like tags)
2022-10-14 13:11:33 +00:00
}
return 0;
2022-10-14 13:11:33 +00:00
});
}
2022-10-14 13:11:33 +00:00
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) {
replaceNodesMatching(pt, (t) => {
if (t.type !== "Directive") {
return;
2022-10-14 13:11:33 +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-10-14 13:11:33 +00:00
});
}