LLM reference — FeatureQL syntax

Complete syntax reference for writing FeatureQL queries. Load this page when you need to write or debug query structure, types, casting, operators, or expression syntax.

Query anatomy

CONST                          -- Optional: compile-time constants
    MY_CONST := 42,
WITH                           -- Local features (only evaluated if referenced in SELECT)
    FEATURE1 := INPUT(BIGINT),
    FEATURE2 := FEATURE1 * 2,
SELECT                         -- Features to return in output
    FEATURE1,
    FEATURE2,
FROM fm.namespace              -- Optional: pull persisted features from registry
FOR                            -- REQUIRED: bind values to INPUT() features
    FEATURE1 := BIND_VALUES(ARRAY[1, 2, 3]),
WHERE FEATURE2 > 2             -- Optional: filter result rows
ORDER BY FEATURE2 DESC         -- Optional: sort
LIMIT 10 OFFSET 5              -- Optional: pagination
;
sql

Key rules:

  • WITH features are not in the output. SELECT features are in the output.
  • Features can reference each other in any order — dependency resolution is automatic.
  • FROM fm.namespace imports persisted features so you can reference them by short name.
  • Trailing commas are allowed everywhere.

Assignment styles

All three are equivalent. The := style is preferred:

NUMBER1 := 1           -- FeatureQL style (preferred)
1 AS NUMBER1           -- SQL style
sql

Type system

No automatic coercion — cast explicitly with ::TYPE or CAST(x AS TYPE).

Scalar types

CategoryTypesAliasesLiteral examples
IntegerBIGINT, INT, SMALLINT, TINYINTINT64, INT32, INTEGER, INT16, INT842, -7
DecimalDECIMAL1.25 (auto-detects precision: DECIMAL(3,2))
FloatFLOAT, DOUBLEFLOAT32, FLOAT641.25e0 (scientific notation)
StringVARCHAR'text' (single quotes only)
BooleanBOOLEANTRUE, FALSE
DateDATEDATE '2024-10-15'
TimestampTIMESTAMPTIMESTAMP '2024-10-15 10:04:00'
IntervalINTERVALINTERVAL '3' DAY
JSONJSONJSON '"value"'

Complex types

TypeAliasLiteral example
ARRAY(T)LIST(T)ARRAY[1, 2, 3]
ROW(field1 T1, field2 T2)STRUCT(...)ROW(1, 'A' AS name)
ARRAY(ROW(...))ARRAY[ROW(1, 'A'), ROW(2, 'B')]::ARRAY(ROW(num BIGINT, letter VARCHAR))

Entity annotations

Entity annotations link types to semantic entities for relationship tracking:

BIGINT#CUSTOMERS           -- This value is a CUSTOMERS entity key
ARRAY(BIGINT#ORDERS)       -- Array of ORDER entity keys
sql

Special values

ValueMeaning
NULLUntyped null
NULL(DOUBLE)Typed null
EMPTYExplicitly empty (cannot be cast)

Casting

CAST('123' AS BIGINT)      -- Standard form
'123'::BIGINT              -- Shorthand (higher precedence than operators)
ID::BIGINT#ORDERS          -- Add entity annotation
ID::BIGINT                 -- Remove entity annotation
sql

Critical type pitfalls

DECIMAL widening works everywhere. Different DECIMAL precisions are unified (widened) automatically in arithmetic, arrays, ROWs, and constructors: ARRAY[1.25, 1.1] produces ARRAY(DECIMAL(3,2)).

Integer promotion to DECIMAL is limited. When a BIGINT literal appears next to a DECIMAL in arithmetic, comparisons, or BETWEEN, it is promoted to DECIMAL(N,0). This promotion does not apply inside ARRAY or ROW constructors — mixing BIGINT and DECIMAL elements is an error.

-- OK: DECIMAL widening in arrays and ROWs (same family, different precision)
arr := ARRAY[1.25, 1.1]                            -- ARRAY(DECIMAL(3,2))
aor := ARRAY[ROW(1.1 AS val), ROW(1.25 AS val)]    -- ARRAY(ROW(val DECIMAL(3,2)))

-- OK: integer widening in arrays (all integers, different sizes)
arr := ARRAY[ROW(2::BIGINT), ROW(INT '2')]          -- ARRAY(ROW(field_1 BIGINT))

-- WRONG: BIGINT + DECIMAL in array (no promotion in constructors)
arr := ARRAY[1.25, 1]     -- Error: DECIMAL + BIGINT are different families

-- WRONG: 1.25 is DECIMAL(3,2), price is DOUBLE → type mismatch
revenue := price * 1.25

-- RIGHT: Use scientific notation for DOUBLE literals
revenue := price * 1.25e0

-- RIGHT: Or cast explicitly
revenue := price * CAST(1.25 AS DOUBLE)
sql

The e0 suffix means "times 10 to the power 0" — mathematically a no-op, but it tells FeatureQL the literal is DOUBLE, not DECIMAL. Use e0 when you need DOUBLE values.

Operators (by precedence, highest first)

PrecedenceOperatorsDescription
1:=Assignment
2()Parentheses
3[]Array/row extraction
4::Type casting
5.Function chaining
6~Bitwise NOT
7^ **Power
8* / // %Multiply, divide, integer div, modulo
9+ -Addition, subtraction
10<< >>Bitwise shift left, bitwise shift right
11&Bitwise AND
12|Bitwise OR
13||String concatenation
14= <> != > >= < <= BETWEEN NOT BETWEEN LIKE NOT LIKE IN NOT IN IS NULL IS NOT NULLComparison
15NOTLogical NOT
16ANDLogical AND
17ORLogical OR

Special operators

OperatorPurposeExample
=.NFloat comparison with N digits precision0.99999e0 =.4 1.00001e0TRUE
||String concatenation'Hello' || ' World'
//Type-safe division6.10 // 32.03
[]Extract field from ROW or element from ARRAYrow[field_name], array[1]
IS NULL / IS NOT NULLNull checksvalue IS NULL

Function chaining

Any function can be called as a method using . syntax. The expression before the dot becomes the first argument:

' HELLO '.TRIM().LOWER()              -- Equivalent to LOWER(TRIM(' HELLO '))
ARRAY[3,1,2].ARRAY_SORT().ARRAY_SUM() -- Chain array operations
(1 + 2).MULTIPLY(3)                   -- Equivalent to MULTIPLY((1 + 2), 3)
sql

Array and ROW access with []

The [] operator is overloaded based on context:

-- ROW field access
row_feature[field_name]              -- Extract one field
row_feature[field1, field2]          -- Extract multiple fields (returns ROW)

-- ARRAY element access (1-indexed)
array_feature[1]                     -- First element
array_feature[2:4]                   -- Slice (elements 2, 3, 4)
array_feature[2,4]                   -- Elements at positions 2 and 4

-- ARRAY(ROW) field extraction (returns ARRAY of that field's values)
array_of_rows[field_name]            -- Extract field as array: [{id:1,name:'A'},{id:2,name:'B'}][name] → ['A','B']
sql

Conditional expressions

-- Ternary
IF(condition, then_value, else_value)

-- Multi-branch
CASE WHEN(
    ARRAY[condition1, condition2],    -- Boolean conditions
    ARRAY[value1, value2],            -- Corresponding results
    default_value                     -- Fallback
)

-- Value matching
CASE WHEN VALUE(
    expression,                       -- Value to match
    ARRAY[match1, match2],            -- Match values
    ARRAY[result1, result2],          -- Corresponding results
    default_value                     -- Fallback
)

-- First non-null
COALESCE(value1, value2, value3)
sql

Metaprogramming

Use when you need compile-time evaluation or dynamic query construction:

CONST MY_VALUE := 42,                          -- Compile-time constant

-- Inject computed literal (preserves type, evaluated once at compile time)
THRESHOLD := @literal(POWER(2, 10)),

-- Non-deterministic functions MUST be wrapped in @literal
TODAY := @literal(CURRENT_TIMESTAMP()),         -- Evaluated once, not per row
RAND := @literal(RANDOM()),                     -- Evaluated once, not per row

-- Inject computed string into query text
DYNAMIC := @echo('SELECT * FROM ' || table_name),

-- Deterministic per-row randomness (NOT @literal — this is per-row by design)
BUCKET := HASH01(USER_ID, 'experiment_name'),   -- Returns 0.0 to 1.0
sql

Persistence (DDL)

CREATE FEATURES IN fm.namespace AS SELECT ...;           -- Persist feature definitions
CREATE OR REPLACE FEATURES IN fm.namespace AS SELECT ...; -- Upsert
CREATE TEMPORARY FEATURES AS SELECT ...;                  -- Session-scoped
DROP FEATURES fm.namespace.feature_name;                  -- Remove
ALTER FEATURE fm.namespace.feature SET COMMENT = 'desc';  -- Add metadata
sql

Features are persisted as definitions (like SQL views), not data. FROM fm.namespace in a query imports them.

Namespace conventions

-- Features use fm.namespace.feature_name pattern
FROM fm.ecomm                         -- Import namespace, use short names
FROM fm.ns1, fm.ns2 AS _ns2           -- Multiple namespaces (alias must start with _)
fm.ecomm.CUSTOMER_NAME                -- Fully qualified reference
sql
Last update at: 2026/03/18 16:18:57
Last updated: 2026-03-18 16:19:34