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
NUMBER1 IS 1           -- Alternative
sql

Type system

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

Scalar types

CategoryTypesAliasesLiteral examples
IntegerBIGINT, MEDIUMINT, SMALLINT, TINYINTINT64, INT32, 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 coercion is inconsistent. Scalar arithmetic between different DECIMAL precisions works: 1.1 + 1.25 produces DECIMAL(4,2). But DECIMAL coercion does not apply inside array or ROW constructors:

-- OK: scalar arithmetic coerces DECIMAL(2,1) + DECIMAL(3,2) → DECIMAL(4,2)
total := 1.1 + 1.25    -- Works, result is 2.35

-- FAILS: array/ROW constructors require identical types
arr := ARRAY[ROW(1.1 AS dec), ROW(1.25 AS dec)]
-- Error: ROW(dec DECIMAL(2,1)) ≠ ROW(dec DECIMAL(3,2))
sql

Rule of thumb: use DOUBLE (scientific notation) for decimal values in arrays, ROWs, and mixed contexts. DECIMAL is safe for simple scalar arithmetic but will bite you in constructors.

-- 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)

-- WRONG: different DECIMAL precisions in array constructor
arr := ARRAY[ROW(1.1 AS val), ROW(1.25 AS val)]

-- RIGHT: use DOUBLE in constructors
arr := ARRAY[ROW(1.1e0 AS val), ROW(1.25e0 AS val)]

-- WRONG: integer + decimal is a type error
total := 1 + 1.0

-- RIGHT: cast to same type
total := 1::DOUBLE + 1.0e0
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 for decimal numbers, especially in arrays and ROWs.

Operators (by precedence, highest first)

PrecedenceOperatorsDescription
1()Parentheses
2:=Assignment
3[]Array/row field extraction
4::Type casting
5. * / // % ^Chaining, multiply, divide, integer div, modulo, power
6+ -Addition, subtraction
7= <> != > >= < <= BETWEEN LIKE INComparison
8AND OR NOTLogical

Special operators

OperatorPurposeExample
=.NFloat comparison with N digits precision0.99999e0 =.4 1.00001e0TRUE
||String concatenation'Hello' || ' World'
//Integer division7 // 32
[]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/02/17 19:13:00
Last updated: 2026-02-17 19:13:38