Skip to content

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(Stats, "category");
@OrderBy(TopCustomers, "total", "DESC");

@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(TopCustomers, 10);

@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:

@Recursive(AllManagers, 20);

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:

@Ground(CustomerRevenue);

@Engine

Selects the target SQL dialect for the whole program:

@Engine("duckdb");

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)