@silverbulletmd/silverbullet 2.5.3 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/client/asset_bundle/bundle.ts +3 -9
- package/client/data/datastore.ts +4 -5
- package/client/markdown_parser/constants.ts +3 -2
- package/client/plugos/hooks/code_widget.ts +3 -5
- package/client/plugos/hooks/command.ts +8 -8
- package/client/plugos/hooks/document_editor.ts +10 -12
- package/client/plugos/hooks/event.ts +33 -36
- package/client/plugos/hooks/mq.ts +17 -17
- package/client/plugos/hooks/plug_namespace.ts +3 -5
- package/client/plugos/hooks/slash_command.ts +12 -27
- package/client/plugos/hooks/syscall.ts +3 -3
- package/client/plugos/manifest_cache.ts +22 -15
- package/client/plugos/plug.ts +2 -5
- package/client/plugos/plug_compile.ts +67 -65
- package/client/plugos/protocol.ts +28 -28
- package/client/plugos/proxy_fetch.ts +7 -6
- package/client/plugos/sandboxes/worker_sandbox.ts +16 -15
- package/client/plugos/syscalls/asset.ts +1 -3
- package/client/plugos/syscalls/code_widget.ts +1 -3
- package/client/plugos/syscalls/config.ts +1 -5
- package/client/plugos/syscalls/datastore.ts +1 -1
- package/client/plugos/syscalls/editor.ts +63 -60
- package/client/plugos/syscalls/event.ts +9 -12
- package/client/plugos/syscalls/fetch.ts +30 -22
- package/client/plugos/syscalls/index.ts +10 -1
- package/client/plugos/syscalls/jsonschema.ts +72 -32
- package/client/plugos/syscalls/language.ts +9 -5
- package/client/plugos/syscalls/markdown.ts +29 -7
- package/client/plugos/syscalls/mq.ts +3 -11
- package/client/plugos/syscalls/service_registry.ts +1 -4
- package/client/plugos/syscalls/shell.ts +2 -5
- package/client/plugos/syscalls/sync.ts +69 -60
- package/client/plugos/syscalls/system.ts +2 -3
- package/client/plugos/system.ts +4 -10
- package/client/plugos/worker_runtime.ts +4 -3
- package/client/space_lua/aggregates.ts +632 -59
- package/client/space_lua/ast.ts +21 -9
- package/client/space_lua/ast_narrow.ts +4 -2
- package/client/space_lua/eval.ts +842 -536
- package/client/space_lua/labels.ts +6 -11
- package/client/space_lua/liq_null.ts +6 -0
- package/client/space_lua/numeric.ts +5 -8
- package/client/space_lua/parse.ts +290 -169
- package/client/space_lua/query_collection.ts +213 -149
- package/client/space_lua/render_lua_markdown.ts +369 -0
- package/client/space_lua/rp.ts +5 -4
- package/client/space_lua/runtime.ts +245 -142
- package/client/space_lua/stdlib/format.ts +34 -20
- package/client/space_lua/stdlib/js.ts +3 -7
- package/client/space_lua/stdlib/load.ts +1 -3
- package/client/space_lua/stdlib/math.ts +15 -14
- package/client/space_lua/stdlib/net.ts +25 -15
- package/client/space_lua/stdlib/os.ts +76 -85
- package/client/space_lua/stdlib/pattern.ts +28 -35
- package/client/space_lua/stdlib/prng.ts +15 -12
- package/client/space_lua/stdlib/space_lua.ts +16 -17
- package/client/space_lua/stdlib/string.ts +7 -17
- package/client/space_lua/stdlib/string_pack.ts +23 -19
- package/client/space_lua/stdlib/table.ts +5 -9
- package/client/space_lua/stdlib.ts +20 -30
- package/client/space_lua/tonumber.ts +79 -40
- package/client/space_lua/util.ts +14 -10
- package/dist/plug-compile.js +44 -41
- package/package.json +24 -22
- package/plug-api/lib/async.ts +19 -6
- package/plug-api/lib/crypto.ts +5 -6
- package/plug-api/lib/dates.ts +15 -7
- package/plug-api/lib/json.ts +10 -4
- package/plug-api/lib/ref.ts +18 -18
- package/plug-api/lib/resolve.ts +7 -11
- package/plug-api/lib/tags.ts +13 -4
- package/plug-api/lib/transclusion.ts +6 -17
- package/plug-api/lib/tree.ts +115 -43
- package/plug-api/lib/yaml.ts +25 -15
- package/plug-api/syscalls/asset.ts +1 -1
- package/plug-api/syscalls/config.ts +1 -4
- package/plug-api/syscalls/editor.ts +14 -14
- package/plug-api/syscalls/jsonschema.ts +1 -3
- package/plug-api/syscalls/lua.ts +3 -9
- package/plug-api/syscalls/mq.ts +1 -4
- package/plug-api/syscalls/shell.ts +4 -1
- package/plug-api/syscalls/space.ts +3 -10
- package/plug-api/syscalls/system.ts +1 -4
- package/plug-api/syscalls/yaml.ts +2 -6
- package/plug-api/types/client.ts +16 -1
- package/plug-api/types/event.ts +6 -4
- package/plug-api/types/manifest.ts +8 -9
- package/plugs/builtin_plugs.ts +2 -2
- package/dist/worker_runtime_bundle.js +0 -233
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
LuaAggregateCallExpression,
|
|
2
3
|
LuaBinaryExpression,
|
|
3
4
|
LuaDynamicField,
|
|
4
5
|
LuaExpression,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
type LuaValue,
|
|
26
27
|
singleResult,
|
|
27
28
|
} from "./runtime.ts";
|
|
29
|
+
import { isSqlNull, LIQ_NULL } from "./liq_null.ts";
|
|
28
30
|
import { evalExpression, luaOp } from "./eval.ts";
|
|
29
31
|
import { asyncMergeSort } from "./util.ts";
|
|
30
32
|
import type { DataStore } from "../data/datastore.ts";
|
|
@@ -35,13 +37,10 @@ import type { QueryCollationConfig } from "../../plug-api/types/config.ts";
|
|
|
35
37
|
import type { KvKey } from "../../plug-api/types/datastore.ts";
|
|
36
38
|
|
|
37
39
|
import { executeAggregate, getAggregateSpec } from "./aggregates.ts";
|
|
40
|
+
import { Config } from "../config.ts";
|
|
38
41
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export function isSqlNull(v: any): boolean {
|
|
43
|
-
return v === LIQ_NULL;
|
|
44
|
-
}
|
|
42
|
+
// Implicit single group map key (aggregates without `group by`)
|
|
43
|
+
const IMPLICIT_GROUP_KEY: unique symbol = Symbol("implicit-group");
|
|
45
44
|
|
|
46
45
|
// Build environment for post-`group by` clauses. Injects `key` and `group`
|
|
47
46
|
// as top-level variables. Unpacks first group item fields and group-by key
|
|
@@ -58,9 +57,8 @@ function buildGroupItemEnv(
|
|
|
58
57
|
if (item instanceof LuaTable) {
|
|
59
58
|
const keyVal = item.rawGet("key");
|
|
60
59
|
const groupVal = item.rawGet("group");
|
|
61
|
-
const firstItem =
|
|
62
|
-
? groupVal.rawGet(1)
|
|
63
|
-
: undefined;
|
|
60
|
+
const firstItem =
|
|
61
|
+
groupVal instanceof LuaTable ? groupVal.rawGet(1) : undefined;
|
|
64
62
|
|
|
65
63
|
if (firstItem) {
|
|
66
64
|
for (const k of luaKeys(firstItem)) {
|
|
@@ -79,19 +77,32 @@ function buildGroupItemEnv(
|
|
|
79
77
|
itemEnv.setLocal("group", groupVal);
|
|
80
78
|
}
|
|
81
79
|
|
|
80
|
+
// Unpack named fields from multi-key LuaTable keys
|
|
82
81
|
if (keyVal instanceof LuaTable) {
|
|
83
82
|
for (const k of luaKeys(keyVal)) {
|
|
84
83
|
if (typeof k !== "string") continue;
|
|
85
84
|
itemEnv.setLocal(k, luaGet(keyVal, k, sf.astCtx ?? null, sf));
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
|
|
88
|
+
// Bind all `group by` aliases/names to their key values. For
|
|
89
|
+
// single key bind the name to the scalar `keyVal`. For multi-key
|
|
90
|
+
// bind each name to the field from the key table.
|
|
91
|
+
if (groupByNames && groupByNames.length > 0) {
|
|
92
|
+
if (!(keyVal instanceof LuaTable)) {
|
|
93
|
+
// Bind all names to scalar
|
|
94
|
+
for (const gbn of groupByNames) {
|
|
95
|
+
itemEnv.setLocal(gbn, keyVal);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Ensure every alias is bound even if `luaKeys` missed it
|
|
99
|
+
for (const gbn of groupByNames) {
|
|
100
|
+
const v = keyVal.rawGet(gbn);
|
|
101
|
+
if (v !== undefined) {
|
|
102
|
+
itemEnv.setLocal(gbn, v);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
return itemEnv;
|
|
@@ -141,6 +152,11 @@ export type LuaOrderBy = {
|
|
|
141
152
|
using?: string | LuaFunctionBody;
|
|
142
153
|
};
|
|
143
154
|
|
|
155
|
+
export type LuaGroupByEntry = {
|
|
156
|
+
expr: LuaExpression;
|
|
157
|
+
alias?: string;
|
|
158
|
+
};
|
|
159
|
+
|
|
144
160
|
/**
|
|
145
161
|
* Represents a query for a collection
|
|
146
162
|
*/
|
|
@@ -158,8 +174,8 @@ export type LuaCollectionQuery = {
|
|
|
158
174
|
offset?: number;
|
|
159
175
|
// Whether to return only distinct values
|
|
160
176
|
distinct?: boolean;
|
|
161
|
-
// The group by
|
|
162
|
-
groupBy?:
|
|
177
|
+
// The group by entries evaluated with Lua
|
|
178
|
+
groupBy?: LuaGroupByEntry[];
|
|
163
179
|
// The having expression evaluated with Lua
|
|
164
180
|
having?: LuaExpression;
|
|
165
181
|
};
|
|
@@ -169,6 +185,7 @@ export interface LuaQueryCollection {
|
|
|
169
185
|
query: LuaCollectionQuery,
|
|
170
186
|
env: LuaEnv,
|
|
171
187
|
sf: LuaStackFrame,
|
|
188
|
+
config?: Config,
|
|
172
189
|
): Promise<any[]>;
|
|
173
190
|
}
|
|
174
191
|
|
|
@@ -176,15 +193,14 @@ export interface LuaQueryCollection {
|
|
|
176
193
|
* Implements a query collection for a regular JavaScript array
|
|
177
194
|
*/
|
|
178
195
|
export class ArrayQueryCollection<T> implements LuaQueryCollection {
|
|
179
|
-
constructor(private readonly array: T[]) {
|
|
180
|
-
}
|
|
196
|
+
constructor(private readonly array: T[]) {}
|
|
181
197
|
query(
|
|
182
198
|
query: LuaCollectionQuery,
|
|
183
199
|
env: LuaEnv,
|
|
184
200
|
sf: LuaStackFrame,
|
|
185
|
-
|
|
201
|
+
config?: Config,
|
|
186
202
|
): Promise<any[]> {
|
|
187
|
-
return applyQuery(this.array, query, env, sf,
|
|
203
|
+
return applyQuery(this.array, query, env, sf, config);
|
|
188
204
|
}
|
|
189
205
|
}
|
|
190
206
|
|
|
@@ -202,46 +218,71 @@ export function toCollection(obj: any): LuaQueryCollection {
|
|
|
202
218
|
return new ArrayQueryCollection([obj]);
|
|
203
219
|
}
|
|
204
220
|
|
|
205
|
-
function containsAggregate(expr: LuaExpression): boolean {
|
|
221
|
+
function containsAggregate(expr: LuaExpression, config?: Config): boolean {
|
|
206
222
|
switch (expr.type) {
|
|
207
223
|
case "FilteredCall": {
|
|
208
224
|
const fc = (expr as LuaFilteredCallExpression).call;
|
|
209
|
-
if (
|
|
225
|
+
if (
|
|
226
|
+
fc.prefix.type === "Variable" &&
|
|
227
|
+
getAggregateSpec(fc.prefix.name, config)
|
|
228
|
+
) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return (
|
|
232
|
+
containsAggregate(fc, config) ||
|
|
233
|
+
containsAggregate((expr as LuaFilteredCallExpression).filter, config)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
case "AggregateCall": {
|
|
237
|
+
const ac = expr as LuaAggregateCallExpression;
|
|
238
|
+
const fc = ac.call;
|
|
239
|
+
if (
|
|
240
|
+
fc.prefix.type === "Variable" &&
|
|
241
|
+
getAggregateSpec(fc.prefix.name, config)
|
|
242
|
+
) {
|
|
210
243
|
return true;
|
|
211
244
|
}
|
|
212
|
-
return containsAggregate(fc)
|
|
213
|
-
containsAggregate((expr as LuaFilteredCallExpression).filter);
|
|
245
|
+
return containsAggregate(fc, config);
|
|
214
246
|
}
|
|
215
247
|
case "FunctionCall": {
|
|
216
248
|
const fc = expr as LuaFunctionCallExpression;
|
|
217
|
-
if (
|
|
249
|
+
if (
|
|
250
|
+
fc.prefix.type === "Variable" &&
|
|
251
|
+
getAggregateSpec(fc.prefix.name, config)
|
|
252
|
+
) {
|
|
218
253
|
return true;
|
|
219
254
|
}
|
|
220
|
-
return fc.args.some(containsAggregate);
|
|
255
|
+
return fc.args.some((a) => containsAggregate(a, config));
|
|
221
256
|
}
|
|
222
257
|
case "Binary": {
|
|
223
258
|
const bin = expr as LuaBinaryExpression;
|
|
224
|
-
return
|
|
259
|
+
return (
|
|
260
|
+
containsAggregate(bin.left, config) ||
|
|
261
|
+
containsAggregate(bin.right, config)
|
|
262
|
+
);
|
|
225
263
|
}
|
|
226
264
|
case "Unary": {
|
|
227
265
|
const un = expr as LuaUnaryExpression;
|
|
228
|
-
return containsAggregate(un.argument);
|
|
266
|
+
return containsAggregate(un.argument, config);
|
|
229
267
|
}
|
|
230
268
|
case "Parenthesized": {
|
|
231
269
|
const p = expr as LuaParenthesizedExpression;
|
|
232
|
-
return containsAggregate(p.expression);
|
|
270
|
+
return containsAggregate(p.expression, config);
|
|
233
271
|
}
|
|
234
272
|
case "TableConstructor":
|
|
235
273
|
return expr.fields.some((f) => {
|
|
236
274
|
switch (f.type) {
|
|
237
275
|
case "PropField":
|
|
238
|
-
return containsAggregate((f as LuaPropField).value);
|
|
276
|
+
return containsAggregate((f as LuaPropField).value, config);
|
|
239
277
|
case "DynamicField": {
|
|
240
278
|
const df = f as LuaDynamicField;
|
|
241
|
-
return
|
|
279
|
+
return (
|
|
280
|
+
containsAggregate(df.key, config) ||
|
|
281
|
+
containsAggregate(df.value, config)
|
|
282
|
+
);
|
|
242
283
|
}
|
|
243
284
|
case "ExpressionField":
|
|
244
|
-
return containsAggregate((f as LuaExpressionField).value);
|
|
285
|
+
return containsAggregate((f as LuaExpressionField).value, config);
|
|
245
286
|
default:
|
|
246
287
|
return false;
|
|
247
288
|
}
|
|
@@ -251,6 +292,12 @@ function containsAggregate(expr: LuaExpression): boolean {
|
|
|
251
292
|
}
|
|
252
293
|
}
|
|
253
294
|
|
|
295
|
+
// Wrap a value for select result tables so that the column key survives
|
|
296
|
+
// in the `LuaTable`
|
|
297
|
+
function selectVal(v: LuaValue): LuaValue {
|
|
298
|
+
return v === null || v === undefined ? LIQ_NULL : v;
|
|
299
|
+
}
|
|
300
|
+
|
|
254
301
|
/**
|
|
255
302
|
* Evaluate an expression in aggregate-aware mode.
|
|
256
303
|
*
|
|
@@ -265,8 +312,9 @@ export async function evalExpressionWithAggregates(
|
|
|
265
312
|
groupItems: LuaTable,
|
|
266
313
|
objectVariable: string | undefined,
|
|
267
314
|
outerEnv: LuaEnv,
|
|
315
|
+
config: Config,
|
|
268
316
|
): Promise<LuaValue> {
|
|
269
|
-
if (!containsAggregate(expr)) {
|
|
317
|
+
if (!containsAggregate(expr, config)) {
|
|
270
318
|
return evalExpression(expr, env, sf);
|
|
271
319
|
}
|
|
272
320
|
const recurse = (e: LuaExpression) =>
|
|
@@ -277,6 +325,7 @@ export async function evalExpressionWithAggregates(
|
|
|
277
325
|
groupItems,
|
|
278
326
|
objectVariable,
|
|
279
327
|
outerEnv,
|
|
328
|
+
config,
|
|
280
329
|
);
|
|
281
330
|
|
|
282
331
|
if (expr.type === "FilteredCall") {
|
|
@@ -284,18 +333,22 @@ export async function evalExpressionWithAggregates(
|
|
|
284
333
|
const fc = filtered.call;
|
|
285
334
|
if (fc.prefix.type === "Variable") {
|
|
286
335
|
const name = fc.prefix.name;
|
|
287
|
-
const spec = getAggregateSpec(name);
|
|
336
|
+
const spec = getAggregateSpec(name, config);
|
|
288
337
|
if (spec) {
|
|
289
338
|
const valueExpr = fc.args.length > 0 ? fc.args[0] : null;
|
|
339
|
+
const extraArgExprs = fc.args.length > 1 ? fc.args.slice(1) : [];
|
|
290
340
|
return executeAggregate(
|
|
291
341
|
spec,
|
|
292
342
|
groupItems,
|
|
293
343
|
valueExpr,
|
|
344
|
+
extraArgExprs,
|
|
294
345
|
objectVariable,
|
|
295
346
|
outerEnv,
|
|
296
347
|
sf,
|
|
297
348
|
evalExpression,
|
|
349
|
+
config,
|
|
298
350
|
filtered.filter,
|
|
351
|
+
fc.orderBy,
|
|
299
352
|
);
|
|
300
353
|
}
|
|
301
354
|
}
|
|
@@ -307,17 +360,22 @@ export async function evalExpressionWithAggregates(
|
|
|
307
360
|
const fc = expr as LuaFunctionCallExpression;
|
|
308
361
|
if (fc.prefix.type === "Variable") {
|
|
309
362
|
const name = fc.prefix.name;
|
|
310
|
-
const spec = getAggregateSpec(name);
|
|
363
|
+
const spec = getAggregateSpec(name, config);
|
|
311
364
|
if (spec) {
|
|
312
365
|
const valueExpr = fc.args.length > 0 ? fc.args[0] : null;
|
|
366
|
+
const extraArgExprs = fc.args.length > 1 ? fc.args.slice(1) : [];
|
|
313
367
|
return executeAggregate(
|
|
314
368
|
spec,
|
|
315
369
|
groupItems,
|
|
316
370
|
valueExpr,
|
|
371
|
+
extraArgExprs,
|
|
317
372
|
objectVariable,
|
|
318
373
|
outerEnv,
|
|
319
374
|
sf,
|
|
320
375
|
evalExpression,
|
|
376
|
+
config,
|
|
377
|
+
undefined,
|
|
378
|
+
fc.orderBy,
|
|
321
379
|
);
|
|
322
380
|
}
|
|
323
381
|
}
|
|
@@ -330,20 +388,20 @@ export async function evalExpressionWithAggregates(
|
|
|
330
388
|
case "PropField": {
|
|
331
389
|
const pf = field as LuaPropField;
|
|
332
390
|
const value = await recurse(pf.value);
|
|
333
|
-
table.set(pf.key, value, sf);
|
|
391
|
+
void table.set(pf.key, selectVal(value), sf);
|
|
334
392
|
break;
|
|
335
393
|
}
|
|
336
394
|
case "DynamicField": {
|
|
337
395
|
const df = field as LuaDynamicField;
|
|
338
396
|
const key = await evalExpression(df.key, env, sf);
|
|
339
397
|
const value = await recurse(df.value);
|
|
340
|
-
table.set(key, value, sf);
|
|
398
|
+
void table.set(key, selectVal(value), sf);
|
|
341
399
|
break;
|
|
342
400
|
}
|
|
343
401
|
case "ExpressionField": {
|
|
344
402
|
const ef = field as LuaExpressionField;
|
|
345
403
|
const value = await recurse(ef.value);
|
|
346
|
-
table.rawSetArrayIndex(nextArrayIndex, value);
|
|
404
|
+
table.rawSetArrayIndex(nextArrayIndex, selectVal(value));
|
|
347
405
|
nextArrayIndex++;
|
|
348
406
|
break;
|
|
349
407
|
}
|
|
@@ -365,30 +423,16 @@ export async function evalExpressionWithAggregates(
|
|
|
365
423
|
}
|
|
366
424
|
const left = singleResult(await recurse(bin.left));
|
|
367
425
|
const right = singleResult(await recurse(bin.right));
|
|
368
|
-
return luaOp(
|
|
369
|
-
bin.operator,
|
|
370
|
-
left,
|
|
371
|
-
right,
|
|
372
|
-
undefined,
|
|
373
|
-
undefined,
|
|
374
|
-
expr.ctx,
|
|
375
|
-
sf,
|
|
376
|
-
);
|
|
426
|
+
return luaOp(bin.operator, left, right, undefined, undefined, expr.ctx, sf);
|
|
377
427
|
}
|
|
378
428
|
if (expr.type === "Unary") {
|
|
379
429
|
const un = expr as LuaUnaryExpression;
|
|
380
430
|
const arg = singleResult(await recurse(un.argument));
|
|
381
431
|
switch (un.operator) {
|
|
382
432
|
case "-":
|
|
383
|
-
return typeof arg === "number"
|
|
384
|
-
|
|
385
|
-
0,
|
|
386
|
-
arg,
|
|
387
|
-
undefined,
|
|
388
|
-
undefined,
|
|
389
|
-
expr.ctx,
|
|
390
|
-
sf,
|
|
391
|
-
);
|
|
433
|
+
return typeof arg === "number"
|
|
434
|
+
? -arg
|
|
435
|
+
: luaOp("-", 0, arg, undefined, undefined, expr.ctx, sf);
|
|
392
436
|
case "not":
|
|
393
437
|
return !luaTruthy(arg);
|
|
394
438
|
case "#":
|
|
@@ -447,14 +491,11 @@ function normalizeSelectResults(results: any[]): any[] {
|
|
|
447
491
|
const rebuilt = new LuaTable();
|
|
448
492
|
for (const k of canonicalKeys) {
|
|
449
493
|
const v = item.rawGet(k);
|
|
450
|
-
rebuilt.rawSet(
|
|
451
|
-
k,
|
|
452
|
-
(v === undefined || v === null) ? LIQ_NULL : v,
|
|
453
|
-
);
|
|
494
|
+
void rebuilt.rawSet(k, v === undefined || v === null ? LIQ_NULL : v);
|
|
454
495
|
}
|
|
455
496
|
for (const k of luaKeys(item)) {
|
|
456
497
|
if (typeof k !== "string") {
|
|
457
|
-
rebuilt.rawSet(k, item.rawGet(k));
|
|
498
|
+
void rebuilt.rawSet(k, item.rawGet(k));
|
|
458
499
|
}
|
|
459
500
|
}
|
|
460
501
|
results[i] = rebuilt;
|
|
@@ -487,14 +528,10 @@ async function usingCompare(
|
|
|
487
528
|
keyIdx: number,
|
|
488
529
|
): Promise<number> {
|
|
489
530
|
const res = luaTruthy(
|
|
490
|
-
singleResult(
|
|
491
|
-
await luaCall(luaCmp, [aVal, bVal], sf.astCtx ?? {}, sf),
|
|
492
|
-
),
|
|
531
|
+
singleResult(await luaCall(luaCmp, [aVal, bVal], sf.astCtx ?? {}, sf)),
|
|
493
532
|
);
|
|
494
533
|
const reverseRes = luaTruthy(
|
|
495
|
-
singleResult(
|
|
496
|
-
await luaCall(luaCmp, [bVal, aVal], sf.astCtx ?? {}, sf),
|
|
497
|
-
),
|
|
534
|
+
singleResult(await luaCall(luaCmp, [bVal, aVal], sf.astCtx ?? {}, sf)),
|
|
498
535
|
);
|
|
499
536
|
|
|
500
537
|
// both true means SWO violation
|
|
@@ -526,6 +563,7 @@ async function precomputeSortKeys(
|
|
|
526
563
|
sf: LuaStackFrame,
|
|
527
564
|
grouped: boolean,
|
|
528
565
|
selectResults: any[] | undefined,
|
|
566
|
+
config: Config,
|
|
529
567
|
): Promise<any[][]> {
|
|
530
568
|
const allKeys: any[][] = new Array(results.length);
|
|
531
569
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -551,6 +589,7 @@ async function precomputeSortKeys(
|
|
|
551
589
|
groupTable,
|
|
552
590
|
objectVariable,
|
|
553
591
|
env,
|
|
592
|
+
config,
|
|
554
593
|
);
|
|
555
594
|
} else {
|
|
556
595
|
keys[j] = await evalExpression(orderBy[j].expr, itemEnv, sf);
|
|
@@ -583,8 +622,8 @@ async function sortKeyCompare(
|
|
|
583
622
|
const bVal = bKeys[idx];
|
|
584
623
|
|
|
585
624
|
// Handle nulls positioning
|
|
586
|
-
const aIsNull = aVal === null || aVal === undefined;
|
|
587
|
-
const bIsNull = bVal === null || bVal === undefined;
|
|
625
|
+
const aIsNull = aVal === null || aVal === undefined || isSqlNull(aVal);
|
|
626
|
+
const bIsNull = bVal === null || bVal === undefined || isSqlNull(bVal);
|
|
588
627
|
if (aIsNull || bIsNull) {
|
|
589
628
|
if (aIsNull && bIsNull) continue;
|
|
590
629
|
// Default: nulls last for asc, nulls first for desc
|
|
@@ -625,23 +664,35 @@ async function sortKeyCompare(
|
|
|
625
664
|
return 0;
|
|
626
665
|
}
|
|
627
666
|
|
|
667
|
+
// Build a select-result table from a non-aggregate select expression
|
|
668
|
+
async function evalSelectExpression(
|
|
669
|
+
selectExpr: LuaExpression,
|
|
670
|
+
itemEnv: LuaEnv,
|
|
671
|
+
sf: LuaStackFrame,
|
|
672
|
+
): Promise<LuaValue> {
|
|
673
|
+
const result = await evalExpression(selectExpr, itemEnv, sf);
|
|
674
|
+
if (!(result instanceof LuaTable)) return result;
|
|
675
|
+
for (const k of luaKeys(result)) {
|
|
676
|
+
const v = result.rawGet(k);
|
|
677
|
+
if (v === null || v === undefined) {
|
|
678
|
+
void result.rawSet(k, LIQ_NULL);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
|
|
628
684
|
export async function applyQuery(
|
|
629
685
|
results: any[],
|
|
630
686
|
query: LuaCollectionQuery,
|
|
631
687
|
env: LuaEnv,
|
|
632
688
|
sf: LuaStackFrame,
|
|
633
|
-
|
|
689
|
+
config: Config = new Config(),
|
|
634
690
|
): Promise<any[]> {
|
|
635
691
|
results = results.slice();
|
|
636
692
|
if (query.where) {
|
|
637
693
|
const filteredResults = [];
|
|
638
694
|
for (const value of results) {
|
|
639
|
-
const itemEnv = buildItemEnvLocal(
|
|
640
|
-
query.objectVariable,
|
|
641
|
-
value,
|
|
642
|
-
env,
|
|
643
|
-
sf,
|
|
644
|
-
);
|
|
695
|
+
const itemEnv = buildItemEnvLocal(query.objectVariable, value, env, sf);
|
|
645
696
|
if (await evalExpression(query.where, itemEnv, sf)) {
|
|
646
697
|
filteredResults.push(value);
|
|
647
698
|
}
|
|
@@ -649,62 +700,72 @@ export async function applyQuery(
|
|
|
649
700
|
results = filteredResults;
|
|
650
701
|
}
|
|
651
702
|
|
|
703
|
+
// Implicit single group
|
|
704
|
+
if (
|
|
705
|
+
!query.groupBy &&
|
|
706
|
+
((query.select && containsAggregate(query.select, config)) ||
|
|
707
|
+
(query.having && containsAggregate(query.having, config)))
|
|
708
|
+
) {
|
|
709
|
+
query = { ...query, groupBy: [] };
|
|
710
|
+
}
|
|
711
|
+
|
|
652
712
|
const grouped = !!query.groupBy;
|
|
653
713
|
|
|
654
714
|
// Collect group-by key names for unpacking into the post-group environment.
|
|
655
715
|
let groupByNames: string[] | undefined;
|
|
656
716
|
|
|
657
717
|
if (query.groupBy) {
|
|
658
|
-
// Extract
|
|
659
|
-
|
|
660
|
-
if (expr.type === "Variable") {
|
|
661
|
-
return expr.name;
|
|
662
|
-
}
|
|
663
|
-
if (expr.type === "PropertyAccess") {
|
|
664
|
-
return expr.property;
|
|
665
|
-
}
|
|
666
|
-
return undefined as unknown as string;
|
|
667
|
-
}).filter(Boolean);
|
|
718
|
+
// Extract expressions and names from `group by` entries
|
|
719
|
+
const groupByEntries = query.groupBy;
|
|
668
720
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
721
|
+
// Derive canonical name (explicit alias first, or from expression)
|
|
722
|
+
groupByNames = groupByEntries
|
|
723
|
+
.map((entry) => {
|
|
724
|
+
if (entry.alias) return entry.alias;
|
|
725
|
+
if (entry.expr.type === "Variable") return entry.expr.name;
|
|
726
|
+
if (entry.expr.type === "PropertyAccess") return entry.expr.property;
|
|
727
|
+
return undefined as unknown as string;
|
|
728
|
+
})
|
|
729
|
+
.filter(Boolean);
|
|
730
|
+
|
|
731
|
+
const groups = new Map<string | symbol, { key: any; items: any[] }>();
|
|
674
732
|
|
|
675
733
|
for (const item of results) {
|
|
676
|
-
const itemEnv = buildItemEnvLocal(
|
|
677
|
-
query.objectVariable,
|
|
678
|
-
item,
|
|
679
|
-
env,
|
|
680
|
-
sf,
|
|
681
|
-
);
|
|
734
|
+
const itemEnv = buildItemEnvLocal(query.objectVariable, item, env, sf);
|
|
682
735
|
|
|
683
736
|
const keyParts: any[] = [];
|
|
684
737
|
const keyRecord: Record<string, any> = {};
|
|
685
738
|
|
|
686
|
-
for (
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
739
|
+
for (let ei = 0; ei < groupByEntries.length; ei++) {
|
|
740
|
+
const entry = groupByEntries[ei];
|
|
741
|
+
const v = await evalExpression(entry.expr, itemEnv, sf);
|
|
742
|
+
keyParts.push(v);
|
|
743
|
+
// Use alias if provided, or from expression
|
|
744
|
+
const name =
|
|
745
|
+
entry.alias ??
|
|
746
|
+
(entry.expr.type === "Variable" ? entry.expr.name : undefined) ??
|
|
747
|
+
(entry.expr.type === "PropertyAccess"
|
|
748
|
+
? entry.expr.property
|
|
749
|
+
: undefined);
|
|
750
|
+
if (name) {
|
|
751
|
+
keyRecord[name] = v;
|
|
698
752
|
}
|
|
699
753
|
}
|
|
700
754
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
755
|
+
// Implicit single group uses a symbol key
|
|
756
|
+
const compositeKey: string | symbol =
|
|
757
|
+
keyParts.length === 0
|
|
758
|
+
? IMPLICIT_GROUP_KEY
|
|
759
|
+
: keyParts.length === 1
|
|
760
|
+
? generateKey(keyParts[0])
|
|
761
|
+
: JSON.stringify(keyParts.map(generateKey));
|
|
704
762
|
let entry = groups.get(compositeKey);
|
|
705
763
|
if (!entry) {
|
|
706
764
|
let keyVal: any;
|
|
707
|
-
if (keyParts.length ===
|
|
765
|
+
if (keyParts.length === 0) {
|
|
766
|
+
// Implicit single group — key is `nil`
|
|
767
|
+
keyVal = null;
|
|
768
|
+
} else if (keyParts.length === 1) {
|
|
708
769
|
keyVal = keyParts[0];
|
|
709
770
|
} else {
|
|
710
771
|
const kt = new LuaTable();
|
|
@@ -714,7 +775,7 @@ export async function applyQuery(
|
|
|
714
775
|
}
|
|
715
776
|
// Additionally set named fields for Variable/PropertyAccess exprs
|
|
716
777
|
for (const name in keyRecord) {
|
|
717
|
-
kt.rawSet(name, keyRecord[name]);
|
|
778
|
+
void kt.rawSet(name, keyRecord[name]);
|
|
718
779
|
}
|
|
719
780
|
keyVal = kt;
|
|
720
781
|
}
|
|
@@ -734,15 +795,14 @@ export async function applyQuery(
|
|
|
734
795
|
const item = items[i];
|
|
735
796
|
groupTable.rawSetArrayIndex(
|
|
736
797
|
i + 1,
|
|
737
|
-
|
|
738
|
-
item === null)
|
|
798
|
+
item instanceof LuaTable || typeof item !== "object" || item === null
|
|
739
799
|
? item
|
|
740
800
|
: jsToLuaValue(item),
|
|
741
801
|
);
|
|
742
802
|
}
|
|
743
803
|
const row = new LuaTable();
|
|
744
|
-
row.rawSet("key", key);
|
|
745
|
-
row.rawSet("group", groupTable);
|
|
804
|
+
void row.rawSet("key", key);
|
|
805
|
+
void row.rawSet("group", groupTable);
|
|
746
806
|
results.push(row);
|
|
747
807
|
}
|
|
748
808
|
}
|
|
@@ -767,14 +827,10 @@ export async function applyQuery(
|
|
|
767
827
|
groupTable,
|
|
768
828
|
query.objectVariable,
|
|
769
829
|
env,
|
|
830
|
+
config,
|
|
770
831
|
);
|
|
771
832
|
} else {
|
|
772
|
-
const itemEnv = buildItemEnvLocal(
|
|
773
|
-
query.objectVariable,
|
|
774
|
-
value,
|
|
775
|
-
env,
|
|
776
|
-
sf,
|
|
777
|
-
);
|
|
833
|
+
const itemEnv = buildItemEnvLocal(query.objectVariable, value, env, sf);
|
|
778
834
|
condResult = await evalExpression(query.having, itemEnv, sf);
|
|
779
835
|
}
|
|
780
836
|
if (condResult) {
|
|
@@ -785,28 +841,27 @@ export async function applyQuery(
|
|
|
785
841
|
}
|
|
786
842
|
|
|
787
843
|
const mkEnv = grouped
|
|
788
|
-
? (
|
|
789
|
-
|
|
790
|
-
item: any,
|
|
791
|
-
e: LuaEnv,
|
|
792
|
-
s: LuaStackFrame,
|
|
793
|
-
) => buildGroupItemEnv(ov, groupByNames, item, e, s)
|
|
844
|
+
? (ov: string | undefined, item: any, e: LuaEnv, s: LuaStackFrame) =>
|
|
845
|
+
buildGroupItemEnv(ov, groupByNames, item, e, s)
|
|
794
846
|
: buildItemEnvLocal;
|
|
795
847
|
|
|
796
848
|
let selectResults: any[] | undefined;
|
|
797
849
|
|
|
850
|
+
// Pre-compute select for grouped + ordered queries
|
|
798
851
|
if (grouped && query.select && query.orderBy) {
|
|
852
|
+
const selectExpr = query.select;
|
|
799
853
|
selectResults = [];
|
|
800
854
|
for (const item of results) {
|
|
801
855
|
const itemEnv = mkEnv(query.objectVariable, item, env, sf);
|
|
802
856
|
const groupTable = (item as LuaTable).rawGet("group");
|
|
803
857
|
const selected = await evalExpressionWithAggregates(
|
|
804
|
-
|
|
858
|
+
selectExpr,
|
|
805
859
|
itemEnv,
|
|
806
860
|
sf,
|
|
807
861
|
groupTable,
|
|
808
862
|
query.objectVariable,
|
|
809
863
|
env,
|
|
864
|
+
config,
|
|
810
865
|
);
|
|
811
866
|
selectResults.push(selected);
|
|
812
867
|
}
|
|
@@ -814,10 +869,7 @@ export async function applyQuery(
|
|
|
814
869
|
}
|
|
815
870
|
|
|
816
871
|
if (query.orderBy) {
|
|
817
|
-
|
|
818
|
-
const config = globalThis.client.config;
|
|
819
|
-
collation = config.get("queryCollation", {});
|
|
820
|
-
}
|
|
872
|
+
const collation = config.get<QueryCollationConfig>("queryCollation", {});
|
|
821
873
|
const collator = Intl.Collator(collation?.locale, collation?.options);
|
|
822
874
|
|
|
823
875
|
const resolvedUsing: (LuaValue | null)[] = [];
|
|
@@ -837,6 +889,7 @@ export async function applyQuery(
|
|
|
837
889
|
sf,
|
|
838
890
|
grouped,
|
|
839
891
|
selectResults,
|
|
892
|
+
config,
|
|
840
893
|
);
|
|
841
894
|
|
|
842
895
|
// Tag each result with its original index for stable sorting
|
|
@@ -855,7 +908,8 @@ export async function applyQuery(
|
|
|
855
908
|
resolvedUsing,
|
|
856
909
|
violated,
|
|
857
910
|
sf,
|
|
858
|
-
)
|
|
911
|
+
),
|
|
912
|
+
);
|
|
859
913
|
|
|
860
914
|
// Check for SWO violations in comparators
|
|
861
915
|
for (let i = 0; i < violated.length; i++) {
|
|
@@ -884,6 +938,7 @@ export async function applyQuery(
|
|
|
884
938
|
}
|
|
885
939
|
|
|
886
940
|
if (query.select) {
|
|
941
|
+
const selectExpr = query.select;
|
|
887
942
|
if (selectResults) {
|
|
888
943
|
results = selectResults;
|
|
889
944
|
} else {
|
|
@@ -894,16 +949,17 @@ export async function applyQuery(
|
|
|
894
949
|
const groupTable = (item as LuaTable).rawGet("group");
|
|
895
950
|
newResult.push(
|
|
896
951
|
await evalExpressionWithAggregates(
|
|
897
|
-
|
|
952
|
+
selectExpr,
|
|
898
953
|
itemEnv,
|
|
899
954
|
sf,
|
|
900
955
|
groupTable,
|
|
901
956
|
query.objectVariable,
|
|
902
957
|
env,
|
|
958
|
+
config,
|
|
903
959
|
),
|
|
904
960
|
);
|
|
905
961
|
} else {
|
|
906
|
-
newResult.push(await
|
|
962
|
+
newResult.push(await evalSelectExpression(query.select, itemEnv, sf));
|
|
907
963
|
}
|
|
908
964
|
}
|
|
909
965
|
results = newResult;
|
|
@@ -942,17 +998,16 @@ export async function queryLua<T = any>(
|
|
|
942
998
|
env: LuaEnv,
|
|
943
999
|
sf: LuaStackFrame = LuaStackFrame.lostFrame,
|
|
944
1000
|
enricher?: (key: KvKey, item: any) => any,
|
|
1001
|
+
config?: Config,
|
|
945
1002
|
): Promise<T[]> {
|
|
946
1003
|
const results: T[] = [];
|
|
947
|
-
for await (
|
|
948
|
-
let { key, value } of kv.query({ prefix })
|
|
949
|
-
) {
|
|
1004
|
+
for await (let { key, value } of kv.query({ prefix })) {
|
|
950
1005
|
if (enricher) {
|
|
951
1006
|
value = enricher(key, value);
|
|
952
1007
|
}
|
|
953
1008
|
results.push(value);
|
|
954
1009
|
}
|
|
955
|
-
return applyQuery(results, query, env, sf);
|
|
1010
|
+
return applyQuery(results, query, env, sf, config);
|
|
956
1011
|
}
|
|
957
1012
|
|
|
958
1013
|
function generateKey(value: any) {
|
|
@@ -979,8 +1034,8 @@ function luaTableToJSWithNulls(
|
|
|
979
1034
|
isSqlNull(v)
|
|
980
1035
|
? "__SQL_NULL__"
|
|
981
1036
|
: v instanceof LuaTable
|
|
982
|
-
|
|
983
|
-
|
|
1037
|
+
? luaTableToJSWithNulls(v, sf)
|
|
1038
|
+
: v,
|
|
984
1039
|
);
|
|
985
1040
|
}
|
|
986
1041
|
return arr;
|
|
@@ -991,8 +1046,8 @@ function luaTableToJSWithNulls(
|
|
|
991
1046
|
obj[key] = isSqlNull(v)
|
|
992
1047
|
? "__SQL_NULL__"
|
|
993
1048
|
: v instanceof LuaTable
|
|
994
|
-
|
|
995
|
-
|
|
1049
|
+
? luaTableToJSWithNulls(v, sf)
|
|
1050
|
+
: v;
|
|
996
1051
|
}
|
|
997
1052
|
return obj;
|
|
998
1053
|
}
|
|
@@ -1006,7 +1061,16 @@ export class DataStoreQueryCollection implements LuaQueryCollection {
|
|
|
1006
1061
|
query: LuaCollectionQuery,
|
|
1007
1062
|
env: LuaEnv,
|
|
1008
1063
|
sf: LuaStackFrame,
|
|
1064
|
+
config?: Config,
|
|
1009
1065
|
): Promise<any[]> {
|
|
1010
|
-
return queryLua(
|
|
1066
|
+
return queryLua(
|
|
1067
|
+
this.dataStore.kv,
|
|
1068
|
+
this.prefix,
|
|
1069
|
+
query,
|
|
1070
|
+
env,
|
|
1071
|
+
sf,
|
|
1072
|
+
undefined,
|
|
1073
|
+
config,
|
|
1074
|
+
);
|
|
1011
1075
|
}
|
|
1012
1076
|
}
|