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: @eval_as_text() and @eval_as_literal().

@eval_as_text() — inject text into the query

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

@eval_as_text() 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 @eval_as_text()) and as a typed literal (via @eval_as_literal()):

@fql-playground(meta_echo_literal_duplicate_constant)

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

@fql-playground(meta_echo_varchar_with_cast_to_int)

@eval_as_literal() — inject a typed value

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

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

Use @eval_as_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 @eval_as_text() and @eval_as_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 @eval_as_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 FEATURES AS
CONST EXTERNAL_CONSTANT := 0
SELECT FM.TUTORIALS.META.CST_EXTERNAL := ADD(eval_const(2*EXTERNAL_CONSTANT AS 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 @eval_as_text() or @eval_as_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 := @eval_as_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 := @eval_as_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 || @eval_as_literal(RANDOM()::VARCHAR))
Result
ID BIGINTRESULT VARCHAR
10.8223445174536488
20.37217093613676144
30.4197024910057665

When to use @eval_as_text() vs @eval_as_literal()

ScenarioUseWhy
Inject a computed number, date, or array@eval_as_literal()Preserves the native type
Construct a feature name or SQL fragment dynamically@eval_as_text()Produces raw text
Wrap RANDOM() or CURRENT_TIMESTAMP()Either@eval_as_literal() is usually cleaner
Persist a compile-time value in a feature definitionEVAL_CONST()Designed for persistence; requires explicit type
Last update at: 2026/05/26 17:22:09