Skip to content

Built-in functions

String functions

Function Description
a ++ b Concatenation
Substr(s, i, l) Substring (1-based index)
Length(s) String length
Upper(s) / Lower(s) Case conversion
Split(s, sep) Split into an array
Join(list, sep) Join array into a string
Like(s, pattern) SQL pattern match (% wildcard)
Format(fmt, ...) printf-style formatting

Array functions

Function Description
Size(a) Number of elements
Element(a, i) Element access (0-based index)
ArrayConcat(a, b) Concatenate arrays
Range(n) Array [0, 1, ..., n-1]

Math functions

Abs, Floor, Ceil, Round, Sqrt, Exp, Log, Sin, Cos.

Type casting

Function Description
ToInt64(x) Cast to integer
ToFloat64(x) Cast to float
ToString(x) Cast to string

Other

Function Description
IsNull(x) Null test as an expression
Coalesce(x, y, ...) First non-null argument
Constraint(expr) Filter rows by a boolean expression

SqlExpr is reserved for the built-in library

SqlExpr(s, r) injects raw, unparsed, non-portable SQL into the compiled query, bypassing every verification and portability guarantee. It is used internally by the dialect library (e.g. ArgMin/ArgMax), but the verifier rejects it in user programs. Express the logic in Synalog instead — for date/time math, use the SubstrToInt64ToString pipeline.

Indexing conventions

Substr is 1-based (SQL convention); Element is 0-based (array convention).

User-defined functions

Define pure functions with =:

Square(x) = x * x;
FullName(first, last) = first ++ " " ++ last;
Greeting(message:) :- Users(first_name:, last_name:),
  message == "Hello, " ++ FullName(first_name, last_name) ++ "!";

Complete example

String, math and casting functions, plus two user-defined functions:

# run: ProductLabel, Rounded, Casts
@Engine("duckdb");

# Tables
Products(name: "espresso machine", price: 249.99);
Products(name: "grinder", price: 79.5);

# User-defined functions
Initial(s) = Upper(Substr(s, 1, 1));
FullLabel(name, price) = Upper(name) ++ " ($" ++ ToString(price) ++ ")";

# Rules

## String functions and concatenation.
@OrderBy(ProductLabel, "name");
ProductLabel(name:, label:, initial:, name_length:) :-
  Products(name:, price:),
  label == FullLabel(name, price),
  initial == Initial(name),
  name_length == Length(name);

## Math functions.
@OrderBy(Rounded, "name");
Rounded(name:, rounded:, floor:, sqrt:) :-
  Products(name:, price:),
  rounded == Round(price),
  floor == Floor(price),
  sqrt == Round(Sqrt(price));

## Type casting.
@OrderBy(Casts, "name");
Casts(name:, cents:, as_text:) :-
  Products(name:, price:),
  cents == ToInt64(price * 100),
  as_text == ToString(price);
Generated SQL and execution results
$ synalog.check('functions.l')
No errors found.

$ synalog.compile('functions.l', 'ProductLabel')
-- 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_Products AS (SELECT * FROM (

    SELECT
      E'espresso machine' AS name,
      249.99 AS price
   UNION ALL

    SELECT
      E'grinder' AS name,
      79.5 AS price

) AS UNUSED_TABLE_NAME  )
SELECT
  Products.name AS name,
  ((((((UPPER(Products.name)) || (E' ($'))) || (CAST(Products.price AS TEXT)))) || (E')')) AS label,
  UPPER(SUBSTR(Products.name, 1, 1)) AS initial,
  LENGTH(Products.name) AS name_length
FROM
  t_0_Products AS Products ORDER BY name;

-- Executed on DuckDB:
| name             | label                      | initial | name_length |
|------------------|----------------------------|---------|-------------|
| espresso machine | ESPRESSO MACHINE ($249.99) | E       | 16          |
| grinder          | GRINDER ($79.50)           | G       | 7           |
(2 rows)

$ synalog.compile('functions.l', 'Rounded')
-- 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_Products AS (SELECT * FROM (

    SELECT
      E'espresso machine' AS name,
      249.99 AS price
   UNION ALL

    SELECT
      E'grinder' AS name,
      79.5 AS price

) AS UNUSED_TABLE_NAME  )
SELECT
  Products.name AS name,
  ROUND(Products.price) AS rounded,
  FLOOR(Products.price) AS floor,
  ROUND(SQRT(Products.price)) AS sqrt
FROM
  t_0_Products AS Products ORDER BY name;

-- Executed on DuckDB:
| name             | rounded | floor | sqrt |
|------------------|---------|-------|------|
| espresso machine | 250     | 249   | 16.0 |
| grinder          | 80      | 79    | 9.0  |
(2 rows)

$ synalog.compile('functions.l', 'Casts')
-- 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_Products AS (SELECT * FROM (

    SELECT
      E'espresso machine' AS name,
      249.99 AS price
   UNION ALL

    SELECT
      E'grinder' AS name,
      79.5 AS price

) AS UNUSED_TABLE_NAME  )
SELECT
  Products.name AS name,
  CAST(((Products.price) * (100)) AS INT64) AS cents,
  CAST(Products.price AS TEXT) AS as_text
FROM
  t_0_Products AS Products ORDER BY name;

-- Executed on DuckDB:
| name             | cents | as_text |
|------------------|-------|---------|
| espresso machine | 24999 | 249.99  |
| grinder          | 7950  | 79.50   |
(2 rows)