Metaprogramming

FeatureQL is a pure functional language — every feature is a deterministic function of its inputs. But sometimes you need to compute a value at compile time and inject it into the query before execution. That's what metaprogramming does.

Traditional SQL typically uses Jinja templates for this. FeatureQL provides two native functions instead: @echo() and @literal().

@echo() — inject text into the query

@echo() evaluates an expression at compile time and splices the result as raw text into the query. The expression must evaluate to VARCHAR.

@echo() is useful when you need to construct identifiers or query fragments dynamically. In this example, a constant string is injected both as a raw text splice (via @echo()) and as a typed literal (via @literal()):

FeatureQL
CONST MY_TEXT := 'hello'
SELECT '@echo(MY_TEXT)' AS RESULT1, @literal(MY_TEXT) AS RESULT2
Result
RESULT1 VARCHARRESULT2 VARCHAR
hellohello

If the value you want to echo isn't already a string, cast it explicitly — @echo() only accepts VARCHAR:

FeatureQL
CONST MY_NUMBER := 42
SELECT @echo(MY_NUMBER::VARCHAR) AS RESULT
Result
RESULT BIGINT
42

@literal() — inject a typed value

@literal() evaluates an expression at compile time and injects the result as a typed literal. Unlike @echo(), it preserves the original type of the computed value.

FeatureQL
SELECT @literal(1+2) AS RESULT
Result
RESULT BIGINT
3

Use @literal() when you need the result to participate in type checking as its native type, rather than being spliced as text.

CONST declarations

CONST defines compile-time constants that @echo() and @literal() can reference. Several syntaxes are supported:

-- Single constant
CONST MY_VALUE := 42

-- Multiple constants on one line
CONST CONST1 := 1, CONST2 := 2

-- Multiple constants across lines
CONST
    CONST1 := 1,
    CONST2 := 2

-- Trailing commas are allowed
CONST
    CONST1 := 1,
    CONST2 := 2,
sql

Constants support complex expressions — they're evaluated before the rest of the query is compiled:

FeatureQL
CONST BASE := 10
SELECT @literal(BASE * 2 + 3) AS RESULT
Result
RESULT BIGINT
23

Persisting features with compile-time values

When you persist features that include compile-time computed parameters, use EVAL_CONST(). You must specify the type explicitly because the value is computed before type inference runs:

FeatureQL
CREATE OR REPLACE FEATURE FM.TUTORIALS.META.CST_EXTERNAL AS ADD(eval_const('2*EXTERNAL_CONSTANT', TYPE 'BIGINT'), 1);
Result
feature_name VARCHARstatus VARCHARmessage VARCHAR
FM.TUTORIALS.META.CST_EXTERNALCREATEDFeature created as not exists

Selecting the persisted feature returns the value that was computed at creation time:

FeatureQL
CONST
    EXTERNAL_CONSTANT := 5
SELECT
    FM.TUTORIALS.META.CST_EXTERNAL
Result
FM.TUTORIALS.META.CST_EXTERNAL BIGINT
11

Non-deterministic functions

RANDOM() and CURRENT_TIMESTAMP() return different values on each invocation, which violates FeatureQL's pure function model. Using them directly produces an error — you must wrap them in @echo() or @literal() so the value is evaluated once at compile time and stays constant for all rows.

Here, CURRENT_TIMESTAMP() is evaluated once and every row gets the same timestamp:

FeatureQL
WITH
	ID := INPUT(BIGINT),
SELECT
    ID := BIND_VALUES(ARRAY[1,2,3]),
    RESULT := @literal(CURRENT_TIMESTAMP())
Result
ID BIGINTRESULT VARCHAR
12025-09-13 10:22:02
22025-09-13 10:22:02
32025-09-13 10:22:02

The same approach works for RANDOM() — every row gets the same random value:

FeatureQL
WITH
	ID := INPUT(BIGINT),
SELECT
    ID := BIND_VALUES(ARRAY[1,2,3]),
    RESULT := @literal(RANDOM())
Result
ID BIGINTRESULT VARCHAR
10.5523897924574934
20.5523897924574934
30.5523897924574934

Per-row deterministic randomness

If you need values that vary per row but remain consistent across executions, use HASH01(). It generates a deterministic value in [0, 1] based on the hash of its input:

FeatureQL
WITH
	ID := INPUT(BIGINT),
SELECT
    ID := BIND_VALUES(ARRAY[1,2,3]),
    RESULT := HASH01(ID::VARCHAR)
Result
ID BIGINTRESULT VARCHAR
10.7687112224818731
20.7817145915735615
30.9249844845138457

Randomness that changes each execution

To get per-row values that also change between executions, combine HASH01() with a compile-time random seed. The seed changes each run, producing different per-row values:

FeatureQL
WITH
	ID := INPUT(BIGINT),
SELECT
    ID := BIND_VALUES(ARRAY[1,2,3]),
    RESULT := HASH01(ID::VARCHAR || @literal(RANDOM()::VARCHAR))
Result
ID BIGINTRESULT VARCHAR
10.8223445174536488
20.37217093613676144
30.4197024910057665

When to use @echo() vs @literal()

ScenarioUseWhy
Inject a computed number, date, or array@literal()Preserves the native type
Construct a feature name or SQL fragment dynamically@echo()Produces raw text
Wrap RANDOM() or CURRENT_TIMESTAMP()Either@literal() is usually cleaner
Persist a compile-time value in a feature definitionEVAL_CONST()Designed for persistence; requires explicit type
Last update at: 2026/03/03 16:47:38
Last updated: 2026-03-03 16:48:19