Migrating from Cube
This guide walks you through migrating an existing Cube.js project to Olytix Core. The migration adds data transformation capabilities while preserving your semantic layer definitions.
Migration Overview
| Aspect | Effort Level | Notes |
|---|---|---|
| Cubes | Medium | Schema translation required |
| Measures | Low | Direct mapping |
| Dimensions | Low | Direct mapping |
| Joins | Low | Similar syntax |
| Pre-aggregations | Low | Nearly identical |
| JavaScript logic | Medium | Convert to SQL/YAML |
Key Differences
| Feature | Cube | Olytix Core |
|---|---|---|
| Configuration | JavaScript/TypeScript | YAML |
| Transformations | None (external) | Built-in (dbt-compatible) |
| Dynamic schemas | JavaScript functions | Jinja templates |
| Lineage | None | Column-level |
| File extension | .js / .ts | .yml |
Before You Begin
Prerequisites
- Python 3.11+
- Olytix Core installed (
pip install olytix-core) - Existing Cube project
- Database credentials
Backup Your Project
cp -r my_cube_project my_cube_project_backup
Step 1: Initialize Olytix Core Project
Create a new Olytix Core project alongside your Cube project:
mkdir my_olytix-core_project
cd my_olytix-core_project
olytix-core init
Project structure:
my_olytix-core_project/
├── olytix-core_project.yml
├── sources/
├── models/
├── cubes/
├── metrics/
└── target/
Step 2: Migrate Database Configuration
Cube Configuration
cube.js:
module.exports = {
dbType: 'postgres',
driverFactory: () => new PostgresDriver({
host: process.env.CUBEJS_DB_HOST,
database: process.env.CUBEJS_DB_NAME,
user: process.env.CUBEJS_DB_USER,
password: process.env.CUBEJS_DB_PASS,
}),
};
Olytix Core Configuration
olytix-core_project.yml:
name: my_analytics
version: 1.0.0
warehouse:
type: postgresql
host: ${OLYTIX_DB_HOST}
port: 5432
database: ${OLYTIX_DB_NAME}
user: ${OLYTIX_DB_USER}
password: ${OLYTIX_DB_PASSWORD}
Set environment variables:
export OLYTIX_DB_HOST=localhost
export OLYTIX_DB_NAME=analytics
export OLYTIX_DB_USER=user
export OLYTIX_DB_PASSWORD=password
Step 3: Migrate Cube Schemas
Basic Cube
Cube (JavaScript):
// schema/Orders.js
cube('Orders', {
sql: `SELECT * FROM public.orders`,
measures: {
count: {
type: 'count',
},
totalRevenue: {
type: 'sum',
sql: 'amount',
format: 'currency',
},
averageOrderValue: {
type: 'avg',
sql: 'amount',
format: 'currency',
},
},
dimensions: {
id: {
type: 'number',
sql: 'id',
primaryKey: true,
},
status: {
type: 'string',
sql: 'status',
},
createdAt: {
type: 'time',
sql: 'created_at',
},
},
});
Olytix Core (YAML):
# cubes/orders.yml
cubes:
- name: orders
sql: "SELECT * FROM public.orders"
measures:
- name: count
type: count
- name: total_revenue
type: sum
sql: amount
format: currency
- name: average_order_value
type: avg
sql: amount
format: currency
dimensions:
- name: id
type: number
sql: id
primary_key: true
- name: status
type: string
sql: status
- name: created_at
type: time
sql: created_at
Naming Convention Changes
| Cube (camelCase) | Olytix Core (snake_case) |
|---|---|
totalRevenue | total_revenue |
orderCount | order_count |
createdAt | created_at |
primaryKey | primary_key |
Step 4: Migrate Joins
Cube Joins
Cube (JavaScript):
cube('Orders', {
sql: `SELECT * FROM public.orders`,
joins: {
Customers: {
relationship: 'belongsTo',
sql: `${CUBE}.customer_id = ${Customers}.id`,
},
Products: {
relationship: 'belongsTo',
sql: `${CUBE}.product_id = ${Products}.id`,
},
},
// measures and dimensions...
});
Olytix Core (YAML):
# cubes/orders.yml
cubes:
- name: orders
sql: "SELECT * FROM public.orders"
joins:
- name: customers
relationship: many_to_one # 'belongsTo' -> 'many_to_one'
sql: "{orders}.customer_id = {customers}.id"
- name: products
relationship: many_to_one
sql: "{orders}.product_id = {products}.id"
# measures and dimensions...
Relationship Mapping
| Cube Relationship | Olytix Core Relationship |
|---|---|
belongsTo | many_to_one |
hasMany | one_to_many |
hasOne | one_to_one |
Reference Syntax
| Cube | Olytix Core |
|---|---|
${CUBE} | {cube_name} |
${CubeName} | {cube_name} |
${CubeName.column} | {cube_name}.column |
Step 5: Migrate Measures
Standard Measures
Cube:
measures: {
count: {
type: 'count',
},
totalAmount: {
type: 'sum',
sql: 'amount',
},
uniqueCustomers: {
type: 'countDistinct',
sql: 'customer_id',
},
}
Olytix Core:
measures:
- name: count
type: count
- name: total_amount
type: sum
sql: amount
- name: unique_customers
type: count_distinct
sql: customer_id
Calculated Measures
Cube:
measures: {
averageOrderValue: {
type: 'number',
sql: `${totalAmount} / NULLIF(${count}, 0)`,
},
}
Olytix Core:
measures:
- name: average_order_value
type: number
sql: "{total_amount} / NULLIF({count}, 0)"
Measure Type Mapping
| Cube Type | Olytix Core Type |
|---|---|
count | count |
countDistinct | count_distinct |
countDistinctApprox | count_distinct_approx |
sum | sum |
avg | avg |
min | min |
max | max |
number | number |
runningTotal | running_total |
Step 6: Migrate Dimensions
Standard Dimensions
Cube:
dimensions: {
id: {
type: 'number',
sql: 'id',
primaryKey: true,
shown: false,
},
name: {
type: 'string',
sql: 'name',
},
createdAt: {
type: 'time',
sql: 'created_at',
},
}
Olytix Core:
dimensions:
- name: id
type: number
sql: id
primary_key: true
hidden: true # 'shown: false' -> 'hidden: true'
- name: name
type: string
sql: name
- name: created_at
type: time
sql: created_at
Dimension with Granularities
Cube:
dimensions: {
createdAt: {
type: 'time',
sql: 'created_at',
},
}
// Cube auto-generates granularities
Olytix Core:
dimensions:
- name: created_at
type: time
sql: created_at
granularities: # Explicitly define granularities
- second
- minute
- hour
- day
- week
- month
- quarter
- year
Calculated Dimensions
Cube:
dimensions: {
fullName: {
type: 'string',
sql: `${CUBE}.first_name || ' ' || ${CUBE}.last_name`,
},
}
Olytix Core:
dimensions:
- name: full_name
type: string
sql: "first_name || ' ' || last_name"
Step 7: Migrate Pre-Aggregations
Basic Pre-Aggregation
Cube:
preAggregations: {
ordersRollup: {
measures: [count, totalRevenue],
dimensions: [status],
timeDimension: createdAt,
granularity: 'day',
refreshKey: {
every: '1 hour',
},
},
}
Olytix Core:
pre_aggregations:
- name: orders_rollup
measures:
- count
- total_revenue
dimensions:
- status
time_dimension: created_at
granularity: day
refresh:
every: "1 hour"
Pre-Aggregation with Partitioning
Cube:
preAggregations: {
ordersRollup: {
measures: [count, totalRevenue],
timeDimension: createdAt,
granularity: 'day',
partitionGranularity: 'month',
},
}
Olytix Core:
pre_aggregations:
- name: orders_rollup
measures:
- count
- total_revenue
time_dimension: created_at
granularity: day
partition_granularity: month
Step 8: Migrate Segments
Cube:
segments: {
completedOrders: {
sql: `${CUBE}.status = 'completed'`,
},
premiumCustomers: {
sql: `${Customers}.tier = 'premium'`,
},
}
Olytix Core:
segments:
- name: completed_orders
sql: "{orders}.status = 'completed'"
- name: premium_customers
sql: "{customers}.tier = 'premium'"
Step 9: Handle Dynamic Schemas
Cube uses JavaScript for dynamic schemas. Olytix Core uses Jinja templates.
Cube with JavaScript Logic
Cube:
const tables = ['orders', 'returns', 'exchanges'];
tables.forEach(table => {
cube(table.charAt(0).toUpperCase() + table.slice(1), {
sql: `SELECT * FROM ${table}`,
measures: {
count: { type: 'count' },
},
});
});
Olytix Core with Jinja
Olytix Core:
# cubes/transactions.yml
{% set tables = ['orders', 'returns', 'exchanges'] %}
cubes:
{% for table in tables %}
- name: {{ table }}
sql: "SELECT * FROM {{ table }}"
measures:
- name: count
type: count
{% endfor %}
Environment-Based Configuration
Cube:
cube('Orders', {
sql: process.env.NODE_ENV === 'production'
? `SELECT * FROM prod.orders`
: `SELECT * FROM staging.orders`,
// ...
});
Olytix Core:
cubes:
- name: orders
sql: |
SELECT * FROM
{% if var('environment') == 'production' %}
prod.orders
{% else %}
staging.orders
{% endif %}
Step 10: Add Transformation Layer (New Capability)
One of Olytix Core's key advantages is the built-in transformation layer. Create models to transform your data:
Define Sources
# sources/raw.yml
sources:
- name: raw
database: analytics
schema: public
tables:
- name: orders
columns:
- name: id
- name: customer_id
- name: amount
- name: created_at
Create Transformation Models
-- models/staging/stg_orders.sql
{{ config(materialized='view') }}
SELECT
id AS order_id,
customer_id,
amount,
created_at AS order_date,
CASE
WHEN amount > 1000 THEN 'high_value'
WHEN amount > 100 THEN 'medium_value'
ELSE 'low_value'
END AS order_tier
FROM {{ source('raw', 'orders') }}
WHERE amount > 0
-- models/marts/fct_orders.sql
{{ config(materialized='table') }}
SELECT
o.order_id,
o.customer_id,
o.amount,
o.order_date,
o.order_tier,
c.country,
c.segment
FROM {{ ref('stg_orders') }} o
LEFT JOIN {{ ref('dim_customers') }} c
ON o.customer_id = c.customer_id
Update Cubes to Reference Models
# cubes/orders.yml
cubes:
- name: orders
sql: "SELECT * FROM {{ ref('fct_orders') }}" # Now references the model
# ... measures, dimensions, joins
Step 11: Create Metrics (New Capability)
Olytix Core adds a metrics layer on top of cubes:
# metrics/revenue.yml
metrics:
- name: monthly_revenue
type: simple
expression: orders.total_revenue
time_grain: month
description: "Total revenue by month"
- name: revenue_per_customer
type: derived
expression: "{orders.total_revenue} / {customers.count}"
description: "Average revenue per customer"
- name: conversion_rate
type: ratio
numerator: orders.count
denominator: sessions.count
description: "Order conversion rate"
Step 12: Validate and Test
Compile the Project
olytix-core compile
Compare Query Results
Test the same queries against both systems:
Cube query:
curl -X POST http://localhost:4000/cubejs-api/v1/load \
-H "Content-Type: application/json" \
-d '{
"measures": ["Orders.count", "Orders.totalRevenue"],
"dimensions": ["Orders.status"],
"timeDimensions": [{
"dimension": "Orders.createdAt",
"granularity": "month"
}]
}'
Olytix Core query:
curl -X POST http://localhost:8000/api/v1/query \
-H "Content-Type: application/json" \
-d '{
"measures": ["orders.count", "orders.total_revenue"],
"dimensions": ["orders.status", "orders.created_at.month"]
}'
Verify the results match.
Migration Checklist
- Initialize Olytix Core project
- Migrate database configuration
- Convert cube schemas from JS to YAML
- Migrate all measures
- Migrate all dimensions
- Migrate joins and relationships
- Migrate pre-aggregations
- Migrate segments
- Convert dynamic schemas to Jinja
- Add source definitions
- Create transformation models (optional)
- Create business metrics
- Validate query results match
- Test pre-aggregation performance
Query API Mapping
| Cube API | Olytix Core API |
|---|---|
/cubejs-api/v1/load | /api/v1/query |
/cubejs-api/v1/sql | /api/v1/query?include_sql=true |
/cubejs-api/v1/meta | /api/v1/meta |
Query Format Changes
Cube:
{
"measures": ["Orders.count"],
"dimensions": ["Orders.status"],
"timeDimensions": [{
"dimension": "Orders.createdAt",
"granularity": "month"
}],
"filters": [{
"dimension": "Orders.status",
"operator": "equals",
"values": ["completed"]
}]
}
Olytix Core:
{
"measures": ["orders.count"],
"dimensions": ["orders.status", "orders.created_at.month"],
"filters": [{
"dimension": "orders.status",
"operator": "equals",
"value": "completed"
}]
}
Common Migration Issues
JavaScript Functions Not Supported
Convert JavaScript functions to SQL or Jinja:
Cube:
sql: (CUBE, SECURITY_CONTEXT) =>
`SELECT * FROM orders WHERE tenant_id = '${SECURITY_CONTEXT.tenantId}'`
Olytix Core:
sql: |
SELECT * FROM orders
WHERE tenant_id = '{{ var('tenant_id') }}'
Context Variables
| Cube | Olytix Core |
|---|---|
SECURITY_CONTEXT.userId | {{ security.user_id }} |
SECURITY_CONTEXT.tenantId | {{ security.tenant_id }} |
COMPILE_CONTEXT | {{ var('...') }} |
Data Source Extensions
Cube's data source extensions need custom adapters in Olytix Core. Contact support for guidance on specific integrations.
Next Steps
After migration:
- Explore column-level lineage - a new capability
- Add more transformation models
- Configure row-level security
- Connect Power BI via DAX - Olytix Core exclusive
Need help? Check the Troubleshooting guide or FAQ.