* 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>
363 lines
7.6 KiB
TypeScript
363 lines
7.6 KiB
TypeScript
type ASTContext = {
|
|
ctx: ASTCtx;
|
|
};
|
|
|
|
export type NumericType = "int" | "float";
|
|
|
|
export type ASTCtx = {
|
|
from?: number;
|
|
to?: number;
|
|
} & Record<string, any>;
|
|
|
|
export type LuaBlock = {
|
|
type: "Block";
|
|
statements: LuaStatement[];
|
|
hasLabel?: boolean;
|
|
hasGoto?: boolean;
|
|
// the first duplicated label is recorded here for later eval
|
|
dupLabelError?: { name: string; ctx: ASTCtx };
|
|
// this block declares top-level locals and therefore needs a new env
|
|
needsEnv?: boolean;
|
|
// true if this block itself (not nested ones) defines at least one label
|
|
hasLabelHere?: boolean;
|
|
// true if this block itself (not nested ones) may create to-be-closed values
|
|
hasCloseHere?: boolean;
|
|
// true if this block's subtree contains any function definition
|
|
hasFunctionDef?: boolean;
|
|
} & ASTContext;
|
|
|
|
// STATEMENTS
|
|
export type LuaReturnStatement = {
|
|
type: "Return";
|
|
expressions: LuaExpression[];
|
|
} & ASTContext;
|
|
|
|
export type LuaStatement =
|
|
| LuaSemicolonStatement
|
|
| LuaLabelStatement
|
|
| LuaBreakStatement
|
|
| LuaGotoStatement
|
|
| LuaReturnStatement
|
|
| LuaBlock
|
|
| LuaWhileStatement
|
|
| LuaRepeatStatement
|
|
| LuaIfStatement
|
|
| LuaForStatement
|
|
| LuaForInStatement
|
|
| LuaFunctionStatement
|
|
| LuaLocalFunctionStatement
|
|
| LuaAssignmentStatement
|
|
| LuaLocalStatement
|
|
| LuaFunctionCallStatement;
|
|
|
|
export type LuaSemicolonStatement = {
|
|
type: "Semicolon";
|
|
} & ASTContext;
|
|
|
|
export type LuaLabelStatement = {
|
|
type: "Label";
|
|
name: string;
|
|
} & ASTContext;
|
|
|
|
export type LuaBreakStatement = {
|
|
type: "Break";
|
|
} & ASTContext;
|
|
|
|
export type LuaGotoStatement = {
|
|
type: "Goto";
|
|
name: string;
|
|
} & ASTContext;
|
|
|
|
export type LuaWhileStatement = {
|
|
type: "While";
|
|
condition: LuaExpression;
|
|
block: LuaBlock;
|
|
} & ASTContext;
|
|
|
|
export type LuaRepeatStatement = {
|
|
type: "Repeat";
|
|
block: LuaBlock;
|
|
condition: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaIfStatement = {
|
|
type: "If";
|
|
conditions: { condition: LuaExpression; block: LuaBlock }[];
|
|
elseBlock?: LuaBlock;
|
|
} & ASTContext;
|
|
|
|
export type LuaForStatement = {
|
|
type: "For";
|
|
name: string;
|
|
start: LuaExpression;
|
|
end: LuaExpression;
|
|
step?: LuaExpression;
|
|
block: LuaBlock;
|
|
// function definition in the block captures the loop variable
|
|
capturesLoopVar?: boolean;
|
|
} & ASTContext;
|
|
|
|
export type LuaForInStatement = {
|
|
type: "ForIn";
|
|
names: string[];
|
|
expressions: LuaExpression[];
|
|
block: LuaBlock;
|
|
// function definition in the block captures any loop variable
|
|
capturesLoopVar?: boolean;
|
|
} & ASTContext;
|
|
|
|
export type LuaFunctionStatement = {
|
|
type: "Function";
|
|
name: LuaFunctionName;
|
|
body: LuaFunctionBody;
|
|
} & ASTContext;
|
|
|
|
export type LuaLocalFunctionStatement = {
|
|
type: "LocalFunction";
|
|
name: string;
|
|
body: LuaFunctionBody;
|
|
} & ASTContext;
|
|
|
|
export type LuaFunctionName = {
|
|
type: "FunctionName";
|
|
propNames: string[];
|
|
colonName?: string;
|
|
} & ASTContext;
|
|
|
|
export type LuaFunctionBody = {
|
|
type: "FunctionBody";
|
|
parameters: string[];
|
|
block: LuaBlock;
|
|
} & ASTContext;
|
|
|
|
export type LuaAssignmentStatement = {
|
|
type: "Assignment";
|
|
variables: LuaLValue[];
|
|
expressions: LuaExpression[];
|
|
} & ASTContext;
|
|
|
|
export type LuaLValue =
|
|
| LuaVariable
|
|
| LuaPropertyAccessExpression
|
|
| LuaTableAccessExpression;
|
|
|
|
export type LuaLocalStatement = {
|
|
type: "Local";
|
|
names: LuaAttName[];
|
|
expressions?: LuaExpression[];
|
|
} & ASTContext;
|
|
|
|
export enum LuaAttribute {
|
|
Const = "const",
|
|
Close = "close",
|
|
}
|
|
|
|
export type LuaAttName = {
|
|
type: "AttName";
|
|
name: string;
|
|
attribute?: string;
|
|
attributes?: LuaAttribute[];
|
|
} & ASTContext;
|
|
|
|
export type LuaFunctionCallStatement = {
|
|
type: "FunctionCallStatement";
|
|
call: LuaFunctionCallExpression;
|
|
} & ASTContext;
|
|
|
|
// EXPRESSIONS
|
|
export type LuaExpression =
|
|
| LuaNilLiteral
|
|
| LuaBooleanLiteral
|
|
| LuaNumberLiteral
|
|
| LuaStringLiteral
|
|
| LuaPrefixExpression
|
|
| LuaBinaryExpression
|
|
| LuaUnaryExpression
|
|
| LuaTableConstructor
|
|
| LuaFunctionDefinition
|
|
| LuaQueryExpression
|
|
| LuaFilteredCallExpression
|
|
| LuaAggregateCallExpression;
|
|
|
|
export type LuaNilLiteral = {
|
|
type: "Nil";
|
|
} & ASTContext;
|
|
|
|
export type LuaBooleanLiteral = {
|
|
type: "Boolean";
|
|
value: boolean;
|
|
} & ASTContext;
|
|
|
|
export type LuaNumberLiteral = {
|
|
type: "Number";
|
|
value: number;
|
|
numericType: NumericType;
|
|
} & ASTContext;
|
|
|
|
export type LuaStringLiteral = {
|
|
type: "String";
|
|
value: string;
|
|
} & ASTContext;
|
|
|
|
export type LuaPrefixExpression =
|
|
| LuaVariableExpression
|
|
| LuaParenthesizedExpression
|
|
| LuaFunctionCallExpression;
|
|
|
|
export type LuaParenthesizedExpression = {
|
|
type: "Parenthesized";
|
|
expression: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaVariableExpression =
|
|
| LuaVariable
|
|
| LuaPropertyAccessExpression
|
|
| LuaTableAccessExpression;
|
|
|
|
export type LuaVariable = {
|
|
type: "Variable";
|
|
name: string;
|
|
} & ASTContext;
|
|
|
|
export type LuaPropertyAccessExpression = {
|
|
type: "PropertyAccess";
|
|
object: LuaPrefixExpression;
|
|
property: string;
|
|
} & ASTContext;
|
|
|
|
export type LuaTableAccessExpression = {
|
|
type: "TableAccess";
|
|
object: LuaPrefixExpression;
|
|
key: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaFunctionCallExpression = {
|
|
type: "FunctionCall";
|
|
prefix: LuaPrefixExpression;
|
|
name?: string;
|
|
args: LuaExpression[];
|
|
orderBy?: LuaOrderBy[];
|
|
} & ASTContext;
|
|
|
|
export type LuaBinaryExpression = {
|
|
type: "Binary";
|
|
operator: string;
|
|
left: LuaExpression;
|
|
right: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaUnaryExpression = {
|
|
type: "Unary";
|
|
operator: string;
|
|
argument: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaTableConstructor = {
|
|
type: "TableConstructor";
|
|
fields: LuaTableField[];
|
|
} & ASTContext;
|
|
|
|
export type LuaTableField = LuaDynamicField | LuaPropField | LuaExpressionField;
|
|
|
|
export type LuaDynamicField = {
|
|
type: "DynamicField";
|
|
key: LuaExpression;
|
|
value: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaPropField = {
|
|
type: "PropField";
|
|
key: string;
|
|
value: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaExpressionField = {
|
|
type: "ExpressionField";
|
|
value: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaFunctionDefinition = {
|
|
type: "FunctionDefinition";
|
|
body: LuaFunctionBody;
|
|
} & ASTContext;
|
|
|
|
// Aggregate with per-row filter
|
|
export type LuaFilteredCallExpression = {
|
|
type: "FilteredCall";
|
|
call: LuaFunctionCallExpression;
|
|
filter: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
// Aggregate call with intra-aggregate order by
|
|
export type LuaAggregateCallExpression = {
|
|
type: "AggregateCall";
|
|
call: LuaFunctionCallExpression;
|
|
orderBy: LuaOrderBy[];
|
|
} & ASTContext;
|
|
|
|
// Query stuff
|
|
export type LuaQueryExpression = {
|
|
type: "Query";
|
|
clauses: LuaQueryClause[];
|
|
} & ASTContext;
|
|
|
|
export type LuaQueryClause =
|
|
| LuaFromClause
|
|
| LuaWhereClause
|
|
| LuaLimitClause
|
|
| LuaOffsetClause
|
|
| LuaOrderByClause
|
|
| LuaSelectClause
|
|
| LuaGroupByClause
|
|
| LuaHavingClause;
|
|
|
|
// Field list used by `from`, `select` and `group by` clauses
|
|
export type LuaFromClause = {
|
|
type: "From";
|
|
fields: LuaTableField[];
|
|
} & ASTContext;
|
|
|
|
export type LuaWhereClause = {
|
|
type: "Where";
|
|
expression: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaLimitClause = {
|
|
type: "Limit";
|
|
limit: LuaExpression;
|
|
offset?: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaOffsetClause = {
|
|
type: "Offset";
|
|
offset: LuaExpression;
|
|
} & ASTContext;
|
|
|
|
export type LuaOrderByClause = {
|
|
type: "OrderBy";
|
|
orderBy: LuaOrderBy[];
|
|
} & ASTContext;
|
|
|
|
export type LuaOrderBy = {
|
|
type: "Order";
|
|
expression: LuaExpression;
|
|
direction: "asc" | "desc";
|
|
nulls?: "first" | "last";
|
|
using?: string | LuaFunctionBody;
|
|
} & ASTContext;
|
|
|
|
export type LuaSelectClause = {
|
|
type: "Select";
|
|
fields: LuaTableField[];
|
|
} & ASTContext;
|
|
|
|
export type LuaGroupByClause = {
|
|
type: "GroupBy";
|
|
fields: LuaTableField[];
|
|
} & ASTContext;
|
|
|
|
export type LuaHavingClause = {
|
|
type: "Having";
|
|
expression: LuaExpression;
|
|
} & ASTContext;
|