一键导入
dc-cube-definition
Create and configure Drizzle Cube semantic layer cube definitions with proper security context, measures, dimensions, and joins.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Create and configure Drizzle Cube semantic layer cube definitions with proper security context, measures, dimensions, and joins.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Build semantic queries with measures, dimensions, filters, and time dimensions for Drizzle Cube.
Create and configure AnalysisConfig objects for query, funnel, and flow analysis modes in Drizzle Cube dashboards.
Configure chart axis mappings and display options for all chart types in Drizzle Cube.
Create and configure dashboards with portlets (charts/widgets) using DashboardConfig in Drizzle Cube.
| name | dc-cube-definition |
| description | Create and configure Drizzle Cube semantic layer cube definitions with proper security context, measures, dimensions, and joins. |
This skill helps you create properly structured cube definitions for Drizzle Cube's semantic layer.
Drizzle Cube is Drizzle ORM-first. All cubes reference Drizzle schema columns directly for compile-time validation and SQL injection protection.
import { defineCube } from 'drizzle-cube/server'
import { eq } from 'drizzle-orm'
import { employees } from './schema'
export const employeesCube = defineCube({
name: 'Employees',
// REQUIRED: Security context filter for multi-tenant isolation
sql: (securityContext) => eq(employees.organisationId, securityContext.organisationId),
measures: {
count: {
type: 'count',
sql: () => employees.id
},
totalSalary: {
type: 'sum',
sql: () => employees.salary
},
averageSalary: {
type: 'avg',
sql: () => employees.salary
}
},
dimensions: {
id: {
type: 'number',
sql: () => employees.id,
primaryKey: true
},
name: {
type: 'string',
sql: () => employees.name
},
email: {
type: 'string',
sql: () => employees.email
},
createdAt: {
type: 'time',
sql: () => employees.createdAt
}
}
})
Every cube MUST implement security filtering. This is mandatory for multi-tenant data isolation.
// REQUIRED pattern - filter by security context
sql: (securityContext) => eq(table.organisationId, securityContext.organisationId)
// For multiple conditions
sql: (securityContext) => and(
eq(table.organisationId, securityContext.organisationId),
eq(table.isDeleted, false)
)
The security context is passed to every query execution:
const result = await semanticLayer.execute(query, {
organisationId: 'org-123',
userId: 'user-456'
})
| Type | Description | Example |
|---|---|---|
count | Count rows | { type: 'count', sql: () => table.id } |
countDistinct | Count unique values | { type: 'countDistinct', sql: () => table.userId } |
sum | Sum numeric values | { type: 'sum', sql: () => table.amount } |
avg | Average numeric values | { type: 'avg', sql: () => table.price } |
min | Minimum value | { type: 'min', sql: () => table.date } |
max | Maximum value | { type: 'max', sql: () => table.score } |
Apply filters within measures:
measures: {
activeCount: {
type: 'count',
sql: () => employees.id,
filters: [{ sql: () => eq(employees.isActive, true) }]
},
highValueOrders: {
type: 'sum',
sql: () => orders.amount,
filters: [{ sql: () => gt(orders.amount, 1000) }]
}
}
| Type | Description | Example |
|---|---|---|
string | Text values | { type: 'string', sql: () => table.name } |
number | Numeric values | { type: 'number', sql: () => table.quantity } |
boolean | True/false values | { type: 'boolean', sql: () => table.isActive } |
time | Date/timestamp values | { type: 'time', sql: () => table.createdAt } |
Mark the primary key for proper aggregations:
dimensions: {
id: {
type: 'number',
sql: () => table.id,
primaryKey: true // Important for multi-cube queries
}
}
| Type | Description | SQL Join |
|---|---|---|
belongsTo | Many-to-one | INNER JOIN |
hasOne | One-to-one | LEFT JOIN |
hasMany | One-to-many | LEFT JOIN (with CTE) |
belongsToMany | Many-to-many | LEFT JOIN (through junction) |
export const employeesCube = defineCube({
name: 'Employees',
sql: (ctx) => eq(employees.organisationId, ctx.organisationId),
joins: {
Departments: {
targetCube: () => departmentsCube,
relationship: 'belongsTo',
on: [
{ source: employees.departmentId, target: departments.id }
]
}
},
measures: { /* ... */ },
dimensions: { /* ... */ }
})
export const departmentsCube = defineCube({
name: 'Departments',
sql: (ctx) => eq(departments.organisationId, ctx.organisationId),
joins: {
Employees: {
targetCube: () => employeesCube,
relationship: 'hasMany',
on: [
{ source: departments.id, target: employees.departmentId }
]
}
},
measures: { /* ... */ },
dimensions: { /* ... */ }
})
Use when relating through a junction table:
export const employeesCube = defineCube({
name: 'Employees',
sql: (ctx) => eq(employees.organisationId, ctx.organisationId),
joins: {
Projects: {
targetCube: () => projectsCube,
relationship: 'belongsToMany',
on: [], // Not used for belongsToMany
through: {
table: employeeProjects, // Junction table
sourceKey: [
{ source: employees.id, target: employeeProjects.employeeId }
],
targetKey: [
{ source: employeeProjects.projectId, target: projects.id }
],
// Optional: Security filter for junction table
securitySql: (securityContext) =>
eq(employeeProjects.organisationId, securityContext.organisationId)
}
}
}
})
For fact-dimension-fact joins, the dimension cube MUST define hasMany relationships back to all fact cubes:
// Dimension cube - MUST define hasMany to both facts
export const productsCube = defineCube({
name: 'Products',
sql: (ctx) => eq(products.organisationId, ctx.organisationId),
joins: {
Sales: {
targetCube: () => salesCube,
relationship: 'hasMany',
on: [{ source: products.id, target: sales.productId }]
},
Inventory: {
targetCube: () => inventoryCube,
relationship: 'hasMany',
on: [{ source: products.id, target: inventory.productId }]
}
},
dimensions: {
name: { type: 'string', sql: () => products.name },
category: { type: 'string', sql: () => products.category }
}
})
// Fact cube #1
export const salesCube = defineCube({
name: 'Sales',
sql: (ctx) => eq(sales.organisationId, ctx.organisationId),
joins: {
Products: {
targetCube: () => productsCube,
relationship: 'belongsTo',
on: [{ source: sales.productId, target: products.id }]
}
},
measures: {
totalRevenue: { type: 'sum', sql: () => sales.revenue }
}
})
// Fact cube #2
export const inventoryCube = defineCube({
name: 'Inventory',
sql: (ctx) => eq(inventory.organisationId, ctx.organisationId),
joins: {
Products: {
targetCube: () => productsCube,
relationship: 'belongsTo',
on: [{ source: inventory.productId, target: products.id }]
}
},
measures: {
totalStock: { type: 'sum', sql: () => inventory.stockLevel }
}
})
import { SemanticLayerCompiler } from 'drizzle-cube/server'
const semanticLayer = new SemanticLayerCompiler({
drizzle: db,
schema: schema
})
// Register cubes
semanticLayer.registerCube(employeesCube)
semanticLayer.registerCube(departmentsCube)
// Execute queries
const result = await semanticLayer.execute({
measures: ['Employees.count'],
dimensions: ['Departments.name']
}, securityContext)
dimensions: {
fullName: {
type: 'string',
sql: () => sql`${employees.firstName} || ' ' || ${employees.lastName}`
}
}
dimensions: {
createdYear: {
type: 'number',
sql: () => sql`EXTRACT(YEAR FROM ${orders.createdAt})`
}
}
measures: {
completedOrders: {
type: 'count',
sql: () => orders.id,
filters: [{ sql: () => eq(orders.status, 'completed') }]
}
}
sql functionsecuritySql in belongsToMany relationshipsCubes work identically across:
The database type is auto-detected from your Drizzle instance.