@hypequery/datasets 0.23.0 → 0.24.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 (223) hide show
  1. package/README.md +14 -0
  2. package/dist/aggregations.d.ts +3 -0
  3. package/dist/aggregations.d.ts.map +1 -1
  4. package/dist/aggregations.js +6 -0
  5. package/dist/cache/definition-identity.js +2 -2
  6. package/dist/cache/query-signature.d.ts.map +1 -1
  7. package/dist/cache/query-signature.js +3 -0
  8. package/dist/catalog.d.ts +3 -0
  9. package/dist/catalog.d.ts.map +1 -1
  10. package/dist/catalog.js +3 -2
  11. package/dist/client-contract/query-schema.d.ts +19 -0
  12. package/dist/client-contract/query-schema.d.ts.map +1 -0
  13. package/dist/client-contract/query-schema.js +85 -0
  14. package/dist/constants.d.ts +5 -4
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +7 -6
  17. package/dist/contract.d.ts +2 -0
  18. package/dist/contract.d.ts.map +1 -1
  19. package/dist/contract.js +4 -3
  20. package/dist/dataset-sql-dialect.d.ts +117 -0
  21. package/dist/dataset-sql-dialect.d.ts.map +1 -1
  22. package/dist/dataset.d.ts +1 -1
  23. package/dist/dataset.d.ts.map +1 -1
  24. package/dist/dataset.js +2 -1
  25. package/dist/in-memory-backend.d.ts.map +1 -1
  26. package/dist/in-memory-backend.js +2 -0
  27. package/dist/index.d.ts +48 -5
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +34 -6
  30. package/dist/internal.d.ts +5 -1
  31. package/dist/internal.d.ts.map +1 -1
  32. package/dist/internal.js +5 -1
  33. package/dist/measure.d.ts +9 -1
  34. package/dist/measure.d.ts.map +1 -1
  35. package/dist/measure.js +9 -1
  36. package/dist/metric-query-engine.d.ts.map +1 -1
  37. package/dist/metric-query-engine.js +4 -3
  38. package/dist/protocol-adapter.d.ts.map +1 -1
  39. package/dist/protocol-adapter.js +5 -3
  40. package/dist/protocol-deployment-adapter.d.ts.map +1 -1
  41. package/dist/protocol-deployment-adapter.js +7 -1
  42. package/dist/protocol-schema-adapter.d.ts +7 -7
  43. package/dist/protocol-schema-adapter.d.ts.map +1 -1
  44. package/dist/protocol-schema-adapter.js +12 -245
  45. package/dist/query-builder-protocol.d.ts +32 -1
  46. package/dist/query-builder-protocol.d.ts.map +1 -1
  47. package/dist/query-planner.d.ts +14 -5
  48. package/dist/query-planner.d.ts.map +1 -1
  49. package/dist/query-planner.js +53 -34
  50. package/dist/relationship-check.d.ts.map +1 -1
  51. package/dist/relationship-check.js +5 -3
  52. package/dist/schemas.d.ts +17 -0
  53. package/dist/schemas.d.ts.map +1 -0
  54. package/dist/schemas.js +16 -0
  55. package/dist/semantic-planner.d.ts.map +1 -1
  56. package/dist/semantic-planner.js +16 -0
  57. package/dist/semantic-query-schema.d.ts +2 -1
  58. package/dist/semantic-query-schema.d.ts.map +1 -1
  59. package/dist/semantic-query-schema.js +12 -12
  60. package/dist/types.d.ts +22 -1
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/utils/aggregate-state-functions.d.ts +24 -0
  63. package/dist/utils/aggregate-state-functions.d.ts.map +1 -0
  64. package/dist/utils/aggregate-state-functions.js +60 -0
  65. package/dist/utils/approximate-measures.d.ts +3 -1
  66. package/dist/utils/approximate-measures.d.ts.map +1 -1
  67. package/dist/utils/approximate-measures.js +8 -2
  68. package/dist/utils/base-measure-definition.d.ts +16 -0
  69. package/dist/utils/base-measure-definition.d.ts.map +1 -1
  70. package/dist/utils/base-measure-definition.js +31 -0
  71. package/dist/utils/build-dataset-query-builder.d.ts.map +1 -1
  72. package/dist/utils/build-dataset-query-builder.js +16 -8
  73. package/dist/utils/by-name.d.ts +8 -0
  74. package/dist/utils/by-name.d.ts.map +1 -0
  75. package/dist/utils/by-name.js +44 -0
  76. package/dist/utils/calendar-shift-sql.d.ts +5 -4
  77. package/dist/utils/calendar-shift-sql.d.ts.map +1 -1
  78. package/dist/utils/calendar-shift-sql.js +8 -8
  79. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +2 -2
  80. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -1
  81. package/dist/utils/clickhouse-dataset-sql-dialect.js +140 -1
  82. package/dist/utils/compile-dataset-query.d.ts.map +1 -1
  83. package/dist/utils/compile-dataset-query.js +9 -6
  84. package/dist/utils/composite-time-measure-sql.d.ts +3 -5
  85. package/dist/utils/composite-time-measure-sql.d.ts.map +1 -1
  86. package/dist/utils/composite-time-measure-sql.js +15 -16
  87. package/dist/utils/count-aggregations.d.ts +4 -0
  88. package/dist/utils/count-aggregations.d.ts.map +1 -0
  89. package/dist/utils/count-aggregations.js +6 -0
  90. package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
  91. package/dist/utils/dataset-definition-validation.js +36 -26
  92. package/dist/utils/dataset-derived-query.d.ts +2 -4
  93. package/dist/utils/dataset-derived-query.d.ts.map +1 -1
  94. package/dist/utils/dataset-derived-query.js +11 -14
  95. package/dist/utils/dataset-having.d.ts +5 -7
  96. package/dist/utils/dataset-having.d.ts.map +1 -1
  97. package/dist/utils/dataset-having.js +9 -12
  98. package/dist/utils/dataset-normalization.d.ts.map +1 -1
  99. package/dist/utils/dataset-normalization.js +1 -0
  100. package/dist/utils/dataset-query-validation.d.ts.map +1 -1
  101. package/dist/utils/dataset-query-validation.js +7 -0
  102. package/dist/utils/dataset-source.d.ts +9 -0
  103. package/dist/utils/dataset-source.d.ts.map +1 -0
  104. package/dist/utils/dataset-source.js +16 -0
  105. package/dist/utils/dataset-sql-dialect.d.ts +13 -2
  106. package/dist/utils/dataset-sql-dialect.d.ts.map +1 -1
  107. package/dist/utils/dataset-sql-dialect.js +74 -1
  108. package/dist/utils/dataset-validation.d.ts.map +1 -1
  109. package/dist/utils/dataset-validation.js +7 -0
  110. package/dist/utils/deployable-formulas.d.ts +33 -0
  111. package/dist/utils/deployable-formulas.d.ts.map +1 -0
  112. package/dist/utils/deployable-formulas.js +102 -0
  113. package/dist/utils/derived-cte-validation.d.ts +5 -0
  114. package/dist/utils/derived-cte-validation.d.ts.map +1 -1
  115. package/dist/utils/derived-cte-validation.js +77 -8
  116. package/dist/utils/derived-measure-sql.d.ts +10 -2
  117. package/dist/utils/derived-measure-sql.d.ts.map +1 -1
  118. package/dist/utils/derived-measure-sql.js +23 -43
  119. package/dist/utils/filter-field-type.d.ts +14 -0
  120. package/dist/utils/filter-field-type.d.ts.map +1 -0
  121. package/dist/utils/filter-field-type.js +21 -0
  122. package/dist/utils/filtered-aggregation-sql.d.ts +2 -1
  123. package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
  124. package/dist/utils/filtered-aggregation-sql.js +18 -35
  125. package/dist/utils/groupable.d.ts +7 -0
  126. package/dist/utils/groupable.d.ts.map +1 -0
  127. package/dist/utils/groupable.js +8 -0
  128. package/dist/utils/metric-derived-query.d.ts +2 -4
  129. package/dist/utils/metric-derived-query.d.ts.map +1 -1
  130. package/dist/utils/metric-derived-query.js +54 -41
  131. package/dist/utils/metric-query-builder.d.ts.map +1 -1
  132. package/dist/utils/metric-query-builder.js +17 -9
  133. package/dist/utils/metric-query-validation.d.ts.map +1 -1
  134. package/dist/utils/metric-query-validation.js +7 -0
  135. package/dist/utils/portable-aggregations.d.ts +1 -0
  136. package/dist/utils/portable-aggregations.d.ts.map +1 -1
  137. package/dist/utils/portable-aggregations.js +5 -0
  138. package/dist/utils/protocol-schema-conversion.d.ts +12 -0
  139. package/dist/utils/protocol-schema-conversion.d.ts.map +1 -0
  140. package/dist/utils/protocol-schema-conversion.js +61 -0
  141. package/dist/utils/query-field-references.d.ts +10 -0
  142. package/dist/utils/query-field-references.d.ts.map +1 -0
  143. package/dist/utils/query-field-references.js +38 -0
  144. package/dist/utils/query-timezone.d.ts +41 -3
  145. package/dist/utils/query-timezone.d.ts.map +1 -1
  146. package/dist/utils/query-timezone.js +75 -8
  147. package/dist/utils/raw-sql-dialect-check.d.ts +12 -0
  148. package/dist/utils/raw-sql-dialect-check.d.ts.map +1 -0
  149. package/dist/utils/raw-sql-dialect-check.js +37 -0
  150. package/dist/utils/relationship-builder-plan.d.ts +25 -8
  151. package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
  152. package/dist/utils/relationship-builder-plan.js +23 -9
  153. package/dist/utils/relationship-measure-sql.d.ts +14 -4
  154. package/dist/utils/relationship-measure-sql.d.ts.map +1 -1
  155. package/dist/utils/relationship-measure-sql.js +33 -10
  156. package/dist/utils/relationship-measures.d.ts.map +1 -1
  157. package/dist/utils/relationship-measures.js +3 -0
  158. package/dist/utils/semantic-tool.d.ts +2 -2
  159. package/dist/utils/semantic-tool.d.ts.map +1 -1
  160. package/dist/utils/semantic-tool.js +1 -1
  161. package/dist/utils/shift-measure-sql.d.ts +5 -4
  162. package/dist/utils/shift-measure-sql.d.ts.map +1 -1
  163. package/dist/utils/shift-measure-sql.js +25 -26
  164. package/dist/utils/shift-wall-time-sql.d.ts +2 -1
  165. package/dist/utils/shift-wall-time-sql.d.ts.map +1 -1
  166. package/dist/utils/shift-wall-time-sql.js +7 -7
  167. package/dist/utils/statement-parameters.d.ts +58 -0
  168. package/dist/utils/statement-parameters.d.ts.map +1 -0
  169. package/dist/utils/statement-parameters.js +101 -0
  170. package/dist/utils/time-axis-calendar-guard.d.ts +2 -1
  171. package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -1
  172. package/dist/utils/time-axis-calendar-guard.js +5 -7
  173. package/dist/utils/time-measure-axis-sql.d.ts +2 -1
  174. package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
  175. package/dist/utils/time-measure-axis-sql.js +19 -22
  176. package/dist/utils/time-measure-context-sql.d.ts +3 -2
  177. package/dist/utils/time-measure-context-sql.d.ts.map +1 -1
  178. package/dist/utils/time-measure-context-sql.js +11 -11
  179. package/dist/utils/time-measure-dataset-sql.d.ts +7 -5
  180. package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
  181. package/dist/utils/time-measure-dataset-sql.js +17 -12
  182. package/dist/utils/time-measure-leaf-sql.d.ts +2 -1
  183. package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -1
  184. package/dist/utils/time-measure-leaf-sql.js +17 -18
  185. package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
  186. package/dist/utils/time-measure-result-sql.js +10 -11
  187. package/dist/utils/time-measure-source-sql.d.ts +2 -2
  188. package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
  189. package/dist/utils/time-measure-source-sql.js +25 -16
  190. package/dist/utils/time-measure-values-sql.d.ts +4 -3
  191. package/dist/utils/time-measure-values-sql.d.ts.map +1 -1
  192. package/dist/utils/time-measure-values-sql.js +26 -29
  193. package/dist/utils/timestamp-instants.d.ts +17 -0
  194. package/dist/utils/timestamp-instants.d.ts.map +1 -0
  195. package/dist/utils/timestamp-instants.js +34 -0
  196. package/dist/utils/timestamp-text.d.ts +12 -0
  197. package/dist/utils/timestamp-text.d.ts.map +1 -0
  198. package/dist/utils/timestamp-text.js +18 -0
  199. package/dist/utils/zod-json-schema.d.ts +8 -0
  200. package/dist/utils/zod-json-schema.d.ts.map +1 -0
  201. package/dist/utils/zod-json-schema.js +18 -0
  202. package/dist/utils/zod-schema-version.d.ts +19 -0
  203. package/dist/utils/zod-schema-version.d.ts.map +1 -0
  204. package/dist/utils/zod-schema-version.js +6 -0
  205. package/dist/utils/zod-v3-protocol-schema.d.ts +10 -0
  206. package/dist/utils/zod-v3-protocol-schema.d.ts.map +1 -0
  207. package/dist/utils/zod-v3-protocol-schema.js +164 -0
  208. package/dist/utils/zod-v4-protocol-schema.d.ts +12 -0
  209. package/dist/utils/zod-v4-protocol-schema.d.ts.map +1 -0
  210. package/dist/utils/zod-v4-protocol-schema.js +171 -0
  211. package/dist/validation.d.ts +7 -0
  212. package/dist/validation.d.ts.map +1 -1
  213. package/dist/validation.js +42 -8
  214. package/package.json +21 -12
  215. package/dist/api.type-test.d.ts +0 -2
  216. package/dist/api.type-test.d.ts.map +0 -1
  217. package/dist/api.type-test.js +0 -251
  218. package/dist/utils/time-arithmetic-sql.d.ts +0 -4
  219. package/dist/utils/time-arithmetic-sql.d.ts.map +0 -1
  220. package/dist/utils/time-arithmetic-sql.js +0 -9
  221. package/dist/utils/window-aggregation-sql.d.ts +0 -5
  222. package/dist/utils/window-aggregation-sql.d.ts.map +0 -1
  223. package/dist/utils/window-aggregation-sql.js +0 -17
@@ -1,27 +1,25 @@
1
- import { GRAIN_FUNCTIONS } from '../constants.js';
2
- import { addTimeSql, subtractTimeSql } from './time-arithmetic-sql.js';
3
1
  import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
4
2
  import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
5
3
  import { shiftWallTimeGuardSql } from './shift-wall-time-sql.js';
6
4
  import { anchoredCalendarShiftSql } from './calendar-shift-sql.js';
7
5
  /** Preserve the selected portion of a bucket and both authored endpoint operators. */
8
- export function shiftRangeSql(shift, axis, period) {
6
+ export function shiftRangeSql(dialect, shift, axis, period) {
9
7
  const first = period ?? '_hq_first';
10
8
  const last = period ?? '_hq_last';
11
- const end = addTimeSql(last, 1, axis.grain);
9
+ const end = dialect.addTime(last, 1, axis.grain);
12
10
  const calendar = calendarShiftAtFineGrain(shift.interval, axis.grain);
13
11
  // Clamp the bucket anchor, then retain its width. Independently clamping both
14
12
  // dates would turn March 30–31 into February 28–28, dropping an entire day.
15
13
  // A partial bucket keeps the matching slice of that shifted range, so both
16
14
  // bounds are placed relative to the anchor rather than shifted on their own.
17
15
  const shiftBound = (point, anchor) => calendar
18
- ? anchoredCalendarShiftSql(point, anchor, shift.interval)
19
- : subtractTimeSql(point, shift.interval.amount, shift.interval.unit);
20
- const upper = shiftBound(`least(${end}, _hq_upper)`, last);
21
- const shiftedEnd = addTimeSql(subtractTimeSql(last, shift.interval.amount, shift.interval.unit), 1, axis.grain);
16
+ ? anchoredCalendarShiftSql(dialect, point, anchor, shift.interval)
17
+ : dialect.subtractTime(point, shift.interval.amount, shift.interval.unit);
18
+ const upper = shiftBound(dialect.least(end, '_hq_upper'), last);
19
+ const shiftedEnd = dialect.addTime(dialect.subtractTime(last, shift.interval.amount, shift.interval.unit), 1, axis.grain);
22
20
  return {
23
- lower: shiftBound(`greatest(${first}, _hq_lower)`, first),
24
- upper: calendar ? `if(_hq_upper >= ${end}, ${shiftedEnd}, ${upper})` : upper,
21
+ lower: shiftBound(dialect.greatest(first, '_hq_lower'), first),
22
+ upper: calendar ? dialect.ifElse(`_hq_upper >= ${end}`, shiftedEnd, upper) : upper,
25
23
  lowerInclusive: period ? `(${period} > _hq_lower OR ${Number(axis.lowerInclusive)})` : String(Number(axis.lowerInclusive)),
26
24
  upperInclusive: `(_hq_upper < ${end} AND ${Number(axis.upperInclusive)})`,
27
25
  };
@@ -31,29 +29,30 @@ export function shiftRangePredicateSql(time, range) {
31
29
  + ` AND (${time} < ${range.upper} OR (${range.upperInclusive} AND ${time} = ${range.upper}))`;
32
30
  }
33
31
  /** Check the shifted scan independently of whether it contains source rows. */
34
- export function shiftCalendarGuardSql(shift, axis, axisGuard) {
32
+ export function shiftCalendarGuardSql(dialect, shift, axis, axisGuard) {
35
33
  const { grain } = axis;
36
- const { lower, upper } = shiftRangeSql(shift, axis);
37
- const first = `${GRAIN_FUNCTIONS[grain]}(${lower})`;
38
- const last = `${GRAIN_FUNCTIONS[grain]}(${upper})`;
39
- const count = `dateDiff('${grain}', ${first}, ${last}) + ${axisGuard}`;
40
- return calendarBucketGuardSql(first, count, grain);
34
+ const { lower, upper } = shiftRangeSql(dialect, shift, axis);
35
+ const first = dialect.truncateTime(lower, grain);
36
+ const last = dialect.truncateTime(upper, grain);
37
+ const count = `${dialect.timeDiff(grain, first, last)} + ${axisGuard}`;
38
+ return calendarBucketGuardSql(dialect, first, count, grain);
41
39
  }
42
40
  /** Validate each output bucket after the series exists, including empty source populations. */
43
- export function shiftWallTimeSeriesGuardSql(shift, grain) {
44
- const end = addTimeSql('_hq_period', 1, grain);
45
- return shiftWallTimeGuardSql(shift.interval, grain, '_hq_series CROSS JOIN _hq_bounds', '_hq_period', 'greatest(_hq_period, _hq_lower)', `least(${end}, _hq_upper)`, end);
41
+ export function shiftWallTimeSeriesGuardSql(dialect, shift, grain) {
42
+ const end = dialect.addTime('_hq_period', 1, grain);
43
+ return shiftWallTimeGuardSql(dialect, shift.interval, grain, '_hq_series CROSS JOIN _hq_bounds', '_hq_period', dialect.greatest('_hq_period', '_hq_lower'), dialect.least(end, '_hq_upper'), end);
46
44
  }
47
45
  /** Map output buckets to their earlier half-open ranges, retaining calendar arithmetic. */
48
- export function shiftBucketCtes(shift, axis, index) {
46
+ export function shiftBucketCtes(dialect, shift, axis, index) {
49
47
  const { grain } = axis;
50
48
  const ranges = `_hq_shift_ranges${index}`;
51
49
  const keys = `_hq_shift_keys${index}`;
52
- const range = shiftRangeSql(shift, axis, '_hq_period');
53
- const bucket = `${GRAIN_FUNCTIONS[grain]}(_hq_shift_lower)`;
54
- const upperBucket = `${GRAIN_FUNCTIONS[grain]}(_hq_shift_upper)`;
55
- const count = `greatest(0, dateDiff('${grain}', ${bucket}, ${upperBucket}) + if(_hq_shift_upper > ${upperBucket} OR _hq_shift_upper_inclusive, 1, 0))`;
56
- const join = ` AS r INNER JOIN ${keys} AS b ON ${GRAIN_FUNCTIONS[grain]}(r._hq_time) = b._hq_source_period WHERE ${shiftRangePredicateSql('r._hq_time', { lower: 'b._hq_shift_lower', upper: 'b._hq_shift_upper', lowerInclusive: 'b._hq_shift_lower_inclusive', upperInclusive: 'b._hq_shift_upper_inclusive' })}`;
50
+ const range = shiftRangeSql(dialect, shift, axis, '_hq_period');
51
+ const bucket = dialect.truncateTime('_hq_shift_lower', grain);
52
+ const upperBucket = dialect.truncateTime('_hq_shift_upper', grain);
53
+ const partialUpper = dialect.ifElse(`_hq_shift_upper > ${upperBucket} OR _hq_shift_upper_inclusive`, '1', '0');
54
+ const count = dialect.greatest('0', `${dialect.timeDiff(grain, bucket, upperBucket)} + ${partialUpper}`);
55
+ const join = ` AS r INNER JOIN ${keys} AS b ON ${dialect.truncateTime('r._hq_time', grain)} = b._hq_source_period WHERE ${shiftRangePredicateSql('r._hq_time', { lower: 'b._hq_shift_lower', upper: 'b._hq_shift_upper', lowerInclusive: 'b._hq_shift_lower_inclusive', upperInclusive: 'b._hq_shift_upper_inclusive' })}`;
57
56
  return {
58
57
  // Membership preserves duplicate source rows while excluding dates and
59
58
  // partial ranges that no output bucket maps to, including clamped overlaps.
@@ -63,7 +62,7 @@ export function shiftBucketCtes(shift, axis, index) {
63
62
  // A DST transition can map a one-hour output bucket to zero or two
64
63
  // source hours. Enumerate source bucket keys and keep exact endpoints.
65
64
  // The equality join avoids a source-rows × output-series Cartesian product.
66
- `${keys} AS (SELECT *, ${addTimeSql(bucket, `arrayJoin(range(toUInt64(${count})))`, grain)} AS _hq_source_period FROM ${ranges})`,
65
+ `${keys} AS (SELECT *, ${dialect.addTime(bucket, dialect.seriesIndex(count), grain)} AS _hq_source_period FROM ${ranges})`,
67
66
  ],
68
67
  rowsSql: `SELECT r.*, b._hq_period FROM _hq_scanned${join}`,
69
68
  };
@@ -1,4 +1,5 @@
1
+ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
1
2
  import type { MeasureTimeInterval, TimeGrain } from '../types.js';
2
3
  /** Reject DST normalization that moves a shifted sub-day bucket to another local time. */
3
- export declare function shiftWallTimeGuardSql(interval: MeasureTimeInterval, grain: TimeGrain, source: string, period: string, lower: string, upper: string, end: string): string;
4
+ export declare function shiftWallTimeGuardSql(dialect: DatasetSqlDialectRules, interval: MeasureTimeInterval, grain: TimeGrain, source: string, period: string, lower: string, upper: string, end: string): string;
4
5
  //# sourceMappingURL=shift-wall-time-sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shift-wall-time-sql.d.ts","sourceRoot":"","sources":["../../src/utils/shift-wall-time-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIlE,0FAA0F;AAC1F,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,MAAM,CAYR"}
1
+ {"version":3,"file":"shift-wall-time-sql.d.ts","sourceRoot":"","sources":["../../src/utils/shift-wall-time-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAKlE,0FAA0F;AAC1F,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,EAC/B,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,MAAM,CAaR"}
@@ -1,17 +1,17 @@
1
- import { subtractTimeSql } from './time-arithmetic-sql.js';
2
1
  import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
2
+ const CLOCK_PARTS = ['Hour', 'Minute', 'Second'];
3
3
  /** Reject DST normalization that moves a shifted sub-day bucket to another local time. */
4
- export function shiftWallTimeGuardSql(interval, grain, source, period, lower, upper, end) {
4
+ export function shiftWallTimeGuardSql(dialect, interval, grain, source, period, lower, upper, end) {
5
5
  if (!calendarShiftAtFineGrain(interval, grain) || (grain !== 'minute' && grain !== 'hour'))
6
6
  return '0';
7
7
  const changesWallTime = (value) => {
8
- const shifted = subtractTimeSql(value, interval.amount, interval.unit);
9
- return ['Hour', 'Minute', 'Second']
10
- .map(part => `to${part}(${value}) != to${part}(${shifted})`)
8
+ const shifted = dialect.subtractTime(value, interval.amount, interval.unit);
9
+ return CLOCK_PARTS
10
+ .map(part => `${dialect.clockPart(part, value)} != ${dialect.clockPart(part, shifted)}`)
11
11
  .join(' OR ');
12
12
  };
13
13
  const changed = `(${changesWallTime(period)}) OR (${changesWallTime(lower)})`
14
14
  + ` OR (${upper} != ${end} AND (${changesWallTime(upper)}))`;
15
- return `(SELECT throwIf(countIf(${changed}) > 0, `
16
- + `'Calendar shift lands on a nonexistent local time; use a UTC query timezone.') FROM ${source})`;
15
+ const guard = dialect.guard(`${dialect.countIf(changed)} > 0`, 'Calendar shift lands on a nonexistent local time; use a UTC query timezone.');
16
+ return `(SELECT ${guard} FROM ${source})`;
17
17
  }
@@ -0,0 +1,58 @@
1
+ import type { DatasetSqlDialectRules, DatasetSqlParameterType } from '../dataset-sql-dialect.js';
2
+ /** A compiled builder statement: SQL with positional `?` markers and their values. */
3
+ export interface BuilderStatement {
4
+ readonly sql: string;
5
+ readonly parameters: readonly unknown[];
6
+ }
7
+ /** A query builder, as far as embedding its compiled statement is concerned. */
8
+ export interface EmbeddableBuilder {
9
+ toSQLWithParams(): BuilderStatement;
10
+ toSQLWithPlaceholders?(placeholder: (value: unknown) => string): string;
11
+ }
12
+ /** An executable statement, rendered for its dialect. */
13
+ export interface RenderedStatement {
14
+ sql: string;
15
+ parameters: unknown[];
16
+ }
17
+ /**
18
+ * Planner output that still carries markers. Not executable: only
19
+ * {@link renderStatement} turns it into SQL, so markers cannot reach callers.
20
+ */
21
+ export interface PlannedStatement {
22
+ readonly text: string;
23
+ readonly parameters: StatementParameters;
24
+ }
25
+ /**
26
+ * Owns every value bound into one statement family (a statement and its
27
+ * preflight statements). Values are bound behind opaque markers; the dialect
28
+ * spells placeholders once, at render time, in the order the markers appear
29
+ * in the final text. See specs/datasets/0001-statement-parameters.md.
30
+ */
31
+ export declare class StatementParameters {
32
+ readonly dialect: DatasetSqlDialectRules;
33
+ private readonly nonce;
34
+ private readonly values;
35
+ private readonly pieces;
36
+ constructor(dialect: DatasetSqlDialectRules);
37
+ /** Bind one value; place the returned marker where the value belongs. */
38
+ bind(value: unknown, type?: DatasetSqlParameterType): string;
39
+ /** Embed a builder statement whose SQL carries its own positional `?` markers. */
40
+ embed(piece: BuilderStatement): string;
41
+ /**
42
+ * Embed a query builder's statement.
43
+ *
44
+ * On positional `?` dialects the builder's own statement is embedded as one
45
+ * block, exactly as before: `toSQLWithPlaceholders` is not called, so a
46
+ * duck-typed builder that answers any method name keeps working. Dialects
47
+ * that number placeholders need each value bound here, so they require the
48
+ * builder to render through `toSQLWithPlaceholders`. A caller that already
49
+ * compiled the builder positionally can pass that statement to reuse it.
50
+ */
51
+ embedBuilder(builder: EmbeddableBuilder, compiled?: BuilderStatement): string;
52
+ /** Replace every marker in text order. Fails closed on foreign or malformed markers. */
53
+ render(text: string): RenderedStatement;
54
+ private marker;
55
+ }
56
+ /** Render planner output for execution. The only exit from a {@link PlannedStatement}. */
57
+ export declare function renderStatement(statement: PlannedStatement): RenderedStatement;
58
+ //# sourceMappingURL=statement-parameters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statement-parameters.d.ts","sourceRoot":"","sources":["../../src/utils/statement-parameters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA4B,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE3H,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,CAAC;CACzC;AAED,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,eAAe,IAAI,gBAAgB,CAAC;IACpC,qBAAqB,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,MAAM,CAAC;CACzE;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,OAAO,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;CAC1C;AAaD;;;;;GAKG;AACH,qBAAa,mBAAmB;IAKlB,QAAQ,CAAC,OAAO,EAAE,sBAAsB;IAJpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;gBAE5B,OAAO,EAAE,sBAAsB;IAEpD,yEAAyE;IACzE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,uBAAuB,GAAG,MAAM;IAK5D,kFAAkF;IAClF,KAAK,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM;IActC;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,MAAM;IAa7E,wFAAwF;IACxF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB;IA2BvC,OAAO,CAAC,MAAM;CAGf;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,SAAS,EAAE,gBAAgB,GAAG,iBAAiB,CAE9E"}
@@ -0,0 +1,101 @@
1
+ // Private-use code points never appear in generated SQL, and the nonce makes
2
+ // a marker unforgeable by any text that reaches a statement.
3
+ const OPEN = '\uE000';
4
+ const CLOSE = '\uE001';
5
+ const MARKER = /\uE000([0-9a-f]+):([vb]):(\d+)\uE001/g;
6
+ function createNonce() {
7
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(8));
8
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
9
+ }
10
+ /**
11
+ * Owns every value bound into one statement family (a statement and its
12
+ * preflight statements). Values are bound behind opaque markers; the dialect
13
+ * spells placeholders once, at render time, in the order the markers appear
14
+ * in the final text. See specs/datasets/0001-statement-parameters.md.
15
+ */
16
+ export class StatementParameters {
17
+ dialect;
18
+ nonce = createNonce();
19
+ values = [];
20
+ pieces = [];
21
+ constructor(dialect) {
22
+ this.dialect = dialect;
23
+ }
24
+ /** Bind one value; place the returned marker where the value belongs. */
25
+ bind(value, type) {
26
+ this.values.push(type === undefined ? { value } : { value, type });
27
+ return this.marker('v', this.values.length - 1);
28
+ }
29
+ /** Embed a builder statement whose SQL carries its own positional `?` markers. */
30
+ embed(piece) {
31
+ if (!this.dialect.embedsPositionalPieces) {
32
+ throw new Error(`The "${this.dialect.name}" datasets dialect cannot embed positional query builder SQL; `
33
+ + 'the query builder must render placeholders for the datasets planner.');
34
+ }
35
+ if (piece.sql.includes(OPEN) || piece.sql.includes(CLOSE)) {
36
+ throw new Error('Query builder SQL cannot contain datasets statement markers.');
37
+ }
38
+ this.pieces.push({ sql: piece.sql, parameters: [...piece.parameters] });
39
+ return this.marker('b', this.pieces.length - 1);
40
+ }
41
+ /**
42
+ * Embed a query builder's statement.
43
+ *
44
+ * On positional `?` dialects the builder's own statement is embedded as one
45
+ * block, exactly as before: `toSQLWithPlaceholders` is not called, so a
46
+ * duck-typed builder that answers any method name keeps working. Dialects
47
+ * that number placeholders need each value bound here, so they require the
48
+ * builder to render through `toSQLWithPlaceholders`. A caller that already
49
+ * compiled the builder positionally can pass that statement to reuse it.
50
+ */
51
+ embedBuilder(builder, compiled) {
52
+ if (this.dialect.embedsPositionalPieces)
53
+ return this.embed(compiled ?? builder.toSQLWithParams());
54
+ if (typeof builder.toSQLWithPlaceholders !== 'function') {
55
+ throw new Error(`The "${this.dialect.name}" datasets dialect numbers placeholders; `
56
+ + 'the query builder must implement toSQLWithPlaceholders.');
57
+ }
58
+ const sql = builder.toSQLWithPlaceholders(value => this.bind(value));
59
+ if (typeof sql !== 'string')
60
+ throw new Error('Query builder toSQLWithPlaceholders must return SQL text.');
61
+ return sql;
62
+ }
63
+ /** Replace every marker in text order. Fails closed on foreign or malformed markers. */
64
+ render(text) {
65
+ const parameters = [];
66
+ const reused = new Map();
67
+ const sql = text.replace(MARKER, (_match, nonce, kind, position) => {
68
+ if (nonce !== this.nonce)
69
+ throw new Error('Statement contains a marker from another compilation.');
70
+ const index = Number(position);
71
+ if (kind === 'b') {
72
+ const piece = this.pieces[index];
73
+ if (!piece)
74
+ throw new Error('Statement contains an unknown builder marker.');
75
+ parameters.push(...piece.parameters);
76
+ return piece.sql;
77
+ }
78
+ const bound = this.values[index];
79
+ if (!bound)
80
+ throw new Error('Statement contains an unknown parameter marker.');
81
+ const existing = this.dialect.reusesPlaceholders ? reused.get(index) : undefined;
82
+ if (existing !== undefined)
83
+ return existing;
84
+ const placeholder = this.dialect.placeholder(parameters.length, bound);
85
+ parameters.push(bound.value);
86
+ reused.set(index, placeholder);
87
+ return placeholder;
88
+ });
89
+ if (sql.includes(OPEN) || sql.includes(CLOSE)) {
90
+ throw new Error('Statement contains a malformed parameter marker.');
91
+ }
92
+ return { sql, parameters };
93
+ }
94
+ marker(kind, index) {
95
+ return `${OPEN}${this.nonce}:${kind}:${index}${CLOSE}`;
96
+ }
97
+ }
98
+ /** Render planner output for execution. The only exit from a {@link PlannedStatement}. */
99
+ export function renderStatement(statement) {
100
+ return statement.parameters.render(statement.text);
101
+ }
@@ -1,3 +1,4 @@
1
+ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
1
2
  import type { TimeGrain } from '../types.js';
2
3
  /**
3
4
  * Calendar arithmetic can clamp a nonexistent date onto the previous bucket
@@ -5,5 +6,5 @@ import type { TimeGrain } from '../types.js';
5
6
  * repeating them would duplicate aggregates and cumulative contributions.
6
7
  * `count` must already include the series/fanout guards, bounding enumeration.
7
8
  */
8
- export declare function calendarBucketGuardSql(start: string, count: string, grain: TimeGrain): string;
9
+ export declare function calendarBucketGuardSql(dialect: DatasetSqlDialectRules, start: string, count: string, grain: TimeGrain): string;
9
10
  //# sourceMappingURL=time-axis-calendar-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"time-axis-calendar-guard.d.ts","sourceRoot":"","sources":["../../src/utils/time-axis-calendar-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAQ7F"}
1
+ {"version":3,"file":"time-axis-calendar-guard.d.ts","sourceRoot":"","sources":["../../src/utils/time-axis-calendar-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAU9H"}
@@ -1,17 +1,15 @@
1
- import { addTimeSql } from './time-arithmetic-sql.js';
2
1
  /**
3
2
  * Calendar arithmetic can clamp a nonexistent date onto the previous bucket
4
3
  * (for example Pacific/Apia, 2011-12-30). Reject non-increasing bucket starts:
5
4
  * repeating them would duplicate aggregates and cumulative contributions.
6
5
  * `count` must already include the series/fanout guards, bounding enumeration.
7
6
  */
8
- export function calendarBucketGuardSql(start, count, grain) {
7
+ export function calendarBucketGuardSql(dialect, start, count, grain) {
9
8
  if (grain === 'minute' || grain === 'hour')
10
9
  return '0';
11
- const current = addTimeSql(start, '_hq_calendar_index', grain);
12
- const next = addTimeSql(start, '_hq_calendar_index + 1', grain);
10
+ const index = '_hq_calendar_index';
11
+ const current = dialect.addTime(start, index, grain);
12
+ const next = dialect.addTime(start, `${index} + 1`, grain);
13
13
  // Include the end boundary, which also bounds the source-row scan.
14
- const indices = `range(toUInt64(greatest(1, ${count})))`;
15
- return `throwIf(arrayExists(_hq_calendar_index -> ${next} <= ${current}, ${indices}), `
16
- + "'Time range crosses a skipped local calendar bucket; use a UTC time key for this range.')";
14
+ return dialect.guard(dialect.anyIndex(index, dialect.greatest('1', count), `${next} <= ${current}`), 'Time range crosses a skipped local calendar bucket; use a UTC time key for this range.');
17
15
  }
@@ -1,3 +1,4 @@
1
+ import type { StatementParameters } from './statement-parameters.js';
1
2
  import type { TimeMeasureAxis } from './time-measure-axis.js';
2
3
  import type { TimeMeasureSqlSource } from './time-measure-source-sql.js';
3
4
  import type { ShiftMeasureDefinition } from '../types.js';
@@ -9,5 +10,5 @@ export interface TimeMeasureSqlAxis {
9
10
  postSeriesGuard: string;
10
11
  }
11
12
  /** Build bounded output buckets using the query timezone. */
12
- export declare function buildTimeMeasureAxisSql(source: TimeMeasureSqlSource, axis: TimeMeasureAxis, shifts?: readonly ShiftMeasureDefinition[]): TimeMeasureSqlAxis;
13
+ export declare function buildTimeMeasureAxisSql(statement: StatementParameters, source: TimeMeasureSqlSource, axis: TimeMeasureAxis, shifts?: readonly ShiftMeasureDefinition[]): TimeMeasureSqlAxis;
13
14
  //# sourceMappingURL=time-measure-axis-sql.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"time-measure-axis-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,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;AAED,6DAA6D;AAC7D,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,mBAAmB,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,SAAS,sBAAsB,EAAO,GAC7C,kBAAkB,CA2CpB"}
@@ -1,42 +1,39 @@
1
- import { GRAIN_FUNCTIONS } from '../constants.js';
2
- import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
3
1
  import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
4
2
  import { shiftCalendarGuardSql, shiftWallTimeSeriesGuardSql } from './shift-measure-sql.js';
5
- function literal(value) {
6
- return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
7
- }
8
3
  /** 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)`;
4
+ export function buildTimeMeasureAxisSql(statement, source, axis, shifts = []) {
5
+ const { dialect } = statement;
6
+ const first = dialect.truncateTime('_hq_lower', axis.grain);
7
+ const upperBucket = dialect.truncateTime('_hq_upper', axis.grain);
12
8
  const last = axis.upperInclusive
13
9
  ? upperBucket
14
- : `if(_hq_upper = ${upperBucket}, ${subtract(upperBucket, 1, axis.grain)}, ${upperBucket})`;
15
- const count = `dateDiff('${axis.grain}', _hq_first, _hq_last) + 1`;
10
+ : dialect.ifElse(`_hq_upper = ${upperBucket}`, dialect.subtractTime(upperBucket, 1, axis.grain), upperBucket);
11
+ const count = `${dialect.timeDiff(axis.grain, '_hq_first', '_hq_last')} + 1`;
16
12
  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.')`;
13
+ const rangeGuard = dialect.guard(`_hq_lower > _hq_upper${emptyRange}`, 'Window measure time range must be non-empty and ordered.');
18
14
  const limitGuard = axis.resultLimit === undefined
19
15
  ? '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))`;
16
+ : dialect.guard(`${count} > ${axis.resultLimit}`, `Window series exceeds the effective result limit of ${axis.resultLimit} buckets.`);
17
+ const calendarGuard = calendarBucketGuardSql(dialect, '_hq_first', `${count} + ${limitGuard} + ${rangeGuard}`, axis.grain);
18
+ const physicalTime = dialect.rowElement(`(SELECT ${source.rowAlias} FROM _hq_raw LIMIT 0)`, 1);
23
19
  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.')`
20
+ ? dialect.guard(dialect.isDateOnlyType(physicalTime), 'Sub-day time measures require a time-of-day column; Date and Date32 are not supported.')
25
21
  : '0';
26
- const shiftGuards = shifts.map(shift => shiftCalendarGuardSql(shift, axis, `${limitGuard} + ${rangeGuard}`));
22
+ const shiftGuards = shifts.map(shift => shiftCalendarGuardSql(dialect, shift, axis, `${limitGuard} + ${rangeGuard}`));
27
23
  const guard = [limitGuard, physicalGuard, calendarGuard, ...shiftGuards].join(' + ');
28
- const postSeriesGuard = shifts.map(shift => shiftWallTimeSeriesGuardSql(shift, axis.grain)).join(' + ') || '0';
24
+ const postSeriesGuard = shifts.map(shift => shiftWallTimeSeriesGuardSql(dialect, shift, axis.grain)).join(' + ') || '0';
29
25
  // A zero-row scalar retains the converted timestamp type and query timezone.
30
26
  // 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)))';
27
+ const timezone = dialect.timezoneOf(dialect.assumeNotNull('(SELECT _hq_time FROM _hq_source LIMIT 0)'));
32
28
  const bounds = [
33
- `parseDateTime64BestEffort(${literal(axis.lower)}, 9, ${timezone}) AS _hq_lower`,
34
- `parseDateTime64BestEffort(${literal(axis.upper)}, 9, ${timezone}) AS _hq_upper`,
29
+ // The bounds come from the caller's time filter: bind them, never inline them.
30
+ `${dialect.parseTimestamp(statement.bind(axis.lower, 'string'), timezone)} AS _hq_lower`,
31
+ `${dialect.parseTimestamp(statement.bind(axis.upper, 'string'), timezone)} AS _hq_upper`,
35
32
  `${first} AS _hq_first`,
36
33
  `${last} AS _hq_last`,
37
34
  ];
38
- const bucketIndices = `arrayJoin(range(toUInt64(${count} + ${guard} + ${rangeGuard})))`;
39
- const period = add('_hq_first', bucketIndices, axis.grain);
35
+ const bucketIndices = dialect.seriesIndex(`${count} + ${guard} + ${rangeGuard}`);
36
+ const period = dialect.addTime('_hq_first', bucketIndices, axis.grain);
40
37
  const ctes = [
41
38
  `_hq_bounds AS (SELECT ${bounds.join(', ')})`,
42
39
  `_hq_series AS (SELECT ${period} AS _hq_period FROM _hq_bounds)`,
@@ -1,6 +1,7 @@
1
+ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
1
2
  import type { MeasureEvaluationContext } from './measure-evaluation-graph.js';
2
3
  import type { TimeMeasureAxis } from './time-measure-axis.js';
3
4
  /** 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;
5
+ export declare function timeMeasureContextCtes(dialect: DatasetSqlDialectRules, contexts: readonly MeasureEvaluationContext[], axis: TimeMeasureAxis): string[];
6
+ export declare function timeMeasureContextGuard(dialect: DatasetSqlDialectRules, context: MeasureEvaluationContext, axis: TimeMeasureAxis): string;
6
7
  //# sourceMappingURL=time-measure-context-sql.d.ts.map
@@ -1 +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;AAO9D,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAerH;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAOxG"}
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,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM9D,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAgBtJ;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAOzI"}
@@ -1,27 +1,27 @@
1
- import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
2
1
  import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
3
2
  import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
4
3
  import { shiftWallTimeGuardSql } from './shift-wall-time-sql.js';
5
4
  import { anchoredCalendarShiftSql } from './calendar-shift-sql.js';
6
5
  /** Keep output coordinates separate from each shifted evaluation interval. */
7
- export function timeMeasureContextCtes(contexts, axis) {
8
- const end = add('_hq_period', 1, axis.grain);
9
- 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)`;
6
+ export function timeMeasureContextCtes(dialect, contexts, axis) {
7
+ const end = dialect.addTime('_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, ${dialect.greatest('_hq_period', '_hq_lower')} AS _hq_eval_lower, ${dialect.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)`;
10
9
  return [root, ...contexts.slice(1).map(context => {
11
10
  const interval = context.interval;
12
11
  const calendar = calendarShiftAtFineGrain(interval, axis.grain);
13
- const shift = (value) => subtract(`p.${value}`, interval.amount, interval.unit);
12
+ const shift = (value) => dialect.subtractTime(`p.${value}`, interval.amount, interval.unit);
14
13
  // Partial bounds stay inside the shifted bucket; see anchoredCalendarShiftSql.
15
14
  const shiftBound = (value) => calendar
16
- ? anchoredCalendarShiftSql(`p.${value}`, 'p._hq_eval_period', interval)
15
+ ? anchoredCalendarShiftSql(dialect, `p.${value}`, 'p._hq_eval_period', interval)
17
16
  : shift(value);
18
17
  const period = shift('_hq_eval_period');
19
- const shiftedEnd = calendar ? add(period, 1, axis.grain) : shift('_hq_eval_end');
20
- return `_hq_context${context.id} AS (SELECT p._hq_period, ${period} AS _hq_eval_period, ${shiftedEnd} AS _hq_eval_end, ${shiftBound('_hq_eval_lower')} AS _hq_eval_lower, if(p._hq_eval_upper = p._hq_eval_end, ${shiftedEnd}, ${shiftBound('_hq_eval_upper')}) AS _hq_eval_upper, p._hq_lower_inclusive, p._hq_upper_inclusive FROM _hq_context${context.parent} AS p)`;
18
+ const shiftedEnd = calendar ? dialect.addTime(period, 1, axis.grain) : shift('_hq_eval_end');
19
+ const upper = dialect.ifElse('p._hq_eval_upper = p._hq_eval_end', shiftedEnd, shiftBound('_hq_eval_upper'));
20
+ return `_hq_context${context.id} AS (SELECT p._hq_period, ${period} AS _hq_eval_period, ${shiftedEnd} AS _hq_eval_end, ${shiftBound('_hq_eval_lower')} AS _hq_eval_lower, ${upper} AS _hq_eval_upper, p._hq_lower_inclusive, p._hq_upper_inclusive FROM _hq_context${context.parent} AS p)`;
21
21
  })];
22
22
  }
23
- export function timeMeasureContextGuard(context, axis) {
24
- const guard = calendarBucketGuardSql('_hq_eval_period', '1', axis.grain);
25
- 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');
23
+ export function timeMeasureContextGuard(dialect, context, axis) {
24
+ const guard = calendarBucketGuardSql(dialect, '_hq_eval_period', '1', axis.grain);
25
+ const wallTimeGuard = context.interval === undefined ? '0' : shiftWallTimeGuardSql(dialect, 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');
26
26
  return `(SELECT coalesce(max(${guard}), 0) FROM _hq_context${context.id}) + ${wallTimeGuard}`;
27
27
  }
@@ -1,9 +1,11 @@
1
1
  import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
2
2
  import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
3
+ import { type PlannedStatement } from './statement-parameters.js';
4
+ /** A time-measure statement and the axis check that must run before it. */
5
+ export interface PlannedTimeMeasureStatements {
6
+ readonly statement: PlannedStatement;
7
+ readonly preflight: PlannedStatement;
8
+ }
3
9
  /** 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
- };
10
+ export declare function buildTimeMeasureDatasetSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions): PlannedTimeMeasureStatements;
9
11
  //# sourceMappingURL=time-measure-dataset-sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"time-measure-dataset-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-dataset-sql.ts"],"names":[],"mappings":"AAGA,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"}
1
+ {"version":3,"file":"time-measure-dataset-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-dataset-sql.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAOxE,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAOvF,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;CACtC;AAED,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,GACpC,4BAA4B,CAsE9B"}
@@ -5,7 +5,9 @@ import { getBaseMeasure, getDerivedMeasure, isShiftMeasure, isWindowMeasure } fr
5
5
  import { selectedTimeMeasures } from './time-query-measures.js';
6
6
  import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
7
7
  import { validateDatasetQueryInput } from './dataset-query-validation.js';
8
- import { addTimeSql as add } from './time-arithmetic-sql.js';
8
+ import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
9
+ import { assertRawSqlForDialect } from './raw-sql-dialect-check.js';
10
+ import { StatementParameters } from './statement-parameters.js';
9
11
  import { shiftBucketCtes } from './shift-measure-sql.js';
10
12
  import { buildTimeMeasureSourceSql } from './time-measure-source-sql.js';
11
13
  import { buildTimeMeasureAxisSql } from './time-measure-axis-sql.js';
@@ -16,6 +18,8 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
16
18
  const validation = validateDatasetQueryInput(ds, query, options.context);
17
19
  if (!validation.valid)
18
20
  throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
21
+ const dialect = resolveDatasetSqlDialect(options.builderFactory);
22
+ assertRawSqlForDialect(ds, dialect);
19
23
  const { axis } = analyzeTimeMeasureAxis(ds, query);
20
24
  if (!axis || !ds.timeKey)
21
25
  throw new Error('Window measures require a bounded time axis.');
@@ -27,8 +31,9 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
27
31
  const derived = getDerivedMeasure(ds.measures, name);
28
32
  return derived && Object.values(derived.uses).some(input => getDerivedMeasure(ds.measures, input));
29
33
  });
34
+ const parameters = new StatementParameters(dialect);
30
35
  if (composite)
31
- return buildCompositeTimeMeasureSql(ds, query, options, axis);
36
+ return buildCompositeTimeMeasureSql(ds, query, options, axis, parameters);
32
37
  const timeMeasures = selectedTimeMeasures(ds, query);
33
38
  const windows = [...timeMeasures.values()].filter(isWindowMeasure);
34
39
  const shifts = [...timeMeasures.values()].filter(isShiftMeasure);
@@ -39,25 +44,25 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
39
44
  Object.values(derived.uses).forEach(input => needed.add(input));
40
45
  }
41
46
  const baseNames = new Set([...needed].map(name => timeMeasures.get(name)?.measure ?? name));
42
- const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options);
43
- const timeAxis = buildTimeMeasureAxisSql(source, axis, shifts);
47
+ const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options, parameters);
48
+ const timeAxis = buildTimeMeasureAxisSql(parameters, source, axis, shifts);
44
49
  const shiftedInputs = new Map();
45
50
  [...needed].forEach((name, index) => {
46
51
  const measure = timeMeasures.get(name);
47
52
  if (isShiftMeasure(measure))
48
- shiftedInputs.set(name, shiftBucketCtes(measure, axis, index));
53
+ shiftedInputs.set(name, shiftBucketCtes(dialect, measure, axis, index));
49
54
  });
50
55
  // Scan only requested populations: intervening rows must not introduce dimensions.
51
- const end = add('_hq_last', 1, axis.grain);
56
+ const end = dialect.addTime('_hq_last', 1, axis.grain);
52
57
  const populations = [];
53
58
  if ([...needed].some(name => name.includes('.') || getBaseMeasure(ds.measures, name))) {
54
59
  populations.push(`(_hq_time >= _hq_first AND _hq_time < ${end})`);
55
60
  }
56
61
  if (windows.length) {
57
62
  const starts = windows.filter(window => !window.cumulative)
58
- .map(window => windowScanStartSql(window, axis.grain));
63
+ .map(window => windowScanStartSql(dialect, window, axis.grain));
59
64
  const lower = windows.some(window => window.cumulative)
60
- ? '' : ` AND _hq_time >= least(${starts.join(', ')})`;
65
+ ? '' : ` AND _hq_time >= ${dialect.least(...starts)}`;
61
66
  populations.push(`(_hq_time < ${end}${lower})`);
62
67
  }
63
68
  for (const shifted of shiftedInputs.values())
@@ -73,9 +78,9 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
73
78
  const shifted = shiftedInputs.get(name);
74
79
  return { name, base, rowsSql: shifted.rowsSql, restrictToAxis: false };
75
80
  }
76
- return { name, base, window, rowsSql: windowMeasureRowsSql(window, axis) };
81
+ return { name, base, window, rowsSql: windowMeasureRowsSql(dialect, window, axis) };
77
82
  });
78
- const values = buildTimeMeasureValuesSql(source.dimensions, inputs, axis);
79
- 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}`);
80
- return { sql, parameters: source.parameters, timeAxisSql: timeAxis.timeAxisSql };
83
+ const values = buildTimeMeasureValuesSql(dialect, source.dimensions, inputs, axis);
84
+ const text = buildTimeMeasureResultSql(ds, query, options, source.dimensions, [...source.ctes, ...timeAxis.ctes, ...[...shiftedInputs.values()].flatMap(input => input.ctes), scanCte, ...values], `${timeAxis.guard} + ${timeAxis.rangeGuard} + ${timeAxis.postSeriesGuard}`);
85
+ return { statement: { text, parameters }, preflight: { text: timeAxis.timeAxisSql, parameters } };
81
86
  }
@@ -1,8 +1,9 @@
1
+ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
1
2
  import type { WindowMeasureDefinition } from '../types.js';
2
3
  import type { TimeMeasureAxis } from './time-measure-axis.js';
3
4
  import type { TimeMeasureSqlBase, TimeMeasureSqlDimension } from './time-measure-source-sql.js';
4
5
  /** 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
+ export declare function timeMeasureLeafSql(dialect: DatasetSqlDialectRules, id: number, context: number, base: TimeMeasureSqlBase, window: WindowMeasureDefinition | undefined, axis: TimeMeasureAxis, dims: readonly TimeMeasureSqlDimension[]): {
6
7
  ctes: string[];
7
8
  population: string;
8
9
  aggregate: string;
@@ -1 +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"}
1
+ {"version":3,"file":"time-measure-leaf-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-leaf-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,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;AAKhG,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,sBAAsB,EAAE,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;;;;;;;;EA8CtO"}