diff --git a/client/space_lua/ast.ts b/client/space_lua/ast.ts index 5c55bc41..dcbea714 100644 --- a/client/space_lua/ast.ts +++ b/client/space_lua/ast.ts @@ -331,6 +331,7 @@ export type LuaOrderBy = { expression: LuaExpression; direction: "asc" | "desc"; nulls?: "first" | "last"; + using?: string | LuaFunctionBody; } & ASTContext; export type LuaSelectClause = { diff --git a/client/space_lua/eval.ts b/client/space_lua/eval.ts index 53a12105..94cd1741 100644 --- a/client/space_lua/eval.ts +++ b/client/space_lua/eval.ts @@ -895,6 +895,7 @@ export function evalExpression( expr: o.expression, desc: o.direction === "desc", nulls: o.nulls, + using: o.using, })); break; } diff --git a/client/space_lua/lua.grammar b/client/space_lua/lua.grammar index 4ccb8a46..c0cd052e 100644 --- a/client/space_lua/lua.grammar +++ b/client/space_lua/lua.grammar @@ -104,7 +104,7 @@ FromClause { ckw<"from"> (Name "=")? exp } WhereClause { ckw<"where"> exp } LimitClause { ckw<"limit"> exp ("," exp)? } OrderByClause { ckw<"order"> ckw<"by"> list } -OrderBy { exp (ckw<"desc"> | ckw<"asc">)? (ckw<"nulls"> (ckw<"first"> | ckw<"last">))? } +OrderBy { exp (ckw<"desc"> | ckw<"asc"> | kw<"using"> (Name | kw<"function"> FuncBody))? (ckw<"nulls"> (ckw<"first"> | ckw<"last">))? } SelectClause { ckw<"select"> exp } GroupByClause { ckw<"group"> ckw<"by"> list } HavingClause { ckw<"having"> exp } diff --git a/client/space_lua/parse-lua.js b/client/space_lua/parse-lua.js index 0f70085a..bd44f2ce 100644 --- a/client/space_lua/parse-lua.js +++ b/client/space_lua/parse-lua.js @@ -1,22 +1,22 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. import {LRParser} from "@lezer/lr" -const spec_identifier = {__proto__:null,break:16, goto:20, do:24, end:26, while:30, nil:32, true:34, false:36, or:80, and:82, not:104, function:114, query:122, from:129, where:133, order:137, by:139, desc:143, asc:145, nulls:147, first:149, last:151, select:155, limit:159, group:163, having:167, filter:171, repeat:174, until:176, if:180, then:182, elseif:184, else:186, for:190, in:198, local:208, return:224} +const spec_identifier = {__proto__:null,break:16, goto:20, do:24, end:26, while:30, nil:32, true:34, false:36, or:80, and:82, not:104, function:114, query:122, from:129, where:133, order:137, by:139, desc:143, asc:145, using:146, nulls:149, first:151, last:153, select:157, limit:161, group:165, having:169, filter:173, repeat:176, until:178, if:182, then:184, elseif:186, else:188, for:192, in:200, local:210, return:226} export const parser = LRParser.deserialize({ version: 14, - states: "GxO!ZQPOOOOQO'#Cc'#CcO!UQPO'#CaO!bQPOOOOQO'#FV'#FVO!vQQO'#CwO$]QPO'#FUOOQO'#FU'#FUO$gQPO'#FUOOQO'#Ej'#EjO%vQPO'#EiOOQO'#FQ'#FQOOQO'#Eq'#EqO%{QPO'#C_OOQO'#C_'#C_QOQPOOO!UQPO'#CeO&`QPO'#CgO!vQQO'#CjO&gQPO'#EUO!vQQO'#EXO!UQPO'#E^O!UQPO'#EeO&nQPO'#EkO&vQQO'#EoO'^QPO,58{OOQO'#Cq'#CqO!UQPO,59^O!vQQO,59`O(gQQO'#C|O(nQQO'#F[OOQO'#FW'#FWOOQO,59f,59fO!UQPO,59fO(uQPO'#FSO,eQPO,59cO,lQPO'#FUOOQO'#Dc'#DcOOQO'#Dd'#DdOOQO'#De'#DeO!vQQO'#DaOOQO'#FS'#FSO/ZQPO'#DfO/`QSO'#DjO/eQPO'#EzO/mQPO,5<[O!vQQO,5;TOOQO-E8o-E8oOOQO,58y,58yOOQO,59P,59PO/uQPO,59RO/zQPO,59UO0RQPO,5:pO0WQPO,5:sO0_QPO'#FoOOQO'#Eb'#EbO0jQPO'#EaO0oQPO,5:xO0tQPO'#EfO/ZQPO,5;PO1PQQO'#EmO2gQPO'#FqOOQO'#El'#ElO!UQPO,5;RO3zQPO,5;VO5[QPO'#F]OOQO'#Ed'#EdOOQO,5;Z,5;ZO6qQPO,5;ZOOQO1G.g1G.gOOQO1G.x1G.xO7VQPO1G.zO7^QWO'#FSO:jQWO'#FVO!vQQO'#DOO;{QSO'#DQOOQO'#FX'#FXOOQO,59h,59hOWQPO,5;vO>]QPO1G/QOOQO1G.}1G.}OOQO'#DX'#DXOOQO'#DY'#DYOOQO'#DZ'#DZOOQO'#D['#D[OOQO'#D^'#D^OOQO'#D_'#D_OOQO'#D`'#D`O!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO>hQPO,5:nO?tQPO,59{OA^QQO'#DhOOQO,5:Q,5:QOBTQQO,5:UOB[QPO,5;fOOQO-E8x-E8xOOQO1G0o1G0oOOQO1G.m1G.mO&`QPO1G.pO!vQQO1G0[OBfQPO1G0_O!vQQO,5:zO!UQPO'#ExOBsQPO,5TQ`O'#FUO!AWQ`O,5:hO!*^QQO,5:jO!AwQSO,5:lOOQO<zAN>zOOQO<mOc$[OlTO#uPOm!]P~O!b$bO!d$cO!f$dO!o$eO!q$fO!s$gO!u$hO~O$b$iO~PAlOv#na#|#na~P#wO]RP#ORP#PRP~P]Ov#eO#V$ca~Og#iOo$tOl#Ya~Ov#oOS$eaU$eaW$eaY$ea[$ea_$eal$ea!Z$ea!y$ea!|$ea#R$ea#[$ea#d$ea#p$ea#u$ea#|$ea]$ea!z$ea#O$ea#P$ea~Ov#sOS$Pa#p$Pam$PaU$PaW$PaY$Pa[$Pa_$Pal$Pa!Z$Pa!y$Pa!|$Pa#R$Pa#[$Pa#d$Pa#u$Pa]$Pa!z$Pa#O$Pa#P$Pa~O`yOayObyOcyOdyOlTOqmO!U'PO!ZzO!_{O#uPO#wjO$SvO$WuO$^wO~Oj%OO~P+aOu%ZO~P'cOu%ZO#}%[O~O$]!|Ox!Tay!Taz!Ta!P!Ta$Q!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta!b!Ta!d!Ta!f!Ta!o!Ta!q!Ta!s!Ta!u!Ta$b!Ta~Ou!Ta#}!Ta~PGgO!P#RO$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Omwixwiywizwi[wi!}wiSwivwi#pwijwiUwiWwiYwi_wilwi!Zwi!ywi!|wi#Rwi#[wi#dwi#uwi]wi!zwi#Owi#Pwi~O$Q!vO~PI_O$Qwi~PI_O!P#RO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Omwixwiywizwi$Qwi$Swi[wi!}wiSwivwi#pwijwiUwiWwiYwi_wilwi!Zwi!ywi!|wi#Rwi#[wi#dwi#uwi]wi!zwi#Owi#Pwi~O$Rwi~PLVO$R!wO~PLVO$X!{O$Y!{O$Z!{O$[!{O$]!|Omwixwiywizwi$Qwi$Rwi$Swi$Twi$Uwi[wi!}wiSwivwi#pwijwiUwiWwiYwi_wilwi!Zwi!ywi!|wi#Rwi#[wi#dwi#uwi]wi!zwi#Owi#Pwi~O!P#RO$V!zO$W!zO~PN}O!Pwi$Vwi$Wwi~PN}O$]!|Omwixwiywi[wi!}wiSwivwi#pwijwiUwiWwiYwi_wilwi!Zwi!ywi!|wi#Rwi#[wi#dwi#uwi]wi!zwi#Owi#Pwi~Ozwi!Pwi$Qwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi~P!#{Oz!}O!P#RO$Q!vO$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Omwixwi[wi!}wiSwivwi#pwijwiUwiWwiYwi_wilwi!Zwi!ywi!|wi#Rwi#[wi#dwi#uwi]wi!zwi#Owi#Pwi~Oy#VO~P!&lOywi~P!&lO!d%^O~Ov%_Om$_X~P#wOv%_Om$_X~Om%aO~O$b%cO~PAlO!g%gO~O`yOayObyOcyOdyOlTOqmO!U'QO!ZzO!_{O#uPO#wjO$SvO$WuO$^wO~O!g%lO~O]%nO~OS!xqU!xqW!xqY!xq[!xq_!xql!xq!Z!xq!y!xq!|!xq#R!xq#[!xq#d!xq#p!xq#u!xq]!xq!z!xq#O!xq#P!xq~P+aO]%qO#O%pO#P%rO~Ov%sO~P+aO]%tO~O$g%vO~OS#gav#ga#p#gam#gaU#gaW#gaY#ga[#ga_#gal#ga!Z#ga!y#ga!|#ga#R#ga#[#ga#d#ga#u#ga]#ga!z#ga#O#ga#P#ga~P+aOusi#}si~P:wO#|%wO~O!P'ZO$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Ouwixwiywizwi#}wi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi~O$Q!vO~P!/[O$Qwi~P!/[O!P'ZO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Ouwixwiywizwi#}wi$Qwi$Swi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi~O$Rwi~P!1ZO$R!wO~P!1ZO$X!{O$Y!{O$Z!{O$[!{O$]!|Ouwixwiywizwi#}wi$Qwi$Rwi$Swi$Twi$Uwi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi~O!P'ZO$V!zO$W!zO~P!3YO!Pwi$Vwi$Wwi~P!3YO$]!|Oxwiywizwi!Pwi$Qwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi~Ouwi#}wi~P!5_Oz'RO!P'ZO$Q!vO$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Ouwixwi#}wi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi~Oy'cO~P!7VOywi~P!7VOu%xO~P'cOc%|OlTO#uPO~Ov%_Om$_a~O#|&PO!b#yX!d#yX!f#yX!o#yX!q#yX!s#yX!u#yX$b#yX~P9SO!b!aa!d!aa!f!aa!o!aa!q!aa!s!aa!u!aa$b!aa~P:wO!b!ca!d!ca!f!ca!o!ca!q!ca!s!ca!u!ca$b!ca~P:wO!b!na!d!na!f!na!o!na!q!na!s!na!u!na$b!na~P:wOv#vXx#vXy#vXz#vX!P#vX!b#vX!d#vX!f#vX!o#vX!q#vX!s#vX!u#vX$Q#vX$R#vX$S#vX$T#vX$U#vX$V#vX$W#vX$X#vX$Y#vX$Z#vX$[#vX$]#vX$b#vX!i#vX!j#vX!k#vX~P!bO!w#WOv#xXx#xXy#xXz#xX!P#xX!b#xX!d#xX!f#xX!o#xX!q#xX!s#xX!u#xX$Q#xX$R#xX$S#xX$T#xX$U#xX$V#xX$W#xX$X#xX$Y#xX$Z#xX$[#xX$]#xX$b#xX!i#xX!j#xX!k#xX~P#wOx'bOy'dOz'SO!P'[O$Q!vO$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|O~Ov&TO!b!pa!d!pa!f!pa!o!pa!q!pa!s!pa!u!pa$b!pa~P!@SO!b!ta!d!ta!f!ta!o!ta!q!ta!s!ta!u!ta$b!ta~P:wO]&YO#O%pO#P&ZO~Om&`O~P+aOm#hav#ha~P#wO]&aO~O!i&dO!j&dO!k&cOv!hX!b!hX!d!hX!f!hX!o!hX!q!hX!s!hX!u!hX$b!hX~P!@SOv&eO!b$aX!d$aX!f$aX!o$aX!q$aX!s$aX!u$aX$b$aX~Ov!Ta!i!Ta!j!Ta!k!Ta~PGgOv'eO!b$PX!d$PX!f$PX!o$PX!q$PX!s$PX!u$PX$b$PX~P!@SO!}&sO~P+aO]&tO~Ov&uO[#Sy~P+aOury#}ry~P:wO!b!aq!d!aq!f!aq!o!aq!q!aq!s!aq!u!aq$b!aq~P:wO!l&vO!m&vO~O!k&wOv!ha!b!ha!d!ha!f!ha!o!ha!q!ha!s!ha!u!ha$b!ha~Ov&eO!b$aa!d$aa!f$aa!o$aa!q$aa!s$aa!u$aa$b$aa~O!P'[O$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Ovwixwiywizwi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi!iwi!jwi!kwi~O$Q!vO~P!HgO$Qwi~P!HgO!P'[O$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Ovwixwiywizwi!bwi!dwi!fwi!owi!qwi!swi!uwi$Qwi$Swi$bwi!iwi!jwi!kwi~O$Rwi~P!JlO$R!wO~P!JlO$X!{O$Y!{O$Z!{O$[!{O$]!|Ovwixwiywizwi!bwi!dwi!fwi!owi!qwi!swi!uwi$Qwi$Rwi$Swi$Twi$Uwi$bwi!iwi!jwi!kwi~O!P'[O$V!zO$W!zO~P!LqO!Pwi$Vwi$Wwi~P!LqOvwi!iwi!jwi!kwi~P!5_Oz'SO!P'[O$Q!vO$R!wO$S!xO$T!yO$U!yO$V!zO$W!zO$X!{O$Y!{O$Z!{O$[!{O$]!|Ovwixwi!bwi!dwi!fwi!owi!qwi!swi!uwi$bwi!iwi!jwi!kwi~Oy'dO~P# ^Oywi~P# ^O!b!pq!d!pq!f!pq!o!pq!q!pq!s!pq!u!pq$b!pq~P:wOv'eO!b$Pa!d$Pa!f$Pa!o$Pa!q$Pa!s$Pa!u$Pa$b$Pa~O]&{O~O!l'OO!m'OO~Ov#ga!b#ga!d#ga!f#ga!o#ga!q#ga!s#ga!u#ga$b#ga~P!@SO[#S!Z~P+aOP$W~", - goto: "E]$fPPP$gP%VP%dP%VP%VPP%VPPPPPP(U)qP)qPP+ePP-XP(UP/T/T/TPP(]PPP/Z0U1V2TP3X4`5j(]P6z6z6z(]P8d8m(]P8p8tP8tP8tPP8xPPPPP8tP8tP8tP8tP(]P%VPP%VPPPP%VP9O9O9RP9U%V9b%VP%V9e%V9r9u9{:OP:_:n:t;O;V;];c;i;o;u;{PPPPWQPO,5;wO>]QPO1G/QOOQO1G.}1G.}OOQO'#DX'#DXOOQO'#DY'#DYOOQO'#DZ'#DZOOQO'#D['#D[OOQO'#D^'#D^OOQO'#D_'#D_OOQO'#D`'#D`O!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO>hQPO,5:oO?tQPO,59{OA^QQO'#DhOOQO,5:Q,5:QOBTQQO,5:UOB[QPO,5;gOOQO-E8y-E8yOOQO1G0p1G0pOOQO1G.m1G.mO&`QPO1G.pO!vQQO1G0]OBfQPO1G0`O!vQQO,5:{O!UQPO'#EyOBsQPO,5<[O!vQQO,5:|O&`QPO1G0eO!UQPO'#EzOB{QPO,5;RO!UQPO,5;ROOQO1G0l1G0lO!UQPO'#EoOOQO,5;Y,5;YO!UQPO'#E|OCWQPO,5<^O/ZQPO1G0nO!vQQO1G0rO!vQQO'#EtODkQPO,5;xOOQO1G0v1G0vOOQO7+$f7+$fOFOQQO,59kOGPQPO,59jOGWQQO1G/SOG_QSO1G/SOOQO1G/S1G/SOITQSO,59{OOQO1G1c1G1cOOQO7+$l7+$lOKwQPO1G/ZOLOQPO1G/ZONoQPO1G/ZONvQPO1G/ZO!#aQPO1G/ZO!#nQPO1G/ZO!%oQPO1G/ZO!)UQPO1G/ZO!)]QPO1G/ZO!)dQPO1G0ZO!)iQPO'#FVO!)sQPO'#FlOOQO'#Di'#DiO!){QPO,5:SOOQO'#Dl'#DlOOQO'#Ev'#EvO!*QQQO1G/pOFOQQO'#DmOFOQQO'#DoO!*XQPO'#DqOFOQQO'#D{O!*^QQO'#D}O!+_QPO'#EPOFOQQO'#EROOQO1G/p1G/pO!+dQPO7+$[O!+iQPO7+%wO!,xQPO7+%zO!-TQPO1G0gOOQO,5;e,5;eOOQO-E8w-E8wOOQO1G0h1G0hO!-[QPO7+&POOQO,5;f,5;fOOQO-E8x-E8xO!UQPO1G0mOOQO1G0m1G0mO!-aQQO,5;ZOOQO,5;h,5;hOOQO-E8z-E8zOOQO7+&Y7+&YOOQO7+&^7+&^O!-fQPO,5;`OOQO-E8r-E8rO!.{QSO1G/VO!/VQPO1G/UO!0{QSO1G/ZO!1SQSO1G/ZO!2zQSO1G/ZO!3RQSO1G/ZO!4sQSO1G/ZO!5QQSO1G/ZO!6{QSO1G/ZO!8vQSO1G/ZO!8}QSO1G/ZOOQO,5;_,5;_OOQO7+$n7+$nO!9UQQO7+$nOOQO-E8q-E8qO!vQQO7+%uO!9]QQO'#EuO!9hQPO,5WQ`O'#FVO!A^Q`O,5:iO!*^QQO,5:kO!A}QSO,5:mOOQO<{AN>{OOQO<lAN>lOFOQQO'#DaO!*^QQO'#DaOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oO!*^QQO'#Et", + stateData: "#(_~O#sOS#tOSPOS~OSZOUQOWZOY`O[aO_bOlTO!ZfO!zcO!}dO#SeO#]gO#ehO#vPO~O#qRP~P]OgkOilOlnOoqOqmO#xjO~O`yOayObyOcyOdyOlTOqmO!UxO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Og#yXi#yXl#yXo#yXq#yX#x#yX~Ov|O#}$eX~P#wOS#uXU#uXW#uXY#uX[#uX_#uXl#uX!Z#uX!z#uX!}#uX#S#uX#]#uX#e#uX#q#uX#v#uX]#uX!{#uX#P#uX#Q#uX~P#wO#}!OO~O#qRX]RX!{RX#PRX#QRX~P]O]RP~P]O!{RP~P]O!Z!aO#vPO~OS!eO#q#dX]#dX!{#dX#P#dX#Q#dX~P!vOU!gO~O`yOayObyOcyOdyOi!lOlTOqmO!U'XO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Ou!oO~P'cOm!rO~P!vOm#wXx#wXy#wXz#wX!P#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX[#wX#O#wXS#wXv#wX#q#wXj#wXU#wXW#wXY#wX_#wX!Z#wX!z#wX!}#wX#S#wX#]#wX#e#wX#v#wX]#wX!{#wX#P#wX#Q#wX~P!bOx#UOy#VOz!}O!P#RO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~Om!uO~P+aO!x#WOm#yXx#yXy#yXz#yX!P#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX[#yX#O#yXS#yXv#yX#q#yXj#yXU#yXW#yXY#yX_#yX!Z#yX!z#yX!}#yX#S#yX#]#yX#e#yX#v#yX]#yX!{#yX#P#yX#Q#yX~P#wOl#YO~O$a#[O~OlTO#vPO~Ov|O#}$ea~O]#`O~O[#aO~P+aO!{#bO~O#O#cO~P+aOv#eO#}#dO#W$dX~O#W#gO~O[#hO~Og#iOo#kOl#ZX~O$g#mOS#cPU#cPW#cPY#cP[#cP_#cPl#cPv#cP!Z#cP!z#cP!}#cP#S#cP#]#cP#e#cP#q#cP#v#cP#}#cP]#cP!{#cP#P#cP#Q#cP~Ov#oOS$fXU$fXW$fXY$fX[$fX_$fXl$fX!Z$fX!z$fX!}$fX#S$fX#]$fX#e$fX#q$fX#v$fX#}$fX]$fX!{$fX#P$fX#Q$fX~O#}#rOS#`aU#`aW#`aY#`a[#`a_#`al#`a!Z#`a!z#`a!}#`a#S#`a#]#`a#e#`a#q#`a#v#`a]#`a!{#`a#P#`a#Q#`a~Ov#sOS$QX#q$QXm$QXU$QXW$QXY$QX[$QX_$QXl$QX!Z$QX!z$QX!}$QX#S$QX#]$QX#e$QX#v$QX]$QX!{$QX#P$QX#Q$QX~P+aOS#uO#q#da]#da!{#da#P#da#Q#da~Oj#vO~P+aOu#wXx#wXy#wXz#wX!P#wX$O#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX!b#wX!d#wX!f#wX!p#wX!r#wX!t#wX!v#wX$c#wX~P!bOg#zXi#zXl#zXo#zXq#zXx#zXy#zXz#zX!P#zX#x#zX$R#zX$S#zX$T#zX$U#zX$V#zX$W#zX$X#zX$Y#zX$Z#zX$[#zX$]#zX$^#zX~O#}#wOu#zX$O#zX~P9SOx'iOy'kOz'ZO!P'cO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~OutX$OtX~P:wOu#{O$O#yO~O!x#WOu#yXx#yXy#yXz#yX!P#yX$O#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX!b#yX!d#yX!f#yX!p#yX!r#yX!t#yX!v#yX$c#yX~P#wOm#}O~OlnOqmO#xjO~Ol$YO~O$^!|Ox!Tay!Taz!Ta!P!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta$]!Tav!Ta~Om!Ta[!Ta#O!TaS!Ta#q!Taj!TaU!TaW!TaY!Ta_!Tal!Ta!Z!Ta!z!Ta!}!Ta#S!Ta#]!Ta#e!Ta#v!Ta]!Ta!{!Ta#P!Ta#Q!Ta~P>mOc$[OlTO#vPOm!]P~O!b$bO!d$cO!f$dO!p$eO!r$fO!t$gO!v$hO~O$c$iO~PAlOv#oa#}#oa~P#wO]RP#PRP#QRP~P]Ov#eO#W$da~Og#iOo$tOl#Za~Ov#oOS$faU$faW$faY$fa[$fa_$fal$fa!Z$fa!z$fa!}$fa#S$fa#]$fa#e$fa#q$fa#v$fa#}$fa]$fa!{$fa#P$fa#Q$fa~Ov#sOS$Qa#q$Qam$QaU$QaW$QaY$Qa[$Qa_$Qal$Qa!Z$Qa!z$Qa!}$Qa#S$Qa#]$Qa#e$Qa#v$Qa]$Qa!{$Qa#P$Qa#Q$Qa~O`yOayObyOcyOdyOlTOqmO!U'XO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Oj%OO~P+aOu%ZO~P'cOu%ZO$O%[O~O$^!|Ox!Tay!Taz!Ta!P!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta$]!Ta!b!Ta!d!Ta!f!Ta!p!Ta!r!Ta!t!Ta!v!Ta$c!Ta~Ou!Ta$O!Ta~PGgO!P#RO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O$R!vO~PI_O$Rwi~PI_O!P#RO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi$Rwi$Twi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O$Swi~PLVO$S!wO~PLVO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi$Rwi$Swi$Twi$Uwi$Vwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O!P#RO$W!zO$X!zO~PN}O!Pwi$Wwi$Xwi~PN}O$^!|Omwixwiywi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~Ozwi!Pwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi$]wi~P!#{Oz!}O!P#RO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~Oy#VO~P!&lOywi~P!&lO!d%^O~Ov%_Om$`X~P#wOv%_Om$`X~Om%aO~O$c%cO~PAlO!g%gO~O`yOayObyOcyOdyOlTOqmO!U'YO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~O!g%lO~O]%nO~OS!yqU!yqW!yqY!yq[!yq_!yql!yq!Z!yq!z!yq!}!yq#S!yq#]!yq#e!yq#q!yq#v!yq]!yq!{!yq#P!yq#Q!yq~P+aO]%qO#P%pO#Q%rO~Ov%sO~P+aO]%tO~O$h%vO~OS#hav#ha#q#ham#haU#haW#haY#ha[#ha_#hal#ha!Z#ha!z#ha!}#ha#S#ha#]#ha#e#ha#v#ha]#ha!{#ha#P#ha#Q#ha~P+aOusi$Osi~P:wO#}%wO~O!P'cO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O$R!vO~P!/[O$Rwi~P!/[O!P'cO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi$Rwi$Twi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O$Swi~P!1ZO$S!wO~P!1ZO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi$Rwi$Swi$Twi$Uwi$Vwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O!P'cO$W!zO$X!zO~P!3YO!Pwi$Wwi$Xwi~P!3YO$^!|Oxwiywizwi!Pwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi$]wi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~Ouwi$Owi~P!5_Oz'ZO!P'cO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwi$Owi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~Oy'kO~P!7VOywi~P!7VOu%xO~P'cOc%|OlTO#vPO~Ov%_Om$`a~O#}&PO!b#zX!d#zX!f#zX!p#zX!r#zX!t#zX!v#zX$c#zX~P9SO!b!aa!d!aa!f!aa!p!aa!r!aa!t!aa!v!aa$c!aa~P:wO!b!ca!d!ca!f!ca!p!ca!r!ca!t!ca!v!ca$c!ca~P:wO!b!oa!d!oa!f!oa!p!oa!r!oa!t!oa!v!oa$c!oa~P:wOv#wXx#wXy#wXz#wX!P#wX!b#wX!d#wX!f#wX!p#wX!r#wX!t#wX!v#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX$c#wX!i#wX!j#wX!k#wX!l#wX~P!bO!x#WOv#yXx#yXy#yXz#yX!P#yX!b#yX!d#yX!f#yX!p#yX!r#yX!t#yX!v#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX$c#yX!i#yX!j#yX!k#yX!l#yX~P#wOx'jOy'lOz'[O!P'dO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~Ov&TO!b!qa!d!qa!f!qa!p!qa!r!qa!t!qa!v!qa$c!qa~P!@YO!b!ua!d!ua!f!ua!p!ua!r!ua!t!ua!v!ua$c!ua~P:wO]&YO#P%pO#Q&ZO~Om&`O~P+aOm#iav#ia~P#wO]&aO~O!i&dO!j&dO!k&eO!l&cOv!hX!b!hX!d!hX!f!hX!p!hX!r!hX!t!hX!v!hX$c!hX~P!@YOv&fO!b$bX!d$bX!f$bX!p$bX!r$bX!t$bX!v$bX$c$bX~Ov!Ta!i!Ta!j!Ta!k!Ta!l!Ta~PGgOv'mO!b$QX!d$QX!f$QX!p$QX!r$QX!t$QX!v$QX$c$QX~P!@YO#O&tO~P+aO]&uO~Ov&vO[#Ty~P+aOury$Ory~P:wO!b!aq!d!aq!f!aq!p!aq!r!aq!t!aq!v!aq$c!aq~P:wO!m&wO!n&wO~O!l&xOv!ha!b!ha!d!ha!f!ha!p!ha!r!ha!t!ha!v!ha$c!ha~O!Z&zO#vPO~Ov&fO!b$ba!d$ba!f$ba!p$ba!r$ba!t$ba!v$ba$c$ba~O!P'dO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi!iwi!jwi!kwi!lwi~O$R!vO~P!H{O$Rwi~P!H{O!P'dO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$Rwi$Twi$cwi!iwi!jwi!kwi!lwi~O$Swi~P!KTO$S!wO~P!KTO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$Rwi$Swi$Twi$Uwi$Vwi$cwi!iwi!jwi!kwi!lwi~O!P'dO$W!zO$X!zO~P!M]O!Pwi$Wwi$Xwi~P!M]Ovwi!iwi!jwi!kwi!lwi~P!5_Oz'[O!P'dO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi!iwi!jwi!kwi!lwi~Oy'lO~P#!OOywi~P#!OO!b!qq!d!qq!f!qq!p!qq!r!qq!t!qq!v!qq$c!qq~P:wOv'mO!b$Qa!d$Qa!f$Qa!p$Qa!r$Qa!t$Qa!v$Qa$c$Qa~O]'OO~O!m'RO!n'RO~O!l'SOv!hi!b!hi!d!hi!f!hi!p!hi!r!hi!t!hi!v!hi$c!hi~Ov#ha!b#ha!d#ha!f#ha!p#ha!r#ha!t#ha!v#ha$c#ha~P!@YO[#T!Z~P+aO!m'UO!n'UO~O!l'VOv!hq!b!hq!d!hq!f!hq!p!hq!r!hq!t!hq!v!hq$c!hq~O!m'WO!n'WO~OP$X~", + goto: "Ed$gPPP$hP%WP%eP%WP%WPP%WPPPPPP(Y)uP)uPP+iPP-]P(YP/X/X/XPP(aPPP/_0Y1Z2XP3]4d5n(aP7O7O7O(aP8h8t(aP8w8{P8{P8{PP9PPPPPPP8{P8{P8{P8{P(aP%WPP%WPPPP%WP9V9V9YP9]%W9i%WP%W9l%W9y9|:S:VP:f:u:{;V;^;d;j;p;v;|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#wk!_!`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#wkO!_>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$`SO#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$bn~DRO$]~~DWOq~~D]O$Q~~DbOu~~DgP$S~!_!`$m", + tokenData: "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#t~XY#f[]#fpq#f#O#P#w~#zQYZ#f]^#f~$VP#s~]^$Y~$_O#s~~$dP#s~YZ$Ym$jP!_!`$mm$rOzmk$uXOY$rZ]$r^r$rrs%bs#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk%gO#xkk%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$_~~)jO$[~~)oO$S~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$Y~~.lO$W~o.sOvc$O[~.xP$X~}!O.{~/QTP~OY.{Z].{^;'S.{;'S;=`/a<%lO.{~/dP;=`<%l.{o/lPgi!O!P/oo/tP!Pm!O!P/wQ/|OcQ~0RQ$Z~!P!Q0X!_!`$m~0^O$]~~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$O[o4YQ$gQzm!^!_4`!_!`$mm4eO$Um~4jP#}~!_!`$mo4tQ$hQzm!_!`$m!`!a4zm5PO$Vm~5US#v~!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#xkO!_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#xk!_!`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#xk!_!`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#xkO!_>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$aSO#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$cn~DRO$^~~DWOq~~D]O$R~~DbOu~~DgP$T~!_!`$m", tokenizers: [0, 1, 2, 3, 4], topRules: {"Chunk":[0,2]}, - dynamicPrecedences: {"138":1}, - specialized: [{term: 129, get: (value) => spec_identifier[value] || -1}], - tokenPrec: 4451 + dynamicPrecedences: {"139":1}, + specialized: [{term: 130, get: (value) => spec_identifier[value] || -1}], + tokenPrec: 4566 }) diff --git a/client/space_lua/parse-lua.terms.js b/client/space_lua/parse-lua.terms.js index f4d2459f..ed589dea 100644 --- a/client/space_lua/parse-lua.terms.js +++ b/client/space_lua/parse-lua.terms.js @@ -20,19 +20,19 @@ export const WhereClause = 65, OrderByClause = 67, OrderBy = 70, - SelectClause = 76, - LimitClause = 78, - GroupByClause = 80, - HavingClause = 82, - IfStatement = 89, - ForStatement = 94, - ForNumeric = 96, - ForGeneric = 97, - NameList = 98, - ExpList = 100, - FuncName = 102, - VarList = 106, - AttNameList = 108, - AttName = 109, - Attrib = 110, - ReturnStatement = 111 + SelectClause = 77, + LimitClause = 79, + GroupByClause = 81, + HavingClause = 83, + IfStatement = 90, + ForStatement = 95, + ForNumeric = 97, + ForGeneric = 98, + NameList = 99, + ExpList = 101, + FuncName = 103, + VarList = 107, + AttNameList = 109, + AttName = 110, + Attrib = 111, + ReturnStatement = 112 diff --git a/client/space_lua/parse.ts b/client/space_lua/parse.ts index 62b23998..830537f7 100644 --- a/client/space_lua/parse.ts +++ b/client/space_lua/parse.ts @@ -35,7 +35,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": + "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": t.keyword, }); @@ -143,6 +143,11 @@ function expressionHasFunctionDef(e: LuaExpression): boolean { if (expressionHasFunctionDef(c.orderBy[j].expression)) { return true; } + if ( + c.orderBy[j].using && typeof c.orderBy[j].using !== "string" + ) { + return true; + } } break; case "GroupBy": @@ -236,6 +241,12 @@ function exprReferencesNames(e: LuaExpression, names: Set): boolean { if (exprReferencesNames(c.orderBy[j].expression, names)) { return true; } + if ( + typeof c.orderBy[j].using === "string" && + names.has(c.orderBy[j].using as string) + ) { + return true; + } } break; case "GroupBy": @@ -546,6 +557,10 @@ function exprCapturesNames(e: LuaExpression, names: Set): boolean { if (exprCapturesNames(c.orderBy[j].expression, names)) { return true; } + const u = c.orderBy[j].using; + if (u && typeof u !== "string") { + if (functionBodyCapturesNames(u, names)) return true; + } } break; case "GroupBy": @@ -1286,12 +1301,23 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause { const kids = child.children!; let direction: "asc" | "desc" = "asc"; let nulls: "first" | "last" | undefined; + let usingVal: string | LuaFunctionBody | undefined; for (let i = 1; i < kids.length; i++) { const typ = kids[i].type; if (typ === "desc") direction = "desc"; else if (typ === "asc") direction = "asc"; else if (typ === "first") nulls = "first"; else if (typ === "last") nulls = "last"; + else if (typ === "using") { + const next = kids[i + 1]; + if (next.type === "function") { + usingVal = parseFunctionBody(kids[i + 2], ctx); + i += 2; + } else { + usingVal = next.children![0].text!; + i++; + } + } } const ob: LuaOrderBy = { type: "Order", @@ -1300,6 +1326,7 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause { ctx: context(child, ctx), }; if (nulls) ob.nulls = nulls; + if (usingVal !== undefined) ob.using = usingVal; orderBy.push(ob); } } diff --git a/client/space_lua/query_collection.ts b/client/space_lua/query_collection.ts index 2765da9a..15a7da37 100644 --- a/client/space_lua/query_collection.ts +++ b/client/space_lua/query_collection.ts @@ -4,6 +4,7 @@ import type { LuaExpression, LuaExpressionField, LuaFilteredCallExpression, + LuaFunctionBody, LuaFunctionCallExpression, LuaParenthesizedExpression, LuaPropField, @@ -11,7 +12,9 @@ import type { } from "./ast.ts"; import { + luaCall, LuaEnv, + LuaFunction, luaGet, luaKeys, LuaStackFrame, @@ -133,6 +136,7 @@ export type LuaOrderBy = { expr: LuaExpression; desc: boolean; nulls?: "first" | "last"; + using?: string | LuaFunctionBody; }; /** @@ -456,6 +460,18 @@ function normalizeSelectResults(results: any[]): any[] { return results; } +function resolveUsing( + using: string | LuaFunctionBody | undefined, + env: LuaEnv, + _sf: LuaStackFrame, +): LuaValue | null { + if (using === undefined) return null; + if (typeof using === "string") { + return env.get(using) ?? null; + } + return new LuaFunction(using, env); +} + async function orderByCompare( a: any, b: any, @@ -472,10 +488,13 @@ async function orderByCompare( grouped: boolean, collation: QueryCollationConfig | undefined, collator: Intl.Collator, + resolvedUsing: (LuaValue | null)[], aSelectRow?: any, bSelectRow?: any, ): Promise { - for (const { expr, desc, nulls } of orderBy) { + for (let idx = 0; idx < orderBy.length; idx++) { + const { expr, desc, nulls } = orderBy[idx]; + const usingFn = resolvedUsing[idx]; const aEnv = mkEnv(objectVariable, a, env, sf); const bEnv = mkEnv(objectVariable, b, env, sf); if (aSelectRow) { @@ -524,7 +543,16 @@ async function orderByCompare( if (aIsNull) return nullsLast ? 1 : -1; return nullsLast ? -1 : 1; } - if ( + if (usingFn) { + const aLtB = luaTruthy( + await luaCall(usingFn, [aVal, bVal], sf.astCtx ?? {}, sf), + ); + const bLtA = luaTruthy( + await luaCall(usingFn, [bVal, aVal], sf.astCtx ?? {}, sf), + ); + if (aLtB && !bLtA) return desc ? 1 : -1; + if (bLtA && !aLtB) return desc ? -1 : 1; + } else if ( collation?.enabled && typeof aVal === "string" && typeof bVal === "string" @@ -731,6 +759,11 @@ export async function applyQuery( } const collator = Intl.Collator(collation?.locale, collation?.options); + const resolvedUsing: (LuaValue | null)[] = []; + for (const ob of query.orderBy) { + resolvedUsing.push(resolveUsing(ob.using, env, sf)); + } + if (selectResults) { const indices = results.map((_, i) => i); await asyncQuickSort(indices, (ai, bi) => @@ -745,6 +778,7 @@ export async function applyQuery( grouped, collation, collator, + resolvedUsing, selectResults![ai], selectResults![bi], )); @@ -764,6 +798,7 @@ export async function applyQuery( grouped, collation, collator, + resolvedUsing, )); } } diff --git a/client/space_lua/query_test.lua b/client/space_lua/query_test.lua index 9830103b..17ef01c8 100644 --- a/client/space_lua/query_test.lua +++ b/client/space_lua/query_test.lua @@ -1943,3 +1943,223 @@ do end end end + +-- 54. `using` with named function comparator + +local function reverseAlpha(a, b) + return a > b +end + +do + local r = query [[ + from + p = pages + order by + p.name using reverseAlpha + select p.name + ]] + assertEquals(r[1], "Greg") + assertEquals(r[#r], "Alice") +end + +-- 55. `using` with inline anonymous function + +do + local r = query [[ + from + p = pages + order by + p.size using function(a, b) return a > b end + select { name = p.name, size = p.size } + ]] + assertEquals(r[1].name, "Bob") -- size 20 + assertEquals(r[2].name, "Dave") -- size 15 +end + +-- 56. `using` with `nulls last` + +do + local r = query [[ + from + p = pages + order by + p.tags[1] using function(a, b) return a < b end nulls last + select { name = p.name, tag = p.tags[1] } + ]] + assertEquals(r[#r].name, "Ed") +end + +-- 57. `using` with `nulls first` + +do + local r = query [[ + from + p = pages + order by + p.tags[1] using function(a, b) return a < b end nulls first + select { name = p.name, tag = p.tags[1] } + ]] + assertEquals(r[1].name, "Ed") +end + +-- 58. `using` named function with `nulls last` + +do + local r = query [[ + from + p = pages + order by + p.tags[1] using reverseAlpha nulls last + select { name = p.name, tag = p.tags[1] } + ]] + assertEquals(r[#r].name, "Ed") + assertEquals(r[1].tag, "work") +end + +-- 59. `using` anonymous function with `nulls first` + +do + local r = query [[ + from + p = pages + order by + p.tags[1] using function(a, b) return a < b end nulls first + select { name = p.name, tag = p.tags[1] } + ]] + assertEquals(r[1].name, "Ed") +end + +-- 60. `using` on one key, normal on another + +do + local r = query [[ + from + p = pages + where + p.tags[1] ~= nil + order by + p.tags[1] using reverseAlpha, + p.name + select { tag = p.tags[1], name = p.name } + ]] + assertEquals(r[1].tag, "work") + assertEquals(r[1].name, "Alice") +end + +-- 61. Multiple keys with mixed using and desc + +do + local data = { + { name = "a", x = 2, y = 10 }, + { name = "b", x = 1, y = 20 }, + { name = "c", x = 2, y = 5 }, + { name = "d", x = 1, y = 15 }, + } + local r = query [[ + from + p = data + order by + p.x using function(a, b) return a < b end, + p.y desc + select { name = p.name } + ]] + assertEquals(r[1].name, "b") + assertEquals(r[2].name, "d") + assertEquals(r[3].name, "a") + assertEquals(r[4].name, "c") +end + +-- 62. `using` anonymous function with `nulls first` + +do + local data = { + { name = "a", val = 3 }, + { name = "b" }, + { name = "c", val = 1 }, + } + local r = query [[ + from + p = data + order by + p.val using function(a, b) return a > b end nulls first + select { name = p.name } + ]] + assertEquals(r[1].name, "b") + assertEquals(r[2].name, "a") + assertEquals(r[3].name, "c") +end + +-- 63. `using` with group by + aggregate order + +do + local r = query [[ + from + p = pages + where + p.tags[1] ~= nil + group by + p.tags[1] + select { + tag = key, + n = count(), + } + order by + n using function(a, b) return a > b end + ]] + assertEquals(r[1].tag, "work") + assertEquals(r[1].n, 3) + assertEquals(r[#r].n, 1) +end + +-- 64. `using` named function in unbound mode + +do + local function byLen(a, b) + return #a < #b + end + local r = query [[ + from + pages + order by + name using byLen + select name + ]] + assertEquals(r[1], "Ed") +end + +-- 65. `using` anonymous function alone + +do + local r = query [[ + from + p = pages + order by + p.age using function(a, b) return a > b end + select { name = p.name, age = p.age } + ]] + assertEquals(r[1].name, "Greg") -- age 63 + assertEquals(r[#r].name, "Ed") -- age 19 +end + +-- 66. `using` with nulls on different keys + +do + local data = { + { name = "a", x = 1, y = 10 }, + { name = "b", x = 1 }, + { name = "c", x = 2, y = 5 }, + { name = "d", x = 2 }, + } + local r = query [[ + from + p = data + order by + p.x using function(a, b) return a < b end, + p.y using function(a, b) return a < b end nulls first + select { name = p.name } + ]] + assertEquals(r[1].name, "b") + assertEquals(r[2].name, "a") + assertEquals(r[3].name, "d") + assertEquals(r[4].name, "c") +end diff --git a/website/Space Lua/Lua Integrated Query.md b/website/Space Lua/Lua Integrated Query.md index 7b439dbf..2e85bb0a 100644 --- a/website/Space Lua/Lua Integrated Query.md +++ b/website/Space Lua/Lua Integrated Query.md @@ -6,13 +6,16 @@ However, in [[Space Lua]] it is interpreted as an SQL (and [LINQ](https://learn. General syntax: - query[[ - from in + query [[ + from where - group by [, , ...] + group by [, ...] having - order by [asc | desc] [nulls { first | last }][, ...] - limit , + order by + [asc | desc | using ] + [nulls { first | last }] + [, ...] + limit [, ...] select ]] @@ -40,7 +43,7 @@ ${some(query[[ # Clauses Here are the clauses that are currently supported: -## from +## `from` The `from` clause specifies the source of your data. There are two syntactic variants: **Recommended:** With explicit variable binding: @@ -69,7 +72,7 @@ ${query[[from n = {1, 2, 3} select n]]} A more realistic example using `index.tag`: ${query[[from p = index.tag "page" order by p.lastModified select p.name limit 3]]} -## where +## `where` The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result. Example: @@ -84,7 +87,7 @@ Or select based on name (including folder) and a [[API/string|string function]]: ${query[[from p = index.tag "page" where p.name:startsWith("Person")]]} -## group by [, , ...] +## `group by` The `group by` clause groups results by one or more key expressions. After grouping, each result row becomes a table with two fields: - `key` — the group key value (single value for one key, table for multi-key) @@ -103,7 +106,7 @@ ${query[[ See [[Space Lua/Lua Integrated Query/Grouping]] for detailed examples. -## having +## `having` The `having` clause filters groups **after** `group by`. It follows SQL semantics: only group key fields, `key`, and `group` are accessible — use `where` to filter individual rows before grouping. Aggregate functions like `count()`, `sum()`, `min()`, `max()`, and `avg()` can be used in `having` expressions. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. @@ -121,13 +124,16 @@ ${query[[ See [[Space Lua/Lua Integrated Query/Grouping]] for detailed examples. -## order by [asc | desc] [nulls {first | last}][, ...] +## `order by` The `order by` clause allows you to sort data. Use `desc` for descending order, or `asc` (the default) for ascending. You can control where `nil` values are placed using `nulls first` or `nulls last`. The defaults follow SQL conventions: - **`asc`**: nulls are placed **last**, - **`desc`**: nulls are placed **first**. +You can also use custom comparator: +- **`using `**. + As an example, the last 3 modified pages: ${query[[ from p = index.tag "page" @@ -167,7 +173,74 @@ query[[ Sorting of strings can be adjusted with `queryCollation` in [[^Library/Std/Config]] -## limit [, ] +### `using` (custom comparators) +The `using` clause specifies a custom comparator. It is mutually exclusive with `asc`/`desc` — the comparator defines the ordering direction. + +> **note** Note +> `using` is a reserved keyword in Space Lua and cannot be used as a variable name. + +Two forms are supported: + +**Named function** — must accept two arguments and return `true` if the first comes before the second. + +Example: + +```lua +local function byLength(a, b) + return #a < #b +end +``` + +And use the custom comparator function in a query: + +```lua +query [[ + from p = index.tag "page" + order by p.name using byLength + select p.name + limit 5 +]] +``` + +Annonymous functions are also supported. Example: + +${query[[ + from n = {5, 1, 3, 2, 4} + order by n using function(a, b) return a < b end +]]} + +The `nulls` clause works with `using`: + +```lua +query [[ + from + p = index.tag "page" + order + by p.name + using + byLength nulls last + select + p.name +]] +``` + +Each sort key can independently use `asc`/`desc` or `using`, combined with `nulls { first | last }`: + +```lua +query [[ + from p = data + order by + p.category using customCategoryCmp, + p.priority desc nulls last + select { + name = p.name + } +]] +``` + +> **note** When `using` is specified, it overrides any `queryCollation` configuration for that sort key. + +## `limit` The `limit` clause allows you to limit the number of results, optionally with an offset. Example: @@ -178,7 +251,7 @@ You can also specify an offset to skip some results: ${query[[from {1, 2, 3, 4, 5} limit 3, 2]]} -## select +## `select` The `select` clause allows you to transform each item in the result set. If omitted, it defaults to returning the item itself. When used with `group by`, aggregate functions like `sum()`, `count()`, `min()`, `max()`, and `avg()` can be used in the `select` expression to compute values across each group. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. @@ -195,18 +268,6 @@ ${query[[ limit 3 ]]} -# Listing available aggregate functions - -You can list all available aggregator functions (built-ins and custom) using LIQ. - -Example: - -${query [[ - from k, v in pairs(config.get("aggregates", {})) - select { name = k, desc = v.description } - order by name -]]} - # Rendering the output To render the output as a template, you can rely on the fact that queries return Lua tables. For example, to apply a template to render every page as a link: