Pure transformations
FeatureQL includes a rich set of functions for transforming data — arithmetic, string manipulation, date math, conditionals, and more. This page shows a representative example for each data type to give you a feel for the syntax. It is not exhaustive: the complete list of all functions is in the syntax reference .
Numerics
Arithmetic operators (+, -, *, /) work as expected. / always returns DOUBLE. // divides, then truncates toward zero at the dividend's scale — not Python floor, not round-to-even. 7 // 2 is 3; -7 // 2 is -3; 1.5 // 0.4 is 3.7 (DECIMAL(2,1)). Compile-time 1 // 0 is UE/ARGUMENT-MUST-BE-NON-ZERO. For a floating quotient use /. FeatureQL also includes rounding and mathematical functions like ABS(), SQRT(), EXP(), and MOD().
DEFINE
price := 49.99e0,
tax_rate := 0.21e0
RETURN
price,
tax := ROUND(price * tax_rate, 2),
total := ROUND(price * (1e0 + tax_rate), 2),
half := price / 2e0,
rounded := CEIL(total)
;| PRICE DOUBLE | TAX DOUBLE | TOTAL DOUBLE | HALF DOUBLE | ROUNDED BIGINT |
|---|---|---|---|---|
| 49.99 | 10.5 | 60.49 | 24.995 | 61 |
Strings
String concatenation uses || or CONCAT(). FeatureQL provides the usual toolkit for splitting, extracting, replacing, and formatting strings.
DEFINE
first_name := 'Alice',
last_name := 'Johnson',
email := 'alice.johnson@example.com'
RETURN
full_name := first_name || ' ' || last_name,
domain := SPLIT_PART(email, '@', 2),
initials := SUBSTR(first_name, 1, 1) || SUBSTR(last_name, 1, 1),
slug := LOWER(REPLACE(full_name, ' ', '-'))
;| FULL_NAME VARCHAR | DOMAIN VARCHAR | INITIALS VARCHAR | SLUG VARCHAR |
|---|---|---|---|
| Alice Johnson | example.com | AJ | alice-johnson |
Conditionals
IF() handles simple branching, CASE WHEN handles multi-branch logic, and COALESCE() picks the first non-null value. All three return typed values — no implicit coercion.
DEFINE
score := 73
RETURN
score,
grade := CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
WHEN score >= 60 THEN 'D'
ELSE 'F'
END,
is_passing := score >= 60,
label := IF(is_passing, 'Pass', 'Fail')
;| SCORE BIGINT | GRADE VARCHAR | IS_PASSING BOOLEAN | LABEL VARCHAR |
|---|---|---|---|
| 73 | C | true | Pass |
Dates and timestamps
Date functions follow a consistent pattern: DATE_ADD(timestamp, count, unit) to shift (DATE_ADD(d, 7, 'day')), DATE_SUBTRACT(end, start, unit) to measure elapsed complete units, and DATE_DIFF(end, start, unit) to count boundary crossings. DATE_FORMAT() renders a timestamp. DATE_PART(d, 'year') pulls out a component; EXTRACT_FROM_DATE() is the permissive alias. The old DATE_ADD(d, 'day', 7) order is refused with a hint. Timestamps support up to microsecond precision.
DEFINE
signup := TIMESTAMP '2025-11-03 14:30:00',
now := TIMESTAMP '2026-02-17 10:00:00'
RETURN
signup,
days_since := DATE_SUBTRACT(now, signup, 'day'),
month := DATE_FORMAT(signup, '%B %Y'),
is_recent := DATE_SUBTRACT(now, signup, 'day') <= 90
;| SIGNUP TIMESTAMP(3) | DAYS_SINCE BIGINT | MONTH VARCHAR | IS_RECENT BOOLEAN |
|---|---|---|---|
| 2025-11-03T14:30:00 | 105 | November 2025 | false |
JSON
FeatureQL can parse, extract from, and format JSON values. Use JSON_EXTRACT_SCALAR() to pull a single value as a string, or JSON_EXTRACT() to get a nested JSON fragment.
DEFINE
event := JSON '{"type": "purchase", "user_id": "u_42", "payload": {"amount": 29.99, "currency": "EUR"}}'
RETURN
event,
event_type := JSON_EXTRACT_SCALAR(event, '$.type'),
user_id := JSON_EXTRACT_SCALAR(event, '$.user_id'),
amount := JSON_EXTRACT_SCALAR(event, '$.payload.amount')
;| EVENT JSON | EVENT_TYPE VARCHAR | USER_ID VARCHAR | AMOUNT VARCHAR |
|---|---|---|---|
| {"type": "purchase", "user_id": "u_42", "payload": {"amount": 29.99, "currency": "EUR"}} | purchase | u_42 | 29.99 |
Arrays
Arrays support set operations (ARRAY_UNION(), ARRAY_INTERSECT(), ARRAY_EXCEPT()), membership checks (CONTAINS()), sorting, and aggregation (ARRAY_SUM(), ARRAY_COUNT()). For vector operations, see COSINE_SIMILARITY(), DOT_PRODUCT(), and EUCLIDEAN_DISTANCE().
DEFINE
tags := ARRAY('premium', 'vip', 'early-adopter'),
other_tags := ARRAY('vip', 'enterprise', 'beta')
RETURN
tags,
tag_count := ARRAY_COUNT(tags),
has_vip := CONTAINS(tags, 'vip'),
sorted := ARRAY_SORT(tags),
common := ARRAY_INTERSECT(tags, other_tags)
;| TAGS ARRAY<VARCHAR> | TAG_COUNT BIGINT | HAS_VIP BOOLEAN | SORTED ARRAY<VARCHAR> | COMMON ARRAY<VARCHAR> |
|---|---|---|---|---|
| [premium, vip, early-adopter] | 3 | true | [early-adopter, premium, vip] | [vip] |
Rows
A ROW is a named struct. You create one with ROW(...), name fields with AS, and extract fields with []. Extracting multiple fields returns a new row with just those fields.
DEFINE
address := ROW(
'123 Main St' AS street,
'Paris' AS city,
'75001' AS postal_code
)
RETURN
address,
city := address[city],
summary := address[city, postal_code]
;| ADDRESS ROW<street VARCHAR, city VARCHAR, postal_code VARCHAR> | CITY VARCHAR | SUMMARY ROW<city VARCHAR, postal_code VARCHAR> |
|---|---|---|
| {street: 123 Main St, city: Paris, postal_code: 75001} | Paris | {city: Paris, postal_code: 75001} |
Array of rows
Arrays of rows are FeatureQL's primary structure for one-to-many relationships — they replace SQL JOINs with nested data. See Array of rows for a full treatment.