drizzle-cube
Version:
Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.
1,423 lines (1,348 loc) • 68.6 kB
TypeScript
import { AnyColumn } from 'drizzle-orm';
import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import { MySql2Database } from 'drizzle-orm/mysql2';
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import { SQL } from 'drizzle-orm';
import { Subquery } from 'drizzle-orm';
import { Table } from 'drizzle-orm';
import { View } from 'drizzle-orm';
/**
* Abstract base class for database executors
*/
export declare abstract class BaseDatabaseExecutor implements DatabaseExecutor {
db: DrizzleDatabase;
schema?: any | undefined;
databaseAdapter: DatabaseAdapter;
constructor(db: DrizzleDatabase, schema?: any | undefined, engineType?: 'postgres' | 'mysql' | 'sqlite' | 'singlestore');
abstract execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>;
abstract getEngineType(): 'postgres' | 'mysql' | 'sqlite' | 'singlestore';
}
/**
* Base query definition that can be extended dynamically
* Returns just the FROM/JOIN/WHERE setup, not a complete SELECT
*/
export declare interface BaseQueryDefinition {
/** Main table to query from */
from: QueryableRelation;
/** Optional joins to other tables */
joins?: Array<{
table: QueryableRelation;
on: SQL;
type?: 'left' | 'right' | 'inner' | 'full';
}>;
/** Base WHERE conditions (typically security context filtering) */
where?: SQL;
}
/**
* Cache configuration options
*/
export declare interface CacheConfig {
/**
* Cache provider implementation
* Required if caching is enabled
*/
provider: CacheProvider;
/**
* Default TTL in milliseconds
* @default 300000 (5 minutes)
*/
defaultTtlMs?: number;
/**
* Prefix for all cache keys
* Useful for multi-environment setups (e.g., 'prod:', 'dev:')
* @default 'drizzle-cube:'
*/
keyPrefix?: string;
/**
* Enable/disable caching globally
* Allows disabling without removing configuration
* @default true
*/
enabled?: boolean;
/**
* Whether to include security context in cache key
* CRITICAL for multi-tenant applications - should almost always be true
* @default true
*/
includeSecurityContext?: boolean;
/**
* Custom function to extract cacheable parts of security context
* Use when security context contains non-serializable values
* @default Uses JSON.stringify on entire security context
*/
securityContextSerializer?: (ctx: SecurityContext) => string;
/**
* Callback for cache errors (get/set failures)
* Cache errors are non-fatal by default - queries still execute
*/
onError?: (error: Error, operation: 'get' | 'set' | 'delete') => void;
/**
* Callback for cache events (hits, misses, sets)
* Useful for monitoring and debugging
*/
onCacheEvent?: (event: CacheEvent) => void;
}
/**
* Metadata about a cached entry
* Used to provide cache information in query responses
*/
export declare interface CacheEntryMetadata {
/** Unix timestamp (ms) when the entry was cached */
cachedAt: number;
/** Original TTL in milliseconds */
ttlMs: number;
/** Remaining TTL in milliseconds */
ttlRemainingMs: number;
}
/**
* Cache event for monitoring and debugging
*/
export declare interface CacheEvent {
/** Type of cache event */
type: 'hit' | 'miss' | 'set' | 'error';
/** Cache key involved */
key: string;
/** Duration of the cache operation in milliseconds */
durationMs: number;
}
/**
* Return type for cache get operations
* Includes value and optional metadata for TTL tracking
*/
export declare interface CacheGetResult<T> {
value: T;
metadata?: CacheEntryMetadata;
}
/**
* Configuration for cache key generation
*/
declare interface CacheKeyConfig {
/** Prefix for all cache keys */
keyPrefix?: string;
/** Whether to include security context in cache key */
includeSecurityContext?: boolean;
/** Custom serializer for security context */
securityContextSerializer?: (ctx: SecurityContext) => string;
}
/**
* Cache provider interface that users implement for their preferred backend
* All methods use async/Promise to support network-based caches (Redis, etc.)
*/
export declare interface CacheProvider {
/**
* Get a cached value by key
* @returns The cached value with metadata, or null/undefined if not found or expired
*/
get<T = unknown>(key: string): Promise<CacheGetResult<T> | null | undefined>;
/**
* Set a value in the cache
* @param key - Cache key
* @param value - Value to cache (must be JSON-serializable)
* @param ttlMs - Time-to-live in milliseconds (optional, uses default if not provided)
*/
set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void>;
/**
* Delete a specific key from the cache
* @returns true if key existed and was deleted, false otherwise
*/
delete(key: string): Promise<boolean>;
/**
* Delete all keys matching a pattern (for cache invalidation)
* Pattern uses glob-style matching: 'prefix:*' matches all keys starting with 'prefix:'
* @returns Number of keys deleted
*/
deletePattern(pattern: string): Promise<number>;
/**
* Check if a key exists in the cache
*/
has(key: string): Promise<boolean>;
/**
* Optional: Called when the cache provider is no longer needed
* Use for cleanup (e.g., closing Redis connections)
*/
close?(): Promise<void>;
}
/**
* Compiled cube with execution function
*/
export declare interface CompiledCube extends Cube {
/** Execute a query against this cube */
queryFn: (query: SemanticQuery, securityContext: SecurityContext) => Promise<QueryResult>;
}
/**
* Auto-detect database type and create appropriate executor
* @param db - Drizzle database instance
* @param schema - Optional schema for type inference
* @param engineType - Optional explicit engine type override
* @returns Appropriate database executor
*/
export declare function createDatabaseExecutor(db: DrizzleDatabase, schema?: any, engineType?: 'postgres' | 'mysql' | 'sqlite' | 'singlestore'): DatabaseExecutor;
/**
* Create a new semantic layer instance with Drizzle integration
* Use this when you need multiple isolated instances
*/
export declare function createDrizzleSemanticLayer(options: {
drizzle: DrizzleDatabase;
schema?: any;
}): SemanticLayerCompiler;
/**
* Helper to create multi-cube query context
*/
export declare function createMultiCubeContext(baseContext: QueryContext, cubes: Map<string, Cube>, currentCube: Cube): MultiCubeQueryContext;
/**
* Factory function for creating MySQL executors
*/
export declare function createMySQLExecutor(db: DrizzleDatabase, schema?: any): MySQLExecutor;
/**
* Factory function for creating PostgreSQL executors
*/
export declare function createPostgresExecutor(db: DrizzleDatabase, schema?: any): PostgresExecutor;
/**
* Factory function for creating SQLite executors
*/
export declare function createSQLiteExecutor(db: DrizzleDatabase, schema?: any): SQLiteExecutor;
/**
* Cube definition focused on Drizzle query building
*/
declare interface Cube {
name: string;
title?: string;
description?: string;
/**
* Base query setup - returns the foundation that can be extended
* Should return FROM/JOIN/WHERE setup, NOT a complete SELECT
*/
sql: (ctx: QueryContext) => BaseQueryDefinition;
/** Cube dimensions using direct column references */
dimensions: Record<string, Dimension>;
/** Cube measures using direct column references */
measures: Record<string, Measure>;
/** Optional joins to other cubes for multi-cube queries */
joins?: Record<string, CubeJoin>;
/** Whether cube is publicly accessible */
public?: boolean;
/** SQL alias for the cube */
sqlAlias?: string;
/** Data source identifier */
dataSource?: string;
/** Additional metadata */
meta?: Record<string, any>;
}
export { Cube }
export { Cube as SemanticCube }
/**
* Helper type for creating type-safe cubes
*/
export declare interface CubeDefiner {
<TName extends string>(name: TName, definition: CubeDefinition): Cube & {
name: TName;
};
}
/**
* Utility type for cube definition with schema inference
*/
export declare type CubeDefinition = Omit<Cube, 'name'> & {
name?: string;
};
/**
* Type-safe cube join definition with lazy loading support
*/
declare interface CubeJoin {
/** Target cube reference - lazy loaded to avoid circular dependencies */
targetCube: Cube | (() => Cube);
/** Semantic relationship - determines join behavior */
relationship: CubeRelationship;
/** Array of join conditions - supports multi-column joins */
on: Array<{
/** Column from source cube */
source: AnyColumn;
/** Column from target cube */
target: AnyColumn;
/** Comparison operator - defaults to eq */
as?: (source: AnyColumn, target: AnyColumn) => SQL;
}>;
/** Override default SQL join type (derived from relationship) */
sqlJoinType?: 'inner' | 'left' | 'right' | 'full';
/**
* Many-to-many relationship configuration through a junction table
* Only used when relationship is 'belongsToMany'
*/
through?: {
/** Junction/join table (Drizzle table reference) */
table: Table;
/** Join conditions from source cube to junction table */
sourceKey: Array<{
source: AnyColumn;
target: AnyColumn;
as?: (source: AnyColumn, target: AnyColumn) => SQL;
}>;
/** Join conditions from junction table to target cube */
targetKey: Array<{
source: AnyColumn;
target: AnyColumn;
as?: (source: AnyColumn, target: AnyColumn) => SQL;
}>;
/** Optional security context SQL for junction table */
securitySql?: (securityContext: SecurityContext) => SQL | SQL[];
};
}
export { CubeJoin }
export { CubeJoin as SemanticJoin }
/**
* Cube metadata for API responses
*/
export declare interface CubeMetadata {
name: string;
title: string;
description?: string;
measures: MeasureMetadata[];
dimensions: DimensionMetadata[];
segments: any[];
relationships?: CubeRelationshipMetadata[];
}
/**
* Relationship types supported by cube joins
*/
export declare type CubeRelationship = 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany';
/**
* Cube relationship metadata for ERD visualization
*/
export declare interface CubeRelationshipMetadata {
targetCube: string;
relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany';
joinFields: Array<{
sourceField: string;
targetField: string;
}>;
}
declare interface DatabaseAdapter {
/**
* Get the database engine type this adapter supports
*/
getEngineType(): 'postgres' | 'mysql' | 'sqlite' | 'singlestore';
/**
* Build time dimension expression with granularity truncation
* @param granularity - Time granularity (day, month, year, etc.)
* @param fieldExpr - The date/timestamp field expression
* @returns SQL expression for truncated time dimension
*/
buildTimeDimension(granularity: TimeGranularity, fieldExpr: AnyColumn | SQL): SQL;
/**
* Build string matching condition
* @param fieldExpr - The field to search in
* @param operator - The string matching operator
* @param value - The value to match
* @returns SQL expression for string matching
*/
buildStringCondition(fieldExpr: AnyColumn | SQL, operator: 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'like' | 'notLike' | 'ilike' | 'regex' | 'notRegex', value: string): SQL;
/**
* Cast expression to specific database type
* @param fieldExpr - The field expression to cast
* @param targetType - Target database type
* @returns SQL expression with type casting
*/
castToType(fieldExpr: AnyColumn | SQL, targetType: 'timestamp' | 'decimal' | 'integer'): SQL;
/**
* Build AVG aggregation expression with database-specific null handling
* @param fieldExpr - The field expression to average
* @returns SQL expression for AVG aggregation (COALESCE vs IFNULL for null handling)
*/
buildAvg(fieldExpr: AnyColumn | SQL): SQL;
/**
* Build CASE WHEN conditional expression
* @param conditions - Array of condition/result pairs
* @param elseValue - Optional ELSE clause value
* @returns SQL expression for CASE WHEN statement
*/
buildCaseWhen(conditions: Array<{
when: SQL;
then: any;
}>, elseValue?: any): SQL;
/**
* Build boolean literal expression
* @param value - Boolean value to represent
* @returns SQL expression for boolean literal (TRUE/FALSE/1/0 depending on database)
*/
buildBooleanLiteral(value: boolean): SQL;
/**
* Convert filter values to database-compatible types
* @param value - The filter value to convert
* @returns Converted value for database queries
*/
convertFilterValue(value: any): any;
/**
* Prepare date value for database-specific storage format
* @param date - Date value to prepare
* @returns Database-compatible date representation
*/
prepareDateValue(date: Date): any;
/**
* Check if this database stores timestamps as integers
* @returns True if timestamps are stored as integers (milliseconds), false for native timestamps
*/
isTimestampInteger(): boolean;
/**
* Convert time dimension result values back to Date objects for consistency
* @param value - The time dimension value from query results
* @returns Date object or original value if not a time dimension
*/
convertTimeDimensionResult(value: any): any;
/**
* Preprocess calculated measure template for database-specific transformations
* This allows each adapter to modify the template before substitution occurs
* @param calculatedSql - The template string with {member} references
* @returns Preprocessed template string
*/
preprocessCalculatedTemplate(calculatedSql: string): string;
/**
* Get database capabilities for feature detection
* Used for graceful degradation when functions aren't supported
*/
getCapabilities(): DatabaseCapabilities;
/**
* Build STDDEV aggregation expression
* @param fieldExpr - The field expression to calculate stddev for
* @param useSample - Use sample stddev (STDDEV_SAMP) vs population (STDDEV_POP). Default: false
* @returns SQL expression or null if unsupported
*/
buildStddev(fieldExpr: AnyColumn | SQL, useSample?: boolean): SQL | null;
/**
* Build VARIANCE aggregation expression
* @param fieldExpr - The field expression to calculate variance for
* @param useSample - Use sample variance (VAR_SAMP) vs population (VAR_POP). Default: false
* @returns SQL expression or null if unsupported
*/
buildVariance(fieldExpr: AnyColumn | SQL, useSample?: boolean): SQL | null;
/**
* Build PERCENTILE aggregation expression
* @param fieldExpr - The field expression to calculate percentile for
* @param percentile - Percentile value (0-100)
* @returns SQL expression or null if unsupported
*/
buildPercentile(fieldExpr: AnyColumn | SQL, percentile: number): SQL | null;
/**
* Build a window function expression
* @param type - Window function type (lag, lead, rank, etc.)
* @param fieldExpr - The field expression (null for rank functions that don't need a field)
* @param partitionBy - PARTITION BY columns
* @param orderBy - ORDER BY columns with direction
* @param config - Additional configuration (offset, default, frame, etc.)
* @returns SQL expression or null if unsupported
*/
buildWindowFunction(type: WindowFunctionType, fieldExpr: AnyColumn | SQL | null, partitionBy?: (AnyColumn | SQL)[], orderBy?: Array<{
field: AnyColumn | SQL;
direction: 'asc' | 'desc';
}>, config?: WindowFunctionConfig): SQL | null;
}
/**
* Database capabilities for feature detection
* Used for graceful degradation when functions aren't supported
*/
declare interface DatabaseCapabilities {
/** Whether the database supports STDDEV_POP/STDDEV_SAMP */
supportsStddev: boolean;
/** Whether the database supports VAR_POP/VAR_SAMP */
supportsVariance: boolean;
/** Whether the database supports PERCENTILE_CONT or similar */
supportsPercentile: boolean;
/** Whether the database supports window functions (LAG, LEAD, RANK, etc.) */
supportsWindowFunctions: boolean;
/** Whether the database supports frame clauses (ROWS BETWEEN, RANGE BETWEEN) */
supportsFrameClause: boolean;
}
/**
* Database executor interface that wraps Drizzle ORM
* Provides type-safe SQL execution with engine-specific implementations
*/
export declare interface DatabaseExecutor {
/** The Drizzle database instance */
db: DrizzleDatabase;
/** Optional schema for type inference */
schema?: any;
/** Database adapter for SQL dialect-specific operations */
databaseAdapter: DatabaseAdapter;
/** Execute a Drizzle SQL query or query object */
execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>;
/** Get the database engine type */
getEngineType(): 'postgres' | 'mysql' | 'sqlite' | 'singlestore';
}
/**
* Helper function to create cubes
*/
export declare function defineCube(name: string, definition: Omit<Cube, 'name'>): Cube;
/**
* Dimension definition
*/
declare interface Dimension {
name: string;
title?: string;
description?: string;
type: DimensionType;
/** Direct column reference or SQL expression */
sql: AnyColumn | SQL | ((ctx: QueryContext) => AnyColumn | SQL);
/** Whether this is a primary key */
primaryKey?: boolean;
/** Whether to show in UI */
shown?: boolean;
/** Display format */
format?: string;
/** Additional metadata */
meta?: Record<string, any>;
}
export { Dimension }
export { Dimension as SemanticDimension }
export declare interface DimensionAnnotation {
title: string;
shortTitle: string;
type: string;
format?: DimensionFormat;
}
export declare type DimensionFormat = 'currency' | 'percent' | 'number' | 'date' | 'datetime' | 'id' | 'link';
/**
* Dimension metadata for API responses
*/
export declare interface DimensionMetadata {
name: string;
title: string;
shortTitle: string;
type: string;
format?: DimensionFormat;
description?: string;
}
export declare type DimensionType = 'string' | 'number' | 'time' | 'boolean';
/**
* Drizzle column reference type
* Use native Drizzle AnyColumn type for better type safety
*/
export declare type DrizzleColumn = AnyColumn;
/**
* Core database interface that supports all Drizzle instances
* Uses flexible typing for maximum compatibility across different database drivers
*/
export declare interface DrizzleDatabase {
select: (fields?: any) => any;
insert: (table: any) => any;
update: (table: any) => any;
delete: (table: any) => any;
execute?: (query: SQL) => Promise<unknown[]>;
run?: (query: SQL) => unknown;
all?: (query: SQL) => unknown[];
get?: (query: SQL) => unknown;
$with: (alias: string) => {
as: (query: any) => any;
};
with: (...args: any[]) => any;
schema?: unknown;
}
/**
* Type-level utility to extract the schema type from a cube reference
* Since we removed generics, this now returns 'any'
*/
export declare type ExtractSchemaFromCubeRef = any;
/**
* Filter definitions with logical operators
*/
export declare type Filter = FilterCondition | LogicalFilter;
/**
* Manages filter SQL caching for a single query execution
*
* Design principles:
* - Immutable SQL: Once cached, SQL objects are never modified
* - Cache lifetime: Created at query start, discarded after query execution
* - Key by content: Filters with same member/operator/values share SQL
* - Thread-safe: No shared mutable state between queries
*/
declare class FilterCacheManager {
private cache;
private stats;
/**
* Get cached SQL or build and cache it
*
* @param key - The cache key (use getFilterKey or getTimeDimensionFilterKey)
* @param builder - Function to build the SQL if not cached
* @returns The cached or freshly built SQL, or null if builder returns null
*/
getOrBuild(key: string, builder: () => SQL | null): SQL | null;
/**
* Check if a key exists in the cache without affecting stats
*/
has(key: string): boolean;
/**
* Get cached SQL without building (returns undefined if not cached)
*/
get(key: string): SQL | undefined;
/**
* Pre-populate cache with multiple filters
* Useful for batch initialization at query start
*/
preload(entries: Array<{
key: string;
sql: SQL;
}>): void;
/**
* Store a single entry in the cache
*/
set(key: string, sql: SQL): void;
/**
* Get cache statistics for debugging
*/
getStats(): FilterCacheStats;
/**
* Clear the cache (normally not needed as cache is per-query)
*/
clear(): void;
}
/**
* Cache statistics for debugging and monitoring
*/
declare interface FilterCacheStats {
hits: number;
misses: number;
cacheSize: number;
}
export declare interface FilterCondition {
member: string;
operator: FilterOperator;
values: any[];
dateRange?: string | string[];
}
/**
* Supported filter operators
*/
export declare type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'startsWith' | 'notStartsWith' | 'endsWith' | 'notEndsWith' | 'gt' | 'gte' | 'lt' | 'lte' | 'set' | 'notSet' | 'inDateRange' | 'beforeDate' | 'afterDate' | 'between' | 'notBetween' | 'in' | 'notIn' | 'like' | 'notLike' | 'ilike' | 'regex' | 'notRegex' | 'isEmpty' | 'isNotEmpty' | 'arrayContains' | 'arrayOverlaps' | 'arrayContained';
/**
* FNV-1a hash - fast, non-cryptographic hash function
* Returns hex string for cache key readability
*
* Properties:
* - O(n) time complexity
* - Low collision rate for similar strings
* - Deterministic across runs
*
* @param str - String to hash
* @returns 8-character hex string
*/
export declare function fnv1aHash(str: string): string;
/**
* Generate a deterministic cache key from query and security context
*
* Key structure: {prefix}query:{queryHash}:ctx:{securityHash}
*
* Uses FNV-1a hash for:
* - Speed: ~3x faster than SHA-256
* - Simplicity: No dependencies required
* - Sufficient collision resistance for cache keys
*
* @param query - The semantic query to cache
* @param securityContext - Security context for tenant isolation
* @param config - Cache key configuration
* @returns Deterministic cache key string
*/
export declare function generateCacheKey(query: SemanticQuery, securityContext: SecurityContext, config?: CacheKeyConfig): string;
/**
* Generate invalidation pattern for a cube
* Used when cube data changes and all related cache entries need clearing
*
* @param cubeName - Name of the cube to invalidate
* @param keyPrefix - Cache key prefix
* @returns Glob pattern for cache invalidation
*/
export declare function getCubeInvalidationPattern(cubeName: string, keyPrefix?: string): string;
/**
* Derive SQL join type from semantic relationship
*/
export declare function getJoinType(relationship: string, override?: string): string;
/**
* Join key information for CTE joins
* Describes how a CTE should be joined to the main query
*/
export declare interface JoinKeyInfo {
/** Column name in the source table */
sourceColumn: string;
/** Column name in the target table (CTE) */
targetColumn: string;
/** Optional Drizzle column object for source */
sourceColumnObj?: AnyColumn;
/** Optional Drizzle column object for target */
targetColumnObj?: AnyColumn;
}
/**
* Single join key pair for composite key support
*/
export declare interface JoinKeyPair {
/** Primary key column on source cube */
source: AnyColumn;
/** Foreign key column on target cube (CTE cube) */
target: AnyColumn;
}
/**
* Complete join path from primary to target cube
*/
export declare interface JoinPathAnalysis {
/** Target cube name */
targetCube: string;
/** Whether a path was found */
pathFound: boolean;
/** The join steps (if found) */
path?: JoinPathStep[];
/** Total path length */
pathLength?: number;
/** Error message if path not found */
error?: string;
/** Cubes that were visited during BFS search */
visitedCubes?: string[];
}
/**
* Single step in a join path
*/
export declare interface JoinPathStep {
/** Source cube name */
fromCube: string;
/** Target cube name */
toCube: string;
/** Relationship type used */
relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany';
/** SQL join type that will be used */
joinType: 'inner' | 'left' | 'right' | 'full';
/** Join condition columns */
joinColumns: Array<{
sourceColumn: string;
targetColumn: string;
}>;
/** Junction table info for belongsToMany */
junctionTable?: {
tableName: string;
sourceColumns: string[];
targetColumns: string[];
};
}
export declare type JoinType = 'left' | 'right' | 'inner' | 'full';
export declare interface LogicalFilter {
and?: Filter[];
or?: Filter[];
}
/**
* Measure definition
*/
declare interface Measure {
name: string;
title?: string;
description?: string;
type: MeasureType;
/**
* Column to aggregate or SQL expression
* Optional for calculated measures (type: 'calculated') which use calculatedSql instead
*/
sql?: AnyColumn | SQL | ((ctx: QueryContext) => AnyColumn | SQL);
/** Display format */
format?: string;
/** Whether to show in UI */
shown?: boolean;
/** Filters applied to this measure */
filters?: Array<(ctx: QueryContext) => SQL>;
/** Rolling window configuration */
rollingWindow?: {
trailing?: string;
leading?: string;
offset?: string;
};
/**
* Calculated measure template with {member} references
* Only used when type === 'calculated'
* Example: "1.0 * {completed} / NULLIF({total}, 0)"
*/
calculatedSql?: string;
/**
* List of measure dependencies for calculated measures
* Auto-detected from calculatedSql if not provided
* Example: ['completed', 'total']
*/
dependencies?: string[];
/** Additional metadata */
meta?: Record<string, any>;
/**
* Statistical function configuration
* Used for percentile, stddev, variance measure types
*/
statisticalConfig?: {
/** Percentile value (0-100) for percentile measures. Default: 50 for median */
percentile?: number;
/** Use sample vs population calculation for stddev/variance. Default: false (population) */
useSample?: boolean;
};
/**
* Window function configuration
* Used for lag, lead, rank, movingAvg, and other window function measure types
*
* Post-aggregation window functions:
* When `measure` is specified, the window function operates on AGGREGATED data.
* The base measure is first aggregated (grouped by query dimensions), then the
* window function is applied to the aggregated results.
*
* Example: Month-over-month revenue change
* ```typescript
* revenueChange: {
* type: 'lag',
* windowConfig: {
* measure: 'totalRevenue', // Reference to aggregate measure
* operation: 'difference', // current - previous
* orderBy: [{ field: 'date', direction: 'asc' }]
* }
* }
* ```
*/
windowConfig?: {
/**
* Reference to the measure this window function operates on.
* The referenced measure will be aggregated first, then the window function applied.
* Can be a simple name ('totalRevenue') or fully qualified ('Sales.totalRevenue').
*/
measure?: string;
/**
* Operation to perform after getting the window result:
* - 'raw': Return the window function result directly (default for rank, rowNumber, ntile)
* - 'difference': Subtract window result from current value (current - window)
* - 'ratio': Divide current value by window result (current / window)
* - 'percentChange': Calculate percentage change ((current - window) / window * 100)
*
* Default: 'difference' for lag/lead, 'raw' for rank/rowNumber/ntile/firstValue/lastValue
*/
operation?: 'raw' | 'difference' | 'ratio' | 'percentChange';
/** Dimension references to partition by (e.g., ['employeeId']) */
partitionBy?: string[];
/** Columns to order by with direction */
orderBy?: Array<{
field: string;
direction: 'asc' | 'desc';
}>;
/** Number of rows to offset for lag/lead. Default: 1 */
offset?: number;
/** Default value when offset is out of bounds for lag/lead */
defaultValue?: any;
/** Number of buckets for ntile. Default: 4 */
nTile?: number;
/** Window frame specification for moving aggregates */
frame?: {
type: 'rows' | 'range';
start: number | 'unbounded';
end: number | 'current' | 'unbounded';
};
};
}
export { Measure }
export { Measure as SemanticMeasure }
/**
* Annotation interfaces for UI metadata
*/
export declare interface MeasureAnnotation {
title: string;
shortTitle: string;
type: MeasureType;
format?: MeasureFormat;
}
export declare type MeasureFormat = 'currency' | 'percent' | 'number' | 'integer';
/**
* Measure metadata for API responses
*/
export declare interface MeasureMetadata {
name: string;
title: string;
shortTitle: string;
type: MeasureType;
format?: MeasureFormat;
description?: string;
}
/**
* Type enums and constants
*/
export declare type MeasureType = 'count' | 'countDistinct' | 'countDistinctApprox' | 'sum' | 'avg' | 'min' | 'max' | 'runningTotal' | 'number' | 'calculated' | 'stddev' | 'stddevSamp' | 'variance' | 'varianceSamp' | 'percentile' | 'median' | 'p95' | 'p99' | 'lag' | 'lead' | 'rank' | 'denseRank' | 'rowNumber' | 'ntile' | 'firstValue' | 'lastValue' | 'movingAvg' | 'movingSum';
/**
* Simple in-memory cache provider implementing the CacheProvider interface
*
* Features:
* - TTL support with automatic expiration on read
* - Optional automatic cleanup of expired entries
* - Optional max entries limit with LRU eviction
* - Full metadata support for TTL tracking
*
* Limitations:
* - Not shared across processes/instances
* - Data lost on process restart
* - Not suitable for distributed deployments
*/
export declare class MemoryCacheProvider implements CacheProvider {
private cache;
private defaultTtlMs;
private maxEntries?;
private cleanupIntervalId?;
private accessOrder;
constructor(options?: MemoryCacheProviderOptions);
/**
* Get a cached value by key
* Returns null if not found or expired
* Automatically removes expired entries on access
*/
get<T>(key: string): Promise<CacheGetResult<T> | null>;
/**
* Set a value in the cache
* Respects maxEntries limit with LRU eviction
*/
set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
/**
* Delete a specific key from the cache
* Returns true if key existed and was deleted
*/
delete(key: string): Promise<boolean>;
/**
* Delete all keys matching a pattern
* Supports glob-style patterns with trailing '*'
* Returns number of keys deleted
*/
deletePattern(pattern: string): Promise<number>;
/**
* Check if a key exists in the cache
* Returns false for expired entries
*/
has(key: string): Promise<boolean>;
/**
* Stop automatic cleanup and clear the cache
* Call this when the cache provider is no longer needed
*/
close(): Promise<void>;
/**
* Remove all expired entries from the cache
* Called automatically by cleanup interval
* Can also be called manually
*
* @returns Number of entries removed
*/
cleanup(): number;
/**
* Get current cache size (number of entries)
* Note: May include expired entries that haven't been cleaned up yet
*/
size(): number;
/**
* Clear all entries from the cache
*/
clear(): void;
/**
* Get cache statistics
*/
stats(): {
size: number;
maxEntries?: number;
defaultTtlMs: number;
};
private touchAccessOrder;
private removeFromAccessOrder;
private evictOldest;
}
/**
* Options for MemoryCacheProvider
*/
declare interface MemoryCacheProviderOptions {
/**
* Default TTL in milliseconds
* @default 300000 (5 minutes)
*/
defaultTtlMs?: number;
/**
* Maximum number of entries in the cache
* When exceeded, oldest entries are evicted (LRU)
* @default undefined (unlimited)
*/
maxEntries?: number;
/**
* Interval in milliseconds to run automatic cleanup
* Set to 0 to disable automatic cleanup
* @default 60000 (1 minute)
*/
cleanupIntervalMs?: number;
}
/**
* Multi-cube query context for cross-cube operations
*/
export declare interface MultiCubeQueryContext extends QueryContext {
/** Available cubes for cross-cube operations */
cubes: Map<string, Cube>;
/** Current cube being processed */
currentCube: Cube;
}
export declare type MySQLDatabase = MySql2Database<any>;
export declare class MySQLExecutor extends BaseDatabaseExecutor {
execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>;
/**
* Convert numeric string fields to numbers (measure fields + numeric dimensions)
*/
private convertNumericFields;
/**
* Coerce a value to a number if it represents a numeric type
*/
private coerceToNumber;
getEngineType(): 'mysql' | 'singlestore';
}
/**
* Normalize query for consistent hashing
* Sorts arrays and object keys to ensure same query = same hash
*
* @param query - The semantic query to normalize
* @returns Normalized query with sorted arrays and keys
*/
export declare function normalizeQuery(query: SemanticQuery): SemanticQuery;
/**
* Period comparison metadata for compareDateRange queries
* Provides information about the periods being compared
*/
export declare interface PeriodComparisonMetadata {
/** The date ranges being compared */
ranges: [string, string][];
/** Human-readable labels for each period */
labels: string[];
/** The time dimension used for comparison */
timeDimension: string;
/** Granularity used for the comparison */
granularity?: TimeGranularity;
}
/**
* Type helpers for specific database types
*/
export declare type PostgresDatabase = PostgresJsDatabase<any>;
export declare class PostgresExecutor extends BaseDatabaseExecutor {
execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>;
/**
* Convert numeric string fields to numbers (only for measure fields)
*/
private convertNumericFields;
/**
* Coerce a value to a number if it represents a numeric type
*/
private coerceToNumber;
getEngineType(): 'postgres';
}
/**
* Pre-aggregation CTE analysis
*/
export declare interface PreAggregationAnalysis {
/** Cube being pre-aggregated */
cubeName: string;
/** CTE alias in the query */
cteAlias: string;
/** Why this cube needs a CTE */
reason: string;
/** Measures included in CTE (aggregate measures + window base measures) */
measures: string[];
/** Join keys used */
joinKeys: Array<{
sourceColumn: string;
targetColumn: string;
}>;
/**
* Type of CTE:
* - 'aggregate': Standard CTE with GROUP BY (count, sum, avg, etc.)
*
* Note: Window function CTEs are no longer used. Post-aggregation window
* functions are applied in the outer query after data is aggregated.
*/
cteType?: 'aggregate';
}
/**
* Pre-aggregation CTE information
* Describes a Common Table Expression used for pre-aggregating hasMany relationships
*/
export declare interface PreAggregationCTEInfo {
/** The cube being pre-aggregated */
cube: Cube;
/** Table alias for this cube in the main query */
alias: string;
/** CTE alias (WITH clause name) */
cteAlias: string;
/** Join keys to connect CTE back to main query */
joinKeys: JoinKeyInfo[];
/** List of measure names included in this CTE (aggregate measures + window base measures) */
measures: string[];
/** Propagating filters from related cubes (for cross-cube filter propagation) */
propagatingFilters?: PropagatingFilter[];
/**
* Type of CTE:
* - 'aggregate': Standard CTE with GROUP BY for count/sum/avg measures
*
* Note: Window function CTEs are no longer used. Post-aggregation window
* functions (lag, lead, rank, etc.) operate on aggregated data and are
* applied in the outer query SELECT clause, not in separate CTEs.
*/
cteType?: 'aggregate';
}
/**
* Primary cube selection analysis
*/
export declare interface PrimaryCubeAnalysis {
/** Name of the selected primary cube */
selectedCube: string;
/** Reason for selection */
reason: PrimaryCubeSelectionReason;
/** Human-readable explanation */
explanation: string;
/** Other candidates that were considered */
candidates?: PrimaryCubeCandidate[];
}
/**
* Candidate cube considered for primary selection
*/
export declare interface PrimaryCubeCandidate {
/** Cube name */
cubeName: string;
/** Number of dimensions from this cube in the query */
dimensionCount: number;
/** Number of join definitions on this cube */
joinCount: number;
/** Whether this cube can reach all other required cubes */
canReachAll: boolean;
}
/**
* Query Analysis Types
* Provides transparency into query planning decisions for debugging
*/
/**
* Reason why a cube was selected as primary (FROM table)
*/
export declare type PrimaryCubeSelectionReason = 'most_dimensions' | 'most_connected' | 'alphabetical_fallback' | 'single_cube';
/**
* Propagating filter information for cross-cube filter propagation
* When cube A has filters and cube B (with hasMany from A) needs a CTE,
* A's filters should propagate into B's CTE via a subquery
*/
export declare interface PropagatingFilter {
/** The source cube whose filters need to propagate */
sourceCube: Cube;
/** Filters from the source cube to apply */
filters: Filter[];
/** Join conditions linking source cube PK(s) to target cube FK(s) - supports composite keys */
joinConditions: JoinKeyPair[];
/** Pre-built filter SQL for parameter deduplication (optional, built during query planning) */
preBuiltFilterSQL?: SQL;
}
/**
* Any queryable relation that can be used in FROM/JOIN clauses
* Supports tables, views, subqueries, and raw SQL expressions
*/
export declare type QueryableRelation = Table | View | Subquery | SQL;
/**
* Complete query analysis result
*/
export declare interface QueryAnalysis {
/** Timestamp of analysis */
timestamp: string;
/** Number of cubes involved */
cubeCount: number;
/** List of all cubes used */
cubesInvolved: string[];
/** Primary cube selection details */
primaryCube: PrimaryCubeAnalysis;
/** Join path analysis for each joined cube */
joinPaths: JoinPathAnalysis[];
/** Pre-aggregation CTE details */
preAggregations: PreAggregationAnalysis[];
/** Overall query structure summary */
querySummary: QuerySummary;
/** Warnings or potential issues */
warnings?: string[];
}
export declare class QueryBuilder {
private dateTimeBuilder;
private filterBuilder;
private groupByBuilder;
private measureBuilder;
constructor(databaseAdapter: DatabaseAdapter);
/**
* Build resolvedMeasures map for a set of measures
* Delegates to MeasureBuilder
*/
buildResolvedMeasures(measureNames: string[], cubeMap: Map<string, Cube>, context: QueryContext, customMeasureBuilder?: (measureName: string, measure: any, cube: Cube) => SQL): ResolvedMeasures;
/**
* Build dynamic selections for measures, dimensions, and time dimensions
* Works for both single and multi-cube queries
* Handles calculated measures with dependency resolution
*/
buildSelections(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext): Record<string, SQL | AnyColumn>;
/**
* Build calculated measure expression by substituting {member} references
* Delegates to MeasureBuilder
*/
buildCalculatedMeasure(measure: any, cube: Cube, allCubes: Map<string, Cube>, resolvedMeasures: ResolvedMeasures, context: QueryContext): SQL;
/**
* Build resolved measures map for a calculated measure from CTE columns
* Delegates to MeasureBuilder
*/
buildCTECalculatedMeasure(measure: any, cube: Cube, cteInfo: {
cteAlias: string;
measures: string[];
cube: Cube;
}, allCubes: Map<string, Cube>, context: QueryContext): SQL;
/**
* Build measure expression for HAVING clause, handling CTE references correctly
* Delegates to MeasureBuilder
*/
private buildHavingMeasureExpression;
/**
* Build measure expression with aggregation and filters
* Delegates to MeasureBuilder
*/
buildMeasureExpression(measure: any, context: QueryContext, cube?: Cube): SQL;
/**
* Build time dimension expression with granularity using database adapter
* Delegates to DateTimeBuilder
*/
buildTimeDimensionExpression(dimensionSql: any, granularity: string | undefined, context: QueryContext): SQL;
/**
* Build WHERE conditions from semantic query filters (dimensions only)
* Works for both single and multi-cube queries
* @param preBuiltFilters - Optional map of cube name to pre-built filter SQL for parameter deduplication
*/
buildWhereConditions(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext, queryPlan?: QueryPlan, preBuiltFilters?: Map<string, SQL[]>): SQL[];
/**
* Build HAVING conditions from semantic query filters (measures only)
* Works for both single and multi-cube queries
*/
buildHavingConditions(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext, queryPlan?: QueryPlan): SQL[];
/**
* Process a single filter (basic or logical)
* @param filterType - 'where' for dimension filters, 'having' for measure filters
*/
private processFilter;
/**
* Build filter condition using Drizzle operators
* Delegates to FilterBuilder
*/
private buildFilterCondition;
/**
* Build date range condition for time dimensions
* Delegates to DateTimeBuilder
*/
buildDateRangeCondition(fieldExpr: AnyColumn | SQL, dateRange: string | string[]): SQL | null;
/**
* Build GROUP BY fields from dimensions and time dimensions
* Delegates to GroupByBuilder
*/
buildGroupByFields(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext, queryPlan?: any): (SQL | AnyColumn)[];
/**
* Build ORDER BY clause with automatic time dimension sorting
*/
buildOrderBy(query: SemanticQuery, selectedFields?: string[]): SQL[];
/**
* Collect numeric field names (measures + numeric dimensions) for type conversion
* Works for both single and multi-cube queries
*/
collectNumericFields(cubes: Map<string, Cube> | Cube, query: SemanticQuery): string[];
/**
* Apply LIMIT and OFFSET to a query with validation
* If offset is provided without limit, add a reasonable default limit
*/
applyLimitAndOffset<T>(query: T, semanticQuery: SemanticQuery): T;
/**
* Public wrapper for buildFilterCondition - used by executor for cache preloading
* This allows pre-building filter SQL before query construction
*/
buildFilterConditionPublic(fieldExpr: AnyColumn | SQL, operator: FilterOperator, values: any[], field?: any, dateRange?: string | string[]): SQL | null;
/**
* Build a logical filter (AND/OR) - used by executor for cache preloading
* This handles nested filter structures and builds combined SQL
* Delegates to FilterBuilder
*/
buildLogicalFilter(filter: Filter, cubes: Map<string, Cube>, context: QueryContext): SQL | null;
}
/**
* Cache metadata included in QueryResult when served from cache
*/
export declare interface QueryCacheMetadata {
/** Always true when this object is present */
hit: true;
/** ISO timestamp when the result was cached */
cachedAt: string;
/** Original TTL in milliseconds */
ttlMs: number;
/** Remaining TTL in milliseconds */
ttlRemainingMs: number;
}
/**
* Query context passed to cube SQL functions
* Provides access to database, schema, and security context
*/
declare interface QueryContext {
/** Drizzle database instance */
db: DrizzleDatabase;
/** Database schema (tables, columns, etc.) */
schema?: any;
/** Security context for filtering */
securityContext: SecurityContext;
/** The semantic query being executed */
query?: SemanticQuery;
/** The compiled cube being queried */
cube?: Cube;
/**
* Filter cache for parameter deduplication across CTEs
* Created at query start and used throughout query building
*/
filterCache?: FilterCacheManager;
}
export { QueryContext }
export { QueryContext as SemanticQueryContext }
export declare class QueryExecutor {
private dbExecutor;
private queryBuilder;
private queryPlanner;
private cteBuilder;
private databaseAdapter;
private comparisonQueryBuilder;
private cacheConfig?;
constructor(dbExecutor: DatabaseExecutor, cacheConfig?: CacheConfig);
/**
* Unified query execution method that handles both single and multi-cube queries
*/
execute(cubes: Map<string, Cube>, query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>;
/**
* Legacy interface for single cube queries
*/
executeQuery(cube: Cube, query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>;
/**
* Execute a comparison query with caching support
* Wraps executeComparisonQuery with cache set logic
*/
private executeComparisonQueryWithCache;
/**
* Execute a comparison query with multiple date periods
* Expands compareDateRange into multiple sub-queries and merges results
*/
private executeComparisonQuery;
/**
* Standard query execution (non-comparison)
* This is the core execution logic extracted for use by comparison queries
*/
private executeStandardQuery;
/**
* Validate that all cubes in the query plan have proper security filtering.
* Emits a warning if a cube's sql() function doesn't return a WHERE clause.
*
* Security is critical in multi-tenant applications - this validation helps
* detect cubes that may leak data across tenants.
*/
private validateSecurityContext;
/**
* Build unified query that works for both single and multi-cube queries
*/
private buildUnifiedQuery;
/**
* Convert query plan to cube map for QueryBuilder methods
*/
private getCubesFromPlan;
/**
* Generate raw SQL for debugging (without execution) - unified approach
*/
generateSQL(cube: Cube, query: SemanticQuery, securityContext: SecurityContext): Promise<{
sql: string;
params?: any[];
}>;
/**
* Generate raw SQL for multi-cube queries without execution - unified approach
*/
generateMultiCube