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/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 } 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; 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!) { diff --git a/client/space_lua/query_test.lua b/client/space_lua/query_test.lua index 26f772da..4b208647 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 \ No newline at end of file diff --git a/website/Space Lua/Lua Integrated Query.md b/website/Space Lua/Lua Integrated Query.md index 841c6bff..d82e2df2 100644 --- a/website/Space Lua/Lua Integrated Query.md +++ b/website/Space Lua/Lua Integrated Query.md @@ -6,22 +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 [, ...] - select - ]] +```postgres +query [[ + from <> + [ where <> ] + [ group by <> [, ...] ] + [ having <> ] + [ order by <> [, ...] ] + [ limit <> [, <>] ] + [ offset <> ] + [ select <> ] +]] +``` + +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. + +The `order by` clause uses `<>`: + +```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. Aggregate functions support an optional intra-aggregate `order by` and/or `filter` clause: @@ -238,16 +259,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.