* [LIQ] Extend with `group by` and `having` support Also update documentation with extensive examples and add some parser tests. Note: `make generate` JS files included. Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz> * Merge `liq-add-support-for-aggregators` branch Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz> * Fix link, remove `_` example Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz> * Wrap example LIQ queries inside `query [[ .. ]]` and replace `sql` code block type to `lua` Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz> * Example self-consistency fix Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz> * Fixed some queries * Add experimental tags to LIQ aggregation/grouping --------- Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz> Co-authored-by: Zef Hemel <zef@zef.me>
123 lines
2.5 KiB
Markdown
123 lines
2.5 KiB
Markdown
---
|
|
description: APIs to define custom aggregate functions for LIQ
|
|
tags: meta/api
|
|
---
|
|
|
|
APIs to define and override aggregate functions used in [[Space Lua/Lua Integrated Query|LIQ]] `select` and `having` clauses after `group by`.
|
|
|
|
Built-in aggregates: `count`, `sum`, `min`, `max`, `avg` and `array_agg`.
|
|
|
|
# API
|
|
|
|
## aggregate.define(spec)
|
|
|
|
Defines a new aggregate function. Required keys:
|
|
|
|
* `name`: name of the aggregate (used in queries as `name(expr)`)
|
|
* `initialize`: function that returns the initial state
|
|
* `iterate`: function(state, value) that returns updated state
|
|
|
|
Optional keys:
|
|
|
|
* `description`: description of the aggregate
|
|
* `finish`: `function(state)` that transforms the final state into the result
|
|
|
|
## aggregate.update(spec)
|
|
|
|
Updates an existing aggregate definition. Same keys as `aggregate.define`. Only the provided keys are overwritten.
|
|
|
|
# Examples
|
|
|
|
## Define a custom aggregate
|
|
|
|
Define a custom aggregate `concat` that concatenates strings.
|
|
|
|
```lua
|
|
aggregate.define {
|
|
name = 'concat',
|
|
|
|
initialize = function()
|
|
return ''
|
|
end,
|
|
|
|
iterate = function(state, value)
|
|
if state == '' then
|
|
return tostring(value)
|
|
end
|
|
return state .. ', ' .. tostring(value)
|
|
end,
|
|
}
|
|
```
|
|
|
|
## Update an existing aggregate
|
|
|
|
```lua
|
|
aggregate.update {
|
|
name = 'count',
|
|
description = 'Custom count aggregate that counts even nils',
|
|
|
|
iterate = function(state, value)
|
|
return state + 1
|
|
end,
|
|
}
|
|
```
|
|
|
|
# Implementation
|
|
|
|
```space-lua
|
|
-- priority: 50
|
|
aggregate = aggregate or {}
|
|
|
|
local aggregateSchema = {
|
|
type = 'object',
|
|
|
|
required = {
|
|
'name',
|
|
'initialize',
|
|
'iterate'
|
|
},
|
|
|
|
properties = {
|
|
name = schema.string(),
|
|
description = schema.string(),
|
|
initialize = schema.func(),
|
|
iterate = schema.func(),
|
|
finish = schema.func(),
|
|
}
|
|
}
|
|
|
|
function aggregate.define(spec)
|
|
local validationResult = jsonschema.validateObject(aggregateSchema, spec)
|
|
|
|
if validationResult then
|
|
error('aggregate.define: ' .. validationResult)
|
|
end
|
|
|
|
config.set({'aggregates', spec.name}, spec)
|
|
end
|
|
|
|
function aggregate.update(spec)
|
|
if not spec.name then
|
|
error('aggregate.update: name is required')
|
|
end
|
|
|
|
local existing = config.get({'aggregates', spec.name}, {})
|
|
|
|
for k, v in pairs(spec) do
|
|
existing[k] = v
|
|
end
|
|
|
|
if not existing.initialize then
|
|
error('aggregate.update: aggregate '
|
|
.. spec.name .. ' has no initialize after merge')
|
|
end
|
|
|
|
if not existing.iterate then
|
|
error('aggregate.update: aggregate '
|
|
.. spec.name .. ' has no iterate after merge')
|
|
end
|
|
|
|
config.set({'aggregates', spec.name}, existing)
|
|
end
|
|
```
|