From a8c05386277d94e5d13cf660579fad20b2d00dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Jan=20Fialka?= Date: Wed, 11 Mar 2026 11:34:26 +0100 Subject: [PATCH 1/6] [LIQ] Implement `offset` (1: grammar) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matouš Jan Fialka --- client/space_lua/lua.grammar | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/space_lua/lua.grammar b/client/space_lua/lua.grammar index 1e15f8b8..32d8e20c 100644 --- a/client/space_lua/lua.grammar +++ b/client/space_lua/lua.grammar @@ -96,6 +96,7 @@ QueryClause { OrderByClause | SelectClause | LimitClause | + OffsetClause | GroupByClause | HavingClause } @@ -103,6 +104,7 @@ QueryClause { FromClause { ckw<"from"> (Name "=")? exp } WhereClause { ckw<"where"> exp } LimitClause { ckw<"limit"> exp ("," exp)? } +OffsetClause { ckw<"offset"> exp } OrderByClause { ckw<"order"> ckw<"by"> list } OrderBy { exp (ckw<"desc"> | ckw<"asc"> | kw<"using"> (Name | kw<"function"> FuncBody))? (ckw<"nulls"> (ckw<"first"> | ckw<"last">))? } SelectClause { ckw<"select"> exp } From 76fe46e59001977733106dc572ad8c4df0df0838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Jan=20Fialka?= Date: Wed, 11 Mar 2026 11:35:19 +0100 Subject: [PATCH 2/6] [LIQ] Implement `offset` (2: update AST, parser and eval) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matouš Jan Fialka --- client/space_lua/ast.ts | 6 ++++++ client/space_lua/eval.ts | 9 +++++++++ client/space_lua/parse.ts | 9 ++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/client/space_lua/ast.ts b/client/space_lua/ast.ts index b9092996..3024476f 100644 --- a/client/space_lua/ast.ts +++ b/client/space_lua/ast.ts @@ -305,6 +305,7 @@ export type LuaQueryClause = | LuaFromClause | LuaWhereClause | LuaLimitClause + | LuaOffsetClause | LuaOrderByClause | LuaSelectClause | LuaGroupByClause @@ -327,6 +328,11 @@ export type LuaLimitClause = { offset?: LuaExpression; } & ASTContext; +export type LuaOffsetClause = { + type: "Offset"; + offset: LuaExpression; +} & ASTContext; + export type LuaOrderByClause = { type: "OrderBy"; orderBy: LuaOrderBy[]; diff --git a/client/space_lua/eval.ts b/client/space_lua/eval.ts index 0b4b5a12..563b730a 100644 --- a/client/space_lua/eval.ts +++ b/client/space_lua/eval.ts @@ -883,6 +883,15 @@ export function evalExpression( } break; } + case "Offset": { + const offsetVal = await evalExpression( + clause.offset, + env, + sf, + ); + query.offset = Number(offsetVal); + break; + } case "GroupBy": { query.groupBy = clause.expressions; break; diff --git a/client/space_lua/parse.ts b/client/space_lua/parse.ts index c31c8629..55876ebb 100644 --- a/client/space_lua/parse.ts +++ b/client/space_lua/parse.ts @@ -36,7 +36,7 @@ const luaStyleTags = styleTags({ CompareOp: t.operator, "true false": t.bool, Comment: t.lineComment, - "return break goto do end while repeat until function local if then else elseif in for nil or and not query from where limit select order by desc asc nulls first last group having filter using": + "return break goto do end while repeat until function local if then else elseif in for nil or and not query from where limit offset select order by desc asc nulls first last group having filter using": t.keyword, }); @@ -1347,6 +1347,13 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause { ctx: context(t, ctx), }; } + case "OffsetClause": { + return { + type: "Offset", + offset: parseExpression(t.children![1], ctx), + ctx: context(t, ctx), + }; + } case "OrderByClause": { const orderBy: LuaOrderBy[] = []; for (const child of t.children!) { From 0efbe59c8c342247fc19978d68db898a284839c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Jan=20Fialka?= Date: Wed, 11 Mar 2026 11:36:04 +0100 Subject: [PATCH 3/6] [LIQ] Implement `offset` (3: update docs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matouš Jan Fialka --- website/Space Lua/Lua Integrated Query.md | 36 +++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/website/Space Lua/Lua Integrated Query.md b/website/Space Lua/Lua Integrated Query.md index 841c6bff..f1ebe654 100644 --- a/website/Space Lua/Lua Integrated Query.md +++ b/website/Space Lua/Lua Integrated Query.md @@ -17,16 +17,21 @@ General syntax: having order by [asc | desc | using ] - [nulls { first | last }] + [nulls {first | last}] [, ...] - limit [, ...] + limit [, ] + offset select ]] Aggregate functions support an optional intra-aggregate `order by` and/or `filter` clause: - ( [order by [asc|desc] [nulls {first|last}], ...]) - ( ...) filter(where ) + ( [, ...] + [order by + [asc | desc | using ] + [nulls {first | last}] + [, ...]] + [filter(where )]) These can be combined. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. @@ -238,16 +243,35 @@ ${query [[ The query engine uses a *stable merge sort* algorithm with guaranteed performance. Items that compare as equal preserve their original order and an invalid comparator cannot cause an infinite loop or crash — the violation is detected and reported as an error. ## `limit` -The `limit` clause allows you to limit the number of results, optionally with an offset. +The `limit` clause allows you to limit the number of results, optionally with an inline offset. Example: ${query[[from {1, 2, 3, 4, 5} limit 3]]} -You can also specify an offset to skip some results: +You can also specify an offset as a second argument to skip some results: ${query[[from {1, 2, 3, 4, 5} limit 3, 2]]} +> **note** Note +> If both an inline offset (`limit 3, 2`) and a standalone `offset` clause are present, the last one encountered wins. + +## `offset` +The `offset` clause skips a number of rows from the beginning of the result set. It can be used with or without `limit`. + +Skip the first 2 results: + +${query[[from {1, 2, 3, 4, 5} offset 2]]} + +Combined with `limit`: + +${query[[from {1, 2, 3, 4, 5} offset 2 limit 3]]} + +If the offset is larger than the number of available rows, the result is empty — this is not an error, matching PostgreSQL semantics. + +> **note** Note +> If both a standalone `offset` clause and an inline offset in `limit` (`limit 3, 2`) are present, the last one encountered wins. + ## `select` The `select` clause allows you to transform each item in the result set. If omitted, it defaults to returning the item itself. From 6970c41a4e8f60dbbd57fc3f627a2d852de62c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Jan=20Fialka?= Date: Wed, 11 Mar 2026 11:36:46 +0100 Subject: [PATCH 4/6] [LIQ] Implement `offset` (1: make generate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matouš Jan Fialka --- client/space_lua/parse-lua.js | 53 +++++++++++++++-------------- client/space_lua/parse-lua.terms.js | 29 ++++++++-------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/client/space_lua/parse-lua.js b/client/space_lua/parse-lua.js index e65135c2..76cc469b 100644 --- a/client/space_lua/parse-lua.js +++ b/client/space_lua/parse-lua.js @@ -27,30 +27,31 @@ const spec_identifier = { where: 153, select: 159, limit: 163, - group: 167, - having: 171, - filter: 175, - repeat: 178, - until: 180, - if: 184, - then: 186, - elseif: 188, - else: 190, - for: 194, - in: 202, - local: 212, - return: 228, + offset: 167, + group: 171, + having: 175, + filter: 179, + repeat: 182, + until: 184, + if: 188, + then: 190, + elseif: 192, + else: 194, + for: 198, + in: 206, + local: 216, + return: 232, }; export const parser = LRParser.deserialize({ version: 14, states: - "JxO!ZQPOOOOQO'#Cc'#CcO!UQPO'#CaO!bQPOOOOQO'#FX'#FXO!vQQO'#CwO$]QPO'#FWOOQO'#FW'#FWO$gQPO'#FWOOQO'#El'#ElO%vQPO'#EkOOQO'#FS'#FSOOQO'#Es'#EsO%{QPO'#C_OOQO'#C_'#C_QOQPOOO!UQPO'#CeO&`QPO'#CgO!vQQO'#CjO&gQPO'#EWO!vQQO'#EZO!UQPO'#E`O!UQPO'#EgO&nQPO'#EmO&vQQO'#EqO'^QPO,58{OOQO'#Cq'#CqO!UQPO,59^O!vQQO,59`O(gQQO'#C|O(nQQO'#F^OOQO'#FY'#FYOOQO,59f,59fO!UQPO,59fO(xQPO'#FUO,wQPO,59cO-OQPO'#FWOOQO'#Dp'#DpOOQO'#Dq'#DqOOQO'#Dr'#DrO!vQQO'#DnOOQO'#FU'#FUO/|QPO'#DsO0RQSO'#DtO0WQPO'#E|O0`QPO,5<^O!vQQO,5;VOOQO-E8q-E8qOOQO,58y,58yOOQO,59P,59PO0hQPO,59RO0mQPO,59UO0tQPO,5:rO0yQPO,5:uO1QQPO'#FqOOQO'#Ed'#EdO1]QPO'#EcO1bQPO,5:zO1gQPO'#EhO/|QPO,5;RO1rQQO'#EoO3YQPO'#FsOOQO'#En'#EnO!UQPO,5;TO4mQPO,5;XO5}QPO'#F_OOQO'#Ef'#EfOOQO,5;],5;]O7gQPO,5;]OOQO1G.g1G.gOOQO1G.x1G.xO7{QPO1G.zO8SQWO'#FUO;`QWO'#FXO!vQQO'#DOO|QPO,5;xO?RQPO,5;xO?ZQPO'#DTO?`QPO1G/QOOQO1G.}1G.}OOQO'#Df'#DfOOQO'#Dg'#DgOOQO'#Dh'#DhOOQO'#Di'#DiOOQO'#Dk'#DkOOQO'#Dl'#DlOOQO'#Dm'#DmO!vQQO,59|O!vQQO,59|O!vQQO,59|O!vQQO,59|O!vQQO,59|O!vQQO,59|O!vQQO,59|O!vQQO,59|O!vQQO,59|O?kQPO,5:pOAWQPO,5:YOBpQQO'#D]OOQO,5:_,5:_OCgQQO,5:`OCnQPO,5;hOOQO-E8z-E8zOOQO1G0q1G0qOOQO1G.m1G.mO&`QPO1G.pO!vQQO1G0^OCxQPO1G0aO!vQQO,5:|O!UQPO'#EzODVQPO,5<]O!vQQO,5:}O&`QPO1G0fO!UQPO'#E{OD_QPO,5;SO!UQPO,5;SOOQO1G0m1G0mO!UQPO'#EpOOQO,5;Z,5;ZO!UQPO'#E}ODjQPO,5<_O/|QPO1G0oO!vQQO1G0sO!vQQO'#EuOE}QPO,5;yOOQO1G0w1G0wOOQO7+$f7+$fOGeQQO,59kOHfQPO,59jOHmQQO1G/SOHtQSO1G/SOOQO1G/S1G/SOJjQSO,5:YOOQO1G1d1G1dOJtQPO1G1dO!vQQO,59oOOQO7+$l7+$lOMrQPO1G/hOMyQPO1G/hO!!yQPO1G/hO!#QQPO1G/hO!%zQPO1G/hO!&XQPO1G/hO!(iQPO1G/hO!,_QPO1G/hO!,fQPO1G/hO!,mQPO1G0[O!,rQPO'#FaO!,|QPO'#FaOOQO'#D^'#D^O!-UQPO,59wOOQO'#Dv'#DvOOQO'#Ex'#ExO!-ZQQO1G/zOGeQQO'#DwOGeQQO'#DyO!-bQPO'#D{OGeQQO'#D|O!-gQQO'#EOO!.hQPO'#EQOGeQQO'#ESOOQO1G/z1G/zO!.mQPO7+$[O!.rQPO7+%xO!0RQPO7+%{O!0^QPO1G0hOOQO,5;f,5;fOOQO-E8x-E8xOOQO1G0i1G0iO!0eQPO7+&QOOQO,5;g,5;gOOQO-E8y-E8yO!UQPO1G0nOOQO1G0n1G0nO!0jQQO,5;[OOQO,5;i,5;iOOQO-E8{-E8{OOQO7+&Z7+&ZOOQO7+&_7+&_O!0oQPO,5;aOOQO-E8s-E8sO!2XQSO1G/VO!2cQPO1G/UO!4XQSO1G/hO!4`QSO1G/hO!6WQSO1G/hO!6_QSO1G/hO!8PQSO1G/hO!8^QSO1G/hO!:XQSO1G/hO!]QSO,5:cO!>yQSO,5:eO!-gQQO,5:gO!?gQSO,5:hO!@TQ`O'#FUO!BSQ`O'#FWO!EYQ`O,5:jO!-gQQO,5:lO!EyQSO,5:nOOQO<|AN>|OOQO<OAN>OOGeQQO'#DnO!-gQQO'#DnOGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|OGeQQO,59|O!-gQQO,59|O!-gQQO'#EuO#-RQPO,59rO!-gQQO'#EwO/|QPO1G/^", + "KUO!ZQPOOOOQO'#Cc'#CcO!UQPO'#CaO!bQPOOOOQO'#FZ'#FZO!vQQO'#CwO$]QPO'#FYOOQO'#FY'#FYO$gQPO'#FYOOQO'#En'#EnO%vQPO'#EmOOQO'#FU'#FUOOQO'#Eu'#EuO%{QPO'#C_OOQO'#C_'#C_QOQPOOO!UQPO'#CeO&`QPO'#CgO!vQQO'#CjO&gQPO'#EYO!vQQO'#E]O!UQPO'#EbO!UQPO'#EiO&nQPO'#EoO&vQQO'#EsO'^QPO,58{OOQO'#Cq'#CqO!UQPO,59^O!vQQO,59`O(gQQO'#C|O(nQQO'#F`OOQO'#F['#F[OOQO,59f,59fO!UQPO,59fO(xQPO'#FWO,wQPO,59cO-OQPO'#FYOOQO'#Dp'#DpOOQO'#Dq'#DqOOQO'#Dr'#DrO!vQQO'#DnOOQO'#FW'#FWO/|QPO'#DsO0RQSO'#DtO0WQPO'#FOO0`QPO,5<`O!vQQO,5;XOOQO-E8s-E8sOOQO,58y,58yOOQO,59P,59PO0hQPO,59RO0mQPO,59UO0tQPO,5:tO0yQPO,5:wO1QQPO'#FsOOQO'#Ef'#EfO1]QPO'#EeO1bQPO,5:|O1gQPO'#EjO/|QPO,5;TO1rQQO'#EqO3YQPO'#FuOOQO'#Ep'#EpO!UQPO,5;VO4mQPO,5;ZO5}QPO'#FaOOQO'#Eh'#EhOOQO,5;_,5;_O7gQPO,5;_OOQO1G.g1G.gOOQO1G.x1G.xO7{QPO1G.zO8SQWO'#FWO;cQWO'#FZO!vQQO'#DOOPQPO,5;}O&`QPO1G/cOOQO-E8x-E8xOOQO7+%f7+%fO!>XQWO,5:cO!>{QSO,5:cO!?lQSO,5:eO!-sQQO,5:gO!@]QSO,5:hO!@|Q`O'#FWO!COQ`O'#FYO!FXQ`O,5:jO!F{QSO,5:lO!-sQQO,5:nO!GlQSO,5:pOOQO<OAN>OOGnQQO'#DnO!-sQQO'#DnOGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|OGnQQO,59|O!-sQQO,59|O!-sQQO'#EwO#/vQPO,59rO!-sQQO'#EyO/|QPO1G/^", stateData: - "#-^~O#tOS#uOSPOS~OSZOUQOWZOY`O[aO_bOlTO!OfO!{cO#OdO#TeO#^gO#fhO#wPO~O#rRP~P]OgkOilOlnOoqOqmO#yjO~O`yOayObyOcyOdyOlTOqmO!OzO!cxO!i{O#wPO#yjO$WvO$[uO$bwO~Og#zXi#zXl#zXo#zXq#zX#y#zX~Ov|O$O$fX~P#wOS#vXU#vXW#vXY#vX[#vX_#vXl#vX!O#vX!{#vX#O#vX#T#vX#^#vX#f#vX#r#vX#w#vX]#vX!|#vX#Q#vX#R#vX~P#wO$O!OO~O#rRX]RX!|RX#QRX#RRX~P]O]RP~P]O!|RP~P]O!O!aO#wPO~OS!eO#r#eX]#eX!|#eX#Q#eX#R#eX~P!vOU!gO~O`yOayObyOcyOdyOi!lOlTOqmO!OzO!c'eO!i{O#wPO#yjO$WvO$[uO$bwO~Ou!oO~P'cOm!rOx!uO~P!vOm#xX!V#xX!W#xX!X#xX!^#xX$U#xX$V#xX$W#xX$X#xX$Y#xX$Z#xX$[#xX$]#xX$^#xX$_#xX$`#xX$a#xX[#xX#P#xXS#xXv#xX#r#xXj#xXx#xXU#xXW#xXY#xX_#xX!O#xX!{#xX#O#xX#T#xX#^#xX#f#xX#w#xX]#xX!|#xX{#xX|#xX}#xX!R#xX#Q#xX#R#xX~P!bO!V#WO!W#XO!X#PO!^#TO$U!xO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OO~Om!wO~P+sO!y#YOm#zX!V#zX!W#zX!X#zX!^#zX$U#zX$V#zX$W#zX$X#zX$Y#zX$Z#zX$[#zX$]#zX$^#zX$_#zX$`#zX$a#zX[#zX#P#zXS#zXv#zX#r#zXj#zXx#zXU#zXW#zXY#zX_#zX!O#zX!{#zX#O#zX#T#zX#^#zX#f#zX#w#zX]#zX!|#zX{#zX|#zX}#zX!R#zX#Q#zX#R#zX~P#wOl#[O~O$c#^O~OlTO#wPO~Ov|O$O$fa~O]#bO~O[#cO~P+sO!|#dO~O#P#eO~P+sOv#gO$O#fO#X$eX~O#X#iO~O[#jO~Og#kOo#mOl#[X~O$h#oOS#dPU#dPW#dPY#dP[#dP_#dPl#dPv#dP!O#dP!{#dP#O#dP#T#dP#^#dP#f#dP#r#dP#w#dP$O#dP]#dP!|#dP#Q#dP#R#dP~Ov#qOS$gXU$gXW$gXY$gX[$gX_$gXl$gX!O$gX!{$gX#O$gX#T$gX#^$gX#f$gX#r$gX#w$gX$O$gX]$gX!|$gX#Q$gX#R$gX~O$O#tOS#aaU#aaW#aaY#aa[#aa_#aal#aa!O#aa!{#aa#O#aa#T#aa#^#aa#f#aa#r#aa#w#aa]#aa!|#aa#Q#aa#R#aa~Ov#uOS$RX#r$RXm$RXx$RXU$RXW$RXY$RX[$RX_$RXl$RX!O$RX!{$RX#O$RX#T$RX#^$RX#f$RX#w$RX]$RX!|$RX#Q$RX#R$RX~P+sOS#wO#r#ea]#ea!|#ea#Q#ea#R#ea~Oj#xO~P+sOu#xX!V#xX!W#xX!X#xX!^#xX$P#xX$U#xX$V#xX$W#xX$X#xX$Y#xX$Z#xX$[#xX$]#xX$^#xX$_#xX$`#xX$a#xXx#xX!l#xX!n#xX!q#xX!s#xX!u#xX!w#xX$d#xX~P!bOg#{Xi#{Xl#{Xo#{Xq#{X!V#{X!W#{X!X#{X!^#{X#y#{X$U#{X$V#{X$W#{X$X#{X$Y#{X$Z#{X$[#{X$]#{X$^#{X$_#{X$`#{X$a#{X~O$O#yOu#{X$P#{X~P9xO!V'uO!W'wO!X'gO!^'oO$U!xO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OO~OutX$PtX~P;mOu#}O$P#{O~O!y#YOu#zX!V#zX!W#zX!X#zX!^#zX$P#zX$U#zX$V#zX$W#zX$X#zX$Y#zX$Z#zX$[#zX$]#zX$^#zX$_#zX$`#zX$a#zXx#zX!l#zX!n#zX!q#zX!s#zX!u#zX!w#zX$d#zX~P#wOm$PO~Om$POx!uO~Oy$RO~OlnOqmO#yjO~Ol$^O~O$a#OO!V!ba!W!ba!X!ba!^!ba$U!ba$V!ba$W!ba$X!ba$Y!ba$Z!ba$[!ba$]!ba$^!ba$_!ba$`!bav!bax!ba{!ba|!ba}!ba!R!ba~Om!ba[!ba#P!baS!ba#r!baj!baU!baW!baY!ba_!bal!ba!O!ba!{!ba#O!ba#T!ba#^!ba#f!ba#w!ba]!ba!|!ba#Q!ba#R!ba~P?pOc$`OlTO#wPOm!QP~Ox$hO!l$fO!n$gO!q$iO!s$jO!u$kO!w$lO~O$d$mO~PCOOv#pa$O#pa~P#wO]RP#QRP#RRP~P]Ov#gO#X$ea~Og#kOo$xOl#[a~Ov#qOS$gaU$gaW$gaY$ga[$ga_$gal$ga!O$ga!{$ga#O$ga#T$ga#^$ga#f$ga#r$ga#w$ga$O$ga]$ga!|$ga#Q$ga#R$ga~Ov#uOS$Ra#r$Ram$Rax$RaU$RaW$RaY$Ra[$Ra_$Ral$Ra!O$Ra!{$Ra#O$Ra#T$Ra#^$Ra#f$Ra#w$Ra]$Ra!|$Ra#Q$Ra#R$Ra~O`yOayObyOcyOdyOlTOqmO!OzO!c'eO!i{O#wPO#yjO$WvO$[uO$bwO~Oj%SO~P+sOu%_O~P'cOu%_O$P%`O~O$a#OO!V!ba!W!ba!X!ba!^!ba$U!ba$V!ba$W!ba$X!ba$Y!ba$Z!ba$[!ba$]!ba$^!ba$_!ba$`!bax!ba!l!ba!n!ba!q!ba!s!ba!u!ba!w!ba$d!ba~Ou!ba$P!ba~PH|Om%bO~O!^#TO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOm!Ui!V!Ui!W!Ui!X!Ui[!Ui#P!UiS!Uiv!Ui#r!Uij!Uix!UiU!UiW!UiY!Ui_!Uil!Ui!O!Ui!{!Ui#O!Ui#T!Ui#^!Ui#f!Ui#w!Ui]!Ui!|!Ui{!Ui|!Ui}!Ui!R!Ui#Q!Ui#R!Ui~O$U!xO~PJyO$U!Ui~PJyO!^#TO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOm!Ui!V!Ui!W!Ui!X!Ui$U!Ui$W!Ui[!Ui#P!UiS!Uiv!Ui#r!Uij!Uix!UiU!UiW!UiY!Ui_!Uil!Ui!O!Ui!{!Ui#O!Ui#T!Ui#^!Ui#f!Ui#w!Ui]!Ui!|!Ui{!Ui|!Ui}!Ui!R!Ui#Q!Ui#R!Ui~O$V!Ui~PNQO$V!yO~PNQO$]!}O$^!}O$_!}O$`!}O$a#OOm!Ui!V!Ui!W!Ui!X!Ui$U!Ui$V!Ui$W!Ui$X!Ui$Y!Ui[!Ui#P!UiS!Uiv!Ui#r!Uij!Uix!UiU!UiW!UiY!Ui_!Uil!Ui!O!Ui!{!Ui#O!Ui#T!Ui#^!Ui#f!Ui#w!Ui]!Ui!|!Ui{!Ui|!Ui}!Ui!R!Ui#Q!Ui#R!Ui~O!^#TO$Z!|O$[!|O~P!#XO!^!Ui$Z!Ui$[!Ui~P!#XO$a#OOm!Ui!V!Ui!W!Ui[!Ui#P!UiS!Uiv!Ui#r!Uij!Uix!UiU!UiW!UiY!Ui_!Uil!Ui!O!Ui!{!Ui#O!Ui#T!Ui#^!Ui#f!Ui#w!Ui]!Ui!|!Ui{!Ui|!Ui}!Ui!R!Ui#Q!Ui#R!Ui~O!X!Ui!^!Ui$U!Ui$V!Ui$W!Ui$X!Ui$Y!Ui$Z!Ui$[!Ui$]!Ui$^!Ui$_!Ui$`!Ui~P!&fO!X#PO!^#TO$U!xO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOm!Ui!V!Ui[!Ui#P!UiS!Uiv!Ui#r!Uij!Uix!UiU!UiW!UiY!Ui_!Uil!Ui!O!Ui!{!Ui#O!Ui#T!Ui#^!Ui#f!Ui#w!Ui]!Ui!|!Ui{!Ui|!Ui}!Ui!R!Ui#Q!Ui#R!Ui~O!W#XO~P!)fO!W!Ui~P!)fO!n%fO~Ov%gOm$TX~P#wOv%gOm$TX~Om%iO~O$d%kO~PCOOy%oO~O`yOayObyOcyOdyOlTOqmO!OzO!c'fO!i{O#wPO#yjO$WvO$[uO$bwO~Oy%tO~O]%vO~OS!zqU!zqW!zqY!zq[!zq_!zql!zq!O!zq!{!zq#O!zq#T!zq#^!zq#f!zq#r!zq#w!zq]!zq!|!zq#Q!zq#R!zq~P+sO]%yO#Q%xO#R%zO~Ov%{O~P+sO]%|O~O$i&OO~OS#iav#ia#r#iam#iax#iaU#iaW#iaY#ia[#ia_#ial#ia!O#ia!{#ia#O#ia#T#ia#^#ia#f#ia#w#ia]#ia!|#ia#Q#ia#R#ia~P+sOusi$Psi~P;mO$O&PO~O!^'oO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOu!Ui!V!Ui!W!Ui!X!Ui$P!Uix!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui~O$U!xO~P!2hO$U!Ui~P!2hO!^'oO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOu!Ui!V!Ui!W!Ui!X!Ui$P!Ui$U!Ui$W!Uix!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui~O$V!Ui~P!4gO$V!yO~P!4gO$]!}O$^!}O$_!}O$`!}O$a#OOu!Ui!V!Ui!W!Ui!X!Ui$P!Ui$U!Ui$V!Ui$W!Ui$X!Ui$Y!Uix!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui~O!^'oO$Z!|O$[!|O~P!6fO!^!Ui$Z!Ui$[!Ui~P!6fO$a#OO!V!Ui!W!Ui!X!Ui!^!Ui$U!Ui$V!Ui$W!Ui$X!Ui$Y!Ui$Z!Ui$[!Ui$]!Ui$^!Ui$_!Ui$`!Uix!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui~Ou!Ui$P!Ui~P!8kO!X'gO!^'oO$U!xO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOu!Ui!V!Ui$P!Uix!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui~O!W'wO~P!:cO!W!Ui~P!:cOu&QO~P'cO{&TO|&TO}&UO!R&SOmzXvzX~P+sOv&VOm$SX~Oc&ZOlTO#wPO~Ov%gOm$Ta~O$O&^Ox#{X!l#{X!n#{X!q#{X!s#{X!u#{X!w#{X$d#{X~P9xOx!ka!l!ka!n!ka!q!ka!s!ka!u!ka!w!ka$d!ka~P;mOx!ma!l!ma!n!ma!q!ma!s!ma!u!ma!w!ma$d!ma~P;mOx!pa!l!pa!n!pa!q!pa!s!pa!u!pa!w!pa$d!pa~P;mOv#xXx#xX!V#xX!W#xX!X#xX!^#xX!l#xX!n#xX!q#xX!s#xX!u#xX!w#xX$U#xX$V#xX$W#xX$X#xX$Y#xX$Z#xX$[#xX$]#xX$^#xX$_#xX$`#xX$a#xX$d#xX{#xX|#xX}#xX!R#xX~P!bO!y#YOv#zXx#zX!V#zX!W#zX!X#zX!^#zX!l#zX!n#zX!q#zX!s#zX!u#zX!w#zX$U#zX$V#zX$W#zX$X#zX$Y#zX$Z#zX$[#zX$]#zX$^#zX$_#zX$`#zX$a#zX$d#zX{#zX|#zX}#zX!R#zX~P#wO!V'vO!W'xO!X'hO!^'pO$U!xO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OO~Ov&bOx!ra!l!ra!n!ra!q!ra!s!ra!u!ra!w!ra$d!ra~P!DUOx!va!l!va!n!va!q!va!s!va!u!va!w!va$d!va~P;mO]&gO#Q%xO#R&hO~O!S&mO!T&mO~O!R&nOmzavza~O!O&pO#wPO~Ov&VOm$Sa~Om&sO~P+sOm#jav#ja~P#wO]&tO~O{&vO|&vO}'zO!R&SOvzXxzX!lzX!nzX!qzX!szX!uzX!wzX$dzX~P!DUOv'{Ox$SX!l$SX!n$SX!q$SX!s$SX!u$SX!w$SX$d$SX~Ov!ba{!ba|!ba}!ba!R!ba~PH|Ov'yOx$RX!l$RX!n$RX!q$RX!s$RX!u$RX!w$RX$d$RX~P!DUO#P'UO~P+sO]'VO~Ov'WO[#Uy~P+sOury$Pry~P;mO!S'XO!T'XO~O!R'YOmzivzi~Ox!kq!l!kq!n!kq!q!kq!s!kq!u!kq!w!kq$d!kq~P;mO!R&nOvzaxza!lza!nza!qza!sza!uza!wza$dza~Ov'{Ox$Sa!l$Sa!n$Sa!q$Sa!s$Sa!u$Sa!w$Sa$d$Sa~O!^'pO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOv!Uix!Ui!V!Ui!W!Ui!X!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui{!Ui|!Ui}!Ui!R!Ui~O$U!xO~P!MoO$U!Ui~P!MoO!^'pO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOv!Uix!Ui!V!Ui!W!Ui!X!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$U!Ui$W!Ui$d!Ui{!Ui|!Ui}!Ui!R!Ui~O$V!Ui~P# wO$V!yO~P# wO$]!}O$^!}O$_!}O$`!}O$a#OOv!Uix!Ui!V!Ui!W!Ui!X!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$U!Ui$V!Ui$W!Ui$X!Ui$Y!Ui$d!Ui{!Ui|!Ui}!Ui!R!Ui~O!^'pO$Z!|O$[!|O~P#$PO!^!Ui$Z!Ui$[!Ui~P#$POv!Ui{!Ui|!Ui}!Ui!R!Ui~P!8kO!X'hO!^'pO$U!xO$V!yO$W!zO$X!{O$Y!{O$Z!|O$[!|O$]!}O$^!}O$_!}O$`!}O$a#OOv!Uix!Ui!V!Ui!l!Ui!n!Ui!q!Ui!s!Ui!u!Ui!w!Ui$d!Ui{!Ui|!Ui}!Ui!R!Ui~O!W'xO~P#&rO!W!Ui~P#&rOx!rq!l!rq!n!rq!q!rq!s!rq!u!rq!w!rq$d!rq~P;mOv'yOx$Ra!l$Ra!n$Ra!q$Ra!s$Ra!u$Ra!w$Ra$d$Ra~O]'^O~O!S'aO!T'aO~O!R'bOmzqvzq~O!R'YOvzixzi!lzi!nzi!qzi!szi!uzi!wzi$dzi~Ov#iax#ia!l#ia!n#ia!q#ia!s#ia!u#ia!w#ia$d#ia~P!DUO[#U!Z~P+sO!S'dO!T'dO~O!R'bOvzqxzq!lzq!nzq!qzq!szq!uzq!wzq$dzq~O!O'|O#wPO~OP$[~", - goto: "Fi$hPPP$iP%XP%fP%XP%XPP%XPPPPPP(`)}P)}PP+sPP-iP(`P/g/g/gPP/mPP/sPPPP/}0^PPP(gPPP0a1]2_3^P4c5k6v(gP8X8X8X(g(gP9s9wP9wP9w9wP9wP9wP9wP(gP%XPP%XPPPP%XP9{9{:OP:R%X:_%XP%X:b%X:o:r:x:{P;[;k;q;{R#Q;'S=o;'S;=`>}<%lO=ok>UVO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kk>nTO#P=o#P#Q7z#Q;'S=o;'S;=`>}<%lO=ok?QP;=`<%l=ok?WTO#P?g#P#Q@P#Q;'S?g;'S;=`?y<%lO?gk?jTO#P?g#P#Q7z#Q;'S?g;'S;=`?y<%lO?gk?|P;=`<%l?gk@UP#yk!_!`8Qk@[VO!_=o!_!`@q!`#P=o#P#QAu#Q;'S=o;'S;=`>}<%lO=ok@tTO#P=o#P#QAT#Q;'S=o;'S;=`>}<%lO=okAYV#ykO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kkArP;=`<%l>kkAxVO!_?g!_!`?T!`#P?g#P#QAu#Q;'S?g;'S;=`?y<%lO?goBdT$cSO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskBvTO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskCYTO#PBs#P#Q%b#Q;'SBs;'S;=`Ci<%lOBskClP;=`<%lBsoCtPjP#P#QCwnC|O$dn~DRO$a~~DWOq~~D]O$U~~DbOu~~DgP$W~!_!`$m", + "Dj~RuXY#fYZ$Q[]#f]^$_pq#fqr$grs$rst)`uv)evw)jwx)oxy.Wyz.]z{.b{|.g|}.l}!O.s!O!P/g!P!Q/|!Q!R0^!R![1u![!]3m!]!^3z!^!_4R!_!`4e!`!a4m!c!}5P!}#O5b#O#P#w#P#QCo#Q#RC|#R#S5P#T#o5P#o#pDR#p#qDW#q#rD]#r#sDb~#kS#w~XY#f[]#fpq#f#O#P#w~#zQYZ#f]^#f~$VP#v~]^$Y~$_O#v~~$dP#v~YZ$Ym$jP!_!`$mm$rO!Xmk$uXOY$rZ]$r^r$rrs%bs#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk%gO#{kk%j]rs$rwx$r!Q![&c#O#P$r#T#U$r#U#V$r#Y#Z$r#b#c$r#f#g$r#h#i$r#i#j(T#l#m(v#n#o$rk&fZOY$rZ]$r^r$rrs%bs!Q$r!Q!['X![#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk'[ZOY$rZ]$r^r$rrs%bs!Q$r!Q![$r![#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk(QP;=`<%l$rk(WP#o#p(Zk(^R!Q![(g!c!i(g#T#Z(gk(jS!Q![(g!c!i(g#T#Z(g#q#r$rk(yR!Q![)S!c!i)S#T#Z)Sk)VR!Q![$r!c!i$r#T#Z$r~)eO$d~~)jO$a~~)oO$X~k)rXOY)oZ])o^w)owx%bx#O)o#O#P*_#P;'S)o;'S;=`,u<%lO)ok*b]rs)owx)o!Q![+Z#O#P)o#T#U)o#U#V)o#Y#Z)o#b#c)o#f#g)o#h#i)o#i#j,{#l#m-n#n#o)ok+^ZOY)oZ])o^w)owx%bx!Q)o!Q![,P![#O)o#O#P*_#P;'S)o;'S;=`,u<%lO)ok,SZOY)oZ])o^w)owx%bx!Q)o!Q![)o![#O)o#O#P*_#P;'S)o;'S;=`,u<%lO)ok,xP;=`<%l)ok-OP#o#p-Rk-UR!Q![-_!c!i-_#T#Z-_k-bS!Q![-_!c!i-_#T#Z-_#q#r)ok-qR!Q![-z!c!i-z#T#Z-zk-}R!Q![)o!c!i)o#T#Z)o~.]Ol~~.bOm~~.gO$_~~.lO$]~o.sOvc$R[~.xP$^~}!O.{~/QTP~OY.{Z].{^;'S.{;'S;=`/a<%lO.{~/dP;=`<%l.{o/lPgi!O!P/oo/tP!^m!O!P/wQ/|OcQ~0RQ$`~!P!Q0X!_!`$m~0^O$b~~0cUd~!O!P0u!Q![1u!g!h1Z!z!{2W#X#Y1Z#l#m2W~0xP!Q![0{~1QRd~!Q![0{!g!h1Z#X#Y1Z~1^R{|1g}!O1g!Q![1m~1jP!Q![1m~1rPd~!Q![1m~1zSd~!O!P0u!Q![1u!g!h1Z#X#Y1Z~2ZR!Q![2d!c!i2d#T#Z2d~2iUd~!O!P2{!Q![2d!c!i2d!r!s1Z#T#Z2d#d#e1Z~3OR!Q![3X!c!i3X#T#Z3X~3^Td~!Q![3X!c!i3X!r!s1Z#T#Z3X#d#e1Z~3rPo~![!]3u~3zOU~_4ROSR$R[o4YQ$jQ!Xm!^!_4`!_!`$mm4eO$Zm~4jP$Q~!_!`$mo4tQ$kQ!Xm!_!`$m!`!a4zm5PO$[m~5US#y~!Q![5P!c!}5P#R#S5P#T#o5Po5gQik!_!`5m!}#OB_k5pQ!_!`5v!}#O=ok5yP!}#O5|k6PTO#P5|#P#Q6`#Q;'S5|;'S;=`8W<%lO5|k6cVO!_6x!_!`:s!`#P6x#P#Q;w#Q;'S6x;'S;=`=i<%lO6xk6{VO!_7b!_!`8^!`#P7b#P#Q8v#Q;'S7b;'S;=`=c<%lO7bk7eTO#P5|#P#Q7t#Q;'S5|;'S;=`8W<%lO5|k7wP!_!`7zk7}P!_!`8Qk8TP#P#Q%bk8ZP;=`<%l5|k8aTO#P8^#P#Q7t#Q;'S8^;'S;=`8p<%lO8^k8sP;=`<%l8^k8yUO!_5|!_!`9]!`#P5|#Q;'S5|;'S;=`8W<%lO5|k9`VO!_5|!_!`9u!`#P5|#P#Q6`#Q;'S5|;'S;=`8W<%lO5|k9xTO#P5|#P#Q:X#Q;'S5|;'S;=`8W<%lO5|k:^V#{kO!_6x!_!`:s!`#P6x#P#Q;w#Q;'S6x;'S;=`=i<%lO6xk:vVO!_8^!_!`;]!`#P8^#P#Q7t#Q;'S8^;'S;=`8p<%lO8^k;`TO#P8^#P#Q;o#Q;'S8^;'S;=`8p<%lO8^k;tP#{k!_!`7zk;zVO!_7b!_!`:s!`#P7b#P#QR#Q;'S=o;'S;=`>}<%lO=ok>UVO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kk>nTO#P=o#P#Q7z#Q;'S=o;'S;=`>}<%lO=ok?QP;=`<%l=ok?WTO#P?g#P#Q@P#Q;'S?g;'S;=`?y<%lO?gk?jTO#P?g#P#Q7z#Q;'S?g;'S;=`?y<%lO?gk?|P;=`<%l?gk@UP#{k!_!`8Qk@[VO!_=o!_!`@q!`#P=o#P#QAu#Q;'S=o;'S;=`>}<%lO=ok@tTO#P=o#P#QAT#Q;'S=o;'S;=`>}<%lO=okAYV#{kO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kkArP;=`<%l>kkAxVO!_?g!_!`?T!`#P?g#P#QAu#Q;'S?g;'S;=`?y<%lO?goBdT$eSO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskBvTO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskCYTO#PBs#P#Q%b#Q;'SBs;'S;=`Ci<%lOBskClP;=`<%lBsoCtPjP#P#QCwnC|O$fn~DRO$c~~DWOq~~D]O$W~~DbOu~~DgP$Y~!_!`$m", tokenizers: [0, 1, 2, 3, 4], topRules: { Chunk: [0, 2] }, - dynamicPrecedences: { 140: 1 }, - specialized: [{ term: 131, get: (value) => spec_identifier[value] || -1 }], - tokenPrec: 4795, + dynamicPrecedences: { 142: 1 }, + specialized: [{ term: 133, get: (value) => spec_identifier[value] || -1 }], + tokenPrec: 4922, }); diff --git a/client/space_lua/parse-lua.terms.js b/client/space_lua/parse-lua.terms.js index ea56ff07..b6c66aa1 100644 --- a/client/space_lua/parse-lua.terms.js +++ b/client/space_lua/parse-lua.terms.js @@ -22,17 +22,18 @@ export const Comment = 1, OrderByClause = 77, SelectClause = 78, LimitClause = 80, - GroupByClause = 82, - HavingClause = 84, - IfStatement = 91, - ForStatement = 96, - ForNumeric = 98, - ForGeneric = 99, - NameList = 100, - ExpList = 102, - FuncName = 104, - VarList = 108, - AttNameList = 110, - AttName = 111, - Attrib = 112, - ReturnStatement = 113; + OffsetClause = 82, + GroupByClause = 84, + HavingClause = 86, + IfStatement = 93, + ForStatement = 98, + ForNumeric = 100, + ForGeneric = 101, + NameList = 102, + ExpList = 104, + FuncName = 106, + VarList = 110, + AttNameList = 112, + AttName = 113, + Attrib = 114, + ReturnStatement = 115; From 5205ab515469fdd35a63a65bab758c6de86317c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Jan=20Fialka?= Date: Wed, 11 Mar 2026 11:47:11 +0100 Subject: [PATCH 5/6] [LIQ] Implement `offset` (4: add tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matouš Jan Fialka --- client/space_lua/query_test.lua | 219 ++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/client/space_lua/query_test.lua b/client/space_lua/query_test.lua index 26f772da..a61ebe15 100644 --- a/client/space_lua/query_test.lua +++ b/client/space_lua/query_test.lua @@ -2887,3 +2887,222 @@ do "expected `order by` error, got: " .. tostring(err) ) end + +-- 92. Standalone offset clause + +-- 92a. Offset only, bound +do + local r = query [[ + from + p = pages + select { name = p.name } + order by + p.name + offset + 2 + ]] + assertEquals(#r, 5) + assertEquals(r[1].name, "Carol") +end + +-- 92b. Offset only, unbound +do + local r = query [[ + from + pages + order by + name + offset + 5 + ]] + assertEquals(#r, 2) + assertEquals(r[1].name, "Fran") + assertEquals(r[2].name, "Greg") +end + +-- 92c. Offset 0 returns everything +do + local r = query [[ + from + pages + offset + 0 + ]] + assertEquals(#r, #pages) +end + +-- 92d. Offset larger than dataset returns empty +do + local r = query [[ + from + pages + offset + 100 + ]] + assertEquals(#r, 0) +end + +-- 92e. Offset equal to dataset size returns empty +do + local r = query [[ + from + pages + offset + 7 + ]] + assertEquals(#r, 0) +end + +-- 93. Standalone offset + limit (separate clauses) + +-- 93a. Offset before limit +do + local r = query [[ + from + p = pages + select { name = p.name } + order by + p.name + offset + 2 + limit + 3 + ]] + -- skip 2, take 3 -> Carol, Dave, Ed + assertEquals(#r, 3) + assertEquals(r[1].name, "Carol") + assertEquals(r[2].name, "Dave") + assertEquals(r[3].name, "Ed") +end + +-- 93b. Limit before offset (order of clauses doesn't matter) +do + local r = query [[ + from + p = pages + select { name = p.name } + order by + p.name + limit + 3 + offset + 2 + ]] + assertEquals(#r, 3) + assertEquals(r[1].name, "Carol") +end + +-- 93c. Offset beyond available rows with limit returns empty +do + local r = query [[ + from + pages + limit + 3 + offset + 100 + ]] + assertEquals(#r, 0) +end + +-- 93d. Limit larger than remaining after offset +do + local r = query [[ + from + p = pages + order by + p.name + offset + 5 + limit + 100 + ]] + assertEquals(#r, 2) +end + +-- 94. Standalone offset with where + order by + +do + local r = query [[ + from + p = pages + where + p.size >= 5 + order by + p.size + select { name = p.name } + offset + 1 + ]] + assertEquals(#r, 3) + assertEquals(r[1].name, "Alice") + assertEquals(r[2].name, "Dave") + assertEquals(r[3].name, "Bob") +end + +-- 95. Standalone offset with group by + +do + local r = query [[ + from + p = pages + where + p.tags[1] ~= nil + group by + p.tags[1] + select { + tag = key, + n = count(), + } + order by + tag + offset + 1 + ]] + assertEquals(#r, 2) + assertEquals(r[1].tag, "random") + assertEquals(r[2].tag, "work") +end + +-- 96. Standalone offset + limit with group by + having + +do + local r = query [[ + from + p = pages + where + p.tags[1] ~= nil + group by + p.tags[1] + having + count() > 0 + select { + tag = key, + n = count(), + } + order by + tag + offset + 1 + limit + 1 + ]] + assertEquals(#r, 1) + assertEquals(r[1].tag, "random") +end + +-- 97. Standalone offset wins over inline offset (last one wins) +do + local r = query [[ + from + p = pages + order by + p.name + limit + 3, 1 + offset + 2 + ]] + assertEquals(#r, 3) + assertEquals(r[1].name, "Carol") +end From 9522437941ee9bc2b1a82f718e04260e2c19bacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Jan=20Fialka?= Date: Wed, 11 Mar 2026 15:17:06 +0100 Subject: [PATCH 6/6] [LIQ] Website: Improve general syntax description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matouš Jan Fialka --- website/Space Lua/Lua Integrated Query.md | 53 +++++++++++++---------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/website/Space Lua/Lua Integrated Query.md b/website/Space Lua/Lua Integrated Query.md index f1ebe654..ea515e4a 100644 --- a/website/Space Lua/Lua Integrated Query.md +++ b/website/Space Lua/Lua Integrated Query.md @@ -6,34 +6,43 @@ Lua Integrated Query (LIQ) is a SilverBullet specific Lua extension. It adds a c The syntax for LIQ is `query[[my query]]`. In regular Lua `[[my query]]` is just another way of writing `"my query"` (it is an alternative string syntax). Function calls that only take a string argument can omit parentheses, therefore `query[[my query]]` is equivalent to `query("my query")`. -However, in [[Space Lua]] it is interpreted as an SQL (and [LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/linq/))-inspired integrated query language. +However, in [[Space Lua]] it is interpreted as an SQL- and [LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/linq/)-inspired integrated query language. General syntax: - query [[ - from - where - group by [, ...] - having - order by - [asc | desc | using ] - [nulls {first | last}] - [, ...] - limit [, ] - offset - select - ]] +```postgres +query [[ + from <> + [ where <> ] + [ group by <> [, ...] ] + [ having <> ] + [ order by <> [, ...] ] + [ limit <> [, <>] ] + [ offset <> ] + [ select <> ] +]] +``` -Aggregate functions support an optional intra-aggregate `order by` and/or `filter` clause: +Unlike in SQL where clauses must be written in a particular order, LIQ clauses can be written in any order, and the only mandatory clause is the `from` clause. - ( [, ...] - [order by - [asc | desc | using ] - [nulls {first | last}] - [, ...]] - [filter(where )]) +The `order by` clause uses `<>`: -These can be combined. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. +```postgres +<> + [ asc | desc | using <> ] + [ nulls { first | last } ] +``` + +If an aggregate function call is used for projection (`select`), the general syntax is: + +```postgres +<>( <> [, ...] + [ order by <> [, ...] ] + [ filter (where <>) ] +) +``` + +See [[Space Lua/Lua Integrated Query/Aggregating]] for details. LIQ operates on any Lua collection.