Skip to content

Cube ML Syntax ​

Cube ML is the template expression language built into EruptCube, based on Velocity syntax. It dynamically generates query logic inside any SQL annotation, applicable to:

dimension.sql() · measure.sql() · EruptCube.sql() · explore.where() etc.

Available Variables ​

KeywordDescriptionExample
queryRequest context: requested dimensions, model name, parameters, etc.${query.limit}
${query.dimensions}
filterCurrent filter values${filter["userName"]}
parameterDynamic model parameters${parameter["xxx"]}
thisReference to the current class; reads other annotations on the same class for derivation and reuse${this.ip.sql()}
userContext of the currently logged-in user.
Structure:
{
uid:"[user id]",
account:"[login name]",
name:"[display name]"
}
${user.name}
tenantIdCurrent tenant ID in multi-tenant setups${tenantId}
UDFExtend the template with registered global functions, see UDF below${cube.add(1, 1)}

Conditionals ​

velocity
#if(${query.parameter["role"]} == "admin")
    -- admin: query everything
    1 = 1
#elseif(${query.parameter["role"]} == "manager")
    -- manager: filter by department
    dept_id = ${filter["deptId"]}
#else
    -- regular user: filter by self
    user_id = '${query.parameter["userId"]}'
#end

Syntax: #if / #elseif / #else / #end; conditions support ==, !=, &&, ||.

Loops ​

velocity
#foreach($item in ${query.parameter["ids"]})
    #if($foreach.index > 0) OR #end
    id = '${item}'
#end

Generated output (ids = ["001", "002", "003"]):

sql
id = '001' OR id = '002' OR id = '003'

Built-in loop variables:

VariableDescription
$foreach.indexCurrent index, starts at 0
$foreach.countCurrent count, starts at 1
$foreach.hasNextWhether there is a next element
$foreach.lastWhether this is the last element

Combined Example ​

Dynamically build an IN clause while restricting data scope by role:

velocity
#if(${query.parameter["role"]} != "admin")
    AND user_id = '${query.parameter["userId"]}'
#end
#if(${query.parameter["ids"]} && !${query.parameter["ids"]}.isEmpty())
    AND order_id IN (
        #foreach($id in ${query.parameter["ids"]})
            '${id}'#if($foreach.hasNext),#end
        #end
    )
#end

UDF (User-Defined Functions) ​

Register global functions with @CubeFunction to extend the semantic model and make SQL more dynamic.

java
@Component
@CubeFunction(space = "cube")
public class CubeDefaultFunction {

    public int add(int a, int b) {
        return a + b;
    }

}

Usage:

java
// once defined, the function can be called from dimensions, measures, or any SQL
@Measure(title = "Max", sql = "concat(max(ip) ,' - ', ${cube.add(100,200)})")
private String max;

// ---> 127.0.0.0 - 300

Data Proxy (CubeProxy) ​

Implement the CubeProxy interface to inject custom logic before and after each query (row-level security, tenant isolation, result post-processing, ...).

java
public class MyAnalysisCubeProxy implements CubeProxy {

    /**
     * Before query: rewrite the query expression dynamically
     * @param expr    current query expression
     * @param context request context (includes user info)
     */
    @Override
    public String beforeQuery(String expr, Map<String, Object> context) {
        // e.g. append data-permission filters based on the current user
        return expr;
    }

    /**
     * After query: post-process the result set
     * @param result  result rows
     * @param context request context
     */
    @Override
    public void afterQuery(List<CubeResultRow> result, Map<String, Object> context) {
        // e.g. compute period-over-period metrics, add derived fields
    }
}

Register the proxy:

java
@EruptCube(
    name      = "Sales Analysis",
    sql       = "sales_order",
    sqlType   = SqlType.TABLE_NAME,
    dataProxy = { MyAnalysisCubeProxy.class }
)
public class SalesCube { ... }

Contributors

The avatar of contributor named as YuePeng YuePeng
The avatar of contributor named as Claude Fable 5.1 Claude Fable 5.1

Changelog

Released under the Apache-2.0 License.