@hypequery/datasets 0.20.0 → 0.22.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 +6 -0
- package/dist/api.type-test.js +7 -1
- package/dist/catalog.d.ts +7 -3
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +15 -3
- package/dist/contract.d.ts +1 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/dataset-compilation.d.ts +45 -0
- package/dist/dataset-compilation.d.ts.map +1 -0
- package/dist/dataset-compilation.js +1 -0
- package/dist/dataset-query.d.ts +3 -3
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +7 -71
- package/dist/dataset-sql-dialect.d.ts +11 -0
- package/dist/dataset-sql-dialect.d.ts.map +1 -0
- package/dist/dataset-sql-dialect.js +1 -0
- package/dist/dataset.d.ts +3 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/execution-capabilities.d.ts +28 -0
- package/dist/execution-capabilities.d.ts.map +1 -0
- package/dist/execution-capabilities.js +17 -0
- package/dist/execution-capabilities.json +13 -0
- package/dist/executor.d.ts +17 -38
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +37 -400
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/measure.d.ts +56 -19
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +5 -65
- package/dist/metric-query-engine.d.ts +29 -0
- package/dist/metric-query-engine.d.ts.map +1 -0
- package/dist/metric-query-engine.js +106 -0
- package/dist/portable-executor.d.ts +20 -6
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +29 -9
- package/dist/query-builder-protocol.d.ts +4 -0
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-helpers.d.ts +10 -10
- package/dist/query-helpers.d.ts.map +1 -1
- package/dist/query-helpers.js +1 -3
- package/dist/query-planner.d.ts +7 -3
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +10 -12
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +2 -0
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +3 -3
- package/dist/types.d.ts +22 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +1 -0
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +2 -1
- package/dist/utils/approximate-measures.d.ts +1 -0
- package/dist/utils/approximate-measures.d.ts.map +1 -1
- package/dist/utils/approximate-measures.js +10 -4
- package/dist/utils/base-measure-definition.d.ts +23 -0
- package/dist/utils/base-measure-definition.d.ts.map +1 -0
- package/dist/utils/base-measure-definition.js +65 -0
- package/dist/utils/build-dataset-query-builder.d.ts +5 -0
- package/dist/utils/build-dataset-query-builder.d.ts.map +1 -0
- package/dist/utils/build-dataset-query-builder.js +62 -0
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +4 -0
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -0
- package/dist/utils/clickhouse-dataset-sql-dialect.js +6 -0
- package/dist/utils/compile-dataset-query.d.ts +10 -0
- package/dist/utils/compile-dataset-query.d.ts.map +1 -0
- package/dist/utils/compile-dataset-query.js +71 -0
- package/dist/utils/composite-time-measure-sql.d.ts +10 -0
- package/dist/utils/composite-time-measure-sql.d.ts.map +1 -0
- package/dist/utils/composite-time-measure-sql.js +61 -0
- package/dist/utils/dataset-derived-query.d.ts +1 -2
- package/dist/utils/dataset-derived-query.d.ts.map +1 -1
- package/dist/utils/dataset-derived-query.js +16 -30
- package/dist/utils/dataset-filter-types.d.ts +28 -0
- package/dist/utils/dataset-filter-types.d.ts.map +1 -0
- package/dist/utils/dataset-filter-types.js +1 -0
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +15 -3
- package/dist/utils/dataset-sql-dialect.d.ts +5 -0
- package/dist/utils/dataset-sql-dialect.d.ts.map +1 -0
- package/dist/utils/dataset-sql-dialect.js +5 -0
- package/dist/utils/dataset-target.d.ts +3 -0
- package/dist/utils/dataset-target.d.ts.map +1 -0
- package/dist/utils/dataset-target.js +3 -0
- package/dist/utils/derived-measure-sql.d.ts +1 -0
- package/dist/utils/derived-measure-sql.d.ts.map +1 -1
- package/dist/utils/derived-measure-sql.js +9 -6
- package/dist/utils/derived-measure-validation.d.ts.map +1 -1
- package/dist/utils/derived-measure-validation.js +4 -25
- package/dist/utils/filter-definition.d.ts +3 -0
- package/dist/utils/filter-definition.d.ts.map +1 -0
- package/dist/utils/filter-definition.js +3 -0
- package/dist/utils/measure-dependencies.d.ts +8 -0
- package/dist/utils/measure-dependencies.d.ts.map +1 -0
- package/dist/utils/measure-dependencies.js +44 -0
- package/dist/utils/measure-evaluation-graph.d.ts +23 -0
- package/dist/utils/measure-evaluation-graph.d.ts.map +1 -0
- package/dist/utils/measure-evaluation-graph.js +38 -0
- package/dist/utils/measure-time-grains.d.ts.map +1 -1
- package/dist/utils/measure-time-grains.js +4 -2
- package/dist/utils/metric-derived-query.d.ts +8 -0
- package/dist/utils/metric-derived-query.d.ts.map +1 -0
- package/dist/utils/metric-derived-query.js +106 -0
- package/dist/utils/metric-query-builder.d.ts +5 -0
- package/dist/utils/metric-query-builder.d.ts.map +1 -0
- package/dist/utils/metric-query-builder.js +50 -0
- package/dist/utils/metric-query-result.d.ts +10 -0
- package/dist/utils/metric-query-result.d.ts.map +1 -0
- package/dist/utils/metric-query-result.js +18 -0
- package/dist/utils/metric-query-validation.d.ts +6 -0
- package/dist/utils/metric-query-validation.d.ts.map +1 -0
- package/dist/utils/metric-query-validation.js +116 -0
- package/dist/utils/relationship-builder-plan.d.ts +4 -2
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +10 -13
- package/dist/utils/relationship-measure-sql.d.ts +14 -0
- package/dist/utils/relationship-measure-sql.d.ts.map +1 -0
- package/dist/utils/relationship-measure-sql.js +44 -0
- package/dist/utils/relationship-measure-types.d.ts +36 -0
- package/dist/utils/relationship-measure-types.d.ts.map +1 -0
- package/dist/utils/relationship-measure-types.js +1 -0
- package/dist/utils/relationship-measures.d.ts +23 -0
- package/dist/utils/relationship-measures.d.ts.map +1 -0
- package/dist/utils/relationship-measures.js +62 -0
- package/dist/utils/relationship-validation.d.ts +1 -1
- package/dist/utils/relationship-validation.d.ts.map +1 -1
- package/dist/utils/relationship-validation.js +3 -5
- package/dist/utils/semantic-query-syntax.d.ts +6 -0
- package/dist/utils/semantic-query-syntax.d.ts.map +1 -0
- package/dist/utils/semantic-query-syntax.js +25 -0
- package/dist/utils/shift-measure-grains.d.ts +5 -0
- package/dist/utils/shift-measure-grains.d.ts.map +1 -0
- package/dist/utils/shift-measure-grains.js +10 -0
- package/dist/utils/shift-measure-sql.d.ts +5 -2
- package/dist/utils/shift-measure-sql.d.ts.map +1 -1
- package/dist/utils/shift-measure-sql.js +25 -8
- package/dist/utils/shift-measure-validation.js +3 -3
- package/dist/utils/shift-wall-time-sql.d.ts +4 -0
- package/dist/utils/shift-wall-time-sql.d.ts.map +1 -0
- package/dist/utils/shift-wall-time-sql.js +17 -0
- package/dist/utils/snapshot-execution-context.d.ts +4 -0
- package/dist/utils/snapshot-execution-context.d.ts.map +1 -0
- package/dist/utils/snapshot-execution-context.js +16 -0
- package/dist/utils/time-measure-axis-sql.d.ts +1 -0
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-axis-sql.js +5 -4
- package/dist/utils/time-measure-axis.d.ts.map +1 -1
- package/dist/utils/time-measure-axis.js +3 -2
- package/dist/utils/time-measure-context-sql.d.ts +6 -0
- package/dist/utils/time-measure-context-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-context-sql.js +21 -0
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-dataset-sql.js +27 -9
- package/dist/utils/time-measure-leaf-sql.d.ts +14 -0
- package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-leaf-sql.js +52 -0
- package/dist/utils/time-measure-result-sql.d.ts +1 -1
- package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-result-sql.js +4 -3
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-source-sql.js +8 -4
- package/dist/utils/time-query-measures.d.ts.map +1 -1
- package/dist/utils/time-query-measures.js +4 -8
- package/dist/utils/window-query-signature.d.ts.map +1 -1
- package/dist/utils/window-query-signature.js +12 -2
- package/dist/validation.js +1 -1
- package/package.json +8 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationship-measures.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-measures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAMjG,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,sBAAsB,CAAC;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAED,iFAAiF;AACjF,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,GAAG,2BAA2B,CAkB5G;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAS9H;AAED,yGAAyG;AACzG,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,kBAAkB,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,kEAM1F;AAED,kFAAkF;AAClF,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,MAAM,EAAO;;;;;;;;SAIlH"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isBaseMeasure } from './dataset-measures.js';
|
|
2
|
+
import { parseQualifiedField, resolveQualifiedField } from './relationship-fields.js';
|
|
3
|
+
const DUPLICATE_INSENSITIVE = new Set(['countDistinct', 'approxCountDistinct', 'min', 'max', 'argMax', 'argMin']);
|
|
4
|
+
/** Resolve only the one-hop, duplicate-safe aggregate vocabulary of RFC 0015. */
|
|
5
|
+
export function resolveRelationshipMeasure(ds, name) {
|
|
6
|
+
const parsed = parseQualifiedField(name);
|
|
7
|
+
if (!parsed || parsed.field.includes('.'))
|
|
8
|
+
throw new Error(`Measure "${name}" must use a one-hop relationship path.`);
|
|
9
|
+
const relationship = Object.hasOwn(ds.relationships, parsed.relationship) ? ds.relationships[parsed.relationship] : undefined;
|
|
10
|
+
if (!relationship)
|
|
11
|
+
throw new Error(`Unknown relationship "${parsed.relationship}" in measure "${name}".`);
|
|
12
|
+
if (relationship.kind === 'hasMany')
|
|
13
|
+
throw new Error(`Measure "${name}" cannot traverse hasMany: it would fan out aggregates.`);
|
|
14
|
+
const target = relationship.target();
|
|
15
|
+
const definition = Object.hasOwn(target.measures, parsed.field) ? target.measures[parsed.field] : undefined;
|
|
16
|
+
if (!definition)
|
|
17
|
+
throw new Error(`Unknown measure "${parsed.field}" on relationship target "${target.name}".`);
|
|
18
|
+
if (!isBaseMeasure(definition))
|
|
19
|
+
throw new Error(`Measure "${name}" must reference a base aggregate; derived, window, and shift measures are not selectable through relationships.`);
|
|
20
|
+
if (relationship.kind === 'belongsTo' && !DUPLICATE_INSENSITIVE.has(definition.aggregation)) {
|
|
21
|
+
throw new Error(`Measure "${name}" cannot use ${definition.aggregation} through belongsTo: repeated target rows would inflate the aggregation. Use a duplicate-insensitive measure or query the target dataset.`);
|
|
22
|
+
}
|
|
23
|
+
const referenced = [definition.field, definition.argField, ...(definition.filters ?? []).map(filter => target.filters[filter.field]?.field ?? filter.field)];
|
|
24
|
+
if (definition.sql || referenced.some(field => field && target.dimensions[field]?.sql)) {
|
|
25
|
+
throw new Error(`SQL-backed measure "${name}" or its inputs cannot be traversed through relationships.`);
|
|
26
|
+
}
|
|
27
|
+
return { relationshipName: parsed.relationship, relationship, target, definition };
|
|
28
|
+
}
|
|
29
|
+
export function listRelationshipMeasures(name, relationship) {
|
|
30
|
+
if (relationship.kind === 'hasMany')
|
|
31
|
+
return {};
|
|
32
|
+
const ds = { relationships: { [name]: relationship } };
|
|
33
|
+
const target = relationship.target();
|
|
34
|
+
return Object.fromEntries(Object.keys(target.measures).flatMap(measure => {
|
|
35
|
+
const qualified = `${name}.${measure}`;
|
|
36
|
+
try {
|
|
37
|
+
return [[qualified, resolveRelationshipMeasure(ds, qualified).definition]];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** Expand selected relationship aggregates for dependency graphs without changing author definitions. */
|
|
45
|
+
export function queryMeasureDefinitions(ds, selected) {
|
|
46
|
+
const qualified = selected.filter(name => name.includes('.'));
|
|
47
|
+
return qualified.length ? {
|
|
48
|
+
...ds.measures,
|
|
49
|
+
...Object.fromEntries(qualified.map(name => [name, resolveRelationshipMeasure(ds, name).definition])),
|
|
50
|
+
} : ds.measures;
|
|
51
|
+
}
|
|
52
|
+
/** Resolve a selected measure or a dimension reference while collecting joins. */
|
|
53
|
+
export function resolveRelationshipReference(ds, name, measures = []) {
|
|
54
|
+
if (!measures.includes(name))
|
|
55
|
+
return resolveQualifiedField(ds, name);
|
|
56
|
+
try {
|
|
57
|
+
return { resolved: resolveRelationshipMeasure(ds, name) };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return { error: error.message };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* dataset-query and metric-query validators.
|
|
4
4
|
*/
|
|
5
5
|
import type { AnyDatasetInstance, DatasetQuery, ExecutionContext, MetricFilter, MetricQuery } from '../types.js';
|
|
6
|
-
type RelationshipQuery = Pick<DatasetQuery & MetricQuery, 'dimensions' | 'filters' | 'orderBy'>;
|
|
6
|
+
type RelationshipQuery = Pick<DatasetQuery & MetricQuery, 'dimensions' | 'filters' | 'orderBy'> & Pick<DatasetQuery, 'measures'>;
|
|
7
7
|
/**
|
|
8
8
|
* Tenant-less base datasets may still reach tenant-scoped data through a
|
|
9
9
|
* relationship. Require either a concrete runtime tenant or the trusted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAWrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAoBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,CA4Bf"}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Shared validation for relationship-qualified filters, used by both the
|
|
3
|
-
* dataset-query and metric-query validators.
|
|
4
|
-
*/
|
|
1
|
+
import { resolveRelationshipReference } from './relationship-measures.js';
|
|
5
2
|
import { validateFilterValue } from '../validation.js';
|
|
6
3
|
import { getRuntimeTenantPredicate, hasTenantRuntime, } from './tenant-runtime.js';
|
|
7
4
|
import { isQualifiedField, resolveQualifiedField, } from './relationship-fields.js';
|
|
@@ -16,11 +13,12 @@ export function validateRelationshipTenantRuntime(ds, query, context) {
|
|
|
16
13
|
}
|
|
17
14
|
const referenced = [
|
|
18
15
|
...(query.dimensions ?? []),
|
|
16
|
+
...(query.measures ?? []),
|
|
19
17
|
...(query.filters ?? []).map((filter) => filter.field),
|
|
20
18
|
...(query.orderBy ?? []).map((order) => order.field),
|
|
21
19
|
].filter(isQualifiedField);
|
|
22
20
|
for (const name of referenced) {
|
|
23
|
-
const resolution =
|
|
21
|
+
const resolution = resolveRelationshipReference(ds, name, query.measures);
|
|
24
22
|
if (resolution?.resolved?.target.tenantKey) {
|
|
25
23
|
return `Dataset "${ds.name}" requires runtime tenant scoping because relationship "${resolution.resolved.relationshipName}" targets a tenant-scoped dataset.`;
|
|
26
24
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DatasetQuery, MetricQuery } from '../types.js';
|
|
2
|
+
/** Reject SQL structure outside the semantic contract before reaching a builder. */
|
|
3
|
+
export declare function semanticQuerySyntaxErrors(query: DatasetQuery | MetricQuery): string[];
|
|
4
|
+
/** Never interpolate a caller-provided direction into raw SQL. */
|
|
5
|
+
export declare function semanticOrderDirection(direction: unknown): 'ASC' | 'DESC';
|
|
6
|
+
//# sourceMappingURL=semantic-query-syntax.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-query-syntax.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-query-syntax.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI7D,oFAAoF;AACpF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,GAAG,MAAM,EAAE,CAarF;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAIzE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SEMANTIC_FILTER_OPERATORS } from '../constants.js';
|
|
2
|
+
const FILTER_OPERATORS = new Set(SEMANTIC_FILTER_OPERATORS);
|
|
3
|
+
/** Reject SQL structure outside the semantic contract before reaching a builder. */
|
|
4
|
+
export function semanticQuerySyntaxErrors(query) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
for (const filter of query.filters ?? []) {
|
|
7
|
+
if (!FILTER_OPERATORS.has(filter.operator)) {
|
|
8
|
+
errors.push(`Unsupported semantic filter operator "${String(filter.operator)}".`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
for (const order of query.orderBy ?? []) {
|
|
12
|
+
if (order.direction !== 'asc' && order.direction !== 'desc') {
|
|
13
|
+
errors.push('Invalid order direction: expected "asc" or "desc".');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return errors;
|
|
17
|
+
}
|
|
18
|
+
/** Never interpolate a caller-provided direction into raw SQL. */
|
|
19
|
+
export function semanticOrderDirection(direction) {
|
|
20
|
+
if (direction === 'asc')
|
|
21
|
+
return 'ASC';
|
|
22
|
+
if (direction === 'desc')
|
|
23
|
+
return 'DESC';
|
|
24
|
+
throw new Error('Invalid order direction: expected "asc" or "desc".');
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MeasureTimeInterval, TimeGrain } from '../types.js';
|
|
2
|
+
/** Calendar shifts preserve dates; they do not convert months into fixed durations. */
|
|
3
|
+
export declare function calendarShiftAtFineGrain(interval: MeasureTimeInterval, grain: TimeGrain): boolean;
|
|
4
|
+
export declare function supportsShiftGrain(interval: MeasureTimeInterval, grain: TimeGrain): boolean;
|
|
5
|
+
//# sourceMappingURL=shift-measure-grains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift-measure-grains.d.ts","sourceRoot":"","sources":["../../src/utils/shift-measure-grains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGlE,uFAAuF;AACvF,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAGjG;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAG3F"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { intervalBuckets } from './time-axis-intervals.js';
|
|
2
|
+
/** Calendar shifts preserve dates; they do not convert months into fixed durations. */
|
|
3
|
+
export function calendarShiftAtFineGrain(interval, grain) {
|
|
4
|
+
return ['month', 'quarter', 'year'].includes(interval.unit)
|
|
5
|
+
&& ['minute', 'hour', 'day', 'week'].includes(grain);
|
|
6
|
+
}
|
|
7
|
+
export function supportsShiftGrain(interval, grain) {
|
|
8
|
+
return calendarShiftAtFineGrain(interval, grain)
|
|
9
|
+
|| intervalBuckets(interval.amount, interval.unit, grain) !== undefined;
|
|
10
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ShiftMeasureDefinition
|
|
1
|
+
import type { ShiftMeasureDefinition } from '../types.js';
|
|
2
2
|
import type { TimeMeasureAxis } from './time-measure-axis.js';
|
|
3
3
|
/** Preserve the selected portion of a bucket and both authored endpoint operators. */
|
|
4
4
|
export declare function shiftRangeSql(shift: ShiftMeasureDefinition, axis: TimeMeasureAxis, period?: string): {
|
|
@@ -9,10 +9,13 @@ export declare function shiftRangeSql(shift: ShiftMeasureDefinition, axis: TimeM
|
|
|
9
9
|
};
|
|
10
10
|
export declare function shiftRangePredicateSql(time: string, range: ReturnType<typeof shiftRangeSql>): string;
|
|
11
11
|
/** Check the shifted scan independently of whether it contains source rows. */
|
|
12
|
-
export declare function shiftCalendarGuardSql(shift: ShiftMeasureDefinition,
|
|
12
|
+
export declare function shiftCalendarGuardSql(shift: ShiftMeasureDefinition, axis: TimeMeasureAxis, axisGuard: string): string;
|
|
13
|
+
/** Validate each output bucket after the series exists, including empty source populations. */
|
|
14
|
+
export declare function shiftWallTimeSeriesGuardSql(shift: ShiftMeasureDefinition, grain: TimeMeasureAxis['grain']): string;
|
|
13
15
|
/** Map output buckets to their earlier half-open ranges, retaining calendar arithmetic. */
|
|
14
16
|
export declare function shiftBucketCtes(shift: ShiftMeasureDefinition, axis: TimeMeasureAxis, index: number): {
|
|
15
17
|
ctes: string[];
|
|
16
18
|
rowsSql: string;
|
|
19
|
+
sourceTimesSql: string;
|
|
17
20
|
};
|
|
18
21
|
//# sourceMappingURL=shift-measure-sql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shift-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/shift-measure-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,
|
|
1
|
+
{"version":3,"file":"shift-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/shift-measure-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAI1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAI9D,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM;;;;;EAclG;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,GAAG,MAAM,CAGpG;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAOrH;AAED,+FAA+F;AAC/F,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,MAAM,CAMlH;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAsBhK"}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { GRAIN_FUNCTIONS } from '../constants.js';
|
|
2
2
|
import { addTimeSql, subtractTimeSql } from './time-arithmetic-sql.js';
|
|
3
3
|
import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
|
|
4
|
+
import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
|
|
5
|
+
import { shiftWallTimeGuardSql } from './shift-wall-time-sql.js';
|
|
4
6
|
/** Preserve the selected portion of a bucket and both authored endpoint operators. */
|
|
5
7
|
export function shiftRangeSql(shift, axis, period) {
|
|
6
|
-
const
|
|
8
|
+
const first = period ?? '_hq_first';
|
|
9
|
+
const last = period ?? '_hq_last';
|
|
10
|
+
const end = addTimeSql(last, 1, axis.grain);
|
|
11
|
+
const upper = subtractTimeSql(`least(${end}, _hq_upper)`, shift.interval.amount, shift.interval.unit);
|
|
12
|
+
// Clamp the bucket anchor, then retain its width. Independently clamping both
|
|
13
|
+
// dates would turn March 30–31 into February 28–28, dropping an entire day.
|
|
14
|
+
const shiftedEnd = addTimeSql(subtractTimeSql(last, shift.interval.amount, shift.interval.unit), 1, axis.grain);
|
|
7
15
|
return {
|
|
8
|
-
lower: subtractTimeSql(
|
|
9
|
-
upper:
|
|
16
|
+
lower: subtractTimeSql(`greatest(${first}, _hq_lower)`, shift.interval.amount, shift.interval.unit),
|
|
17
|
+
upper: calendarShiftAtFineGrain(shift.interval, axis.grain) ? `if(_hq_upper >= ${end}, ${shiftedEnd}, ${upper})` : upper,
|
|
10
18
|
lowerInclusive: period ? `(${period} > _hq_lower OR ${Number(axis.lowerInclusive)})` : String(Number(axis.lowerInclusive)),
|
|
11
|
-
upperInclusive:
|
|
19
|
+
upperInclusive: `(_hq_upper < ${end} AND ${Number(axis.upperInclusive)})`,
|
|
12
20
|
};
|
|
13
21
|
}
|
|
14
22
|
export function shiftRangePredicateSql(time, range) {
|
|
@@ -16,14 +24,19 @@ export function shiftRangePredicateSql(time, range) {
|
|
|
16
24
|
+ ` AND (${time} < ${range.upper} OR (${range.upperInclusive} AND ${time} = ${range.upper}))`;
|
|
17
25
|
}
|
|
18
26
|
/** Check the shifted scan independently of whether it contains source rows. */
|
|
19
|
-
export function shiftCalendarGuardSql(shift,
|
|
20
|
-
const
|
|
21
|
-
const upper =
|
|
27
|
+
export function shiftCalendarGuardSql(shift, axis, axisGuard) {
|
|
28
|
+
const { grain } = axis;
|
|
29
|
+
const { lower, upper } = shiftRangeSql(shift, axis);
|
|
22
30
|
const first = `${GRAIN_FUNCTIONS[grain]}(${lower})`;
|
|
23
31
|
const last = `${GRAIN_FUNCTIONS[grain]}(${upper})`;
|
|
24
32
|
const count = `dateDiff('${grain}', ${first}, ${last}) + ${axisGuard}`;
|
|
25
33
|
return calendarBucketGuardSql(first, count, grain);
|
|
26
34
|
}
|
|
35
|
+
/** Validate each output bucket after the series exists, including empty source populations. */
|
|
36
|
+
export function shiftWallTimeSeriesGuardSql(shift, grain) {
|
|
37
|
+
const end = addTimeSql('_hq_period', 1, grain);
|
|
38
|
+
return shiftWallTimeGuardSql(shift.interval, grain, '_hq_series CROSS JOIN _hq_bounds', '_hq_period', 'greatest(_hq_period, _hq_lower)', `least(${end}, _hq_upper)`, end);
|
|
39
|
+
}
|
|
27
40
|
/** Map output buckets to their earlier half-open ranges, retaining calendar arithmetic. */
|
|
28
41
|
export function shiftBucketCtes(shift, axis, index) {
|
|
29
42
|
const { grain } = axis;
|
|
@@ -33,7 +46,11 @@ export function shiftBucketCtes(shift, axis, index) {
|
|
|
33
46
|
const bucket = `${GRAIN_FUNCTIONS[grain]}(_hq_shift_lower)`;
|
|
34
47
|
const upperBucket = `${GRAIN_FUNCTIONS[grain]}(_hq_shift_upper)`;
|
|
35
48
|
const count = `greatest(0, dateDiff('${grain}', ${bucket}, ${upperBucket}) + if(_hq_shift_upper > ${upperBucket} OR _hq_shift_upper_inclusive, 1, 0))`;
|
|
49
|
+
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' })}`;
|
|
36
50
|
return {
|
|
51
|
+
// Membership preserves duplicate source rows while excluding dates and
|
|
52
|
+
// partial ranges that no output bucket maps to, including clamped overlaps.
|
|
53
|
+
sourceTimesSql: `SELECT r._hq_time FROM _hq_source${join}`,
|
|
37
54
|
ctes: [
|
|
38
55
|
`${ranges} AS (SELECT _hq_period, ${range.lower} AS _hq_shift_lower, ${range.upper} AS _hq_shift_upper, ${range.lowerInclusive} AS _hq_shift_lower_inclusive, ${range.upperInclusive} AS _hq_shift_upper_inclusive FROM _hq_series CROSS JOIN _hq_bounds)`,
|
|
39
56
|
// A DST transition can map a one-hour output bucket to zero or two
|
|
@@ -41,6 +58,6 @@ export function shiftBucketCtes(shift, axis, index) {
|
|
|
41
58
|
// The equality join avoids a source-rows × output-series Cartesian product.
|
|
42
59
|
`${keys} AS (SELECT *, ${addTimeSql(bucket, `arrayJoin(range(toUInt64(${count})))`, grain)} AS _hq_source_period FROM ${ranges})`,
|
|
43
60
|
],
|
|
44
|
-
rowsSql: `SELECT r.*, b._hq_period FROM _hq_scanned
|
|
61
|
+
rowsSql: `SELECT r.*, b._hq_period FROM _hq_scanned${join}`,
|
|
45
62
|
};
|
|
46
63
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isSafeSQLIdentifier } from '../sql-utils.js';
|
|
2
2
|
import { isSupportedTimeGrain } from '../constants.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isShiftMeasure } from './dataset-measures.js';
|
|
4
4
|
export function validateShiftMeasures(datasetName, timeKey, measures) {
|
|
5
5
|
for (const [name, definition] of Object.entries(measures)) {
|
|
6
6
|
if (!isShiftMeasure(definition))
|
|
@@ -10,8 +10,8 @@ export function validateShiftMeasures(datasetName, timeKey, measures) {
|
|
|
10
10
|
throw new Error(`${prefix} name is not a safe identifier.`);
|
|
11
11
|
if (!timeKey)
|
|
12
12
|
throw new Error(`${prefix} requires a timeKey.`);
|
|
13
|
-
if (!
|
|
14
|
-
throw new Error(`${prefix}
|
|
13
|
+
if (!Object.hasOwn(measures, definition.measure))
|
|
14
|
+
throw new Error(`${prefix} references missing measure "${String(definition.measure)}".`);
|
|
15
15
|
const interval = definition.interval;
|
|
16
16
|
if (!interval || !Number.isSafeInteger(interval.amount) || interval.amount <= 0 || !isSupportedTimeGrain(interval.unit)) {
|
|
17
17
|
throw new Error(`${prefix} interval needs a positive safe integer and supported unit.`);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MeasureTimeInterval, TimeGrain } from '../types.js';
|
|
2
|
+
/** 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
|
+
//# sourceMappingURL=shift-wall-time-sql.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { subtractTimeSql } from './time-arithmetic-sql.js';
|
|
2
|
+
import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
|
|
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) {
|
|
5
|
+
if (!calendarShiftAtFineGrain(interval, grain) || (grain !== 'minute' && grain !== 'hour'))
|
|
6
|
+
return '0';
|
|
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})`)
|
|
11
|
+
.join(' OR ');
|
|
12
|
+
};
|
|
13
|
+
const changed = `(${changesWallTime(period)}) OR (${changesWallTime(lower)})`
|
|
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})`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExecutionContext } from '../types.js';
|
|
2
|
+
/** Bind cache identity and deferred execution to the same request-owned scope. */
|
|
3
|
+
export declare function snapshotExecutionContext(context?: ExecutionContext): ExecutionContext | undefined;
|
|
4
|
+
//# sourceMappingURL=snapshot-execution-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-execution-context.d.ts","sourceRoot":"","sources":["../../src/utils/snapshot-execution-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,SAAS,CAajG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Bind cache identity and deferred execution to the same request-owned scope. */
|
|
2
|
+
export function snapshotExecutionContext(context) {
|
|
3
|
+
if (context === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
return {
|
|
6
|
+
...context,
|
|
7
|
+
...(context.runtime === undefined ? {} : {
|
|
8
|
+
runtime: {
|
|
9
|
+
...context.runtime,
|
|
10
|
+
// Copy tenant objects and ID lists; retain adapter and AbortSignal identity.
|
|
11
|
+
tenant: structuredClone(context.runtime.tenant),
|
|
12
|
+
},
|
|
13
|
+
}),
|
|
14
|
+
...(context.cache && { cache: { ...context.cache } }),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -6,6 +6,7 @@ export interface TimeMeasureSqlAxis {
|
|
|
6
6
|
timeAxisSql: string;
|
|
7
7
|
guard: string;
|
|
8
8
|
rangeGuard: string;
|
|
9
|
+
postSeriesGuard: string;
|
|
9
10
|
}
|
|
10
11
|
/** Build bounded output buckets using the query timezone. */
|
|
11
12
|
export declare function buildTimeMeasureAxisSql(source: TimeMeasureSqlSource, axis: TimeMeasureAxis, shifts?: readonly ShiftMeasureDefinition[]): TimeMeasureSqlAxis;
|
|
@@ -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;
|
|
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,7 +1,7 @@
|
|
|
1
1
|
import { GRAIN_FUNCTIONS } from '../constants.js';
|
|
2
2
|
import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
|
|
3
3
|
import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
|
|
4
|
-
import { shiftCalendarGuardSql } from './shift-measure-sql.js';
|
|
4
|
+
import { shiftCalendarGuardSql, shiftWallTimeSeriesGuardSql } from './shift-measure-sql.js';
|
|
5
5
|
function literal(value) {
|
|
6
6
|
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
7
7
|
}
|
|
@@ -23,8 +23,9 @@ export function buildTimeMeasureAxisSql(source, axis, shifts = []) {
|
|
|
23
23
|
const physicalGuard = axis.grain === 'minute' || axis.grain === 'hour'
|
|
24
24
|
? `throwIf(position(${physicalTimeType}, 'Date') > 0 AND position(${physicalTimeType}, 'DateTime') = 0, 'Sub-day time measures require a time-of-day column; Date and Date32 are not supported.')`
|
|
25
25
|
: '0';
|
|
26
|
-
const shiftGuards = shifts.map(shift => shiftCalendarGuardSql(shift, axis
|
|
26
|
+
const shiftGuards = shifts.map(shift => shiftCalendarGuardSql(shift, axis, `${limitGuard} + ${rangeGuard}`));
|
|
27
27
|
const guard = [limitGuard, physicalGuard, calendarGuard, ...shiftGuards].join(' + ');
|
|
28
|
+
const postSeriesGuard = shifts.map(shift => shiftWallTimeSeriesGuardSql(shift, axis.grain)).join(' + ') || '0';
|
|
28
29
|
// A zero-row scalar retains the converted timestamp type and query timezone.
|
|
29
30
|
// Reading any(time) here would scan the entire population just for its type.
|
|
30
31
|
const timezone = 'timezoneOf(assumeNotNull((SELECT _hq_time FROM _hq_source LIMIT 0)))';
|
|
@@ -42,6 +43,6 @@ export function buildTimeMeasureAxisSql(source, axis, shifts = []) {
|
|
|
42
43
|
];
|
|
43
44
|
// Always check the singleton axis before aggregating. ClickHouse can skip
|
|
44
45
|
// a series guard when a dimensional CROSS JOIN has an empty population.
|
|
45
|
-
const timeAxisSql = `WITH ${[...source.ctes, ctes
|
|
46
|
-
return { ctes, timeAxisSql, guard, rangeGuard };
|
|
46
|
+
const timeAxisSql = `WITH ${[...source.ctes, ...ctes].join(',\n')} SELECT ${guard} + ${rangeGuard} + ${postSeriesGuard} AS _hq_validated FROM _hq_bounds`;
|
|
47
|
+
return { ctes, timeAxisSql, guard, rangeGuard, postSeriesGuard };
|
|
47
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-measure-axis.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"time-measure-axis.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAS7F,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE3E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qFAAqF;AACrF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,GAClB;IAAE,IAAI,CAAC,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAmC9C"}
|
|
@@ -2,7 +2,8 @@ import { isSupportedTimeGrain } from '../constants.js';
|
|
|
2
2
|
import { selectedTimeMeasures } from './time-query-measures.js';
|
|
3
3
|
import { isShiftMeasure } from './dataset-measures.js';
|
|
4
4
|
import { resolveTimeAxisRange, isDefinitelyEmptyTimeRange } from './time-axis-bounds.js';
|
|
5
|
-
import {
|
|
5
|
+
import { windowGrainErrors, estimateTimeAxisBuckets, exceedsEstimatedTimeAxisLimit } from './time-axis-intervals.js';
|
|
6
|
+
import { supportsShiftGrain } from './shift-measure-grains.js';
|
|
6
7
|
export { intervalBuckets, utcBucketStart } from './time-axis-intervals.js';
|
|
7
8
|
/** Validate time-measure grains, resolve bounds, then estimate the output series. */
|
|
8
9
|
export function analyzeTimeMeasureAxis(dataset, query) {
|
|
@@ -16,7 +17,7 @@ export function analyzeTimeMeasureAxis(dataset, query) {
|
|
|
16
17
|
const grain = query.by;
|
|
17
18
|
for (const [name, window] of timeMeasures) {
|
|
18
19
|
if (isShiftMeasure(window)) {
|
|
19
|
-
if (
|
|
20
|
+
if (!supportsShiftGrain(window.interval, grain))
|
|
20
21
|
errors.push(`Shift measure "${name}" must span whole "${grain}" buckets.`);
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MeasureEvaluationContext } from './measure-evaluation-graph.js';
|
|
2
|
+
import type { TimeMeasureAxis } from './time-measure-axis.js';
|
|
3
|
+
/** Keep output coordinates separate from each shifted evaluation interval. */
|
|
4
|
+
export declare function timeMeasureContextCtes(contexts: readonly MeasureEvaluationContext[], axis: TimeMeasureAxis): string[];
|
|
5
|
+
export declare function timeMeasureContextGuard(context: MeasureEvaluationContext, axis: TimeMeasureAxis): string;
|
|
6
|
+
//# sourceMappingURL=time-measure-context-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-measure-context-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-context-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM9D,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAUrH;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAOxG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
|
|
2
|
+
import { calendarShiftAtFineGrain } from './shift-measure-grains.js';
|
|
3
|
+
import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
|
|
4
|
+
import { shiftWallTimeGuardSql } from './shift-wall-time-sql.js';
|
|
5
|
+
/** Keep output coordinates separate from each shifted evaluation interval. */
|
|
6
|
+
export function timeMeasureContextCtes(contexts, axis) {
|
|
7
|
+
const end = add('_hq_period', 1, axis.grain);
|
|
8
|
+
const root = `_hq_context0 AS (SELECT _hq_period, _hq_period AS _hq_eval_period, ${end} AS _hq_eval_end, greatest(_hq_period, _hq_lower) AS _hq_eval_lower, least(${end}, _hq_upper) AS _hq_eval_upper, (_hq_period > _hq_lower OR ${Number(axis.lowerInclusive)}) AS _hq_lower_inclusive, (_hq_upper < ${end} AND ${Number(axis.upperInclusive)}) AS _hq_upper_inclusive FROM _hq_series CROSS JOIN _hq_bounds)`;
|
|
9
|
+
return [root, ...contexts.slice(1).map(context => {
|
|
10
|
+
const interval = context.interval;
|
|
11
|
+
const shift = (value) => subtract(`p.${value}`, interval.amount, interval.unit);
|
|
12
|
+
const period = shift('_hq_eval_period');
|
|
13
|
+
const shiftedEnd = calendarShiftAtFineGrain(interval, axis.grain) ? add(period, 1, axis.grain) : shift('_hq_eval_end');
|
|
14
|
+
return `_hq_context${context.id} AS (SELECT p._hq_period, ${period} AS _hq_eval_period, ${shiftedEnd} AS _hq_eval_end, ${shift('_hq_eval_lower')} AS _hq_eval_lower, if(p._hq_eval_upper = p._hq_eval_end, ${shiftedEnd}, ${shift('_hq_eval_upper')}) AS _hq_eval_upper, p._hq_lower_inclusive, p._hq_upper_inclusive FROM _hq_context${context.parent} AS p)`;
|
|
15
|
+
})];
|
|
16
|
+
}
|
|
17
|
+
export function timeMeasureContextGuard(context, axis) {
|
|
18
|
+
const guard = calendarBucketGuardSql('_hq_eval_period', '1', axis.grain);
|
|
19
|
+
const wallTimeGuard = context.interval === undefined ? '0' : shiftWallTimeGuardSql(context.interval, axis.grain, `_hq_context${context.parent} AS p`, 'p._hq_eval_period', 'p._hq_eval_lower', 'p._hq_eval_upper', 'p._hq_eval_end');
|
|
20
|
+
return `(SELECT coalesce(max(${guard}), 0) FROM _hq_context${context.id}) + ${wallTimeGuard}`;
|
|
21
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-measure-dataset-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-dataset-sql.ts"],"names":[],"mappings":"
|
|
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,9 +1,12 @@
|
|
|
1
|
+
import { queryMeasureDefinitions } from './relationship-measures.js';
|
|
2
|
+
import { buildCompositeTimeMeasureSql } from './composite-time-measure-sql.js';
|
|
3
|
+
import { measureDependencyNames } from './measure-dependencies.js';
|
|
1
4
|
import { getBaseMeasure, getDerivedMeasure, isShiftMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
2
5
|
import { selectedTimeMeasures } from './time-query-measures.js';
|
|
3
6
|
import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
|
|
4
7
|
import { validateDatasetQueryInput } from './dataset-query-validation.js';
|
|
5
8
|
import { addTimeSql as add } from './time-arithmetic-sql.js';
|
|
6
|
-
import { shiftBucketCtes
|
|
9
|
+
import { shiftBucketCtes } from './shift-measure-sql.js';
|
|
7
10
|
import { buildTimeMeasureSourceSql } from './time-measure-source-sql.js';
|
|
8
11
|
import { buildTimeMeasureAxisSql } from './time-measure-axis-sql.js';
|
|
9
12
|
import { buildTimeMeasureValuesSql, windowMeasureRowsSql, windowScanStartSql } from './time-measure-values-sql.js';
|
|
@@ -16,6 +19,16 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
|
|
|
16
19
|
const { axis } = analyzeTimeMeasureAxis(ds, query);
|
|
17
20
|
if (!axis || !ds.timeKey)
|
|
18
21
|
throw new Error('Window measures require a bounded time axis.');
|
|
22
|
+
const dependencies = measureDependencyNames(queryMeasureDefinitions(ds, query.measures ?? []), query.measures ?? []);
|
|
23
|
+
const composite = dependencies.some(name => {
|
|
24
|
+
const definition = ds.measures[name];
|
|
25
|
+
if (isShiftMeasure(definition))
|
|
26
|
+
return !getBaseMeasure(ds.measures, definition.measure);
|
|
27
|
+
const derived = getDerivedMeasure(ds.measures, name);
|
|
28
|
+
return derived && Object.values(derived.uses).some(input => getDerivedMeasure(ds.measures, input));
|
|
29
|
+
});
|
|
30
|
+
if (composite)
|
|
31
|
+
return buildCompositeTimeMeasureSql(ds, query, options, axis);
|
|
19
32
|
const timeMeasures = selectedTimeMeasures(ds, query);
|
|
20
33
|
const windows = [...timeMeasures.values()].filter(isWindowMeasure);
|
|
21
34
|
const shifts = [...timeMeasures.values()].filter(isShiftMeasure);
|
|
@@ -28,10 +41,16 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
|
|
|
28
41
|
const baseNames = new Set([...needed].map(name => timeMeasures.get(name)?.measure ?? name));
|
|
29
42
|
const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options);
|
|
30
43
|
const timeAxis = buildTimeMeasureAxisSql(source, axis, shifts);
|
|
44
|
+
const shiftedInputs = new Map();
|
|
45
|
+
[...needed].forEach((name, index) => {
|
|
46
|
+
const measure = timeMeasures.get(name);
|
|
47
|
+
if (isShiftMeasure(measure))
|
|
48
|
+
shiftedInputs.set(name, shiftBucketCtes(measure, axis, index));
|
|
49
|
+
});
|
|
31
50
|
// Scan only requested populations: intervening rows must not introduce dimensions.
|
|
32
51
|
const end = add('_hq_last', 1, axis.grain);
|
|
33
52
|
const populations = [];
|
|
34
|
-
if ([...needed].some(name => getBaseMeasure(ds.measures, name))) {
|
|
53
|
+
if ([...needed].some(name => name.includes('.') || getBaseMeasure(ds.measures, name))) {
|
|
35
54
|
populations.push(`(_hq_time >= _hq_first AND _hq_time < ${end})`);
|
|
36
55
|
}
|
|
37
56
|
if (windows.length) {
|
|
@@ -41,23 +60,22 @@ export function buildTimeMeasureDatasetSql(ds, query, options) {
|
|
|
41
60
|
? '' : ` AND _hq_time >= least(${starts.join(', ')})`;
|
|
42
61
|
populations.push(`(_hq_time < ${end}${lower})`);
|
|
43
62
|
}
|
|
44
|
-
for (const
|
|
45
|
-
populations.push(`(
|
|
46
|
-
}
|
|
63
|
+
for (const shifted of shiftedInputs.values())
|
|
64
|
+
populations.push(`(_hq_time IN (${shifted.sourceTimesSql}))`);
|
|
47
65
|
const scanCte = `_hq_scanned AS (SELECT * FROM _hq_source CROSS JOIN _hq_bounds WHERE ${populations.join(' OR ')})`;
|
|
48
|
-
const inputs = [...needed].map(
|
|
66
|
+
const inputs = [...needed].map(name => {
|
|
49
67
|
const timeMeasure = timeMeasures.get(name);
|
|
50
68
|
const window = isWindowMeasure(timeMeasure) ? timeMeasure : undefined;
|
|
51
69
|
const base = source.bases.find(base => base.name === (timeMeasure?.measure ?? name));
|
|
52
70
|
if (!base)
|
|
53
71
|
throw new Error(`Missing base input for measure "${name}".`);
|
|
54
72
|
if (isShiftMeasure(timeMeasure)) {
|
|
55
|
-
const shifted =
|
|
56
|
-
return { name, base, rowsSql: shifted.rowsSql,
|
|
73
|
+
const shifted = shiftedInputs.get(name);
|
|
74
|
+
return { name, base, rowsSql: shifted.rowsSql, restrictToAxis: false };
|
|
57
75
|
}
|
|
58
76
|
return { name, base, window, rowsSql: windowMeasureRowsSql(window, axis) };
|
|
59
77
|
});
|
|
60
78
|
const values = buildTimeMeasureValuesSql(source.dimensions, inputs, axis);
|
|
61
|
-
const sql = buildTimeMeasureResultSql(ds, query, options, source.dimensions, [...source.ctes, ...timeAxis.ctes, scanCte, ...values], `${timeAxis.guard} + ${timeAxis.rangeGuard}`);
|
|
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}`);
|
|
62
80
|
return { sql, parameters: source.parameters, timeAxisSql: timeAxis.timeAxisSql };
|
|
63
81
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WindowMeasureDefinition } from '../types.js';
|
|
2
|
+
import type { TimeMeasureAxis } from './time-measure-axis.js';
|
|
3
|
+
import type { TimeMeasureSqlBase, TimeMeasureSqlDimension } from './time-measure-source-sql.js';
|
|
4
|
+
/** Keyed source membership retains distinct/average semantics and row multiplicity. */
|
|
5
|
+
export declare function timeMeasureLeafSql(id: number, context: number, base: TimeMeasureSqlBase, window: WindowMeasureDefinition | undefined, axis: TimeMeasureAxis, dims: readonly TimeMeasureSqlDimension[]): {
|
|
6
|
+
ctes: string[];
|
|
7
|
+
population: string;
|
|
8
|
+
aggregate: string;
|
|
9
|
+
context: number;
|
|
10
|
+
cumulative: boolean;
|
|
11
|
+
guard: string;
|
|
12
|
+
count: boolean;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=time-measure-leaf-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-measure-leaf-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-leaf-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAMhG,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,uBAAuB,EAAE;;;;;;;;EA6CrM"}
|