UNPKG

drizzle-cube

Version:

Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.

1,356 lines (1,286 loc) 56.7 kB
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; } /** * 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'; /** * 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'; /** * 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'; } /** * 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; } /** * 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; constructor(dbExecutor: DatabaseExecutor); /** * 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>; /** * 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 */ generateMultiCubeSQL(cubes: Map<string, Cube>, query: SemanticQuery, securityContext: SecurityContext): Promise<{ sql: string; params?: any[]; }>; /** * Generate SQL using unified approach (works for both single and multi-cube) */ private generateUnifiedSQL; /** * Generate annotations for UI metadata - unified approach */ private generateAnnotations; /** * Pre-build filter SQL and store in cache for reuse across CTEs and main query * This enables parameter deduplication - the same filter values are shared * rather than appearing as separate parameters in different parts of the query */ private preloadFilterCache; /** * Build post-aggregation window function expression * * Post-aggregation windows operate on already-aggregated data: * 1. The base measure is aggregated (e.g., SUM(revenue)) * 2. The window function is applied (e.g., LAG(...) OVER ORDER BY date) * 3. An optional operation is applied (e.g., current - previous) * * @param measure - The window function measure definition * @param baseMeasureExpr - The aggregated base measure expression * @param query - The semantic query (for dimension context) * @param context - Query context * @param cube - The cube containing this measure * @returns SQL expression for the window function */ private buildPostAggregationWindowExpression; } /** * Unified Query Plan for both single and multi-cube queries * - For single-cube queries: joinCubes array is empty * - For multi-cube queries: joinCubes contains the additional cubes to join * - selections, whereConditions, and groupByFields are populated by QueryBuilder */ export declare interface QueryPlan { /** Primary cube that drives the query */ primaryCube: Cube; /** Additional cubes to join (empty for single-cube queries) */ joinCubes: Array<{ cube: Cube; alias: string; joinType: 'inner' | 'left' | 'right' | 'full'; joinCondition: SQL; /** Junction table information for belongsToMany relationships */ junctionTable?: { table: Table; alias: string; joinType: 'inner' | 'left' | 'right' | 'full'; joinCondition: SQL; /** Optional security SQL function to apply to junction table */ securitySql?: (securityContext: SecurityContext) => SQL | SQL[]; }; }>; /** Combined field selections across all cubes (built by QueryBuilder) */ selections: Record<string, SQL | AnyColumn>; /** WHERE conditions for the entire query (built by QueryBuilder) */ whereConditions: SQL[]; /** GROUP BY fields if aggregations are present (built by QueryBuilder) */ groupByFields: (SQL | AnyColumn)[]; /** Pre-aggregation CTEs for hasMany relationships to prevent fan-out */ preAggregationCTEs?: PreAggregationCTEInfo[]; } /** * Pre-aggregation plan for handling hasMany relationships */ export declare class QueryPlanner { private resolverCache; /** * Get or create a JoinPathResolver for the given cubes map */ private getResolver; /** * Analyze a semantic query to determine which cubes are involved */ analyzeCubeUsage(query: SemanticQuery): Set<string>; /** * Recursively extract cube names from filters (handles logical filters) */ private extractCubeNamesFromFilter; /** * Extract measures referenced in filters (for CTE inclusion) */ private extractMeasuresFromFilters; /** * Recursively extract measures from filters for a specific cube * Only includes filter members that are actually measures (not dimensions) */ private extractMeasuresFromFilter; /** * Create a unified query plan that works for both single and multi-cube queries */ createQueryPlan(cubes: Map<string, Cube>, query: SemanticQuery, ctx: QueryContext): QueryPlan; /** * Choose the primary cube based on query analysis * Uses a consistent strategy to avoid measure order dependencies * * Delegates to analyzePrimaryCubeSelection() for the actual logic, * ensuring a single source of truth for primary cube selection. */ choosePrimaryCube(cubeNames: string[], query: SemanticQuery, cubes?: Map<string, Cube>): string; /** * Build join plan for multi-cube query * Supports both direct joins and transitive joins through intermediate cubes */ private buildJoinPlan; /** * Plan pre-aggregation CTEs for hasMany relationships to prevent fan-out * Note: belongsToMany relationships handle fan-out differently through their junction table structure * and don't require CTEs - the two-hop join with the junction table provides natural grouping */ private planPreAggregationCTEs; /** * Expand calculated measures to include their dependencies */ private expandCalculatedMeasureDependencies; /** * Extract measure references from calculatedSql template */ private extractDependenciesFromTemplate; /** * Find hasMany join definition from primary cube to target cube */ private findHasManyJoinDef; /** * Find filters that need to propagate from related cubes to a CTE cube. * When cube A has filters and a hasMany relationship to cube B (the CTE cube), * A's filters should propagate into B's CTE via a subquery. * * Example: Employees.createdAt filter should propagate to Productivity CTE * via: employee_id IN (SELECT id FROM employees WHERE created_at >= $date) */ private findPropagatingFilters; /** * Extract cube names from filters into a Set (helper for findPropagatingFilters) */ private extractFilterCubeNamesToSet; /** * Extract filters for a specific cube from the filter array * * Logic for preserving filter semantics: * - AND: Safe to extract only matching branches (AND of fewer conditions is more permissive) * - OR: Must include ALL branches or skip entirely (partial OR changes semantics) * If any branch belongs to another cube, skip the entire OR to be safe * since we can't evaluate the other cube's conditions */ private extractFiltersForCube; /** * Check if all simple filters in a filter array belong to the specified cube * Recursively checks nested AND/OR filters */ private allFiltersFromCube; /** * Extract time dimension date range filters as regular filters for a specific cube */ private extractTimeDimensionFiltersForCube; /** * Analyze query planning decisions without building the full query * Returns detailed metadata about how the query plan would be constructed * Used for debugging and transparency in the playground UI */ analyzeQueryPlan(cubes: Map<string, Cube>, query: SemanticQuery, _ctx: QueryContext): QueryAnalysis; /** * Analyze why a particular cube was chosen as primary */ private analyzePrimaryCubeSelection; /** * Analyze the join path between two cubes with detailed step information * * Uses JoinPathResolver.findPath() for the actual path finding, * then converts the result to human-readable analysis format. */ private analyzeJoinPath; /** * Analyze pre-aggregation requirements for hasMany relationships * This mirrors the logic in planPreAggregationCTEs to ensure analysis matches execution */ private analyzePreAggregations; } /** * Query execution result */ export declare interface QueryResult { data: Record<string, unknown>[]; annotation: { measures: Record<string, MeasureAnnotation>; dimensions: Record<string, DimensionAnnotation>; segments: Record<string, unknown>; timeDimensions: Record<string, TimeDimensionAnnotation>; }; } /** * Query structure summary */ export declare interface QuerySummary { /** Query type: 'single_cube', 'multi_cube_join', or 'multi_cube_cte' */ queryType: 'single_cube' | 'multi_cube_join' | 'multi_cube_cte'; /** Total number of joins */ joinCount: number; /** Total number of CTEs */ cteCount: number; /** Has hasMany relationships requiring aggregation */ hasPreAggregation: boolean; /** * Whether query contains post-aggregation window function measures. * These window functions (lag, lead, rank, etc.) operate on aggregated * data and are applied in the outer query after CTEs. */ hasWindowFunctions?: boolean; } /** * Resolve cube reference (handles both direct and lazy references) */ export declare function resolveCubeReference(ref: Cube | (() => Cube)): Cube; /** * Resolved measure SQL builders * Maps full measure names (e.g., "Cube.measure") to functions that build their SQL * Using functions instead of SQL objects avoids mutation and shared reference issues */ declare type ResolvedMeasures = Map<string, () => SQL>; /** * Helper to resolve SQL expressions with mutation protection * * Evaluates function-based SQL expressions and applies isolation to prevent * Drizzle's internal mutation from corrupting reused SQL objects. * * @param expr - Column, SQL object, or function that returns one * @param ctx - Query context for function evaluation * @returns Isolated SQL expression safe for reuse */ export declare function resolveSqlExpression(expr: AnyColumn | SQL | ((ctx: QueryContext) => AnyColumn | SQL), ctx: QueryContext): AnyColumn | SQL; /** * Security context passed to cube SQL functions * Contains user/tenant-specific data for filtering */ export declare interface SecurityContext { [key: string]: unknown; } export declare class SemanticLayerCompiler { private cubes; private dbExecutor?; private metadataCache?; constructor(options?: { drizzle?: DatabaseExecutor['db']; schema?: any; databaseExecutor?: DatabaseExecutor; engineType?: 'postgres' | 'mysql' | 'sqlite' | 'singlestore'; }); /** * Set or update the database executor */ setDatabaseExecutor(executor: DatabaseExecutor): void; /** * Get the database engine type for SQL formatting */ getEngineType(): 'postgres' | 'mysql' | 'sqlite' | 'single