* Website: Make manual build ref configurable
* [LIQ] Unify `from`, `select`, and `group by` clauses to use field lists
Query clauses `from`, `select`, and `group by` now accept the same field
syntax as Lua table constructors (`name = expr`, bare expr, or `[expr]
= expr`), giving them a consistent grammar and enabling named bindings
everywhere. #feature
What changed:
* New `FieldList` rule in `lua.grammar`. All three clauses now parse
their arguments through it instead of using expression lists or the
special `Name "=" exp` pattern.
* AST types `LuaFromClause`, `LuaSelectClause`, and `LuaGroupByClause`
now carry fields: `LuaTableField[]` instead of a single expression or
an expression array. The parser, static analysis helpers and the
evaluator are all updated to work with this new shape.
Named bindings in `select`
--------------------------
`select name, age` now derives column names from bare variable or
property access expressions so the result table has string keys (`name`,
`age`) rather than integer indices. Explicit naming still works.
Named bindings in `group by`
----------------------------
`group b`y fields can carry explicit aliases that propagate into the
post-grouping scope. For example:
```sql
group by
n = name
select {
label = n
}
```
The alias `n` is now visible in `select`, `having`, and `order by`.
Previously only the raw expression form (`name`) was bound and an
explicit alias was silently lost. #bugfix
A new `LuaGroupByEntry` type pairs each `group by` expression with its
optional alias. The environment builder binds every
declared alias to the corresponding key value, regardless of whether the
group key is a scalar or a multi-key table.
Named bindings in `from`
------------------------
`from p = page` continues to work as before. The clause now also accepts
the full field-list syntax which lays the groundwork for multi-source
from (cross join) in a future change. #todo #feature
Complete LIQ syntax now
-----------------------
Complete LIQ syntax (in Postgres documentation style) could be now
roughly expressed as follows: #todo #documentation
```text
query[[ clause [...] ]]
where clause is one of:
FROM field [, ...]
WHERE condition
GROUP BY field [, ...]
HAVING condition
SELECT field [, ...]
ORDER BY sort_key [, ...]
LIMIT count [, offset]
OFFSET start
where field is one of:
expression
name = expression
[expression] = expression
where sort_key is:
expression [ ASC | DESC ]
[ NULLS { FIRST | LAST } ]
[ USING name ]
[ USING FUNCTION function_body ]
where expression is any valid Lua expression, including:
aggregate_call
aggregate_call FILTER ( WHERE condition )
```
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Fix type error caused by earlier merge conflict resolution
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [LIQ] Cross-join support for multi-source `from`
Examples of what works now:
```
${query [[
from
x = { 1, 2 },
y = { 10, 20 },
z = { 100, 200 }
select {
sum = x + y + z
}
]]}
```
Or (more complicated three-way cross-join) to illustrate:
```
${query [[
from
s = {
{ id = 1, name = 'Eva', },
{ id = 2, name = 'Adam', },
{ id = 3, name = 'John', },
{ id = 4, name = 'Zef', },
},
c = {
{ id = 101, title = 'Mathematics', },
{ id = 102, title = 'Arts', },
{ id = 103, title = 'Physics', },
{ id = 104, title = 'Computer Science', },
{ id = 105, title = 'Literature', },
},
e = {
{ sid = 1, cid = 101, },
{ sid = 1, cid = 103, },
{ sid = 2, cid = 101, },
{ sid = 2, cid = 102, },
{ sid = 2, cid = 103, },
{ sid = 3, cid = 102, },
{ sid = 3, cid = 105, },
{ sid = 4, cid = 101, },
{ sid = 4, cid = 103, },
{ sid = 4, cid = 104, },
}
where
s.id == e.sid and
e.cid == c.id
order by
s.name
group by
s.name
having
s.name:match('^A') or
s.name:match('f$')
select
student = s.name:upper(),
courses = string_agg(c.title, ', '
order by c.title desc
)
]]}
```
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
---------
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Co-authored-by: Zef Hemel <zef@zef.me>
222 lines
4.8 KiB
Plaintext
222 lines
4.8 KiB
Plaintext
/* Based on: https://github.com/R167/lezer-lua */
|
|
@precedence {
|
|
call,
|
|
power @right,
|
|
prefix,
|
|
times @left,
|
|
plus @left,
|
|
concat @right,
|
|
shift @left,
|
|
bitand @left,
|
|
xor @left,
|
|
bitor @left,
|
|
compare @left,
|
|
and @left,
|
|
or @left
|
|
}
|
|
|
|
@top Chunk { Block }
|
|
|
|
kw<term> { @specialize[@name={term}]<identifier, term> }
|
|
ckw<term> { @extend[@name={term}]<identifier, term> }
|
|
list<term> { term ("," term)* }
|
|
|
|
Block { statement* ReturnStatement? }
|
|
|
|
ReturnStatement { kw<"return"> ExpList? ";"?}
|
|
|
|
@skip { newline | space | Comment }
|
|
|
|
statement[@isGroup=Statement] {
|
|
";" |
|
|
Label |
|
|
kw<"break"> |
|
|
Goto{ kw<"goto"> Name } |
|
|
Scope { kw<"do"> Block kw<"end"> } |
|
|
WhileStatement { kw<"while"> exp kw<"do"> Block kw<"end"> } |
|
|
RepeatStatement { kw<"repeat"> Block kw<"until"> exp } |
|
|
IfStatement |
|
|
ForStatement |
|
|
Function { kw<"function"> FuncName FuncBody } |
|
|
LocalFunction { kw<"local"> kw<"function"> Name FuncBody } |
|
|
Assign { VarList "=" ExpList } |
|
|
Local { kw<"local"> AttNameList ("=" ExpList)? } |
|
|
FunctionCall ~fcall
|
|
}
|
|
|
|
IfStatement {
|
|
kw<"if"> exp kw<"then"> Block
|
|
(kw<"elseif"> exp kw<"then"> Block)*
|
|
(kw<"else"> Block)?
|
|
kw<"end">
|
|
}
|
|
|
|
ForNumeric { Name "=" exp "," exp ("," exp)? }
|
|
|
|
ForGeneric { NameList kw<"in"> ExpList }
|
|
|
|
ForStatement {
|
|
kw<"for"> (ForNumeric | ForGeneric) kw<"do"> Block kw<"end">
|
|
}
|
|
|
|
FuncName { Name ("." Name)* (":" Name)? }
|
|
FuncBody { "(" ArgList ")" Block kw<"end"> }
|
|
|
|
|
|
NameList { list<Name> }
|
|
ExpList { list<exp> }
|
|
VarList { list<var> }
|
|
ArgList { (list<var | "...">)? }
|
|
|
|
|
|
AttNameList { list<AttName> }
|
|
AttName { Name Attrib }
|
|
Attrib { ( "<" Name ">" )? }
|
|
|
|
FieldList { field ("," field)* }
|
|
|
|
exp {
|
|
kw<"nil"> | kw<"true"> | kw<"false"> | "..." |
|
|
Number |
|
|
LiteralString |
|
|
prefixexp |
|
|
BinaryExpression |
|
|
UnaryExpression |
|
|
TableConstructor |
|
|
FunctionDef { kw<"function"> FuncBody } |
|
|
Query |
|
|
FilteredCall { FunctionCall ckw<"filter"> "(" ckw<"where"> exp ")" }
|
|
}
|
|
|
|
Query {
|
|
kw<"query"> "[[" QueryClause* "]]"
|
|
}
|
|
|
|
QueryClause {
|
|
FromClause |
|
|
WhereClause |
|
|
OrderByClause |
|
|
SelectClause |
|
|
LimitClause |
|
|
OffsetClause |
|
|
GroupByClause |
|
|
HavingClause
|
|
}
|
|
|
|
FromClause { ckw<"from"> FieldList }
|
|
WhereClause { ckw<"where"> exp }
|
|
LimitClause { ckw<"limit"> exp ("," exp)? }
|
|
OffsetClause { ckw<"offset"> exp }
|
|
OrderByClause { ckw<"order"> ckw<"by"> list<OrderBy> }
|
|
OrderBy { exp (ckw<"desc"> | ckw<"asc"> | kw<"using"> (Name | kw<"function"> FuncBody))? (ckw<"nulls"> (ckw<"first"> | ckw<"last">))? }
|
|
SelectClause { ckw<"select"> FieldList }
|
|
GroupByClause { ckw<"group"> ckw<"by"> FieldList }
|
|
HavingClause { ckw<"having"> exp }
|
|
|
|
AggOrderBy { ckw<"order"> ckw<"by"> list<OrderBy> }
|
|
|
|
|
|
field[@isGroup=Field] {
|
|
FieldDynamic { "[" exp "]" "=" exp } |
|
|
FieldProp { Name "=" exp } |
|
|
FieldExp { exp }
|
|
}
|
|
|
|
prefixexp {
|
|
var |
|
|
Parens { "(" exp ")" ~parens } |
|
|
FunctionCall ~fcall
|
|
}
|
|
|
|
FunctionCall { prefixexp (":" Name)? !call args }
|
|
args {
|
|
LiteralString |
|
|
TableConstructor |
|
|
funcParams[@dynamicPrecedence=1] { "(" list<exp>? AggOrderBy? ")" ~parens }
|
|
}
|
|
|
|
var {
|
|
Name | Property { (prefixexp "." Name) } | MemberExpression { (prefixexp "[" exp "]") }
|
|
}
|
|
|
|
Name { identifier }
|
|
Label { "::" Name "::" }
|
|
LiteralString { simpleString }
|
|
|
|
BinaryExpression {
|
|
exp !or kw<"or"> exp |
|
|
exp !and kw<"and"> exp |
|
|
exp !compare CompareOp exp |
|
|
exp !bitor BitOp{"|"} exp |
|
|
exp !bitand BitOp{"&"} exp |
|
|
exp !xor BitOp{"~"} exp |
|
|
exp !shift BitOp{"<<" | ">>"} exp |
|
|
exp !concat ".." exp |
|
|
exp !plus ArithOp{"+" | minus} exp |
|
|
exp !times ArithOp{"*" | "/" | "%" | "//"} exp |
|
|
exp !power ArithOp{"^"} exp
|
|
}
|
|
|
|
UnaryExpression {
|
|
!prefix kw<"not"> exp |
|
|
!prefix (ArithOp{minus} | BitOp{"~"} | LenOp{"#"}) exp
|
|
}
|
|
|
|
TableConstructor { "{" (field (fieldsep field)* fieldsep?)? "}" }
|
|
|
|
@tokens {
|
|
CompareOp { "<" | ">" | $[<>=~/!] "=" }
|
|
|
|
identifier { (std.asciiLetter | "_") (std.digit | std.asciiLetter | "_")* }
|
|
|
|
stringEscape {
|
|
"\\" ($[abfnrtz"'\\] | digit digit? digit?) |
|
|
"\\x" hex hex |
|
|
"\\u{" hex+ "}"
|
|
}
|
|
|
|
// Any sequence of characters except two consecutive ]]
|
|
longStringContent { (![\]] | $[\]] ![\]])* }
|
|
|
|
singleEqualStringContent {
|
|
(![\]] | $[\]] ![=] ![\]])* $[\]]* ![\]]*
|
|
}
|
|
|
|
doubleEqualStringContent {
|
|
(![\]] | $[\]] ![=] ![=] ![\]])* $[\]]* ![\]]*
|
|
}
|
|
|
|
simpleString {
|
|
"'" (stringEscape | ![\r\n\\'])* "'" |
|
|
'"' (stringEscape | ![\r\n\\"])* '"' |
|
|
'[[' longStringContent ']]' |
|
|
'[=[' singleEqualStringContent ']=]' |
|
|
'[==[' doubleEqualStringContent ']==]'
|
|
}
|
|
|
|
hex { $[0-9a-fA-F] }
|
|
digit { std.digit }
|
|
|
|
Number {
|
|
digit+ ("." digit+)? ($[eE] $[+\-]? digit+)? |
|
|
"0" $[xX] hex+ ("." hex+)? ($[pP] $[+\-]? digit+)?
|
|
}
|
|
|
|
Comment { "--" ![\n\r]* }
|
|
|
|
space { ($[ \t\f] | "\\" $[\n\r])+ }
|
|
newline { $[\n\r] | "\n\r" | "\r\n" }
|
|
|
|
"..."[@name=Ellipsis]
|
|
".."[@name=Concat]
|
|
|
|
@precedence { Comment, minus }
|
|
|
|
minus {"-"}
|
|
fieldsep { $[,;] }
|
|
|
|
"(" ")" "[" "]" "{" "}"
|
|
|
|
"." "," ";" ":" "::"
|
|
}
|