Skip to main content

Migrating from Cube

For Data Analysts

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

AspectEffort LevelNotes
CubesMediumSchema translation required
MeasuresLowDirect mapping
DimensionsLowDirect mapping
JoinsLowSimilar syntax
Pre-aggregationsLowNearly identical
JavaScript logicMediumConvert to SQL/YAML

Key Differences

FeatureCubeOlytix Core
ConfigurationJavaScript/TypeScriptYAML
TransformationsNone (external)Built-in (dbt-compatible)
Dynamic schemasJavaScript functionsJinja templates
LineageNoneColumn-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)
totalRevenuetotal_revenue
orderCountorder_count
createdAtcreated_at
primaryKeyprimary_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 RelationshipOlytix Core Relationship
belongsTomany_to_one
hasManyone_to_many
hasOneone_to_one

Reference Syntax

CubeOlytix 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 TypeOlytix Core Type
countcount
countDistinctcount_distinct
countDistinctApproxcount_distinct_approx
sumsum
avgavg
minmin
maxmax
numbernumber
runningTotalrunning_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 APIOlytix 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

CubeOlytix 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:

  1. Explore column-level lineage - a new capability
  2. Add more transformation models
  3. Configure row-level security
  4. Connect Power BI via DAX - Olytix Core exclusive

Need help? Check the Troubleshooting guide or FAQ.