drizzle-cube 0.2.16 → 0.2.17

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.
@@ -535,6 +535,16 @@ export declare interface JoinKeyInfo {
535
535
  targetColumnObj?: AnyColumn;
536
536
  }
537
537
 
538
+ /**
539
+ * Single join key pair for composite key support
540
+ */
541
+ export declare interface JoinKeyPair {
542
+ /** Primary key column on source cube */
543
+ source: AnyColumn;
544
+ /** Foreign key column on target cube (CTE cube) */
545
+ target: AnyColumn;
546
+ }
547
+
538
548
  /**
539
549
  * Complete join path from primary to target cube
540
550
  */
@@ -820,13 +830,8 @@ export declare interface PropagatingFilter {
820
830
  sourceCube: Cube;
821
831
  /** Filters from the source cube to apply */
822
832
  filters: Filter[];
823
- /** Join condition linking source cube PK to target cube FK */
824
- joinCondition: {
825
- /** Primary key column on source cube */
826
- source: AnyColumn;
827
- /** Foreign key column on target cube (CTE cube) */
828
- target: AnyColumn;
829
- };
833
+ /** Join conditions linking source cube PK(s) to target cube FK(s) - supports composite keys */
834
+ joinConditions: JoinKeyPair[];
830
835
  /** Pre-built filter SQL for parameter deduplication (optional, built during query planning) */
831
836
  preBuiltFilterSQL?: SQL;
832
837
  }
@@ -860,18 +865,14 @@ export declare interface QueryAnalysis {
860
865
  }
861
866
 
862
867
  export declare class QueryBuilder {
863
- private databaseAdapter;
868
+ private dateTimeBuilder;
869
+ private filterBuilder;
870
+ private groupByBuilder;
871
+ private measureBuilder;
864
872
  constructor(databaseAdapter: DatabaseAdapter);
865
873
  /**
866
874
  * Build resolvedMeasures map for a set of measures
867
- * This centralizes the logic for building both regular and calculated measures
868
- * in dependency order, avoiding duplication across main queries and CTEs
869
- *
870
- * @param measureNames - Array of measure names to resolve (e.g., ["Employees.count", "Employees.activePercentage"])
871
- * @param cubeMap - Map of all cubes involved in the query
872
- * @param context - Query context with database and security context
873
- * @param customMeasureBuilder - Optional function to override how individual measures are built
874
- * @returns Map of measure names to SQL builder functions
875
+ * Delegates to MeasureBuilder
875
876
  */
876
877
  buildResolvedMeasures(measureNames: string[], cubeMap: Map<string, Cube>, context: QueryContext, customMeasureBuilder?: (measureName: string, measure: any, cube: Cube) => SQL): ResolvedMeasures;
877
878
  /**
@@ -882,22 +883,12 @@ export declare class QueryBuilder {
882
883
  buildSelections(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext): Record<string, SQL | AnyColumn>;
883
884
  /**
884
885
  * Build calculated measure expression by substituting {member} references
885
- * with resolved SQL expressions
886
+ * Delegates to MeasureBuilder
886
887
  */
887
888
  buildCalculatedMeasure(measure: any, cube: Cube, allCubes: Map<string, Cube>, resolvedMeasures: ResolvedMeasures, context: QueryContext): SQL;
888
889
  /**
889
890
  * Build resolved measures map for a calculated measure from CTE columns
890
- * This handles re-aggregating pre-aggregated CTE columns for calculated measures
891
- *
892
- * IMPORTANT: For calculated measures in CTEs, we cannot sum/avg pre-computed ratios.
893
- * We must recalculate from the base measures that were pre-aggregated in the CTE.
894
- *
895
- * @param measure - The calculated measure to build
896
- * @param cube - The cube containing this measure
897
- * @param cteInfo - CTE metadata (alias, measures, cube reference)
898
- * @param allCubes - Map of all cubes in the query
899
- * @param context - Query context
900
- * @returns SQL expression for the calculated measure using CTE column references
891
+ * Delegates to MeasureBuilder
901
892
  */
902
893
  buildCTECalculatedMeasure(measure: any, cube: Cube, cteInfo: {
903
894
  cteAlias: string;
@@ -906,19 +897,17 @@ export declare class QueryBuilder {
906
897
  }, allCubes: Map<string, Cube>, context: QueryContext): SQL;
907
898
  /**
908
899
  * Build measure expression for HAVING clause, handling CTE references correctly
900
+ * Delegates to MeasureBuilder
909
901
  */
910
902
  private buildHavingMeasureExpression;
911
903
  /**
912
904
  * Build measure expression with aggregation and filters
913
- * Note: This should NOT be called for calculated measures
914
- *
915
- * @param measure - The measure definition
916
- * @param context - Query context with security context and database info
917
- * @param cube - Optional cube reference for resolving dimension references (window functions)
905
+ * Delegates to MeasureBuilder
918
906
  */
919
907
  buildMeasureExpression(measure: any, context: QueryContext, cube?: Cube): SQL;
920
908
  /**
921
909
  * Build time dimension expression with granularity using database adapter
910
+ * Delegates to DateTimeBuilder
922
911
  */
923
912
  buildTimeDimensionExpression(dimensionSql: any, granularity: string | undefined, context: QueryContext): SQL;
924
913
  /**
@@ -939,37 +928,17 @@ export declare class QueryBuilder {
939
928
  private processFilter;
940
929
  /**
941
930
  * Build filter condition using Drizzle operators
931
+ * Delegates to FilterBuilder
942
932
  */
943
933
  private buildFilterCondition;
944
934
  /**
945
935
  * Build date range condition for time dimensions
936
+ * Delegates to DateTimeBuilder
946
937
  */
947
938
  buildDateRangeCondition(fieldExpr: AnyColumn | SQL, dateRange: string | string[]): SQL | null;
948
- /**
949
- * Parse relative date range expressions like "today", "yesterday", "last 7 days", "this month", etc.
950
- * Handles all 14 DATE_RANGE_OPTIONS from the client
951
- */
952
- private parseRelativeDateRange;
953
- /**
954
- * Normalize date values to handle strings, numbers, and Date objects
955
- * Returns ISO string for PostgreSQL/MySQL, Unix timestamp for SQLite, or null
956
- * Ensures dates are in the correct format for each database engine
957
- */
958
- private normalizeDate;
959
- /**
960
- * Check if a measure type is a window function
961
- */
962
- private isWindowFunctionType;
963
- /**
964
- * Check if a measure type is an aggregate function (requires GROUP BY)
965
- */
966
- private isAggregateFunctionType;
967
939
  /**
968
940
  * Build GROUP BY fields from dimensions and time dimensions
969
- * Works for both single and multi-cube queries
970
- *
971
- * NOTE: GROUP BY is only added when there are AGGREGATE measures.
972
- * Window functions do not require GROUP BY and operate on individual rows.
941
+ * Delegates to GroupByBuilder
973
942
  */
974
943
  buildGroupByFields(cubes: Map<string, Cube> | Cube, query: SemanticQuery, context: QueryContext, queryPlan?: any): (SQL | AnyColumn)[];
975
944
  /**
@@ -994,13 +963,9 @@ export declare class QueryBuilder {
994
963
  /**
995
964
  * Build a logical filter (AND/OR) - used by executor for cache preloading
996
965
  * This handles nested filter structures and builds combined SQL
966
+ * Delegates to FilterBuilder
997
967
  */
998
968
  buildLogicalFilter(filter: Filter, cubes: Map<string, Cube>, context: QueryContext): SQL | null;
999
- /**
1000
- * Build SQL for a single filter condition (simple or logical)
1001
- * Used for cache preloading to build filters independently of query context
1002
- */
1003
- private buildSingleFilter;
1004
969
  }
1005
970
 
1006
971
  /**
@@ -1031,6 +996,7 @@ export declare class QueryExecutor {
1031
996
  private dbExecutor;
1032
997
  private queryBuilder;
1033
998
  private queryPlanner;
999
+ private cteBuilder;
1034
1000
  private databaseAdapter;
1035
1001
  constructor(dbExecutor: DatabaseExecutor);
1036
1002
  /**
@@ -1042,21 +1008,13 @@ export declare class QueryExecutor {
1042
1008
  */
1043
1009
  executeQuery(cube: Cube, query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>;
1044
1010
  /**
1045
- * Build pre-aggregation CTE for hasMany relationships
1046
- */
1047
- private buildPreAggregationCTE;
1048
- /**
1049
- * Build join condition for CTE
1050
- */
1051
- private buildCTEJoinCondition;
1052
- /**
1053
- * Build a subquery filter for propagating filters from related cubes.
1054
- * This generates: cteCube.FK IN (SELECT sourceCube.PK FROM sourceCube WHERE filters...)
1011
+ * Validate that all cubes in the query plan have proper security filtering.
1012
+ * Emits a warning if a cube's sql() function doesn't return a WHERE clause.
1055
1013
  *
1056
- * Example: For Productivity CTE with Employees.createdAt filter:
1057
- * employee_id IN (SELECT id FROM employees WHERE organisation_id = $1 AND created_at >= $date)
1014
+ * Security is critical in multi-tenant applications - this validation helps
1015
+ * detect cubes that may leak data across tenants.
1058
1016
  */
1059
- private buildPropagatingFilterSubquery;
1017
+ private validateSecurityContext;
1060
1018
  /**
1061
1019
  * Build unified query that works for both single and multi-cube queries
1062
1020
  */
@@ -1134,6 +1092,11 @@ export declare interface QueryPlan {
1134
1092
  * Pre-aggregation plan for handling hasMany relationships
1135
1093
  */
1136
1094
  export declare class QueryPlanner {
1095
+ private resolverCache;
1096
+ /**
1097
+ * Get or create a JoinPathResolver for the given cubes map
1098
+ */
1099
+ private getResolver;
1137
1100
  /**
1138
1101
  * Analyze a semantic query to determine which cubes are involved
1139
1102
  */
@@ -1158,26 +1121,16 @@ export declare class QueryPlanner {
1158
1121
  /**
1159
1122
  * Choose the primary cube based on query analysis
1160
1123
  * Uses a consistent strategy to avoid measure order dependencies
1124
+ *
1125
+ * Delegates to analyzePrimaryCubeSelection() for the actual logic,
1126
+ * ensuring a single source of truth for primary cube selection.
1161
1127
  */
1162
1128
  choosePrimaryCube(cubeNames: string[], query: SemanticQuery, cubes?: Map<string, Cube>): string;
1163
- /**
1164
- * Check if a cube can reach all other cubes in the list via joins
1165
- */
1166
- private canReachAllCubes;
1167
1129
  /**
1168
1130
  * Build join plan for multi-cube query
1169
1131
  * Supports both direct joins and transitive joins through intermediate cubes
1170
1132
  */
1171
1133
  private buildJoinPlan;
1172
- /**
1173
- * Build join condition from new array-based join definition
1174
- */
1175
- private buildJoinCondition;
1176
- /**
1177
- * Find join path from source cube to target cube
1178
- * Returns array of join steps to reach target
1179
- */
1180
- private findJoinPath;
1181
1134
  /**
1182
1135
  * Plan pre-aggregation CTEs for hasMany relationships to prevent fan-out
1183
1136
  * Note: belongsToMany relationships handle fan-out differently through their junction table structure
@@ -1211,8 +1164,19 @@ export declare class QueryPlanner {
1211
1164
  private extractFilterCubeNamesToSet;
1212
1165
  /**
1213
1166
  * Extract filters for a specific cube from the filter array
1167
+ *
1168
+ * Logic for preserving filter semantics:
1169
+ * - AND: Safe to extract only matching branches (AND of fewer conditions is more permissive)
1170
+ * - OR: Must include ALL branches or skip entirely (partial OR changes semantics)
1171
+ * If any branch belongs to another cube, skip the entire OR to be safe
1172
+ * since we can't evaluate the other cube's conditions
1214
1173
  */
1215
1174
  private extractFiltersForCube;
1175
+ /**
1176
+ * Check if all simple filters in a filter array belong to the specified cube
1177
+ * Recursively checks nested AND/OR filters
1178
+ */
1179
+ private allFiltersFromCube;
1216
1180
  /**
1217
1181
  * Extract time dimension date range filters as regular filters for a specific cube
1218
1182
  */
@@ -1229,6 +1193,9 @@ export declare class QueryPlanner {
1229
1193
  private analyzePrimaryCubeSelection;
1230
1194
  /**
1231
1195
  * Analyze the join path between two cubes with detailed step information
1196
+ *
1197
+ * Uses JoinPathResolver.findPath() for the actual path finding,
1198
+ * then converts the result to human-readable analysis format.
1232
1199
  */
1233
1200
  private analyzeJoinPath;
1234
1201
  /**
@@ -1301,8 +1268,6 @@ export declare class SemanticLayerCompiler {
1301
1268
  private cubes;
1302
1269
  private dbExecutor?;
1303
1270
  private metadataCache?;
1304
- private metadataCacheTimestamp?;
1305
- private readonly METADATA_CACHE_TTL;
1306
1271
  constructor(options?: {
1307
1272
  drizzle?: DatabaseExecutor['db'];
1308
1273
  schema?: any;
@@ -1362,6 +1327,7 @@ export declare class SemanticLayerCompiler {
1362
1327
  /**
1363
1328
  * Get metadata for all cubes (for API responses)
1364
1329
  * Uses caching to improve performance for repeated requests
1330
+ * Cache is invalidated when cubes are modified (registerCube, removeCube, clearCubes)
1365
1331
  */
1366
1332
  getMetadata(): CubeMetadata[];
1367
1333
  /**