Directives¶
Directives control predicate behavior. They must be placed before the rule definition they apply to.
@OrderBy(TopCustomers, "total", "DESC");
@Limit(TopCustomers, 10);
TopCustomers(customer_id:, total? += amount) distinct :- Orders(customer_id:, amount:);
| Directive | Purpose |
|---|---|
@OrderBy(Pred, "col1", ...) |
Sort order. Append "DESC" for descending. |
@Limit(Pred, n) |
Maximum number of rows. |
@Recursive(Pred, n) |
Allow recursion with an iteration limit. See Recursion. |
@Ground(Pred) |
Force materialization before dependents (performance). |
@Engine(name) |
Target SQL engine. See Supported engines. |
@OrderBy¶
@OrderBy is mandatory in practice
Put @OrderBy on every concept and rule. Without a stable sort order, pagination (limit/offset in compile()) returns rows in a non-deterministic order between calls.
@Limit¶
@Limit combines with the limit argument of compile(): the effective limit is min(limit, @Limit).
@Recursive¶
Enables recursion on a predicate, with a maximum number of iterations:
The full signature is @Recursive(Pred, iterations, stop?, satellites?). See Recursion for usage.
@Ground¶
Forces a predicate to be materialized before its dependents are evaluated — useful when a predicate is reused by many rules and recomputing it inline would be wasteful:
@Engine¶
Selects the target SQL dialect for the whole program:
The engine keyword of the Python API functions overrides this annotation.
Complete example¶
@OrderBy and @Limit combined — the top 3 customers by total spend:
# run: TopCustomers
@Engine("duckdb");
# Tables
Orders(customer_id: 100, amount: 250);
Orders(customer_id: 100, amount: 1200);
Orders(customer_id: 200, amount: 80);
Orders(customer_id: 300, amount: 430);
Orders(customer_id: 400, amount: 990);
# Rules
## Directives go BEFORE the rule they apply to.
@OrderBy(TopCustomers, "total", "DESC");
@Limit(TopCustomers, 3);
TopCustomers(customer_id:, total? += amount) distinct :- Orders(customer_id:, amount:);
Generated SQL and execution results
$ synalog.check('directives.l')
No errors found.
$ synalog.compile('directives.l', 'TopCustomers')
-- Initializing DuckDB environment.
create schema if not exists logica_home;
-- Empty record, has to have a field by DuckDB syntax.
drop type if exists logicarecord893574736 cascade; create type logicarecord893574736 as struct(nirvana numeric);
create sequence if not exists eternal_logical_sequence;
WITH t_0_Orders AS (SELECT * FROM (
SELECT
100 AS customer_id,
250 AS amount
UNION ALL
SELECT
100 AS customer_id,
1200 AS amount
UNION ALL
SELECT
200 AS customer_id,
80 AS amount
UNION ALL
SELECT
300 AS customer_id,
430 AS amount
UNION ALL
SELECT
400 AS customer_id,
990 AS amount
) AS UNUSED_TABLE_NAME )
SELECT
Orders.customer_id AS customer_id,
SUM(Orders.amount) AS total
FROM
t_0_Orders AS Orders
GROUP BY Orders.customer_id ORDER BY total DESC LIMIT 3;
-- Executed on DuckDB:
| customer_id | total |
|-------------|-------|
| 100 | 1450 |
| 400 | 990 |
| 300 | 430 |
(3 rows)