UNPKG

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