@hypequery/datasets 0.19.0 → 0.21.0

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.
Files changed (171) hide show
  1. package/dist/agent-catalog.d.ts +2 -1
  2. package/dist/agent-catalog.d.ts.map +1 -1
  3. package/dist/api.type-test.js +22 -1
  4. package/dist/cache/definition-identity.d.ts +26 -0
  5. package/dist/cache/definition-identity.d.ts.map +1 -0
  6. package/dist/cache/definition-identity.js +164 -0
  7. package/dist/cache/protocol-cache-keys.d.ts +38 -0
  8. package/dist/cache/protocol-cache-keys.d.ts.map +1 -0
  9. package/dist/cache/protocol-cache-keys.js +191 -0
  10. package/dist/cache/query-signature.d.ts +6 -0
  11. package/dist/cache/query-signature.d.ts.map +1 -1
  12. package/dist/cache/query-signature.js +10 -13
  13. package/dist/cache/semantic-query-cache.d.ts +23 -0
  14. package/dist/cache/semantic-query-cache.d.ts.map +1 -1
  15. package/dist/catalog.d.ts +6 -5
  16. package/dist/catalog.d.ts.map +1 -1
  17. package/dist/catalog.js +29 -9
  18. package/dist/contract.d.ts +3 -2
  19. package/dist/contract.d.ts.map +1 -1
  20. package/dist/contract.js +2 -0
  21. package/dist/dataset-query.d.ts.map +1 -1
  22. package/dist/dataset-query.js +21 -4
  23. package/dist/dataset.d.ts +2 -2
  24. package/dist/dataset.d.ts.map +1 -1
  25. package/dist/dataset.js +9 -4
  26. package/dist/executor.d.ts +7 -1
  27. package/dist/executor.d.ts.map +1 -1
  28. package/dist/executor.js +60 -18
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/measure.d.ts +12 -2
  32. package/dist/measure.d.ts.map +1 -1
  33. package/dist/measure.js +11 -1
  34. package/dist/protocol-adapter.d.ts.map +1 -1
  35. package/dist/protocol-adapter.js +10 -1
  36. package/dist/protocol-deployment-adapter.d.ts.map +1 -1
  37. package/dist/protocol-deployment-adapter.js +2 -1
  38. package/dist/query-planner.d.ts +1 -1
  39. package/dist/query-planner.d.ts.map +1 -1
  40. package/dist/query-planner.js +5 -17
  41. package/dist/semantic-plan.d.ts +0 -1
  42. package/dist/semantic-plan.d.ts.map +1 -1
  43. package/dist/semantic-planner.d.ts.map +1 -1
  44. package/dist/semantic-planner.js +10 -3
  45. package/dist/semantic-query-schema.d.ts.map +1 -1
  46. package/dist/semantic-query-schema.js +2 -0
  47. package/dist/types.d.ts +61 -14
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/utils/agent-catalog-projection.d.ts +3 -2
  50. package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
  51. package/dist/utils/agent-catalog-projection.js +2 -10
  52. package/dist/utils/approximate-measures.d.ts +3 -2
  53. package/dist/utils/approximate-measures.d.ts.map +1 -1
  54. package/dist/utils/approximate-measures.js +11 -8
  55. package/dist/utils/composite-time-measure-sql.d.ts +10 -0
  56. package/dist/utils/composite-time-measure-sql.d.ts.map +1 -0
  57. package/dist/utils/composite-time-measure-sql.js +60 -0
  58. package/dist/utils/dataset-contract.d.ts.map +1 -1
  59. package/dist/utils/dataset-contract.js +2 -1
  60. package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
  61. package/dist/utils/dataset-definition-validation.js +6 -24
  62. package/dist/utils/dataset-derived-query.d.ts.map +1 -1
  63. package/dist/utils/dataset-derived-query.js +12 -55
  64. package/dist/utils/dataset-measures.d.ts +13 -6
  65. package/dist/utils/dataset-measures.d.ts.map +1 -1
  66. package/dist/utils/dataset-measures.js +25 -4
  67. package/dist/utils/dataset-metric-ref.d.ts +2 -2
  68. package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
  69. package/dist/utils/dataset-query-result.d.ts.map +1 -1
  70. package/dist/utils/dataset-query-result.js +2 -1
  71. package/dist/utils/dataset-query-validation.d.ts.map +1 -1
  72. package/dist/utils/dataset-query-validation.js +7 -3
  73. package/dist/utils/derived-measure-sql.d.ts +4 -0
  74. package/dist/utils/derived-measure-sql.d.ts.map +1 -0
  75. package/dist/utils/derived-measure-sql.js +49 -0
  76. package/dist/utils/derived-measure-validation.d.ts.map +1 -1
  77. package/dist/utils/derived-measure-validation.js +7 -27
  78. package/dist/utils/measure-dependencies.d.ts +8 -0
  79. package/dist/utils/measure-dependencies.d.ts.map +1 -0
  80. package/dist/utils/measure-dependencies.js +44 -0
  81. package/dist/utils/measure-evaluation-graph.d.ts +23 -0
  82. package/dist/utils/measure-evaluation-graph.d.ts.map +1 -0
  83. package/dist/utils/measure-evaluation-graph.js +38 -0
  84. package/dist/utils/measure-time-grains.d.ts +4 -0
  85. package/dist/utils/measure-time-grains.d.ts.map +1 -0
  86. package/dist/utils/measure-time-grains.js +18 -0
  87. package/dist/utils/query-timezone.d.ts +9 -0
  88. package/dist/utils/query-timezone.d.ts.map +1 -0
  89. package/dist/utils/query-timezone.js +35 -0
  90. package/dist/utils/semantic-metadata-validation.js +1 -1
  91. package/dist/utils/semantic-metadata.d.ts.map +1 -1
  92. package/dist/utils/semantic-metadata.js +3 -1
  93. package/dist/utils/shift-measure-definition.d.ts +4 -0
  94. package/dist/utils/shift-measure-definition.d.ts.map +1 -0
  95. package/dist/utils/shift-measure-definition.js +9 -0
  96. package/dist/utils/shift-measure-grains.d.ts +5 -0
  97. package/dist/utils/shift-measure-grains.d.ts.map +1 -0
  98. package/dist/utils/shift-measure-grains.js +10 -0
  99. package/dist/utils/shift-measure-sql.d.ts +21 -0
  100. package/dist/utils/shift-measure-sql.d.ts.map +1 -0
  101. package/dist/utils/shift-measure-sql.js +63 -0
  102. package/dist/utils/shift-measure-validation.d.ts +3 -0
  103. package/dist/utils/shift-measure-validation.d.ts.map +1 -0
  104. package/dist/utils/shift-measure-validation.js +20 -0
  105. package/dist/utils/shift-wall-time-sql.d.ts +4 -0
  106. package/dist/utils/shift-wall-time-sql.d.ts.map +1 -0
  107. package/dist/utils/shift-wall-time-sql.js +17 -0
  108. package/dist/utils/sql-under-joins.d.ts +10 -0
  109. package/dist/utils/sql-under-joins.d.ts.map +1 -0
  110. package/dist/utils/sql-under-joins.js +15 -0
  111. package/dist/utils/time-arithmetic-sql.d.ts +4 -0
  112. package/dist/utils/time-arithmetic-sql.d.ts.map +1 -0
  113. package/dist/utils/time-arithmetic-sql.js +9 -0
  114. package/dist/utils/time-axis-bounds.d.ts +24 -0
  115. package/dist/utils/time-axis-bounds.d.ts.map +1 -0
  116. package/dist/utils/time-axis-bounds.js +71 -0
  117. package/dist/utils/time-axis-calendar-guard.d.ts +9 -0
  118. package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -0
  119. package/dist/utils/time-axis-calendar-guard.js +17 -0
  120. package/dist/utils/time-axis-intervals.d.ts +13 -0
  121. package/dist/utils/time-axis-intervals.d.ts.map +1 -0
  122. package/dist/utils/time-axis-intervals.js +81 -0
  123. package/dist/utils/time-measure-axis-sql.d.ts +13 -0
  124. package/dist/utils/time-measure-axis-sql.d.ts.map +1 -0
  125. package/dist/utils/time-measure-axis-sql.js +48 -0
  126. package/dist/utils/time-measure-axis.d.ts +19 -0
  127. package/dist/utils/time-measure-axis.d.ts.map +1 -0
  128. package/dist/utils/time-measure-axis.js +46 -0
  129. package/dist/utils/time-measure-context-sql.d.ts +6 -0
  130. package/dist/utils/time-measure-context-sql.d.ts.map +1 -0
  131. package/dist/utils/time-measure-context-sql.js +21 -0
  132. package/dist/utils/time-measure-dataset-sql.d.ts +9 -0
  133. package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -0
  134. package/dist/utils/time-measure-dataset-sql.js +80 -0
  135. package/dist/utils/time-measure-leaf-sql.d.ts +14 -0
  136. package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -0
  137. package/dist/utils/time-measure-leaf-sql.js +52 -0
  138. package/dist/utils/time-measure-result-sql.d.ts +6 -0
  139. package/dist/utils/time-measure-result-sql.d.ts.map +1 -0
  140. package/dist/utils/time-measure-result-sql.js +30 -0
  141. package/dist/utils/time-measure-source-sql.d.ts +22 -0
  142. package/dist/utils/time-measure-source-sql.d.ts.map +1 -0
  143. package/dist/utils/time-measure-source-sql.js +75 -0
  144. package/dist/utils/time-measure-values-sql.d.ts +20 -0
  145. package/dist/utils/time-measure-values-sql.d.ts.map +1 -0
  146. package/dist/utils/time-measure-values-sql.js +87 -0
  147. package/dist/utils/time-query-measures.d.ts +6 -0
  148. package/dist/utils/time-query-measures.d.ts.map +1 -0
  149. package/dist/utils/time-query-measures.js +24 -0
  150. package/dist/utils/window-aggregation-sql.d.ts +5 -0
  151. package/dist/utils/window-aggregation-sql.d.ts.map +1 -0
  152. package/dist/utils/window-aggregation-sql.js +17 -0
  153. package/dist/utils/window-catalog-metadata.d.ts +14 -0
  154. package/dist/utils/window-catalog-metadata.d.ts.map +1 -0
  155. package/dist/utils/window-catalog-metadata.js +17 -0
  156. package/dist/utils/window-measure-definition.d.ts +4 -0
  157. package/dist/utils/window-measure-definition.d.ts.map +1 -0
  158. package/dist/utils/window-measure-definition.js +11 -0
  159. package/dist/utils/window-measure-dependencies.d.ts +4 -0
  160. package/dist/utils/window-measure-dependencies.d.ts.map +1 -0
  161. package/dist/utils/window-measure-dependencies.js +5 -0
  162. package/dist/utils/window-measure-validation.d.ts +3 -0
  163. package/dist/utils/window-measure-validation.d.ts.map +1 -0
  164. package/dist/utils/window-measure-validation.js +49 -0
  165. package/dist/utils/window-query-measures.d.ts +4 -0
  166. package/dist/utils/window-query-measures.d.ts.map +1 -0
  167. package/dist/utils/window-query-measures.js +17 -0
  168. package/dist/utils/window-query-signature.d.ts +4 -0
  169. package/dist/utils/window-query-signature.d.ts.map +1 -0
  170. package/dist/utils/window-query-signature.js +35 -0
  171. package/package.json +7 -7
@@ -0,0 +1,13 @@
1
+ import type { TimeGrain, WindowMeasureDefinition } from '../types.js';
2
+ import type { ParsedTimeBound } from './time-axis-bounds.js';
3
+ /** Exact conversions from RFC 0015; calendar months never become days. */
4
+ export declare function intervalBuckets(amount: number, unit: TimeGrain, grain: TimeGrain): number | undefined;
5
+ /** UTC estimate; SQL uses the time expression's timezone for exact buckets. */
6
+ export declare function utcBucketStart(timestamp: number, grain: TimeGrain): number;
7
+ /** Check the authored window's compatibility and maximum per-row contribution. */
8
+ export declare function windowGrainErrors(name: string, window: WindowMeasureDefinition, grain: TimeGrain): string[];
9
+ /** Estimate only: SQL determines exact bucket counts in the query timezone. */
10
+ export declare function estimateTimeAxisBuckets(start: ParsedTimeBound, end: ParsedTimeBound, upperInclusive: boolean, grain: TimeGrain): number;
11
+ /** Leave mixed bounds and local sub-day ranges to SQL's timezone-aware check. */
12
+ export declare function exceedsEstimatedTimeAxisLimit(start: ParsedTimeBound, end: ParsedTimeBound, grain: TimeGrain, bucketCount: number, resultLimit: number | undefined): boolean;
13
+ //# sourceMappingURL=time-axis-intervals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-axis-intervals.d.ts","sourceRoot":"","sources":["../../src/utils/time-axis-intervals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAa7D,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAQrG;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAgB1E;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAU3G;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,eAAe,EACpB,cAAc,EAAE,OAAO,EACvB,KAAK,EAAE,SAAS,GACf,MAAM,CAcR;AAED,iFAAiF;AACjF,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAMT"}
@@ -0,0 +1,81 @@
1
+ const FIXED_MINUTES = { minute: 1, hour: 60, day: 1440, week: 10080 };
2
+ const CALENDAR_MONTHS = { month: 1, quarter: 3, year: 12 };
3
+ const MAX_TO_DATE_BUCKETS = {
4
+ hour: { minute: 60 },
5
+ day: { minute: 1440, hour: 24 },
6
+ week: { minute: 10080, hour: 168, day: 7 },
7
+ month: { minute: 44640, hour: 744, day: 31 },
8
+ quarter: { minute: 132480, hour: 2208, day: 92, month: 3 },
9
+ year: { minute: 527040, hour: 8784, day: 366, month: 12, quarter: 4 },
10
+ };
11
+ /** Exact conversions from RFC 0015; calendar months never become days. */
12
+ export function intervalBuckets(amount, unit, grain) {
13
+ const units = FIXED_MINUTES[unit] ?? CALENDAR_MONTHS[unit];
14
+ const buckets = FIXED_MINUTES[grain] ?? CALENDAR_MONTHS[grain];
15
+ const bothFixed = FIXED_MINUTES[unit] !== undefined && FIXED_MINUTES[grain] !== undefined;
16
+ const bothCalendar = CALENDAR_MONTHS[unit] !== undefined && CALENDAR_MONTHS[grain] !== undefined;
17
+ if (units === undefined || buckets === undefined || (!bothFixed && !bothCalendar))
18
+ return undefined;
19
+ const count = amount * units / buckets;
20
+ return Number.isSafeInteger(count) && count > 0 ? count : undefined;
21
+ }
22
+ /** UTC estimate; SQL uses the time expression's timezone for exact buckets. */
23
+ export function utcBucketStart(timestamp, grain) {
24
+ const date = new Date(timestamp);
25
+ if (grain === 'minute') {
26
+ date.setUTCSeconds(0, 0);
27
+ return date.getTime();
28
+ }
29
+ if (grain === 'hour') {
30
+ date.setUTCMinutes(0, 0, 0);
31
+ return date.getTime();
32
+ }
33
+ date.setUTCHours(0, 0, 0, 0);
34
+ if (grain === 'week')
35
+ date.setUTCDate(date.getUTCDate() - date.getUTCDay());
36
+ if (grain === 'month' || grain === 'quarter' || grain === 'year')
37
+ date.setUTCDate(1);
38
+ if (grain === 'quarter')
39
+ date.setUTCMonth(Math.floor(date.getUTCMonth() / 3) * 3);
40
+ if (grain === 'year')
41
+ date.setUTCMonth(0);
42
+ return date.getTime();
43
+ }
44
+ /** Check the authored window's compatibility and maximum per-row contribution. */
45
+ export function windowGrainErrors(name, window, grain) {
46
+ const count = window.trailing
47
+ ? intervalBuckets(window.trailing.amount, window.trailing.unit, grain)
48
+ : window.toDate ? MAX_TO_DATE_BUCKETS[window.toDate]?.[grain] : 1;
49
+ if (count === undefined) {
50
+ const toDateRequirement = window.toDate ? ' at a strictly coarser grain' : '';
51
+ return [`Window measure "${name}" must span whole "${grain}" buckets${toDateRequirement}.`];
52
+ }
53
+ if (count > 1000)
54
+ return [`Window measure "${name}" would make a row contribute to more than 1,000 buckets.`];
55
+ return [];
56
+ }
57
+ /** Estimate only: SQL determines exact bucket counts in the query timezone. */
58
+ export function estimateTimeAxisBuckets(start, end, upperInclusive, grain) {
59
+ const first = utcBucketStart(start.milliseconds, grain);
60
+ const upperBucket = utcBucketStart(end.milliseconds, grain);
61
+ const excludesUpperBucket = !upperInclusive && !end.subMillisecond && end.milliseconds === upperBucket;
62
+ const last = utcBucketStart(end.milliseconds - (excludesUpperBucket ? 1 : 0), grain);
63
+ const calendar = CALENDAR_MONTHS[grain];
64
+ if (calendar !== undefined) {
65
+ const firstDate = new Date(first);
66
+ const lastDate = new Date(last);
67
+ const months = (lastDate.getUTCFullYear() - firstDate.getUTCFullYear()) * 12
68
+ + lastDate.getUTCMonth() - firstDate.getUTCMonth();
69
+ return months / calendar + 1;
70
+ }
71
+ return (last - first) / ((FIXED_MINUTES[grain] ?? 1) * 60000) + 1;
72
+ }
73
+ /** Leave mixed bounds and local sub-day ranges to SQL's timezone-aware check. */
74
+ export function exceedsEstimatedTimeAxisLimit(start, end, grain, bucketCount, resultLimit) {
75
+ if (resultLimit === undefined)
76
+ return false;
77
+ const canEstimate = start.hasOffset === end.hasOffset
78
+ && (start.hasOffset || (grain !== 'minute' && grain !== 'hour'));
79
+ // Allow two boundary buckets because the query timezone is unknown.
80
+ return resultLimit === 0 || (canEstimate && bucketCount - 2 > resultLimit);
81
+ }
@@ -0,0 +1,13 @@
1
+ import type { TimeMeasureAxis } from './time-measure-axis.js';
2
+ import type { TimeMeasureSqlSource } from './time-measure-source-sql.js';
3
+ import type { ShiftMeasureDefinition } from '../types.js';
4
+ export interface TimeMeasureSqlAxis {
5
+ ctes: string[];
6
+ timeAxisSql: string;
7
+ guard: string;
8
+ rangeGuard: string;
9
+ postSeriesGuard: string;
10
+ }
11
+ /** Build bounded output buckets using the query timezone. */
12
+ export declare function buildTimeMeasureAxisSql(source: TimeMeasureSqlSource, axis: TimeMeasureAxis, shifts?: readonly ShiftMeasureDefinition[]): TimeMeasureSqlAxis;
13
+ //# sourceMappingURL=time-measure-axis-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-axis-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAMD,6DAA6D;AAC7D,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,SAAS,sBAAsB,EAAO,GAC7C,kBAAkB,CAyCpB"}
@@ -0,0 +1,48 @@
1
+ import { GRAIN_FUNCTIONS } from '../constants.js';
2
+ import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
3
+ import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
4
+ import { shiftCalendarGuardSql, shiftWallTimeSeriesGuardSql } from './shift-measure-sql.js';
5
+ function literal(value) {
6
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
7
+ }
8
+ /** Build bounded output buckets using the query timezone. */
9
+ export function buildTimeMeasureAxisSql(source, axis, shifts = []) {
10
+ const first = `${GRAIN_FUNCTIONS[axis.grain]}(_hq_lower)`;
11
+ const upperBucket = `${GRAIN_FUNCTIONS[axis.grain]}(_hq_upper)`;
12
+ const last = axis.upperInclusive
13
+ ? upperBucket
14
+ : `if(_hq_upper = ${upperBucket}, ${subtract(upperBucket, 1, axis.grain)}, ${upperBucket})`;
15
+ const count = `dateDiff('${axis.grain}', _hq_first, _hq_last) + 1`;
16
+ const emptyRange = axis.lowerInclusive && axis.upperInclusive ? '' : ' OR _hq_lower = _hq_upper';
17
+ const rangeGuard = `throwIf(_hq_lower > _hq_upper${emptyRange}, 'Window measure time range must be non-empty and ordered.')`;
18
+ const limitGuard = axis.resultLimit === undefined
19
+ ? '0'
20
+ : `throwIf(${count} > ${axis.resultLimit}, 'Window series exceeds the effective result limit of ${axis.resultLimit} buckets.')`;
21
+ const calendarGuard = calendarBucketGuardSql('_hq_first', `${count} + ${limitGuard} + ${rangeGuard}`, axis.grain);
22
+ const physicalTimeType = `toTypeName(tupleElement((SELECT ${source.rowAlias} FROM _hq_raw LIMIT 0), 1))`;
23
+ const physicalGuard = axis.grain === 'minute' || axis.grain === 'hour'
24
+ ? `throwIf(position(${physicalTimeType}, 'Date') > 0 AND position(${physicalTimeType}, 'DateTime') = 0, 'Sub-day time measures require a time-of-day column; Date and Date32 are not supported.')`
25
+ : '0';
26
+ const shiftGuards = shifts.map(shift => shiftCalendarGuardSql(shift, axis, `${limitGuard} + ${rangeGuard}`));
27
+ const guard = [limitGuard, physicalGuard, calendarGuard, ...shiftGuards].join(' + ');
28
+ const postSeriesGuard = shifts.map(shift => shiftWallTimeSeriesGuardSql(shift, axis.grain)).join(' + ') || '0';
29
+ // A zero-row scalar retains the converted timestamp type and query timezone.
30
+ // Reading any(time) here would scan the entire population just for its type.
31
+ const timezone = 'timezoneOf(assumeNotNull((SELECT _hq_time FROM _hq_source LIMIT 0)))';
32
+ const bounds = [
33
+ `parseDateTime64BestEffort(${literal(axis.lower)}, 9, ${timezone}) AS _hq_lower`,
34
+ `parseDateTime64BestEffort(${literal(axis.upper)}, 9, ${timezone}) AS _hq_upper`,
35
+ `${first} AS _hq_first`,
36
+ `${last} AS _hq_last`,
37
+ ];
38
+ const bucketIndices = `arrayJoin(range(toUInt64(${count} + ${guard} + ${rangeGuard})))`;
39
+ const period = add('_hq_first', bucketIndices, axis.grain);
40
+ const ctes = [
41
+ `_hq_bounds AS (SELECT ${bounds.join(', ')})`,
42
+ `_hq_series AS (SELECT ${period} AS _hq_period FROM _hq_bounds)`,
43
+ ];
44
+ // Always check the singleton axis before aggregating. ClickHouse can skip
45
+ // a series guard when a dimensional CROSS JOIN has an empty population.
46
+ const timeAxisSql = `WITH ${[...source.ctes, ...ctes].join(',\n')} SELECT ${guard} + ${rangeGuard} + ${postSeriesGuard} AS _hq_validated FROM _hq_bounds`;
47
+ return { ctes, timeAxisSql, guard, rangeGuard, postSeriesGuard };
48
+ }
@@ -0,0 +1,19 @@
1
+ import type { AnyDatasetInstance, DatasetQuery, MetricFilter, TimeGrain } from '../types.js';
2
+ export { intervalBuckets, utcBucketStart } from './time-axis-intervals.js';
3
+ export interface TimeMeasureAxis {
4
+ grain: TimeGrain;
5
+ lower: string;
6
+ upper: string;
7
+ lowerInclusive: boolean;
8
+ upperInclusive: boolean;
9
+ filters: MetricFilter[];
10
+ /** UTC estimate; execution enforces the exact timezone-aware count. */
11
+ bucketCount: number;
12
+ resultLimit?: number;
13
+ }
14
+ /** Validate time-measure grains, resolve bounds, then estimate the output series. */
15
+ export declare function analyzeTimeMeasureAxis(dataset: AnyDatasetInstance, query: DatasetQuery): {
16
+ axis?: TimeMeasureAxis;
17
+ errors: string[];
18
+ };
19
+ //# sourceMappingURL=time-measure-axis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-axis.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAS7F,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE3E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qFAAqF;AACrF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,GAClB;IAAE,IAAI,CAAC,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAmC9C"}
@@ -0,0 +1,46 @@
1
+ import { isSupportedTimeGrain } from '../constants.js';
2
+ import { selectedTimeMeasures } from './time-query-measures.js';
3
+ import { isShiftMeasure } from './dataset-measures.js';
4
+ import { resolveTimeAxisRange, isDefinitelyEmptyTimeRange } from './time-axis-bounds.js';
5
+ import { windowGrainErrors, estimateTimeAxisBuckets, exceedsEstimatedTimeAxisLimit } from './time-axis-intervals.js';
6
+ import { supportsShiftGrain } from './shift-measure-grains.js';
7
+ export { intervalBuckets, utcBucketStart } from './time-axis-intervals.js';
8
+ /** Validate time-measure grains, resolve bounds, then estimate the output series. */
9
+ export function analyzeTimeMeasureAxis(dataset, query) {
10
+ const timeMeasures = selectedTimeMeasures(dataset, query);
11
+ if (timeMeasures.size === 0)
12
+ return { errors: [] };
13
+ const errors = [];
14
+ const subjects = [...timeMeasures.values()].some(isShiftMeasure) ? 'Window and shift measures' : 'Window measures';
15
+ if (!isSupportedTimeGrain(query.by))
16
+ return { errors: [`${subjects} require a supported "by" grain.`] };
17
+ const grain = query.by;
18
+ for (const [name, window] of timeMeasures) {
19
+ if (isShiftMeasure(window)) {
20
+ if (!supportsShiftGrain(window.interval, grain))
21
+ errors.push(`Shift measure "${name}" must span whole "${grain}" buckets.`);
22
+ continue;
23
+ }
24
+ errors.push(...windowGrainErrors(name, window, grain));
25
+ }
26
+ const range = resolveTimeAxisRange(dataset, query.filters ?? []);
27
+ if (!range) {
28
+ return { errors: [...errors, `${subjects} require exactly one bounded time range: between, or gt/gte with lt/lte, using ISO timestamps.`] };
29
+ }
30
+ if (isDefinitelyEmptyTimeRange(range)) {
31
+ return { errors: [...errors, 'Window measure time range must be non-empty and ordered.'] };
32
+ }
33
+ const { start, end, lowerInclusive, upperInclusive, filters } = range;
34
+ const bucketCount = estimateTimeAxisBuckets(start, end, upperInclusive, grain);
35
+ const resultLimit = query.limit ?? dataset.limits?.maxResultSize;
36
+ if (exceedsEstimatedTimeAxisLimit(start, end, grain, bucketCount, resultLimit)) {
37
+ errors.push(`Window series exceeds the effective result limit of ${resultLimit} buckets.`);
38
+ }
39
+ return {
40
+ errors,
41
+ axis: {
42
+ grain, lower: start.text, upper: end.text,
43
+ lowerInclusive, upperInclusive, filters, bucketCount, resultLimit,
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,6 @@
1
+ import type { MeasureEvaluationContext } from './measure-evaluation-graph.js';
2
+ import type { TimeMeasureAxis } from './time-measure-axis.js';
3
+ /** Keep output coordinates separate from each shifted evaluation interval. */
4
+ export declare function timeMeasureContextCtes(contexts: readonly MeasureEvaluationContext[], axis: TimeMeasureAxis): string[];
5
+ export declare function timeMeasureContextGuard(context: MeasureEvaluationContext, axis: TimeMeasureAxis): string;
6
+ //# sourceMappingURL=time-measure-context-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-context-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-context-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM9D,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAUrH;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAOxG"}
@@ -0,0 +1,21 @@
1
+ import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
2
+ import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
3
+ import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
4
+ import { shiftWallTimeGuardSql } from './shift-wall-time-sql.js';
5
+ /** Keep output coordinates separate from each shifted evaluation interval. */
6
+ export function timeMeasureContextCtes(contexts, axis) {
7
+ const end = add('_hq_period', 1, axis.grain);
8
+ const root = `_hq_context0 AS (SELECT _hq_period, _hq_period AS _hq_eval_period, ${end} AS _hq_eval_end, greatest(_hq_period, _hq_lower) AS _hq_eval_lower, least(${end}, _hq_upper) AS _hq_eval_upper, (_hq_period > _hq_lower OR ${Number(axis.lowerInclusive)}) AS _hq_lower_inclusive, (_hq_upper < ${end} AND ${Number(axis.upperInclusive)}) AS _hq_upper_inclusive FROM _hq_series CROSS JOIN _hq_bounds)`;
9
+ return [root, ...contexts.slice(1).map(context => {
10
+ const interval = context.interval;
11
+ const shift = (value) => subtract(`p.${value}`, interval.amount, interval.unit);
12
+ const period = shift('_hq_eval_period');
13
+ const shiftedEnd = calendarShiftAtFineGrain(interval, axis.grain) ? add(period, 1, axis.grain) : shift('_hq_eval_end');
14
+ return `_hq_context${context.id} AS (SELECT p._hq_period, ${period} AS _hq_eval_period, ${shiftedEnd} AS _hq_eval_end, ${shift('_hq_eval_lower')} AS _hq_eval_lower, if(p._hq_eval_upper = p._hq_eval_end, ${shiftedEnd}, ${shift('_hq_eval_upper')}) AS _hq_eval_upper, p._hq_lower_inclusive, p._hq_upper_inclusive FROM _hq_context${context.parent} AS p)`;
15
+ })];
16
+ }
17
+ export function timeMeasureContextGuard(context, axis) {
18
+ const guard = calendarBucketGuardSql('_hq_eval_period', '1', axis.grain);
19
+ const wallTimeGuard = context.interval === undefined ? '0' : shiftWallTimeGuardSql(context.interval, axis.grain, `_hq_context${context.parent} AS p`, 'p._hq_eval_period', 'p._hq_eval_lower', 'p._hq_eval_upper', 'p._hq_eval_end');
20
+ return `(SELECT coalesce(max(${guard}), 0) FROM _hq_context${context.id}) + ${wallTimeGuard}`;
21
+ }
@@ -0,0 +1,9 @@
1
+ import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
2
+ import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
3
+ /** Plan source rows → output axis → window/shift values → final projection. */
4
+ export declare function buildTimeMeasureDatasetSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions): {
5
+ sql: string;
6
+ parameters: unknown[];
7
+ timeAxisSql: string;
8
+ };
9
+ //# sourceMappingURL=time-measure-dataset-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-dataset-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-dataset-sql.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAYxE,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,GACpC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAmE7D"}
@@ -0,0 +1,80 @@
1
+ import { buildCompositeTimeMeasureSql } from './composite-time-measure-sql.js';
2
+ import { measureDependencyNames } from './measure-dependencies.js';
3
+ import { getBaseMeasure, getDerivedMeasure, isShiftMeasure, isWindowMeasure } from './dataset-measures.js';
4
+ import { selectedTimeMeasures } from './time-query-measures.js';
5
+ import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
6
+ import { validateDatasetQueryInput } from './dataset-query-validation.js';
7
+ import { addTimeSql as add } from './time-arithmetic-sql.js';
8
+ import { shiftBucketCtes } from './shift-measure-sql.js';
9
+ import { buildTimeMeasureSourceSql } from './time-measure-source-sql.js';
10
+ import { buildTimeMeasureAxisSql } from './time-measure-axis-sql.js';
11
+ import { buildTimeMeasureValuesSql, windowMeasureRowsSql, windowScanStartSql } from './time-measure-values-sql.js';
12
+ import { buildTimeMeasureResultSql } from './time-measure-result-sql.js';
13
+ /** Plan source rows → output axis → window/shift values → final projection. */
14
+ export function buildTimeMeasureDatasetSql(ds, query, options) {
15
+ const validation = validateDatasetQueryInput(ds, query, options.context);
16
+ if (!validation.valid)
17
+ throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
18
+ const { axis } = analyzeTimeMeasureAxis(ds, query);
19
+ if (!axis || !ds.timeKey)
20
+ throw new Error('Window measures require a bounded time axis.');
21
+ const dependencies = measureDependencyNames(ds.measures, query.measures ?? []);
22
+ const composite = dependencies.some(name => {
23
+ const definition = ds.measures[name];
24
+ if (isShiftMeasure(definition))
25
+ return !getBaseMeasure(ds.measures, definition.measure);
26
+ const derived = getDerivedMeasure(ds.measures, name);
27
+ return derived && Object.values(derived.uses).some(input => getDerivedMeasure(ds.measures, input));
28
+ });
29
+ if (composite)
30
+ return buildCompositeTimeMeasureSql(ds, query, options, axis);
31
+ const timeMeasures = selectedTimeMeasures(ds, query);
32
+ const windows = [...timeMeasures.values()].filter(isWindowMeasure);
33
+ const shifts = [...timeMeasures.values()].filter(isShiftMeasure);
34
+ const needed = new Set((query.measures ?? []).filter(name => !getDerivedMeasure(ds.measures, name)));
35
+ for (const name of query.measures ?? []) {
36
+ const derived = getDerivedMeasure(ds.measures, name);
37
+ if (derived)
38
+ Object.values(derived.uses).forEach(input => needed.add(input));
39
+ }
40
+ const baseNames = new Set([...needed].map(name => timeMeasures.get(name)?.measure ?? name));
41
+ const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options);
42
+ const timeAxis = buildTimeMeasureAxisSql(source, axis, shifts);
43
+ const shiftedInputs = new Map();
44
+ [...needed].forEach((name, index) => {
45
+ const measure = timeMeasures.get(name);
46
+ if (isShiftMeasure(measure))
47
+ shiftedInputs.set(name, shiftBucketCtes(measure, axis, index));
48
+ });
49
+ // Scan only requested populations: intervening rows must not introduce dimensions.
50
+ const end = add('_hq_last', 1, axis.grain);
51
+ const populations = [];
52
+ if ([...needed].some(name => getBaseMeasure(ds.measures, name))) {
53
+ populations.push(`(_hq_time >= _hq_first AND _hq_time < ${end})`);
54
+ }
55
+ if (windows.length) {
56
+ const starts = windows.filter(window => !window.cumulative)
57
+ .map(window => windowScanStartSql(window, axis.grain));
58
+ const lower = windows.some(window => window.cumulative)
59
+ ? '' : ` AND _hq_time >= least(${starts.join(', ')})`;
60
+ populations.push(`(_hq_time < ${end}${lower})`);
61
+ }
62
+ for (const shifted of shiftedInputs.values())
63
+ populations.push(`(_hq_time IN (${shifted.sourceTimesSql}))`);
64
+ const scanCte = `_hq_scanned AS (SELECT * FROM _hq_source CROSS JOIN _hq_bounds WHERE ${populations.join(' OR ')})`;
65
+ const inputs = [...needed].map(name => {
66
+ const timeMeasure = timeMeasures.get(name);
67
+ const window = isWindowMeasure(timeMeasure) ? timeMeasure : undefined;
68
+ const base = source.bases.find(base => base.name === (timeMeasure?.measure ?? name));
69
+ if (!base)
70
+ throw new Error(`Missing base input for measure "${name}".`);
71
+ if (isShiftMeasure(timeMeasure)) {
72
+ const shifted = shiftedInputs.get(name);
73
+ return { name, base, rowsSql: shifted.rowsSql, restrictToAxis: false };
74
+ }
75
+ return { name, base, window, rowsSql: windowMeasureRowsSql(window, axis) };
76
+ });
77
+ const values = buildTimeMeasureValuesSql(source.dimensions, inputs, axis);
78
+ const sql = buildTimeMeasureResultSql(ds, query, options, source.dimensions, [...source.ctes, ...timeAxis.ctes, ...[...shiftedInputs.values()].flatMap(input => input.ctes), scanCte, ...values], `${timeAxis.guard} + ${timeAxis.rangeGuard} + ${timeAxis.postSeriesGuard}`);
79
+ return { sql, parameters: source.parameters, timeAxisSql: timeAxis.timeAxisSql };
80
+ }
@@ -0,0 +1,14 @@
1
+ import type { WindowMeasureDefinition } from '../types.js';
2
+ import type { TimeMeasureAxis } from './time-measure-axis.js';
3
+ import type { TimeMeasureSqlBase, TimeMeasureSqlDimension } from './time-measure-source-sql.js';
4
+ /** Keyed source membership retains distinct/average semantics and row multiplicity. */
5
+ export declare function timeMeasureLeafSql(id: number, context: number, base: TimeMeasureSqlBase, window: WindowMeasureDefinition | undefined, axis: TimeMeasureAxis, dims: readonly TimeMeasureSqlDimension[]): {
6
+ ctes: string[];
7
+ population: string;
8
+ aggregate: string;
9
+ context: number;
10
+ cumulative: boolean;
11
+ guard: string;
12
+ count: boolean;
13
+ };
14
+ //# sourceMappingURL=time-measure-leaf-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-leaf-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-leaf-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAMhG,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,uBAAuB,EAAE;;;;;;;;EA6CrM"}
@@ -0,0 +1,52 @@
1
+ import { GRAIN_FUNCTIONS } from '../constants.js';
2
+ import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
3
+ import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
4
+ import { shiftRangePredicateSql } from './shift-measure-sql.js';
5
+ import { isCountAggregation, windowAggregateSql } from './window-aggregation-sql.js';
6
+ /** Keyed source membership retains distinct/average semantics and row multiplicity. */
7
+ export function timeMeasureLeafSql(id, context, base, window, axis, dims) {
8
+ const keys = dims.map(dim => dim.alias);
9
+ const ranges = `_hq_ranges${id}`;
10
+ const rows = `_hq_rows${id}`;
11
+ const aggregate = `_hq_leaf${id}`;
12
+ const value = `_hq_value${id}`;
13
+ const group = `_hq_period${keys.length ? `, ${keys.join(', ')}` : ''}`;
14
+ const expression = windowAggregateSql(base.definition, base.value, base.arg);
15
+ if (window?.cumulative) {
16
+ const population = `_hq_history${id}`;
17
+ const partial = `_hq_partial${id}`;
18
+ const operation = base.definition.aggregation === 'count' ? 'sum' : base.definition.aggregation;
19
+ const partition = keys.length ? `tuple(${keys.join(', ')})` : 'tuple(0)';
20
+ const historyGroup = `_hq_time${keys.length ? `, ${keys.join(', ')}` : ''}`;
21
+ return {
22
+ ctes: [
23
+ `${population} AS (SELECT * FROM _hq_source WHERE _hq_time < (SELECT max(_hq_eval_end) FROM _hq_context${context}))`,
24
+ `${partial} AS (SELECT ${historyGroup}, ${partition} AS _hq_partition, toNullable(${expression}) AS _hq_partial FROM ${population} GROUP BY ${historyGroup})`,
25
+ `${aggregate} AS (SELECT _hq_time, _hq_partition, ${operation}OrNull(_hq_partial) OVER (PARTITION BY _hq_partition ORDER BY _hq_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS ${value} FROM ${partial})`,
26
+ ],
27
+ population, aggregate, context, cumulative: true, guard: '0',
28
+ count: isCountAggregation(base.definition),
29
+ };
30
+ }
31
+ const lower = window?.trailing ? subtract('_hq_eval_end', window.trailing.amount, window.trailing.unit)
32
+ : window?.toDate ? `${GRAIN_FUNCTIONS[window.toDate]}(_hq_eval_period)` : '_hq_eval_lower';
33
+ const upper = window ? '_hq_eval_end' : '_hq_eval_upper';
34
+ const lowerInclusive = window ? '1' : '_hq_lower_inclusive';
35
+ const upperInclusive = window ? '0' : '_hq_upper_inclusive';
36
+ const first = `${GRAIN_FUNCTIONS[axis.grain]}(_hq_scan_lower)`;
37
+ const last = `${GRAIN_FUNCTIONS[axis.grain]}(_hq_scan_upper)`;
38
+ const count = `greatest(0, dateDiff('${axis.grain}', ${first}, ${last}) + if(_hq_scan_upper > ${last} OR _hq_scan_upper_inclusive, 1, 0))`;
39
+ const fanout = window ? `throwIf(${count} > 1000, 'A window row contributes to more than 1000 buckets.')` : '0';
40
+ const guard = `${fanout} + ${calendarBucketGuardSql(first, `${count} + ${fanout}`, axis.grain)}`;
41
+ const predicate = shiftRangePredicateSql('r._hq_time', { lower: 'b._hq_scan_lower', upper: 'b._hq_scan_upper', lowerInclusive: 'b._hq_scan_lower_inclusive', upperInclusive: 'b._hq_scan_upper_inclusive' });
42
+ return {
43
+ ctes: [
44
+ `${ranges} AS (SELECT _hq_period, ${lower} AS _hq_scan_lower, ${upper} AS _hq_scan_upper, ${lowerInclusive} AS _hq_scan_lower_inclusive, ${upperInclusive} AS _hq_scan_upper_inclusive FROM _hq_context${context})`,
45
+ `_hq_keys${id} AS (SELECT *, ${add(first, `arrayJoin(range(toUInt64(${count} + ${guard})))`, axis.grain)} AS _hq_source_period FROM ${ranges})`,
46
+ `${rows} AS (SELECT r.*, b._hq_period FROM _hq_source AS r INNER JOIN _hq_keys${id} AS b ON ${GRAIN_FUNCTIONS[axis.grain]}(r._hq_time) = b._hq_source_period WHERE ${predicate})`,
47
+ `${aggregate} AS (SELECT ${group}, toNullable(${expression}) AS ${value} FROM ${rows} GROUP BY ${group})`,
48
+ ],
49
+ population: rows, aggregate, context, cumulative: false,
50
+ guard: `(SELECT coalesce(max(${guard}), 0) FROM ${ranges})`, count: isCountAggregation(base.definition),
51
+ };
52
+ }
@@ -0,0 +1,6 @@
1
+ import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
2
+ import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
3
+ import type { TimeMeasureSqlDimension } from './time-measure-source-sql.js';
4
+ /** Evaluate formulas after aggregation, then apply output ordering and pagination. */
5
+ export declare function buildTimeMeasureResultSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, dims: readonly TimeMeasureSqlDimension[], ctes: readonly string[], validationGuard: string, formulasEvaluated?: boolean): string;
6
+ //# sourceMappingURL=time-measure-result-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-result-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-result-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAIxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5E,sFAAsF;AACtF,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,IAAI,EAAE,SAAS,uBAAuB,EAAE,EACxC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,eAAe,EAAE,MAAM,EACvB,iBAAiB,UAAQ,GACxB,MAAM,CAuBR"}
@@ -0,0 +1,30 @@
1
+ import { quoteSQLIdentifier } from '../sql-utils.js';
2
+ import { getDerivedMeasure } from './dataset-measures.js';
3
+ import { derivedProjection } from './derived-measure-sql.js';
4
+ /** Evaluate formulas after aggregation, then apply output ordering and pagination. */
5
+ export function buildTimeMeasureResultSql(ds, query, options, dims, ctes, validationGuard, formulasEvaluated = false) {
6
+ const selected = query.measures ?? [];
7
+ const projections = [
8
+ quoteSQLIdentifier('period'),
9
+ ...dims.map(dimension => quoteSQLIdentifier(dimension.name)),
10
+ ...selected.map(name => {
11
+ const derived = getDerivedMeasure(ds.measures, name);
12
+ return derived && !formulasEvaluated ? derivedProjection(name, derived) : quoteSQLIdentifier(name);
13
+ }),
14
+ ];
15
+ // Keep toSQL independently guarded too. This branch produces no valid
16
+ // result rows, and forces axis validation even when _hq_values is empty.
17
+ const outputNames = ['period', ...dims.map(dimension => dimension.name), ...selected];
18
+ const nullProjections = outputNames.map(name => `NULL AS ${quoteSQLIdentifier(name)}`).join(', ');
19
+ const resultCte = `_hq_result AS (SELECT ${projections.join(', ')} FROM _hq_values`
20
+ + ` UNION ALL SELECT ${nullProjections} FROM _hq_bounds WHERE ${validationGuard} = 1)`;
21
+ let sql = `WITH ${[...ctes, resultCte].join(',\n')} SELECT ${outputNames.map(quoteSQLIdentifier).join(', ')} FROM _hq_result`;
22
+ const order = query.orderBy?.length ? query.orderBy : [{ field: 'period', direction: 'asc' }];
23
+ sql += ` ORDER BY ${order.map(item => `${quoteSQLIdentifier(item.field)} ${item.direction === 'asc' ? 'ASC' : 'DESC'}`).join(', ')}`;
24
+ const limit = options.executionLimit ?? query.limit;
25
+ if (limit !== undefined)
26
+ sql += ` LIMIT ${limit}`;
27
+ if (query.offset !== undefined)
28
+ sql += ` OFFSET ${query.offset}`;
29
+ return sql;
30
+ }
@@ -0,0 +1,22 @@
1
+ import type { AnyDatasetInstance, DatasetQuery, MeasureDefinition, MetricFilter } from '../types.js';
2
+ import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
3
+ export interface TimeMeasureSqlDimension {
4
+ name: string;
5
+ alias: string;
6
+ }
7
+ export interface TimeMeasureSqlBase {
8
+ name: string;
9
+ definition: MeasureDefinition;
10
+ value: string;
11
+ arg: string;
12
+ }
13
+ export interface TimeMeasureSqlSource {
14
+ ctes: string[];
15
+ parameters: unknown[];
16
+ dimensions: TimeMeasureSqlDimension[];
17
+ bases: TimeMeasureSqlBase[];
18
+ rowAlias: string;
19
+ }
20
+ /** Isolate authored expressions, joins, tenancy and filters from internal SQL aliases. */
21
+ export declare function buildTimeMeasureSourceSql(ds: AnyDatasetInstance, query: DatasetQuery, baseNames: Iterable<string>, filters: readonly MetricFilter[], options: DatasetQueryExecutionOptions): TimeMeasureSqlSource;
22
+ //# sourceMappingURL=time-measure-source-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-source-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-source-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAUxE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,0FAA0F;AAC1F,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,EAAE,4BAA4B,GACpC,oBAAoB,CA2DtB"}
@@ -0,0 +1,75 @@
1
+ import { queryTimeSql } from './query-timezone.js';
2
+ import { resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn } from '../query-planner.js';
3
+ import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn } from './relationship-builder-plan.js';
4
+ import { getRuntimeTenantPredicate } from './tenant-runtime.js';
5
+ import { segmentFilters } from './segments.js';
6
+ import { getBaseMeasure } from './dataset-measures.js';
7
+ import { measureToAggregationSpec } from './dataset-normalization.js';
8
+ import { applyFilteredAggregationExpression } from './filtered-aggregation-sql.js';
9
+ import { assertNoRawSqlUnderJoins } from './sql-under-joins.js';
10
+ /** Isolate authored expressions, joins, tenancy and filters from internal SQL aliases. */
11
+ export function buildTimeMeasureSourceSql(ds, query, baseNames, filters, options) {
12
+ const bases = [];
13
+ const joinCtx = buildRelationshipBuilderContext(ds, query, options.context);
14
+ // Keep source expressions in their own SELECT scope: internal aliases cannot
15
+ // capture authored SQL identifiers, even if a physical column shares a name.
16
+ let raw = applyRelationshipJoins(options.builderFactory.table(ds.source), joinCtx);
17
+ const dims = (query.dimensions ?? []).map((name, i) => ({ name, alias: `_hq_d${i}` }));
18
+ const sourceParts = [resolveDimensionExpression(ds, ds.timeKey, joinCtx)];
19
+ for (const dimension of dims)
20
+ sourceParts.push(resolveDimensionExpression(ds, dimension.name, joinCtx));
21
+ for (const name of baseNames) {
22
+ const definition = getBaseMeasure(ds.measures, name);
23
+ if (!definition)
24
+ throw new Error(`Window input "${name}" is not a base measure.`);
25
+ const spec = measureToAggregationSpec(name, definition);
26
+ if (spec.sql)
27
+ assertNoRawSqlUnderJoins('measure', name, spec.sql, joinCtx);
28
+ const value = `_hq_v${bases.length}`;
29
+ const arg = `_hq_a${bases.length}`;
30
+ bases.push({ name, definition, value, arg });
31
+ const expression = spec.sql ?? resolveDimensionExpression(ds, spec.field, joinCtx);
32
+ sourceParts.push(applyFilteredAggregationExpression(ds, spec, expression, joinCtx));
33
+ sourceParts.push(spec.argField ? resolveDimensionExpression(ds, spec.argField, joinCtx) : 'NULL');
34
+ }
35
+ // A positional tuple isolates internal names from the source table's names.
36
+ const sourceIdentifiers = [
37
+ ...sourceParts,
38
+ ...Object.keys(ds.dimensions),
39
+ ...Object.values(ds.dimensions).map(dimension => dimension.sql ?? dimension.column ?? ''),
40
+ ...Object.values(ds.filters).map(filter => filter.field ?? ''),
41
+ ds.tenantKey ?? '',
42
+ ];
43
+ let rowAlias = '_hq_row';
44
+ while (sourceIdentifiers.some(text => text.includes(rowAlias)))
45
+ rowAlias += '_';
46
+ raw = raw.select([`tuple(${sourceParts.join(', ')}) AS ${rowAlias}`]);
47
+ const tenant = getRuntimeTenantPredicate(options.context);
48
+ const tenantColumn = resolveTenantFilterColumn(ds, options.context);
49
+ if (tenant && tenantColumn)
50
+ raw = raw.where(qualifyBaseColumn(joinCtx, tenantColumn), tenant.operator, tenant.value);
51
+ for (const filter of filters) {
52
+ raw = raw.where(resolveFilterField(ds, filter.field, joinCtx), filter.operator, filter.value);
53
+ }
54
+ for (const filter of segmentFilters(ds, query.segments)) {
55
+ raw = raw.where(resolveDimensionExpression(ds, filter.field, joinCtx), filter.operator, filter.value);
56
+ }
57
+ const { sql: rawSql, parameters } = raw.toSQLWithParams();
58
+ const sourceColumns = [`${queryTimeSql(`assumeNotNull(tupleElement(${rowAlias}, 1))`, query.timezone)} AS _hq_time`];
59
+ dims.forEach((dimension, i) => sourceColumns.push(`tupleElement(${rowAlias}, ${i + 2}) AS ${dimension.alias}`));
60
+ bases.forEach((base, i) => {
61
+ const valuePosition = dims.length + 2 + i * 2;
62
+ sourceColumns.push(`tupleElement(${rowAlias}, ${valuePosition}) AS ${base.value}`);
63
+ sourceColumns.push(`tupleElement(${rowAlias}, ${valuePosition + 1}) AS ${base.arg}`);
64
+ });
65
+ return {
66
+ ctes: [
67
+ `_hq_raw AS (${rawSql})`,
68
+ `_hq_source AS (SELECT ${sourceColumns.join(', ')} FROM _hq_raw WHERE isNotNull(tupleElement(${rowAlias}, 1)))`,
69
+ ],
70
+ parameters,
71
+ dimensions: dims,
72
+ bases,
73
+ rowAlias,
74
+ };
75
+ }
@@ -0,0 +1,20 @@
1
+ import type { TimeGrain, WindowMeasureDefinition } from '../types.js';
2
+ import type { TimeMeasureAxis } from './time-measure-axis.js';
3
+ import type { TimeMeasureSqlBase, TimeMeasureSqlDimension } from './time-measure-source-sql.js';
4
+ export interface TimeMeasureSqlInput {
5
+ name: string;
6
+ base: TimeMeasureSqlBase;
7
+ window?: WindowMeasureDefinition;
8
+ /** Source rows mapped to output periods before aggregation. */
9
+ rowsSql: string;
10
+ /** Extra population CTEs, such as aligned shift ranges. */
11
+ rowCtes?: string[];
12
+ /** Shift populations already map exclusively to the requested output series. */
13
+ restrictToAxis?: boolean;
14
+ }
15
+ export declare function windowScanStartSql(window: WindowMeasureDefinition, grain: TimeGrain): string;
16
+ /** Base measures respect partial endpoints; windows use entire contributing buckets. */
17
+ export declare function windowMeasureRowsSql(window: WindowMeasureDefinition | undefined, axis: TimeMeasureAxis): string;
18
+ /** Fill dimension combinations, aggregate populations, and join measure values onto the series. */
19
+ export declare function buildTimeMeasureValuesSql(dims: readonly TimeMeasureSqlDimension[], inputs: readonly TimeMeasureSqlInput[], axis: TimeMeasureAxis): string[];
20
+ //# sourceMappingURL=time-measure-values-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-measure-values-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-values-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAKhG,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAwBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAI5F;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAG/G;AAED,mGAAmG;AACnG,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,SAAS,uBAAuB,EAAE,EACxC,MAAM,EAAE,SAAS,mBAAmB,EAAE,EACtC,IAAI,EAAE,eAAe,GACpB,MAAM,EAAE,CA0CV"}