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 Substr → ToInt64 →
ToString pipeline.
Indexing conventions
Substr is 1-based (SQL convention); Element is 0-based (array convention).
User-defined functions¶
Define pure functions with =:
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)