Lua Queries: make object attributes variables

pull/1210/head
Zef Hemel 2025-01-13 21:17:08 +01:00
parent bf6a34f82c
commit cbf227fa49
5 changed files with 42 additions and 24 deletions

View File

@ -257,7 +257,7 @@ export function evalExpression(
if (!findFromClause) { if (!findFromClause) {
throw new LuaRuntimeError("No from clause found", sf.withCtx(e.ctx)); throw new LuaRuntimeError("No from clause found", sf.withCtx(e.ctx));
} }
const objectVariable = findFromClause.name || "_"; const objectVariable = findFromClause.name;
const objectExpression = findFromClause.expression; const objectExpression = findFromClause.expression;
return Promise.resolve(evalExpression(objectExpression, env, sf)).then( return Promise.resolve(evalExpression(objectExpression, env, sf)).then(
async (collection: LuaValue) => { async (collection: LuaValue) => {

View File

@ -124,11 +124,10 @@ Deno.test("ArrayQueryCollection", async () => {
assertEquals(result8[2], "Jane Doe"); assertEquals(result8[2], "Jane Doe");
assertEquals(result8[3], "Bob Johnson"); assertEquals(result8[3], "Bob Johnson");
// Test select with native function // Test select with native function and implicit object variable
const result9 = await collection2.query( const result9 = await collection2.query(
{ {
objectVariable: "p", select: parseExpressionString("build_name(firstName, lastName)"),
select: parseExpressionString("build_name(p.firstName, p.lastName)"),
}, },
rootEnv, rootEnv,
LuaStackFrame.lostFrame, LuaStackFrame.lostFrame,

View File

@ -1,12 +1,31 @@
import type { LuaExpression } from "$common/space_lua/ast.ts"; import type { LuaExpression } from "$common/space_lua/ast.ts";
import { LuaEnv, type LuaStackFrame } from "$common/space_lua/runtime.ts"; import {
LuaEnv,
luaGet,
luaKeys,
type LuaStackFrame,
} from "$common/space_lua/runtime.ts";
import { evalExpression } from "$common/space_lua/eval.ts"; import { evalExpression } from "$common/space_lua/eval.ts";
import { asyncQuickSort } from "$common/space_lua/util.ts"; import { asyncQuickSort } from "$common/space_lua/util.ts";
import type { DataStore } from "$lib/data/datastore.ts"; import type { DataStore } from "$lib/data/datastore.ts";
function buildItemEnv(objectVariable: string, item: any, env: LuaEnv): LuaEnv { function buildItemEnv(
objectVariable: string | undefined,
item: any,
env: LuaEnv,
sf: LuaStackFrame,
): LuaEnv {
const itemEnv = new LuaEnv(env); const itemEnv = new LuaEnv(env);
itemEnv.setLocal(objectVariable, item); if (!objectVariable) {
// Inject all item keys as variables
for (const key of luaKeys(item)) {
itemEnv.setLocal(key, luaGet(item, key, sf));
}
// As well as _
itemEnv.setLocal("_", item);
} else {
itemEnv.setLocal(objectVariable, item);
}
return itemEnv; return itemEnv;
} }
@ -19,7 +38,7 @@ export type LuaOrderBy = {
* Represents a query for a collection * Represents a query for a collection
*/ */
export type LuaCollectionQuery = { export type LuaCollectionQuery = {
objectVariable: string; objectVariable?: string;
// The filter expression evaluated with Lua // The filter expression evaluated with Lua
where?: LuaExpression; where?: LuaExpression;
// The order by expression evaluated with Lua // The order by expression evaluated with Lua
@ -55,7 +74,7 @@ export class ArrayQueryCollection<T> implements LuaQueryCollection {
// Filter the array // Filter the array
for (const item of this.array) { for (const item of this.array) {
const itemEnv = buildItemEnv(query.objectVariable, item, env); const itemEnv = buildItemEnv(query.objectVariable, item, env, sf);
if (query.where && !await evalExpression(query.where, itemEnv, sf)) { if (query.where && !await evalExpression(query.where, itemEnv, sf)) {
continue; continue;
} }
@ -77,8 +96,8 @@ async function applyTransforms(
result = await asyncQuickSort(result, async (a, b) => { result = await asyncQuickSort(result, async (a, b) => {
// Compare each orderBy clause until we find a difference // Compare each orderBy clause until we find a difference
for (const { expr, desc } of query.orderBy!) { for (const { expr, desc } of query.orderBy!) {
const aEnv = buildItemEnv(query.objectVariable, a, env); const aEnv = buildItemEnv(query.objectVariable, a, env, sf);
const bEnv = buildItemEnv(query.objectVariable, b, env); const bEnv = buildItemEnv(query.objectVariable, b, env, sf);
const aVal = await evalExpression(expr, aEnv, sf); const aVal = await evalExpression(expr, aEnv, sf);
const bVal = await evalExpression(expr, bEnv, sf); const bVal = await evalExpression(expr, bEnv, sf);
@ -99,7 +118,7 @@ async function applyTransforms(
if (query.select) { if (query.select) {
const newResult = []; const newResult = [];
for (const item of result) { for (const item of result) {
const itemEnv = buildItemEnv(query.objectVariable, item, env); const itemEnv = buildItemEnv(query.objectVariable, item, env, sf);
newResult.push(await evalExpression(query.select, itemEnv, sf)); newResult.push(await evalExpression(query.select, itemEnv, sf));
} }
result = newResult; result = newResult;
@ -134,7 +153,7 @@ export class DataStoreQueryCollection implements LuaQueryCollection {
) { ) {
// Enrich // Enrich
this.dataStore.enrichObject(value); this.dataStore.enrichObject(value);
const itemEnv = buildItemEnv(query.objectVariable, value, env); const itemEnv = buildItemEnv(query.objectVariable, value, env, sf);
if (query.where && !await evalExpression(query.where, itemEnv, sf)) { if (query.where && !await evalExpression(query.where, itemEnv, sf)) {
continue; continue;
} }

View File

@ -61,8 +61,8 @@ Space Lua has a feature called [[Space Lua/Lua Integrated Query]], which integra
${query[[ ${query[[
from tag "page" from tag "page"
order by _.lastModified desc order by lastModified desc
select _.name select name
limit 3 limit 3
]]} ]]}

View File

@ -44,7 +44,7 @@ And the shorter:
from <<expression>> from <<expression>>
implicitly binding each item to the variable `_`. implicitly binding each item to the variable `_` as well as making all attributes directly available as variables.
Example without variable binding: Example without variable binding:
${query[[from {1, 2, 3} select _]]} ${query[[from {1, 2, 3} select _]]}
@ -52,8 +52,8 @@ ${query[[from {1, 2, 3} select _]]}
With variable binding: With variable binding:
${query[[from n = {1, 2, 3} select n]]} ${query[[from n = {1, 2, 3} select n]]}
A more realist example using `tag`: A more realistic example using `tag`:
${query[[from t = tag "page" limit 3 select t.name]]} ${query[[from tag "page" order by lastModified select name limit 3]]}
## `where <expression>` ## `where <expression>`
The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result. The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result.
@ -64,7 +64,7 @@ ${query[[from {1, 2, 3, 4, 5} where _ > 2]]}
Or to select all pages tagged with `#meta`: Or to select all pages tagged with `#meta`:
${query[[from tag "page" where table.includes(_.tags, "meta")]]} ${query[[from tag "page" where table.includes(tags, "meta")]]}
## `order by <expression> [desc]` ## `order by <expression> [desc]`
The `order by` clause allows you to sort data, when `desc` is specified it reverts the sort order. The `order by` clause allows you to sort data, when `desc` is specified it reverts the sort order.
@ -72,8 +72,8 @@ The `order by` clause allows you to sort data, when `desc` is specified it rever
As an example, the last 3 modified pages: As an example, the last 3 modified pages:
${query[[ ${query[[
from tag "page" from tag "page"
order by _.lastModified desc order by lastModified desc
select _.name select name
limit 3 limit 3
]]} ]]}
@ -101,10 +101,10 @@ ${query[[from tag "page" select _.name limit 3]]}
You can also return tables or other complex values: You can also return tables or other complex values:
${query[[ ${query[[
from tag "page" from p = tag "page"
select { select {
name = _.name, name = p.name,
modified = _.lastModified modified = p.lastModified
} }
limit 3 limit 3
]]} ]]}