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:
SELECT
NUMBER1 + NUMBER2 as NUMBER3, -- Automatic Dependency Resolution: you can use features not yet defined
1 AS NUMBER1,
2 AS NUMBER2| NUMBER3 BIGINT | NUMBER1 BIGINT | NUMBER2 BIGINT |
|---|---|---|
| 3 | 1 | 2 |
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:
SELECT
-- Traditionnal SQL way
1 AS NUMBER1,
NUMBER1 + 1 AS NUMBER2,
-- Reversed way
NUMBER3 := 1,
NUMBER4 := NUMBER3 + 1,
NUMBER5 IS NUMBER3 + 2| NUMBER1 BIGINT | NUMBER2 BIGINT | NUMBER3 BIGINT | NUMBER4 BIGINT | NUMBER5 BIGINT |
|---|---|---|---|---|
| 1 | 2 | 1 | 2 | 3 |
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.
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 '))| ?_0 BIGINT | ?_1 BIGINT | ?_2 BIGINT | ?_3 VARCHAR |
|---|---|---|---|
| 9 | 9 | 3 | hello |
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":
SELECT
FEATURE1 := 1,
FEATURE1.ADD(2), -- = ADD(FEATURE1, 2)
FM.MYFEATURES.FEATURE1 := 1,
FM.MYFEATURES.FEATURE1.ADD(2) -- = ADD(FM.MYFEATURES.FEATURE1, 2)
;| FEATURE1 BIGINT | ?_1 BIGINT | FM.MYFEATURES.FEATURE1 BIGINT | ?_3 BIGINT |
|---|---|---|---|
| 1 | 3 | 1 | 3 |
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).
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
;| SOME.LENGTH BIGINT | SOME.WIDTH BIGINT | ?_2 BIGINT | ?_3 BIGINT |
|---|---|---|---|
| 2 | 3 | 6 | 6 |
Flexible formatting
Trailing commas, arbitrary whitespace, and multi-line expressions are all valid:
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| NUMBER1 BIGINT | NUMBER2 BIGINT | NUMBER3 BIGINT | ARRAY_OF_INTS ARRAY | NUMBER4 BIGINT |
|---|---|---|---|---|
| 1 | 2 | 3 | [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:
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| ARRAY_1 ARRAY | ARRAY_2 ARRAY | SLICED_1 ARRAY | SLICED_2 ARRAY | SLICED_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:
WITH
1 AS NUMBER1, -- Intermediate calculation
2 AS NUMBER2, -- Intermediate calculation
SELECT
NUMBER1, -- Exposed in results
NUMBER1 + NUMBER2 AS NUMBER3 -- Exposed in results
;| NUMBER1 BIGINT | NUMBER3 BIGINT |
|---|---|
| 1 | 3 |
NUMBER2 is used to compute NUMBER3 but doesn't appear in the output. This keeps query results focused on what consumers actually need.
Some functions produce internal values that can't be returned directly:
| Function type | Why it can't be returned | What to return instead |
|---|---|---|
INPUT() | Declares a parameter placeholder | The bound values via BIND_XXX() |
ENTITY() | Declares an entity | The bound values of the INPUT() referencing the entity |
SOURCE_XXX() | Declares a connection | The EXTERNAL_XXX() that uses the source |
UDF() / MACRO() | Declares a function | The function applied to concrete parameters |