Macros

MACRO() turns a feature expression into a reusable function. You specify which inputs become parameters, and the result is a callable that you can invoke with different values — like defining a function in any programming language.

MACRO(expression USING param1, param2, ...)
sql

Creating and calling a macro

Define a feature expression, then wrap it in MACRO() to make it callable. Here, AREA_CIRCLE_FEATURE computes the area of a circle, and AREA_CIRCLE_FUNCTION turns it into a function that accepts a radius:

FeatureQL
DEFINE
    radius := INPUT(DOUBLE),
    area_circle_feature := PI() * POW(radius, 2e0),
    area_circle_function := MACRO(area_circle_feature USING radius)
RETURN
    area_circle_value_for_2 := "AREA_CIRCLE_FUNCTION"(2e0),
    area_circle_value_for_5 := "AREA_CIRCLE_FUNCTION"(5e0)
;
Result
AREA_CIRCLE_VALUE_FOR_2 DOUBLEAREA_CIRCLE_VALUE_FOR_5 DOUBLE
12.56637061435917278.53981633974483

The macro is called like a regular function — AREA_CIRCLE_FUNCTION(2e0) evaluates the area formula with radius 2.

Inline expressions

You don't need to name the intermediate feature. The expression can go directly inside MACRO(), and arguments can be expressions too:

FeatureQL
DEFINE
    radius := INPUT(DOUBLE),
    area_circle_function := MACRO(PI() * POW(radius, 2e0) USING radius)
RETURN
    area_circle_value_for_2 := "AREA_CIRCLE_FUNCTION"(1e0 + 1e0),
    area_circle_value_for_5 := "AREA_CIRCLE_FUNCTION"(2.5e0 * 2e0)
;
Result
AREA_CIRCLE_VALUE_FOR_2 DOUBLEAREA_CIRCLE_VALUE_FOR_5 DOUBLE
12.56637061435917278.53981633974483

Multiple parameters

Macros accept any number of input parameters. The order of parameters in the USING clause determines the argument order when calling:

FeatureQL
DEFINE
    length := INPUT(DOUBLE),
    width := INPUT(DOUBLE),
    area_rectangle_function := MACRO(length * width USING length, width)
RETURN
    area_rectangle_value_for_2_5 := "AREA_RECTANGLE_FUNCTION"(2e0, 5e0),
    area_rectangle_value_for_5_3 := "AREA_RECTANGLE_FUNCTION"(5e0, 3e0)
;
Result
AREA_RECTANGLE_VALUE_FOR_2_5 DOUBLEAREA_RECTANGLE_VALUE_FOR_5_3 DOUBLE
10.015.0

Chaining syntax

Macros support FeatureQL's chaining syntax. The chained value becomes the first argument:

FeatureQL
DEFINE
    radius := INPUT(DOUBLE),
    area_circle_function := MACRO(PI() * POW(radius, 2e0) USING radius)
RETURN
    area_circle_value_for_2_chaining := 2e0."AREA_CIRCLE_FUNCTION"()
;
Result
AREA_CIRCLE_VALUE_FOR_2_CHAINING DOUBLE
12.566370614359172

2e0.AREA_CIRCLE_FUNCTION() is equivalent to AREA_CIRCLE_FUNCTION(2e0).

How arguments are substituted

A call is a variant of the macro body that replaces each parameter with its argument. Three rules follow:

  • Arguments reach the whole body graph. Every body feature that depends on a parameter is rewritten, not only the top expression.
  • Arguments are computed in the caller's graph. An argument can be a feature that the body also uses, or another parameter, without looping.
  • Calls are independent. Two calls of the same macro in one query do not affect each other or the original features.

Here TOTAL depends on PRICE both directly and through TAX. The call TOTAL_FN(TAX, 0.5e0) uses the caller's TAX (20) as the price, so the body computes 20 + 20 × 0.5 = 30:

FeatureQL
DEFINE
    price := INPUT(DOUBLE),
    rate := INPUT(DOUBLE),
    tax := price * rate,
    total := price + tax,
    total_fn := MACRO(total USING price, rate)
RETURN
    total,
    total_of_tax := "TOTAL_FN"(tax, 0.5e0),
    total_of_200 := "TOTAL_FN"(200e0, rate)
FOR CROSS
    price := BIND_VALUE(100e0),
    rate := BIND_VALUE(0.2e0)
;
Result
TOTAL DOUBLETOTAL_OF_TAX DOUBLETOTAL_OF_200 DOUBLE
120.030.0240.0

Limitations

Macros are not polymorphic — each input parameter accepts only the type declared in its INPUT() definition. If you need a function that works across multiple types (e.g., both DATE and TIMESTAMP), use a UDF instead.

When to use macros vs variants

MACRO()VARIANT()
PurposeCreate reusable function templatesCreate alternative versions of existing features
MechanismParameterizes a feature expressionReplaces dependencies in an existing DAG
Best forDRY patterns, reusable calculationsExperimentation, A/B testing, what-if analysis

See Variants for dependency replacement and UDFs for polymorphic custom functions.