@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.
- package/README.md +14 -0
- package/dist/aggregations.d.ts +3 -0
- package/dist/aggregations.d.ts.map +1 -1
- package/dist/aggregations.js +6 -0
- package/dist/cache/definition-identity.js +2 -2
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +3 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +3 -2
- package/dist/client-contract/query-schema.d.ts +19 -0
- package/dist/client-contract/query-schema.d.ts.map +1 -0
- package/dist/client-contract/query-schema.js +85 -0
- package/dist/constants.d.ts +5 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +7 -6
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +4 -3
- package/dist/dataset-sql-dialect.d.ts +117 -0
- package/dist/dataset-sql-dialect.d.ts.map +1 -1
- package/dist/dataset.d.ts +1 -1
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +2 -1
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +2 -0
- package/dist/index.d.ts +48 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -6
- package/dist/internal.d.ts +5 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +5 -1
- package/dist/measure.d.ts +9 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +9 -1
- package/dist/metric-query-engine.d.ts.map +1 -1
- package/dist/metric-query-engine.js +4 -3
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +5 -3
- package/dist/protocol-deployment-adapter.d.ts.map +1 -1
- package/dist/protocol-deployment-adapter.js +7 -1
- package/dist/protocol-schema-adapter.d.ts +7 -7
- package/dist/protocol-schema-adapter.d.ts.map +1 -1
- package/dist/protocol-schema-adapter.js +12 -245
- package/dist/query-builder-protocol.d.ts +32 -1
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-planner.d.ts +14 -5
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +53 -34
- package/dist/relationship-check.d.ts.map +1 -1
- package/dist/relationship-check.js +5 -3
- package/dist/schemas.d.ts +17 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +16 -0
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +16 -0
- package/dist/semantic-query-schema.d.ts +2 -1
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +12 -12
- package/dist/types.d.ts +22 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/aggregate-state-functions.d.ts +24 -0
- package/dist/utils/aggregate-state-functions.d.ts.map +1 -0
- package/dist/utils/aggregate-state-functions.js +60 -0
- package/dist/utils/approximate-measures.d.ts +3 -1
- package/dist/utils/approximate-measures.d.ts.map +1 -1
- package/dist/utils/approximate-measures.js +8 -2
- package/dist/utils/base-measure-definition.d.ts +16 -0
- package/dist/utils/base-measure-definition.d.ts.map +1 -1
- package/dist/utils/base-measure-definition.js +31 -0
- package/dist/utils/build-dataset-query-builder.d.ts.map +1 -1
- package/dist/utils/build-dataset-query-builder.js +16 -8
- package/dist/utils/by-name.d.ts +8 -0
- package/dist/utils/by-name.d.ts.map +1 -0
- package/dist/utils/by-name.js +44 -0
- package/dist/utils/calendar-shift-sql.d.ts +5 -4
- package/dist/utils/calendar-shift-sql.d.ts.map +1 -1
- package/dist/utils/calendar-shift-sql.js +8 -8
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +2 -2
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -1
- package/dist/utils/clickhouse-dataset-sql-dialect.js +140 -1
- package/dist/utils/compile-dataset-query.d.ts.map +1 -1
- package/dist/utils/compile-dataset-query.js +9 -6
- package/dist/utils/composite-time-measure-sql.d.ts +3 -5
- package/dist/utils/composite-time-measure-sql.d.ts.map +1 -1
- package/dist/utils/composite-time-measure-sql.js +15 -16
- package/dist/utils/count-aggregations.d.ts +4 -0
- package/dist/utils/count-aggregations.d.ts.map +1 -0
- package/dist/utils/count-aggregations.js +6 -0
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
- package/dist/utils/dataset-definition-validation.js +36 -26
- package/dist/utils/dataset-derived-query.d.ts +2 -4
- package/dist/utils/dataset-derived-query.d.ts.map +1 -1
- package/dist/utils/dataset-derived-query.js +11 -14
- package/dist/utils/dataset-having.d.ts +5 -7
- package/dist/utils/dataset-having.d.ts.map +1 -1
- package/dist/utils/dataset-having.js +9 -12
- package/dist/utils/dataset-normalization.d.ts.map +1 -1
- package/dist/utils/dataset-normalization.js +1 -0
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +7 -0
- package/dist/utils/dataset-source.d.ts +9 -0
- package/dist/utils/dataset-source.d.ts.map +1 -0
- package/dist/utils/dataset-source.js +16 -0
- package/dist/utils/dataset-sql-dialect.d.ts +13 -2
- package/dist/utils/dataset-sql-dialect.d.ts.map +1 -1
- package/dist/utils/dataset-sql-dialect.js +74 -1
- package/dist/utils/dataset-validation.d.ts.map +1 -1
- package/dist/utils/dataset-validation.js +7 -0
- package/dist/utils/deployable-formulas.d.ts +33 -0
- package/dist/utils/deployable-formulas.d.ts.map +1 -0
- package/dist/utils/deployable-formulas.js +102 -0
- package/dist/utils/derived-cte-validation.d.ts +5 -0
- package/dist/utils/derived-cte-validation.d.ts.map +1 -1
- package/dist/utils/derived-cte-validation.js +77 -8
- package/dist/utils/derived-measure-sql.d.ts +10 -2
- package/dist/utils/derived-measure-sql.d.ts.map +1 -1
- package/dist/utils/derived-measure-sql.js +23 -43
- package/dist/utils/filter-field-type.d.ts +14 -0
- package/dist/utils/filter-field-type.d.ts.map +1 -0
- package/dist/utils/filter-field-type.js +21 -0
- package/dist/utils/filtered-aggregation-sql.d.ts +2 -1
- package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
- package/dist/utils/filtered-aggregation-sql.js +18 -35
- package/dist/utils/groupable.d.ts +7 -0
- package/dist/utils/groupable.d.ts.map +1 -0
- package/dist/utils/groupable.js +8 -0
- package/dist/utils/metric-derived-query.d.ts +2 -4
- package/dist/utils/metric-derived-query.d.ts.map +1 -1
- package/dist/utils/metric-derived-query.js +54 -41
- package/dist/utils/metric-query-builder.d.ts.map +1 -1
- package/dist/utils/metric-query-builder.js +17 -9
- package/dist/utils/metric-query-validation.d.ts.map +1 -1
- package/dist/utils/metric-query-validation.js +7 -0
- package/dist/utils/portable-aggregations.d.ts +1 -0
- package/dist/utils/portable-aggregations.d.ts.map +1 -1
- package/dist/utils/portable-aggregations.js +5 -0
- package/dist/utils/protocol-schema-conversion.d.ts +12 -0
- package/dist/utils/protocol-schema-conversion.d.ts.map +1 -0
- package/dist/utils/protocol-schema-conversion.js +61 -0
- package/dist/utils/query-field-references.d.ts +10 -0
- package/dist/utils/query-field-references.d.ts.map +1 -0
- package/dist/utils/query-field-references.js +38 -0
- package/dist/utils/query-timezone.d.ts +41 -3
- package/dist/utils/query-timezone.d.ts.map +1 -1
- package/dist/utils/query-timezone.js +75 -8
- package/dist/utils/raw-sql-dialect-check.d.ts +12 -0
- package/dist/utils/raw-sql-dialect-check.d.ts.map +1 -0
- package/dist/utils/raw-sql-dialect-check.js +37 -0
- package/dist/utils/relationship-builder-plan.d.ts +25 -8
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +23 -9
- package/dist/utils/relationship-measure-sql.d.ts +14 -4
- package/dist/utils/relationship-measure-sql.d.ts.map +1 -1
- package/dist/utils/relationship-measure-sql.js +33 -10
- package/dist/utils/relationship-measures.d.ts.map +1 -1
- package/dist/utils/relationship-measures.js +3 -0
- package/dist/utils/semantic-tool.d.ts +2 -2
- package/dist/utils/semantic-tool.d.ts.map +1 -1
- package/dist/utils/semantic-tool.js +1 -1
- package/dist/utils/shift-measure-sql.d.ts +5 -4
- package/dist/utils/shift-measure-sql.d.ts.map +1 -1
- package/dist/utils/shift-measure-sql.js +25 -26
- package/dist/utils/shift-wall-time-sql.d.ts +2 -1
- package/dist/utils/shift-wall-time-sql.d.ts.map +1 -1
- package/dist/utils/shift-wall-time-sql.js +7 -7
- package/dist/utils/statement-parameters.d.ts +58 -0
- package/dist/utils/statement-parameters.d.ts.map +1 -0
- package/dist/utils/statement-parameters.js +101 -0
- package/dist/utils/time-axis-calendar-guard.d.ts +2 -1
- package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -1
- package/dist/utils/time-axis-calendar-guard.js +5 -7
- package/dist/utils/time-measure-axis-sql.d.ts +2 -1
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-axis-sql.js +19 -22
- package/dist/utils/time-measure-context-sql.d.ts +3 -2
- package/dist/utils/time-measure-context-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-context-sql.js +11 -11
- package/dist/utils/time-measure-dataset-sql.d.ts +7 -5
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-dataset-sql.js +17 -12
- package/dist/utils/time-measure-leaf-sql.d.ts +2 -1
- package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-leaf-sql.js +17 -18
- package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-result-sql.js +10 -11
- package/dist/utils/time-measure-source-sql.d.ts +2 -2
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-source-sql.js +25 -16
- package/dist/utils/time-measure-values-sql.d.ts +4 -3
- package/dist/utils/time-measure-values-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-values-sql.js +26 -29
- package/dist/utils/timestamp-instants.d.ts +17 -0
- package/dist/utils/timestamp-instants.d.ts.map +1 -0
- package/dist/utils/timestamp-instants.js +34 -0
- package/dist/utils/timestamp-text.d.ts +12 -0
- package/dist/utils/timestamp-text.d.ts.map +1 -0
- package/dist/utils/timestamp-text.js +18 -0
- package/dist/utils/zod-json-schema.d.ts +8 -0
- package/dist/utils/zod-json-schema.d.ts.map +1 -0
- package/dist/utils/zod-json-schema.js +18 -0
- package/dist/utils/zod-schema-version.d.ts +19 -0
- package/dist/utils/zod-schema-version.d.ts.map +1 -0
- package/dist/utils/zod-schema-version.js +6 -0
- package/dist/utils/zod-v3-protocol-schema.d.ts +10 -0
- package/dist/utils/zod-v3-protocol-schema.d.ts.map +1 -0
- package/dist/utils/zod-v3-protocol-schema.js +164 -0
- package/dist/utils/zod-v4-protocol-schema.d.ts +12 -0
- package/dist/utils/zod-v4-protocol-schema.d.ts.map +1 -0
- package/dist/utils/zod-v4-protocol-schema.js +171 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +42 -8
- package/package.json +21 -12
- package/dist/api.type-test.d.ts +0 -2
- package/dist/api.type-test.d.ts.map +0 -1
- package/dist/api.type-test.js +0 -251
- package/dist/utils/time-arithmetic-sql.d.ts +0 -4
- package/dist/utils/time-arithmetic-sql.d.ts.map +0 -1
- package/dist/utils/time-arithmetic-sql.js +0 -9
- package/dist/utils/window-aggregation-sql.d.ts +0 -5
- package/dist/utils/window-aggregation-sql.d.ts.map +0 -1
- 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 =
|
|
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
|
-
:
|
|
20
|
-
const upper = shiftBound(
|
|
21
|
-
const shiftedEnd =
|
|
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(
|
|
24
|
-
upper: calendar ? `
|
|
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 =
|
|
38
|
-
const last =
|
|
39
|
-
const count =
|
|
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 =
|
|
45
|
-
return shiftWallTimeGuardSql(shift.interval, grain, '_hq_series CROSS JOIN _hq_bounds', '_hq_period',
|
|
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 =
|
|
54
|
-
const upperBucket =
|
|
55
|
-
const
|
|
56
|
-
const
|
|
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 *, ${
|
|
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;
|
|
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 =
|
|
9
|
-
return
|
|
10
|
-
.map(part =>
|
|
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
|
-
|
|
16
|
-
|
|
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;
|
|
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
|
|
12
|
-
const
|
|
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
|
-
|
|
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":"
|
|
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
|
|
11
|
-
const
|
|
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
|
-
: `
|
|
15
|
-
const count =
|
|
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 = `
|
|
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
|
-
:
|
|
21
|
-
const calendarGuard = calendarBucketGuardSql('_hq_first', `${count} + ${limitGuard} + ${rangeGuard}`, axis.grain);
|
|
22
|
-
const
|
|
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
|
-
?
|
|
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 =
|
|
27
|
+
const timezone = dialect.timezoneOf(dialect.assumeNotNull('(SELECT _hq_time FROM _hq_source LIMIT 0)'));
|
|
32
28
|
const bounds = [
|
|
33
|
-
|
|
34
|
-
|
|
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 =
|
|
39
|
-
const period =
|
|
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;
|
|
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 =
|
|
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(
|
|
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) =>
|
|
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 ?
|
|
20
|
-
|
|
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;
|
|
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 {
|
|
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 =
|
|
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 >=
|
|
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
|
|
80
|
-
return {
|
|
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":"
|
|
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"}
|