159 lines
4.1 KiB
TypeScript
159 lines
4.1 KiB
TypeScript
import type { FunctionMap, KV, Query, QueryExpression } from "../types.ts";
|
|
import { evalQueryExpression } from "./query_expression.ts";
|
|
|
|
/**
|
|
* Looks for an attribute assignment in the expression, and returns the expression assigned to the attribute or throws an error when not found
|
|
* Side effect: effectively removes the attribute assignment from the expression (by replacing it with true = true)
|
|
*/
|
|
export function liftAttributeFilter(
|
|
expression: QueryExpression | undefined,
|
|
attributeName: string,
|
|
): QueryExpression {
|
|
if (!expression) {
|
|
throw new Error(`Cannot find attribute assignment for ${attributeName}`);
|
|
}
|
|
switch (expression[0]) {
|
|
case "=": {
|
|
if (expression[1][0] === "attr" && expression[1][1] === attributeName) {
|
|
const val = expression[2];
|
|
// Remove the filter by changing it to true = true
|
|
expression[1] = ["boolean", true];
|
|
expression[2] = ["boolean", true];
|
|
return val;
|
|
}
|
|
break;
|
|
}
|
|
case "and":
|
|
case "or": {
|
|
const newOp1 = liftAttributeFilter(expression[1], attributeName);
|
|
if (newOp1) {
|
|
return newOp1;
|
|
}
|
|
const newOp2 = liftAttributeFilter(expression[2], attributeName);
|
|
if (newOp2) {
|
|
return newOp2;
|
|
}
|
|
throw new Error(`Cannot find attribute assignment for ${attributeName}`);
|
|
}
|
|
}
|
|
throw new Error(`Cannot find attribute assignment for ${attributeName}`);
|
|
}
|
|
|
|
export async function applyQuery<T>(
|
|
query: Query,
|
|
allItems: T[],
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<T[]> {
|
|
// Filter
|
|
if (query.filter) {
|
|
const filteredItems: T[] = [];
|
|
for (const item of allItems) {
|
|
if (
|
|
await evalQueryExpression(
|
|
query.filter,
|
|
item,
|
|
variables,
|
|
functionMap,
|
|
)
|
|
) {
|
|
filteredItems.push(item);
|
|
}
|
|
}
|
|
allItems = filteredItems;
|
|
}
|
|
// Add dummy keys, then remove them
|
|
return (await applyQueryNoFilterKV(
|
|
query,
|
|
allItems.map((v) => ({ key: [], value: v })),
|
|
variables,
|
|
functionMap,
|
|
)).map((v) => v.value);
|
|
}
|
|
|
|
export async function applyQueryNoFilterKV(
|
|
query: Query,
|
|
allItems: KV[],
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<KV[]> {
|
|
// Order by
|
|
if (query.orderBy) {
|
|
allItems = allItems.sort((a, b) => {
|
|
const aVal = a.value;
|
|
const bVal = b.value;
|
|
for (const { expr, desc } of query.orderBy!) {
|
|
const evalA = evalQueryExpression(
|
|
expr,
|
|
aVal,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
if (evalA instanceof Promise) {
|
|
throw new Error("Cannot order by a promise");
|
|
}
|
|
const evalB = evalQueryExpression(
|
|
expr,
|
|
bVal,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
if (evalB instanceof Promise) {
|
|
throw new Error("Cannot order by a promise");
|
|
}
|
|
if (
|
|
evalA < evalB || evalA === undefined
|
|
) {
|
|
return desc ? 1 : -1;
|
|
}
|
|
if (
|
|
evalA > evalB || evalB === undefined
|
|
) {
|
|
return desc ? -1 : 1;
|
|
}
|
|
}
|
|
// Consider them equal. This helps with comparing arrays (like tags)
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
if (query.select) {
|
|
for (let i = 0; i < allItems.length; i++) {
|
|
const rec = allItems[i].value;
|
|
const newRec: any = {};
|
|
for (const { name, expr } of query.select) {
|
|
newRec[name] = expr
|
|
? await evalQueryExpression(expr, rec, variables, functionMap)
|
|
: rec[name];
|
|
}
|
|
allItems[i].value = newRec;
|
|
}
|
|
}
|
|
if (query.distinct) {
|
|
// Remove duplicates
|
|
const valueSet = new Set<string>();
|
|
const uniqueItems: KV[] = [];
|
|
for (const item of allItems) {
|
|
const value = JSON.stringify(item.value);
|
|
if (!valueSet.has(value)) {
|
|
valueSet.add(value);
|
|
uniqueItems.push(item);
|
|
}
|
|
}
|
|
allItems = uniqueItems;
|
|
}
|
|
|
|
if (query.limit) {
|
|
const limit = await evalQueryExpression(
|
|
query.limit,
|
|
{},
|
|
variables,
|
|
functionMap,
|
|
);
|
|
if (allItems.length > limit) {
|
|
allItems = allItems.slice(0, limit);
|
|
}
|
|
}
|
|
return allItems;
|
|
}
|