Execution Environment
Cube Data Model Compiler uses Node.js VM to execute data model compiler code. It gives required flexibility allowing transpiling data model files before they get executed, storing data models in external databases and executing untrusted code in a safe manner. Cube data model JavaScript is standard JavaScript supported by Node.js starting in version 8 with the following exceptions.
Being executed in VM, data model JavaScript code doesn't have access to Node.js
require directly. Instead require() is implemented by Data
Model Compiler to provide access to other data model files and to regular
Node.js modules. Besides that, the data model require() can resolve Cube
packages such as Funnels unlike standard Node.js require().
Data model JavaScript code doesn't have access to any standard Node.js globals
like process or console. In order to access process.env, utility functions
can be added outside the model/ directory:
tablePrefix.js:
exports.tableSchema = () => process.env.TABLE_SCHEMA;model/cubes/Users.js:
import { tableSchema } from "../tablePrefix";
cube(`users`, {
sql_table: `${tableSchema()}.users`,
// ...
});Data models cannot access console.log due to a separate VM
instance that runs it. Suppose you find yourself writing complex
logic for SQL generation that depends on a lot of external input. In that case,
you probably want to introduce a helper service outside of schema directory
that you can debug as usual Node.js code.
Cube defines cube(), context() and asyncModule() global variable functions
in order to provide API for data model configuration which aren't normally
accessible outside of a Cube data model.
Data model JavaScript files are transpiled to convert ES6 import and export
expressions to corresponding Node.js calls. In fact import is routed to
Require method.
export can be used to define named exports as well as default ones:
constants.js:
export const TEST_USER_IDS = [1, 2, 3, 4, 5];usersSql.js:
export default (usersTable) => `select * from ${usersTable}`;Later, you can import into the cube, wherever needed:
Users.js:
// in users.js
import { TEST_USER_IDS } from "./constants";
import usersSql from "./usersSql";
cube(`users`, {
sql: usersSql(`users`),
measures: {
/* ... */
},
dimensions: {
/* ... */
},
segments: {
excludeTestUsers: {
sql: `${CUBE}.id NOT IN (${TEST_USER_IDS.join(", ")})`,
},
},
});Data models can be externally stored and retrieved through an asynchronous
operation using the asyncModule(). For more information, consult the Dynamic
Schema Creation page.
Cube uses a custom transpiler to optimize boilerplate code around referencing
cubes and cube members. There are reserved property names inside cube
definition that undergo reference resolve transpiling process:
sqlmeasuresdimensionssegmentstime_dimensiondrill_memberscontext_members
Each of these properties inside cube and context definitions are transpiled
to functions with resolved arguments. For example:
cube(`users`, {
// ...
measures: {
count: {
type: `count`,
},
ratio: {
sql: `SUM(${CUBE}.amount) / ${count}`,
type: `number`,
},
},
});is transpiled to:
cube(`users`, {
// ...
measures: {
count: {
type: `count`,
},
ratio: {
sql: (CUBE, count) => `SUM(${CUBE}.amount) / ${count}`,
type: `number`,
},
},
});So for example if you want to pass the definition of ratio outside of the
cube, you would define it as:
const measureRatioDefinition = {
sql: (CUBE, count) => `sum(${CUBE}.amount) / ${count}`,
type: `number`,
};
cube(`users`, {
// ...
measures: {
count: {
type: `count`,
},
ratio: measureRatioDefinition,
},
});Did you find this page useful?