drizzle-cube 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -285,6 +285,13 @@ declare interface DatabaseAdapter {
285
285
  * @returns Date object or original value if not a time dimension
286
286
  */
287
287
  convertTimeDimensionResult(value: any): any;
288
+ /**
289
+ * Preprocess calculated measure template for database-specific transformations
290
+ * This allows each adapter to modify the template before substitution occurs
291
+ * @param calculatedSql - The template string with {member} references
292
+ * @returns Preprocessed template string
293
+ */
294
+ preprocessCalculatedTemplate(calculatedSql: string): string;
288
295
  }
289
296
 
290
297
  /**
@@ -413,6 +420,21 @@ export declare type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notC
413
420
  */
414
421
  export declare function getJoinType(relationship: string, override?: string): string;
415
422
 
423
+ /**
424
+ * Join key information for CTE joins
425
+ * Describes how a CTE should be joined to the main query
426
+ */
427
+ export declare interface JoinKeyInfo {
428
+ /** Column name in the source table */
429
+ sourceColumn: string;
430
+ /** Column name in the target table (CTE) */
431
+ targetColumn: string;
432
+ /** Optional Drizzle column object for source */
433
+ sourceColumnObj?: AnyColumn;
434
+ /** Optional Drizzle column object for target */
435
+ targetColumnObj?: AnyColumn;
436
+ }
437
+
416
438
  export declare type JoinType = 'left' | 'right' | 'inner' | 'full';
417
439
 
418
440
  export declare interface LogicalFilter {
@@ -442,6 +464,18 @@ declare interface Measure {
442
464
  leading?: string;
443
465
  offset?: string;
444
466
  };
467
+ /**
468
+ * Calculated measure template with {member} references
469
+ * Only used when type === 'calculated'
470
+ * Example: "1.0 * {completed} / NULLIF({total}, 0)"
471
+ */
472
+ calculatedSql?: string;
473
+ /**
474
+ * List of measure dependencies for calculated measures
475
+ * Auto-detected from calculatedSql if not provided
476
+ * Example: ['completed', 'total']
477
+ */
478
+ dependencies?: string[];
445
479
  /** Additional metadata */
446
480
  meta?: Record<string, any>;
447
481
  }
@@ -475,7 +509,7 @@ export declare interface MeasureMetadata {
475
509
  /**
476
510
  * Type enums and constants
477
511
  */
478
- export declare type MeasureType = 'count' | 'countDistinct' | 'countDistinctApprox' | 'sum' | 'avg' | 'min' | 'max' | 'runningTotal' | 'number';
512
+ export declare type MeasureType = 'count' | 'countDistinct' | 'countDistinctApprox' | 'sum' | 'avg' | 'min' | 'max' | 'runningTotal' | 'number' | 'calculated';
479
513
 
480
514
  /**
481
515
  * Multi-cube query context for cross-cube operations
@@ -520,6 +554,23 @@ export declare class PostgresExecutor extends BaseDatabaseExecutor {
520
554
  getEngineType(): 'postgres';
521
555
  }
522
556
 
557
+ /**
558
+ * Pre-aggregation CTE information
559
+ * Describes a Common Table Expression used for pre-aggregating hasMany relationships
560
+ */
561
+ export declare interface PreAggregationCTEInfo {
562
+ /** The cube being pre-aggregated */
563
+ cube: Cube;
564
+ /** Table alias for this cube in the main query */
565
+ alias: string;
566
+ /** CTE alias (WITH clause name) */
567
+ cteAlias: string;
568
+ /** Join keys to connect CTE back to main query */
569
+ joinKeys: JoinKeyInfo[];
570
+ /** List of measure names included in this CTE */
571
+ measures: string[];
572
+ }
573
+
523
574
  /**
524
575
  * Any queryable relation that can be used in FROM/JOIN clauses
525
576
  * Supports tables, views, subqueries, and raw SQL expressions
@@ -529,17 +580,55 @@ export declare type QueryableRelation = Table | View | Subquery | SQL;
529
580
  export declare class QueryBuilder {
530
581
  private databaseAdapter;
531
582
  constructor(databaseAdapter: DatabaseAdapter);
583
+ /**
584
+ * Build resolvedMeasures map for a set of measures
585
+ * This centralizes the logic for building both regular and calculated measures
586
+ * in dependency order, avoiding duplication across main queries and CTEs
587
+ *
588
+ * @param measureNames - Array of measure names to resolve (e.g., ["Employees.count", "Employees.activePercentage"])
589
+ * @param cubeMap - Map of all cubes involved in the query
590
+ * @param context - Query context with database and security context
591
+ * @param customMeasureBuilder - Optional function to override how individual measures are built
592
+ * @returns Map of measure names to SQL builder functions
593
+ */
594
+ buildResolvedMeasures(measureNames: string[], cubeMap: Map<string, Cube>, context: QueryContext, customMeasureBuilder?: (measureName: string, measure: any, cube: Cube) => SQL): ResolvedMeasures;
532
595
  /**
533
596
  * Build dynamic selections for measures, dimensions, and time dimensions
534
597
  * Works for both single and multi-cube queries
598
+ * Handles calculated measures with dependency resolution
535
599
  */
536
600
  buildSelections(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext): Record<string, SQL | AnyColumn>;
601
+ /**
602
+ * Build calculated measure expression by substituting {member} references
603
+ * with resolved SQL expressions
604
+ */
605
+ buildCalculatedMeasure(measure: any, cube: Cube, allCubes: Map<string, Cube>, resolvedMeasures: ResolvedMeasures, context: QueryContext): SQL;
606
+ /**
607
+ * Build resolved measures map for a calculated measure from CTE columns
608
+ * This handles re-aggregating pre-aggregated CTE columns for calculated measures
609
+ *
610
+ * IMPORTANT: For calculated measures in CTEs, we cannot sum/avg pre-computed ratios.
611
+ * We must recalculate from the base measures that were pre-aggregated in the CTE.
612
+ *
613
+ * @param measure - The calculated measure to build
614
+ * @param cube - The cube containing this measure
615
+ * @param cteInfo - CTE metadata (alias, measures, cube reference)
616
+ * @param allCubes - Map of all cubes in the query
617
+ * @param context - Query context
618
+ * @returns SQL expression for the calculated measure using CTE column references
619
+ */
620
+ buildCTECalculatedMeasure(measure: any, cube: Cube, cteInfo: {
621
+ cteAlias: string;
622
+ measures: string[];
623
+ cube: Cube;
624
+ }, allCubes: Map<string, Cube>, context: QueryContext): SQL;
537
625
  /**
538
626
  * Build measure expression for HAVING clause, handling CTE references correctly
539
627
  */
540
628
  private buildHavingMeasureExpression;
541
629
  /**
542
630
  * Build measure expression with aggregation and filters
631
+ * Note: This should NOT be called for calculated measures
543
632
  */
544
633
  buildMeasureExpression(measure: any, context: QueryContext): SQL;
545
634
  /**
@@ -576,8 +665,8 @@ export declare class QueryBuilder {
576
665
  private parseRelativeDateRange;
577
666
  /**
578
667
  * Normalize date values to handle strings, numbers, and Date objects
579
- * Always returns a JavaScript Date object or null
580
- * Database-agnostic - just ensures we have a valid Date
668
+ * Returns ISO string for PostgreSQL/MySQL, Unix timestamp for SQLite, or null
669
+ * Ensures dates are in the correct format for each database engine
581
670
  */
582
671
  private normalizeDate;
583
672
  /**
@@ -706,18 +795,7 @@ export declare interface QueryPlan {
706
795
  /** GROUP BY fields if aggregations are present (built by QueryBuilder) */
707
796
  groupByFields: (SQL | AnyColumn)[];
708
797
  /** Pre-aggregation CTEs for hasMany relationships to prevent fan-out */
709
- preAggregationCTEs?: Array<{
710
- cube: Cube;
711
- alias: string;
712
- cteAlias: string;
713
- joinKeys: Array<{
714
- sourceColumn: string;
715
- targetColumn: string;
716
- sourceColumnObj?: AnyColumn;
717
- targetColumnObj?: AnyColumn;
718
- }>;
719
- measures: string[];
720
- }>;
798
+ preAggregationCTEs?: PreAggregationCTEInfo[];
721
799
  }
722
800
 
723
801
  /**
@@ -773,6 +851,14 @@ export declare class QueryPlanner {
773
851
  * and don't require CTEs - the two-hop join with the junction table provides natural grouping
774
852
  */
775
853
  private planPreAggregationCTEs;
854
+ /**
855
+ * Expand calculated measures to include their dependencies
856
+ */
857
+ private expandCalculatedMeasureDependencies;
858
+ /**
859
+ * Extract measure references from calculatedSql template
860
+ */
861
+ private extractDependenciesFromTemplate;
776
862
  /**
777
863
  * Find hasMany join definition from primary cube to target cube
778
864
  */
@@ -798,7 +884,21 @@ export declare interface QueryResult {
798
884
  export declare function resolveCubeReference(ref: Cube | (() => Cube)): Cube;
799
885
 
800
886
  /**
801
- * Helper to resolve SQL expressions
887
+ * Resolved measure SQL builders
888
+ * Maps full measure names (e.g., "Cube.measure") to functions that build their SQL
889
+ * Using functions instead of SQL objects avoids mutation and shared reference issues
890
+ */
891
+ declare type ResolvedMeasures = Map<string, () => SQL>;
892
+
893
+ /**
894
+ * Helper to resolve SQL expressions with mutation protection
895
+ *
896
+ * Evaluates function-based SQL expressions and applies isolation to prevent
897
+ * Drizzle's internal mutation from corrupting reused SQL objects.
898
+ *
899
+ * @param expr - Column, SQL object, or function that returns one
900
+ * @param ctx - Query context for function evaluation
901
+ * @returns Isolated SQL expression safe for reuse
802
902
  */
803
903
  export declare function resolveSqlExpression(expr: AnyColumn | SQL | ((ctx: QueryContext) => AnyColumn | SQL), ctx: QueryContext): AnyColumn | SQL;
804
904
 
@@ -842,8 +942,14 @@ export declare class SemanticLayerCompiler {
842
942
  hasExecutor(): boolean;
843
943
  /**
844
944
  * Register a simplified cube with dynamic query building
945
+ * Validates calculated measures during registration
845
946
  */
846
947
  registerCube(cube: Cube): void;
948
+ /**
949
+ * Validate calculated measures in a cube
950
+ * Checks template syntax, dependency existence, and circular dependencies
951
+ */
952
+ private validateCalculatedMeasures;
847
953
  /**
848
954
  * Get a cube by name
849
955
  */