Friendly syntax

FeatureQL adds several conveniences on top of standard SQL that make queries shorter and easier to maintain. None of these change what you can compute — they just make it more pleasant to write.

Automatic dependency resolution

In SQL, you must define things before you reference them. In FeatureQL, order doesn't matter — reference a feature before it's defined and the engine resolves the dependency graph for you:

FeatureQL
SELECT
    NUMBER1 + NUMBER2 as NUMBER3,  -- Automatic Dependency Resolution: you can use features not yet defined
    1 AS NUMBER1,
    2 AS NUMBER2
Result
NUMBER3 BIGINTNUMBER1 BIGINTNUMBER2 BIGINT
312

Alternative assignment syntax

The SQL-style expr AS name works, but FeatureQL also supports := and IS for assignment. The := form puts the name first, which many people find easier to scan:

FeatureQL
SELECT
    -- Traditionnal SQL way
    1 AS NUMBER1,
    NUMBER1 + 1 AS NUMBER2,
    -- Reversed way
    NUMBER3 := 1,
    NUMBER4 := NUMBER3 + 1,
    NUMBER5 IS NUMBER3 + 2
Result
NUMBER1 BIGINTNUMBER2 BIGINTNUMBER3 BIGINTNUMBER4 BIGINTNUMBER5 BIGINT
12123

All three forms — 1 AS NUMBER1, NUMBER3 := 1, and NUMBER5 IS NUMBER3 + 2 — are equivalent.

Function chaining

Instead of nesting function calls like LOWER(TRIM(' HELLO ')), you can chain them left-to-right using dot notation. The rule: expr.FUNC(args) becomes FUNC(expr, args) — the expression before the dot is inserted as the first argument.

FeatureQL
SELECT
    (1+2).MULTIPLY(3),                                  -- = MULTIPLY((1+2), 3)
    (1).ADD(2).MULTIPLY(3),                             -- = MULTIPLY(ADD(1, 2), 3)
    (1).ADD(2).MULTIPLY(3)::DOUBLE.SQRT()::BIGINT,      -- You can also use casting
    ' HELLO '.TRIM().LOWER()                            -- = LOWER(TRIM(' HELLO '))
Result
?_0 BIGINT?_1 BIGINT?_2 BIGINT?_3 VARCHAR
993hello

Chains read left-to-right: (1).ADD(2).MULTIPLY(3) means MULTIPLY(ADD(1, 2), 3). When chaining after a type cast, wrap the type in parentheses — ::(DOUBLE).SQRT() — otherwise ::DOUBLE.SQRT() would be ambiguous because DOUBLE.SQRT() looks like a namespaced reference.

Chaining on namespaced features

Features can have dotted names like FM.MYFEATURES.FEATURE1. This is never ambiguous: the last segment before the opening parenthesis is always the function call, so FM.MYFEATURES.FEATURE1.ADD(2) can only mean "call ADD on feature FM.MYFEATURES.FEATURE1":

FeatureQL
SELECT
    FEATURE1 := 1,
    FEATURE1.ADD(2),                          -- = ADD(FEATURE1, 2)
    FM.MYFEATURES.FEATURE1 := 1,
    FM.MYFEATURES.FEATURE1.ADD(2)             -- = ADD(FM.MYFEATURES.FEATURE1, 2)
;
Result
FEATURE1 BIGINT?_1 BIGINTFM.MYFEATURES.FEATURE1 BIGINT?_3 BIGINT
1313

Chaining with namespaced functions

When the function itself has a dotted name (like macro FM.FUNCTIONS.AREA), wrap it in double-quotes so FeatureQL can tell where the feature ends and the function begins. Without quotes, SOME.LENGTH.FM.FUNCTIONS.AREA(SOME.WIDTH) is genuinely ambiguous — it could be feature SOME.LENGTH.FM.FUNCTIONS calling AREA, or feature SOME.LENGTH calling FM.FUNCTIONS.AREA. Quoting resolves it: SOME.LENGTH."FM.FUNCTIONS.AREA"(SOME.WIDTH).

FeatureQL
WITH
    PARAM1 := INPUT(BIGINT),
    PARAM2 := INPUT(BIGINT),
    FM.FUNCTIONS.AREA := MACRO(PARAM1*PARAM2 USING INPUTS PARAM1, PARAM2)
SELECT
    SOME.LENGTH := 2,
    SOME.WIDTH := 3,
    "FM.FUNCTIONS.AREA"(SOME.LENGTH, SOME.WIDTH),       -- Standard call
    SOME.LENGTH."FM.FUNCTIONS.AREA"(SOME.WIDTH)         -- Chained: SOME.LENGTH becomes first arg
;
Result
SOME.LENGTH BIGINTSOME.WIDTH BIGINT?_2 BIGINT?_3 BIGINT
2366

Flexible formatting

Trailing commas, arbitrary whitespace, and multi-line expressions are all valid:

FeatureQL
select
    number1        := 1,                -- Arbitrary whitespaces
    number2        := nuMbeR1 + 1,      -- Case insensitive
    number3 := ' 2 '
  		.TRIM()
		::BIGINT
		.ADD(1),                        -- Chaining on multi-lines
    array_of_ints  := ARRAY[
        1,
        2,
        3,                              -- Trailing comma in arrays and rows
    ],
    number4        := number2 + 2,      -- Trailing comma in feature list
Result
NUMBER1 BIGINTNUMBER2 BIGINTNUMBER3 BIGINTARRAY_OF_INTS ARRAYNUMBER4 BIGINT
123[1, 2, 3]4

Trailing commas mean you can reorder features without worrying about which one is last. Multi-line chaining (.TRIM() on one line, ::BIGINT on the next) keeps complex expressions readable.

Function aliases

SQL dialects use different names for the same operation. FeatureQL accepts common aliases — ARRAY and LIST, SLICE and ARRAY_SLICE, POWER and POW — so you can use the names you already know:

FeatureQL
SELECT
    ARRAY[1,2,3,4,5,6] as ARRAY_1,
    ARRAY(1,2,3,4,5,6) as ARRAY_2,              -- For Array and Row, you can use brackets or parentheses
    SLICE(ARRAY_1, 2, 3) as SLICED_1,
    LIST_SLICE(ARRAY_1, 2, 3) as SLICED_2,      -- You can use functions aliases
    ARRAY_SLICE(ARRAY_2, 2, 3) as SLICED_3
Result
ARRAY_1 ARRAYARRAY_2 ARRAYSLICED_1 ARRAYSLICED_2 ARRAYSLICED_3 ARRAY
[1, 2, 3, 4, 5, 6][1, 2, 3, 4, 5, 6][2, 3, 4][2, 3, 4][2, 3, 4]

Beyond name aliases, constructors also accept both brackets and parentheses: ARRAY[1,2,3] and ARRAY(1,2,3) are equivalent, as are ROW(...) and ROW[...].

FeatureQL normalizes aliases to a single canonical name when features are saved, so stored definitions stay consistent regardless of which alias you used.

Intermediate features with WITH

Complex queries often need intermediate calculations that shouldn't appear in the output. The WITH clause defines features that are available for computation but excluded from the result:

FeatureQL
WITH
    1 AS NUMBER1,            -- Intermediate calculation
    2 AS NUMBER2,            -- Intermediate calculation
SELECT
    NUMBER1,                        -- Exposed in results
    NUMBER1 + NUMBER2 AS NUMBER3    -- Exposed in results
;
Result
NUMBER1 BIGINTNUMBER3 BIGINT
13

NUMBER2 is used to compute NUMBER3 but doesn't appear in the output. This keeps query results focused on what consumers actually need.

Functions that must live in WITH

Some functions produce internal values that can't be returned directly:

Function typeWhy it can't be returnedWhat to return instead
INPUT()Declares a parameter placeholderThe bound values via BIND_XXX()
ENTITY()Declares an entityThe bound values of the INPUT() referencing the entity
SOURCE_XXX()Declares a connectionThe EXTERNAL_XXX() that uses the source
UDF() / MACRO()Declares a functionThe function applied to concrete parameters
Last update at: 2026/03/03 16:47:38
Last updated: 2026-03-03 16:48:19