@hypequery/datasets 0.19.0 → 0.20.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/dist/agent-catalog.d.ts +2 -1
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/api.type-test.js +22 -0
- package/dist/cache/definition-identity.d.ts +26 -0
- package/dist/cache/definition-identity.d.ts.map +1 -0
- package/dist/cache/definition-identity.js +164 -0
- package/dist/cache/protocol-cache-keys.d.ts +38 -0
- package/dist/cache/protocol-cache-keys.d.ts.map +1 -0
- package/dist/cache/protocol-cache-keys.js +191 -0
- package/dist/cache/query-signature.d.ts +6 -0
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +10 -13
- package/dist/cache/semantic-query-cache.d.ts +23 -0
- package/dist/cache/semantic-query-cache.d.ts.map +1 -1
- package/dist/catalog.d.ts +3 -2
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +25 -8
- package/dist/contract.d.ts +2 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +2 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +21 -4
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +9 -4
- package/dist/executor.d.ts +7 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +60 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/measure.d.ts +12 -2
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +11 -1
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +10 -1
- package/dist/protocol-deployment-adapter.d.ts.map +1 -1
- package/dist/protocol-deployment-adapter.js +2 -1
- package/dist/query-planner.d.ts +1 -1
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +5 -17
- package/dist/semantic-plan.d.ts +0 -1
- package/dist/semantic-plan.d.ts.map +1 -1
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +10 -3
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +2 -0
- package/dist/types.d.ts +62 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +3 -2
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +2 -10
- package/dist/utils/approximate-measures.d.ts +2 -2
- package/dist/utils/approximate-measures.d.ts.map +1 -1
- package/dist/utils/approximate-measures.js +3 -6
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +2 -1
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
- package/dist/utils/dataset-definition-validation.js +6 -24
- package/dist/utils/dataset-derived-query.d.ts.map +1 -1
- package/dist/utils/dataset-derived-query.js +7 -50
- package/dist/utils/dataset-measures.d.ts +13 -6
- package/dist/utils/dataset-measures.d.ts.map +1 -1
- package/dist/utils/dataset-measures.js +25 -4
- package/dist/utils/dataset-metric-ref.d.ts +2 -2
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
- package/dist/utils/dataset-query-result.d.ts.map +1 -1
- package/dist/utils/dataset-query-result.js +2 -1
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +7 -3
- package/dist/utils/derived-measure-sql.d.ts +3 -0
- package/dist/utils/derived-measure-sql.d.ts.map +1 -0
- package/dist/utils/derived-measure-sql.js +46 -0
- package/dist/utils/derived-measure-validation.d.ts.map +1 -1
- package/dist/utils/derived-measure-validation.js +6 -5
- package/dist/utils/measure-time-grains.d.ts +4 -0
- package/dist/utils/measure-time-grains.d.ts.map +1 -0
- package/dist/utils/measure-time-grains.js +16 -0
- package/dist/utils/query-timezone.d.ts +9 -0
- package/dist/utils/query-timezone.d.ts.map +1 -0
- package/dist/utils/query-timezone.js +35 -0
- package/dist/utils/semantic-metadata-validation.js +1 -1
- package/dist/utils/semantic-metadata.d.ts.map +1 -1
- package/dist/utils/semantic-metadata.js +3 -1
- package/dist/utils/shift-measure-definition.d.ts +4 -0
- package/dist/utils/shift-measure-definition.d.ts.map +1 -0
- package/dist/utils/shift-measure-definition.js +9 -0
- package/dist/utils/shift-measure-sql.d.ts +18 -0
- package/dist/utils/shift-measure-sql.d.ts.map +1 -0
- package/dist/utils/shift-measure-sql.js +46 -0
- package/dist/utils/shift-measure-validation.d.ts +3 -0
- package/dist/utils/shift-measure-validation.d.ts.map +1 -0
- package/dist/utils/shift-measure-validation.js +20 -0
- package/dist/utils/sql-under-joins.d.ts +10 -0
- package/dist/utils/sql-under-joins.d.ts.map +1 -0
- package/dist/utils/sql-under-joins.js +15 -0
- package/dist/utils/time-arithmetic-sql.d.ts +4 -0
- package/dist/utils/time-arithmetic-sql.d.ts.map +1 -0
- package/dist/utils/time-arithmetic-sql.js +9 -0
- package/dist/utils/time-axis-bounds.d.ts +24 -0
- package/dist/utils/time-axis-bounds.d.ts.map +1 -0
- package/dist/utils/time-axis-bounds.js +71 -0
- package/dist/utils/time-axis-calendar-guard.d.ts +9 -0
- package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -0
- package/dist/utils/time-axis-calendar-guard.js +17 -0
- package/dist/utils/time-axis-intervals.d.ts +13 -0
- package/dist/utils/time-axis-intervals.d.ts.map +1 -0
- package/dist/utils/time-axis-intervals.js +81 -0
- package/dist/utils/time-measure-axis-sql.d.ts +12 -0
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-axis-sql.js +47 -0
- package/dist/utils/time-measure-axis.d.ts +19 -0
- package/dist/utils/time-measure-axis.d.ts.map +1 -0
- package/dist/utils/time-measure-axis.js +45 -0
- package/dist/utils/time-measure-dataset-sql.d.ts +9 -0
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-dataset-sql.js +63 -0
- package/dist/utils/time-measure-result-sql.d.ts +6 -0
- package/dist/utils/time-measure-result-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-result-sql.js +30 -0
- package/dist/utils/time-measure-source-sql.d.ts +22 -0
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-source-sql.js +75 -0
- package/dist/utils/time-measure-values-sql.d.ts +20 -0
- package/dist/utils/time-measure-values-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-values-sql.js +87 -0
- package/dist/utils/time-query-measures.d.ts +6 -0
- package/dist/utils/time-query-measures.d.ts.map +1 -0
- package/dist/utils/time-query-measures.js +28 -0
- package/dist/utils/window-aggregation-sql.d.ts +5 -0
- package/dist/utils/window-aggregation-sql.d.ts.map +1 -0
- package/dist/utils/window-aggregation-sql.js +17 -0
- package/dist/utils/window-catalog-metadata.d.ts +14 -0
- package/dist/utils/window-catalog-metadata.d.ts.map +1 -0
- package/dist/utils/window-catalog-metadata.js +17 -0
- package/dist/utils/window-measure-definition.d.ts +4 -0
- package/dist/utils/window-measure-definition.d.ts.map +1 -0
- package/dist/utils/window-measure-definition.js +11 -0
- package/dist/utils/window-measure-dependencies.d.ts +4 -0
- package/dist/utils/window-measure-dependencies.d.ts.map +1 -0
- package/dist/utils/window-measure-dependencies.js +5 -0
- package/dist/utils/window-measure-validation.d.ts +3 -0
- package/dist/utils/window-measure-validation.d.ts.map +1 -0
- package/dist/utils/window-measure-validation.js +49 -0
- package/dist/utils/window-query-measures.d.ts +4 -0
- package/dist/utils/window-query-measures.d.ts.map +1 -0
- package/dist/utils/window-query-measures.js +17 -0
- package/dist/utils/window-query-signature.d.ts +4 -0
- package/dist/utils/window-query-signature.d.ts.map +1 -0
- package/dist/utils/window-query-signature.js +25 -0
- package/package.json +3 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery, MeasureDefinition, MetricFilter } from '../types.js';
|
|
2
|
+
import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
|
|
3
|
+
export interface TimeMeasureSqlDimension {
|
|
4
|
+
name: string;
|
|
5
|
+
alias: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TimeMeasureSqlBase {
|
|
8
|
+
name: string;
|
|
9
|
+
definition: MeasureDefinition;
|
|
10
|
+
value: string;
|
|
11
|
+
arg: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TimeMeasureSqlSource {
|
|
14
|
+
ctes: string[];
|
|
15
|
+
parameters: unknown[];
|
|
16
|
+
dimensions: TimeMeasureSqlDimension[];
|
|
17
|
+
bases: TimeMeasureSqlBase[];
|
|
18
|
+
rowAlias: string;
|
|
19
|
+
}
|
|
20
|
+
/** Isolate authored expressions, joins, tenancy and filters from internal SQL aliases. */
|
|
21
|
+
export declare function buildTimeMeasureSourceSql(ds: AnyDatasetInstance, query: DatasetQuery, baseNames: Iterable<string>, filters: readonly MetricFilter[], options: DatasetQueryExecutionOptions): TimeMeasureSqlSource;
|
|
22
|
+
//# sourceMappingURL=time-measure-source-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-measure-source-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-source-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAUxE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,0FAA0F;AAC1F,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,EAAE,4BAA4B,GACpC,oBAAoB,CA2DtB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { queryTimeSql } from './query-timezone.js';
|
|
2
|
+
import { resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn } from '../query-planner.js';
|
|
3
|
+
import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn } from './relationship-builder-plan.js';
|
|
4
|
+
import { getRuntimeTenantPredicate } from './tenant-runtime.js';
|
|
5
|
+
import { segmentFilters } from './segments.js';
|
|
6
|
+
import { getBaseMeasure } from './dataset-measures.js';
|
|
7
|
+
import { measureToAggregationSpec } from './dataset-normalization.js';
|
|
8
|
+
import { applyFilteredAggregationExpression } from './filtered-aggregation-sql.js';
|
|
9
|
+
import { assertNoRawSqlUnderJoins } from './sql-under-joins.js';
|
|
10
|
+
/** Isolate authored expressions, joins, tenancy and filters from internal SQL aliases. */
|
|
11
|
+
export function buildTimeMeasureSourceSql(ds, query, baseNames, filters, options) {
|
|
12
|
+
const bases = [];
|
|
13
|
+
const joinCtx = buildRelationshipBuilderContext(ds, query, options.context);
|
|
14
|
+
// Keep source expressions in their own SELECT scope: internal aliases cannot
|
|
15
|
+
// capture authored SQL identifiers, even if a physical column shares a name.
|
|
16
|
+
let raw = applyRelationshipJoins(options.builderFactory.table(ds.source), joinCtx);
|
|
17
|
+
const dims = (query.dimensions ?? []).map((name, i) => ({ name, alias: `_hq_d${i}` }));
|
|
18
|
+
const sourceParts = [resolveDimensionExpression(ds, ds.timeKey, joinCtx)];
|
|
19
|
+
for (const dimension of dims)
|
|
20
|
+
sourceParts.push(resolveDimensionExpression(ds, dimension.name, joinCtx));
|
|
21
|
+
for (const name of baseNames) {
|
|
22
|
+
const definition = getBaseMeasure(ds.measures, name);
|
|
23
|
+
if (!definition)
|
|
24
|
+
throw new Error(`Window input "${name}" is not a base measure.`);
|
|
25
|
+
const spec = measureToAggregationSpec(name, definition);
|
|
26
|
+
if (spec.sql)
|
|
27
|
+
assertNoRawSqlUnderJoins('measure', name, spec.sql, joinCtx);
|
|
28
|
+
const value = `_hq_v${bases.length}`;
|
|
29
|
+
const arg = `_hq_a${bases.length}`;
|
|
30
|
+
bases.push({ name, definition, value, arg });
|
|
31
|
+
const expression = spec.sql ?? resolveDimensionExpression(ds, spec.field, joinCtx);
|
|
32
|
+
sourceParts.push(applyFilteredAggregationExpression(ds, spec, expression, joinCtx));
|
|
33
|
+
sourceParts.push(spec.argField ? resolveDimensionExpression(ds, spec.argField, joinCtx) : 'NULL');
|
|
34
|
+
}
|
|
35
|
+
// A positional tuple isolates internal names from the source table's names.
|
|
36
|
+
const sourceIdentifiers = [
|
|
37
|
+
...sourceParts,
|
|
38
|
+
...Object.keys(ds.dimensions),
|
|
39
|
+
...Object.values(ds.dimensions).map(dimension => dimension.sql ?? dimension.column ?? ''),
|
|
40
|
+
...Object.values(ds.filters).map(filter => filter.field ?? ''),
|
|
41
|
+
ds.tenantKey ?? '',
|
|
42
|
+
];
|
|
43
|
+
let rowAlias = '_hq_row';
|
|
44
|
+
while (sourceIdentifiers.some(text => text.includes(rowAlias)))
|
|
45
|
+
rowAlias += '_';
|
|
46
|
+
raw = raw.select([`tuple(${sourceParts.join(', ')}) AS ${rowAlias}`]);
|
|
47
|
+
const tenant = getRuntimeTenantPredicate(options.context);
|
|
48
|
+
const tenantColumn = resolveTenantFilterColumn(ds, options.context);
|
|
49
|
+
if (tenant && tenantColumn)
|
|
50
|
+
raw = raw.where(qualifyBaseColumn(joinCtx, tenantColumn), tenant.operator, tenant.value);
|
|
51
|
+
for (const filter of filters) {
|
|
52
|
+
raw = raw.where(resolveFilterField(ds, filter.field, joinCtx), filter.operator, filter.value);
|
|
53
|
+
}
|
|
54
|
+
for (const filter of segmentFilters(ds, query.segments)) {
|
|
55
|
+
raw = raw.where(resolveDimensionExpression(ds, filter.field, joinCtx), filter.operator, filter.value);
|
|
56
|
+
}
|
|
57
|
+
const { sql: rawSql, parameters } = raw.toSQLWithParams();
|
|
58
|
+
const sourceColumns = [`${queryTimeSql(`assumeNotNull(tupleElement(${rowAlias}, 1))`, query.timezone)} AS _hq_time`];
|
|
59
|
+
dims.forEach((dimension, i) => sourceColumns.push(`tupleElement(${rowAlias}, ${i + 2}) AS ${dimension.alias}`));
|
|
60
|
+
bases.forEach((base, i) => {
|
|
61
|
+
const valuePosition = dims.length + 2 + i * 2;
|
|
62
|
+
sourceColumns.push(`tupleElement(${rowAlias}, ${valuePosition}) AS ${base.value}`);
|
|
63
|
+
sourceColumns.push(`tupleElement(${rowAlias}, ${valuePosition + 1}) AS ${base.arg}`);
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
ctes: [
|
|
67
|
+
`_hq_raw AS (${rawSql})`,
|
|
68
|
+
`_hq_source AS (SELECT ${sourceColumns.join(', ')} FROM _hq_raw WHERE isNotNull(tupleElement(${rowAlias}, 1)))`,
|
|
69
|
+
],
|
|
70
|
+
parameters,
|
|
71
|
+
dimensions: dims,
|
|
72
|
+
bases,
|
|
73
|
+
rowAlias,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TimeGrain, WindowMeasureDefinition } from '../types.js';
|
|
2
|
+
import type { TimeMeasureAxis } from './time-measure-axis.js';
|
|
3
|
+
import type { TimeMeasureSqlBase, TimeMeasureSqlDimension } from './time-measure-source-sql.js';
|
|
4
|
+
export interface TimeMeasureSqlInput {
|
|
5
|
+
name: string;
|
|
6
|
+
base: TimeMeasureSqlBase;
|
|
7
|
+
window?: WindowMeasureDefinition;
|
|
8
|
+
/** Source rows mapped to output periods before aggregation. */
|
|
9
|
+
rowsSql: string;
|
|
10
|
+
/** Extra population CTEs, such as aligned shift ranges. */
|
|
11
|
+
rowCtes?: string[];
|
|
12
|
+
/** Shift populations already map exclusively to the requested output series. */
|
|
13
|
+
restrictToAxis?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function windowScanStartSql(window: WindowMeasureDefinition, grain: TimeGrain): string;
|
|
16
|
+
/** Base measures respect partial endpoints; windows use entire contributing buckets. */
|
|
17
|
+
export declare function windowMeasureRowsSql(window: WindowMeasureDefinition | undefined, axis: TimeMeasureAxis): string;
|
|
18
|
+
/** Fill dimension combinations, aggregate populations, and join measure values onto the series. */
|
|
19
|
+
export declare function buildTimeMeasureValuesSql(dims: readonly TimeMeasureSqlDimension[], inputs: readonly TimeMeasureSqlInput[], axis: TimeMeasureAxis): string[];
|
|
20
|
+
//# sourceMappingURL=time-measure-values-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-measure-values-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-values-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAKhG,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAwBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAI5F;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAG/G;AAED,mGAAmG;AACnG,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,SAAS,uBAAuB,EAAE,EACxC,MAAM,EAAE,SAAS,mBAAmB,EAAE,EACtC,IAAI,EAAE,eAAe,GACpB,MAAM,EAAE,CA0CV"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { GRAIN_FUNCTIONS } from '../constants.js';
|
|
2
|
+
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
3
|
+
import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
|
|
4
|
+
import { isCountAggregation, windowAggregateSql } from './window-aggregation-sql.js';
|
|
5
|
+
import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
|
|
6
|
+
/** One row is emitted into each bounded window it contributes to. */
|
|
7
|
+
function contributionSql(window, grain) {
|
|
8
|
+
const bucket = `${GRAIN_FUNCTIONS[grain]}(_hq_time)`;
|
|
9
|
+
if (!window)
|
|
10
|
+
return bucket;
|
|
11
|
+
if (window.cumulative)
|
|
12
|
+
return `greatest(${bucket}, _hq_first)`;
|
|
13
|
+
let count;
|
|
14
|
+
if (window.trailing) {
|
|
15
|
+
const end = add(bucket, window.trailing.amount, window.trailing.unit);
|
|
16
|
+
count = `dateDiff('${grain}', ${bucket}, ${end})`;
|
|
17
|
+
}
|
|
18
|
+
else if (window.toDate) {
|
|
19
|
+
const calendarStart = `${GRAIN_FUNCTIONS[window.toDate]}(_hq_time)`;
|
|
20
|
+
const end = add(calendarStart, 1, window.toDate);
|
|
21
|
+
count = `dateDiff('${grain}', ${bucket}, ${end})`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
throw new Error('Window measure has no supported mode.');
|
|
25
|
+
}
|
|
26
|
+
const fanoutGuard = `throwIf(${count} > 1000, 'A window row contributes to more than 1000 buckets.')`;
|
|
27
|
+
const calendarGuard = calendarBucketGuardSql(bucket, `${count} + ${fanoutGuard}`, grain);
|
|
28
|
+
const bucketIndices = `arrayJoin(range(toUInt64(${count} + ${fanoutGuard} + ${calendarGuard})))`;
|
|
29
|
+
return add(bucket, bucketIndices, grain);
|
|
30
|
+
}
|
|
31
|
+
export function windowScanStartSql(window, grain) {
|
|
32
|
+
if (window.trailing)
|
|
33
|
+
return subtract(add('_hq_first', 1, grain), window.trailing.amount, window.trailing.unit);
|
|
34
|
+
if (window.toDate)
|
|
35
|
+
return `${GRAIN_FUNCTIONS[window.toDate]}(_hq_first)`;
|
|
36
|
+
throw new Error('Cumulative windows have unbounded history.');
|
|
37
|
+
}
|
|
38
|
+
/** Base measures respect partial endpoints; windows use entire contributing buckets. */
|
|
39
|
+
export function windowMeasureRowsSql(window, axis) {
|
|
40
|
+
const range = window ? '' : ` WHERE _hq_time ${axis.lowerInclusive ? '>=' : '>'} _hq_lower AND _hq_time ${axis.upperInclusive ? '<=' : '<'} _hq_upper`;
|
|
41
|
+
return `SELECT *, ${contributionSql(window, axis.grain)} AS _hq_period FROM _hq_scanned${range}`;
|
|
42
|
+
}
|
|
43
|
+
/** Fill dimension combinations, aggregate populations, and join measure values onto the series. */
|
|
44
|
+
export function buildTimeMeasureValuesSql(dims, inputs, axis) {
|
|
45
|
+
const ctes = [];
|
|
46
|
+
const keys = dims.map(dimension => dimension.alias);
|
|
47
|
+
const combinations = keys.length
|
|
48
|
+
? ` CROSS JOIN (SELECT DISTINCT ${keys.join(', ')} FROM _hq_scanned) AS _hq_combinations`
|
|
49
|
+
: '';
|
|
50
|
+
ctes.push(`_hq_skeleton AS (SELECT * FROM _hq_series${combinations})`);
|
|
51
|
+
const projections = ['s._hq_period AS period', ...dims.map(dimension => `s.${dimension.alias} AS ${quoteSQLIdentifier(dimension.name)}`)];
|
|
52
|
+
const joins = [];
|
|
53
|
+
let index = 0;
|
|
54
|
+
for (const input of inputs) {
|
|
55
|
+
const { name, base, window, rowsSql, rowCtes = [], restrictToAxis = true } = input;
|
|
56
|
+
const output = `_hq_m${index}`;
|
|
57
|
+
const fan = `_hq_fan${index}`;
|
|
58
|
+
const aggregate = `_hq_agg${index}`;
|
|
59
|
+
ctes.push(...rowCtes, `${fan} AS (${rowsSql})`);
|
|
60
|
+
// toNullable makes unmatched LEFT JOIN values NULL regardless of join_use_nulls.
|
|
61
|
+
const expression = windowAggregateSql(base.definition, base.value, base.arg);
|
|
62
|
+
const groupColumns = `_hq_period${keys.length ? `, ${keys.join(', ')}` : ''}`;
|
|
63
|
+
const outputRange = restrictToAxis ? ' WHERE _hq_period BETWEEN _hq_first AND _hq_last' : '';
|
|
64
|
+
const trailingRange = window?.trailing
|
|
65
|
+
? ` AND _hq_time >= ${subtract(add('_hq_period', 1, axis.grain), window.trailing.amount, window.trailing.unit)}`
|
|
66
|
+
: '';
|
|
67
|
+
const aggregationSql = `SELECT ${groupColumns}, toNullable(${expression}) AS ${output}`
|
|
68
|
+
+ ` FROM ${fan}${outputRange}${trailingRange} GROUP BY ${groupColumns}`;
|
|
69
|
+
ctes.push(`${aggregate} AS (${aggregationSql})`);
|
|
70
|
+
const alias = `m${index}`;
|
|
71
|
+
const condition = [`s._hq_period = ${alias}._hq_period`, ...keys.map(key => `isNotDistinctFrom(s.${key}, ${alias}.${key})`)];
|
|
72
|
+
joins.push(`LEFT JOIN ${aggregate} AS ${alias} ON ${condition.join(' AND ')}`);
|
|
73
|
+
let value = `${alias}.${output}`;
|
|
74
|
+
if (window?.cumulative) {
|
|
75
|
+
const operation = base.definition.aggregation === 'count' ? 'sum' : base.definition.aggregation;
|
|
76
|
+
const partition = keys.length ? `PARTITION BY ${keys.map(key => `s.${key}`).join(', ')} ` : '';
|
|
77
|
+
const frame = `${partition}ORDER BY s._hq_period ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`;
|
|
78
|
+
value = `${operation}OrNull(${value}) OVER (${frame})`;
|
|
79
|
+
}
|
|
80
|
+
if (isCountAggregation(base.definition))
|
|
81
|
+
value = `coalesce(${value}, 0)`;
|
|
82
|
+
projections.push(`${value} AS ${quoteSQLIdentifier(name)}`);
|
|
83
|
+
index++;
|
|
84
|
+
}
|
|
85
|
+
ctes.push(`_hq_values AS (SELECT ${projections.join(', ')} FROM _hq_skeleton AS s ${joins.join(' ')})`);
|
|
86
|
+
return ctes;
|
|
87
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery, ShiftMeasureDefinition, WindowMeasureDefinition, DerivedMeasureDefinition, DatasetMeasureDefinition } from '../types.js';
|
|
2
|
+
export type TimeMeasureDefinition = WindowMeasureDefinition | ShiftMeasureDefinition;
|
|
3
|
+
export declare function selectedTimeMeasures(dataset: AnyDatasetInstance, query: DatasetQuery): Map<string, TimeMeasureDefinition>;
|
|
4
|
+
export declare function usesTimeMeasure(definition: DerivedMeasureDefinition, measures: Readonly<Record<string, DatasetMeasureDefinition>>): boolean;
|
|
5
|
+
export declare function rejectTimeMeasuresOnBackend(dataset: AnyDatasetInstance, query: DatasetQuery): void;
|
|
6
|
+
//# sourceMappingURL=time-query-measures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-query-measures.d.ts","sourceRoot":"","sources":["../../src/utils/time-query-measures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAGzK,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,GAAG,sBAAsB,CAAC;AAErF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAYzH;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,wBAAwB,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,GAAG,OAAO,CAE3I;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAOlG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getDerivedMeasure, isShiftMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
2
|
+
export function selectedTimeMeasures(dataset, query) {
|
|
3
|
+
const names = new Set(query.measures ?? []);
|
|
4
|
+
for (const name of query.measures ?? []) {
|
|
5
|
+
const derived = getDerivedMeasure(dataset.measures, name);
|
|
6
|
+
if (derived)
|
|
7
|
+
Object.values(derived.uses).forEach(input => names.add(input));
|
|
8
|
+
}
|
|
9
|
+
const measures = new Map();
|
|
10
|
+
for (const name of names) {
|
|
11
|
+
const definition = Object.hasOwn(dataset.measures, name) ? dataset.measures[name] : undefined;
|
|
12
|
+
if (isWindowMeasure(definition) || isShiftMeasure(definition))
|
|
13
|
+
measures.set(name, definition);
|
|
14
|
+
}
|
|
15
|
+
return measures;
|
|
16
|
+
}
|
|
17
|
+
export function usesTimeMeasure(definition, measures) {
|
|
18
|
+
return Object.values(definition.uses).some(name => Object.hasOwn(measures, name) && (isWindowMeasure(measures[name]) || isShiftMeasure(measures[name])));
|
|
19
|
+
}
|
|
20
|
+
export function rejectTimeMeasuresOnBackend(dataset, query) {
|
|
21
|
+
const measures = [...selectedTimeMeasures(dataset, query).values()];
|
|
22
|
+
if (!measures.length)
|
|
23
|
+
return;
|
|
24
|
+
const windows = measures.some(isWindowMeasure);
|
|
25
|
+
const shifts = measures.some(isShiftMeasure);
|
|
26
|
+
const subject = windows && shifts ? 'Window and shift' : shifts ? 'Shift' : 'Window';
|
|
27
|
+
throw new Error(`${subject} dataset measures require the queryBuilder execution path.`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MeasureDefinition } from '../types.js';
|
|
2
|
+
/** NULL for an empty non-count population, including nullable measure inputs. */
|
|
3
|
+
export declare function windowAggregateSql(definition: MeasureDefinition, value: string, arg: string): string;
|
|
4
|
+
export declare function isCountAggregation(definition: MeasureDefinition): boolean;
|
|
5
|
+
//# sourceMappingURL=window-aggregation-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-aggregation-sql.d.ts","sourceRoot":"","sources":["../../src/utils/window-aggregation-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAYpG;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAEzE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** NULL for an empty non-count population, including nullable measure inputs. */
|
|
2
|
+
export function windowAggregateSql(definition, value, arg) {
|
|
3
|
+
switch (definition.aggregation) {
|
|
4
|
+
case 'count': return `count(${value})`;
|
|
5
|
+
case 'countDistinct': return `uniqExact(${value})`;
|
|
6
|
+
case 'approxCountDistinct': return `uniq(${value})`;
|
|
7
|
+
case 'percentile': return `quantileOrNull(${definition.level})(${value})`;
|
|
8
|
+
case 'argMax': return `argMaxOrNull(${value}, ${arg})`;
|
|
9
|
+
case 'argMin': return `argMinOrNull(${value}, ${arg})`;
|
|
10
|
+
case 'stddev': return `stddevSampOrNull(${value})`;
|
|
11
|
+
case 'variance': return `varSampOrNull(${value})`;
|
|
12
|
+
default: return `${definition.aggregation}OrNull(${value})`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function isCountAggregation(definition) {
|
|
16
|
+
return ['count', 'countDistinct', 'approxCountDistinct'].includes(definition.aggregation);
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MeasureTimeInterval, TimeGrain, WindowMeasureDefinition, ShiftMeasureDefinition } from '../types.js';
|
|
2
|
+
/** Additive metadata shared by local catalogs and their agent-safe projection. */
|
|
3
|
+
export interface WindowCatalogMetadata {
|
|
4
|
+
kind?: 'window' | 'shift';
|
|
5
|
+
interval?: MeasureTimeInterval;
|
|
6
|
+
measure?: string;
|
|
7
|
+
trailing?: MeasureTimeInterval;
|
|
8
|
+
toDate?: Exclude<TimeGrain, 'minute'>;
|
|
9
|
+
cumulative?: true;
|
|
10
|
+
requiresTimeRange?: true;
|
|
11
|
+
supportedGrains?: TimeGrain[];
|
|
12
|
+
}
|
|
13
|
+
export declare function windowCatalogMetadata(definition: WindowMeasureDefinition | ShiftMeasureDefinition | WindowCatalogMetadata): WindowCatalogMetadata;
|
|
14
|
+
//# sourceMappingURL=window-catalog-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-catalog-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/window-catalog-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEnH,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,iBAAiB,CAAC,EAAE,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;CAC/B;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,uBAAuB,GAAG,sBAAsB,GAAG,qBAAqB,GAAG,qBAAqB,CAgBjJ"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function windowCatalogMetadata(definition) {
|
|
2
|
+
const grains = 'supportedGrains' in definition && definition.supportedGrains
|
|
3
|
+
? { supportedGrains: [...definition.supportedGrains] } : {};
|
|
4
|
+
if ('interval' in definition && definition.interval) {
|
|
5
|
+
return { ...grains, kind: 'shift', measure: definition.measure, interval: { ...definition.interval }, requiresTimeRange: true };
|
|
6
|
+
}
|
|
7
|
+
if ('__type' in definition || definition.kind === 'window') {
|
|
8
|
+
return {
|
|
9
|
+
...grains,
|
|
10
|
+
kind: 'window', measure: definition.measure, requiresTimeRange: true,
|
|
11
|
+
...('trailing' in definition && definition.trailing ? { trailing: { ...definition.trailing } } : {}),
|
|
12
|
+
...('toDate' in definition && definition.toDate ? { toDate: definition.toDate } : {}),
|
|
13
|
+
...('cumulative' in definition && definition.cumulative ? { cumulative: true } : {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return definition.requiresTimeRange ? { ...grains, requiresTimeRange: true } : grains;
|
|
17
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WindowMeasureDefinition, WindowMeasureMode } from '../types.js';
|
|
2
|
+
export type WindowMeasureOptions = Pick<WindowMeasureDefinition, 'label' | 'description' | 'examples' | 'synonyms' | 'format' | 'unit' | 'currency' | 'sensitivity'>;
|
|
3
|
+
export declare function createWindowMeasure<const TMeasureName extends string>(baseMeasure: TMeasureName, window: WindowMeasureMode, options?: WindowMeasureOptions): WindowMeasureDefinition<TMeasureName>;
|
|
4
|
+
//# sourceMappingURL=window-measure-definition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-measure-definition.d.ts","sourceRoot":"","sources":["../../src/utils/window-measure-definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG9E,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAC7D,OAAO,GAAG,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC;AAEtG,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,YAAY,SAAS,MAAM,EACnE,WAAW,EAAE,YAAY,EACzB,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,uBAAuB,CAAC,YAAY,CAAC,CASvC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { snapshotSemanticMetadata } from './semantic-metadata.js';
|
|
2
|
+
export function createWindowMeasure(baseMeasure, window, options) {
|
|
3
|
+
return {
|
|
4
|
+
__type: 'window_measure_definition',
|
|
5
|
+
measure: baseMeasure,
|
|
6
|
+
...window,
|
|
7
|
+
...snapshotSemanticMetadata(options ?? {}),
|
|
8
|
+
...(options?.label === undefined ? {} : { label: options.label }),
|
|
9
|
+
...(options?.description === undefined ? {} : { description: options.description }),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatasetMeasureDefinition, DerivedMeasureDefinition } from '../types.js';
|
|
2
|
+
/** Whether a derived measure requires window planning to execute. */
|
|
3
|
+
export declare function usesWindowMeasure(definition: DerivedMeasureDefinition, measures: Readonly<Record<string, DatasetMeasureDefinition>>): boolean;
|
|
4
|
+
//# sourceMappingURL=window-measure-dependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-measure-dependencies.d.ts","sourceRoot":"","sources":["../../src/utils/window-measure-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAGtF,qEAAqE;AACrE,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,wBAAwB,EACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,GAC3D,OAAO,CAIT"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { isWindowMeasure } from './dataset-measures.js';
|
|
2
|
+
/** Whether a derived measure requires window planning to execute. */
|
|
3
|
+
export function usesWindowMeasure(definition, measures) {
|
|
4
|
+
return Object.values(definition.uses).some(name => Object.hasOwn(measures, name) && isWindowMeasure(measures[name]));
|
|
5
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DatasetMeasureDefinition } from '../types.js';
|
|
2
|
+
export declare function validateWindowMeasures(datasetName: string, timeKey: string | undefined, measures: Record<string, DatasetMeasureDefinition>): void;
|
|
3
|
+
//# sourceMappingURL=window-measure-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-measure-validation.d.ts","sourceRoot":"","sources":["../../src/utils/window-measure-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAA8C,MAAM,aAAa,CAAC;AASxG,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GACjD,IAAI,CAQN"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SUPPORTED_TIME_GRAINS } from '../constants.js';
|
|
2
|
+
import { isSafeSQLIdentifier } from '../sql-utils.js';
|
|
3
|
+
import { getBaseMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
4
|
+
const TRAILING_UNITS = new Set(SUPPORTED_TIME_GRAINS);
|
|
5
|
+
const TO_DATE_GRAINS = new Set(SUPPORTED_TIME_GRAINS.filter(grain => grain !== 'minute'));
|
|
6
|
+
const CUMULATIVE_AGGREGATIONS = new Set(['sum', 'count', 'min', 'max']);
|
|
7
|
+
export function validateWindowMeasures(datasetName, timeKey, measures) {
|
|
8
|
+
for (const [name, definition] of Object.entries(measures)) {
|
|
9
|
+
if (!isWindowMeasure(definition))
|
|
10
|
+
continue;
|
|
11
|
+
const problem = windowMeasureProblem(name, definition, timeKey, measures);
|
|
12
|
+
if (problem) {
|
|
13
|
+
throw new Error(`Invalid dataset "${datasetName}": window measure "${name}" ${problem}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Why a window measure is invalid, or undefined when it is valid. */
|
|
18
|
+
function windowMeasureProblem(name, definition, timeKey, measures) {
|
|
19
|
+
if (!isSafeSQLIdentifier(name))
|
|
20
|
+
return 'name is not a safe identifier.';
|
|
21
|
+
if (!timeKey)
|
|
22
|
+
return 'requires a timeKey.';
|
|
23
|
+
const base = getBaseMeasure(measures, definition.measure);
|
|
24
|
+
if (!base)
|
|
25
|
+
return `must wrap a base measure; "${String(definition.measure)}" is not one.`;
|
|
26
|
+
return windowModeProblem(definition, base);
|
|
27
|
+
}
|
|
28
|
+
/** Checks that exactly one of trailing / toDate / cumulative is declared, and that it is well formed. */
|
|
29
|
+
function windowModeProblem(definition, base) {
|
|
30
|
+
const { trailing, toDate, cumulative } = definition;
|
|
31
|
+
const declaredModes = [trailing, toDate, cumulative].filter(mode => mode !== undefined).length;
|
|
32
|
+
if (declaredModes !== 1)
|
|
33
|
+
return 'must declare exactly one window mode.';
|
|
34
|
+
if (trailing !== undefined) {
|
|
35
|
+
const valid = typeof trailing === 'object' && trailing !== null
|
|
36
|
+
&& Number.isSafeInteger(trailing.amount) && trailing.amount > 0
|
|
37
|
+
&& TRAILING_UNITS.has(trailing.unit);
|
|
38
|
+
return valid ? undefined : 'trailing interval needs a positive safe integer and supported unit.';
|
|
39
|
+
}
|
|
40
|
+
if (toDate !== undefined) {
|
|
41
|
+
return TO_DATE_GRAINS.has(toDate) ? undefined : 'toDate needs a supported grain coarser than minute.';
|
|
42
|
+
}
|
|
43
|
+
if (cumulative !== true)
|
|
44
|
+
return 'cumulative must be true.';
|
|
45
|
+
if (!CUMULATIVE_AGGREGATIONS.has(base.aggregation)) {
|
|
46
|
+
return `cannot accumulate ${base.aggregation}; use sum, count, min, or max.`;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery, WindowMeasureDefinition } from '../types.js';
|
|
2
|
+
/** Windows selected directly or needed by a selected formula. */
|
|
3
|
+
export declare function selectedWindowMeasures(dataset: AnyDatasetInstance, query: DatasetQuery): Map<string, WindowMeasureDefinition>;
|
|
4
|
+
//# sourceMappingURL=window-query-measures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-query-measures.d.ts","sourceRoot":"","sources":["../../src/utils/window-query-measures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG7F,iEAAiE;AACjE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,GAClB,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAYtC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getDerivedMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
2
|
+
/** Windows selected directly or needed by a selected formula. */
|
|
3
|
+
export function selectedWindowMeasures(dataset, query) {
|
|
4
|
+
const names = new Set(query.measures ?? []);
|
|
5
|
+
for (const name of query.measures ?? []) {
|
|
6
|
+
const derived = getDerivedMeasure(dataset.measures, name);
|
|
7
|
+
if (derived)
|
|
8
|
+
Object.values(derived.uses).forEach(input => names.add(input));
|
|
9
|
+
}
|
|
10
|
+
const windows = new Map();
|
|
11
|
+
for (const name of names) {
|
|
12
|
+
const definition = Object.hasOwn(dataset.measures, name) ? dataset.measures[name] : undefined;
|
|
13
|
+
if (isWindowMeasure(definition))
|
|
14
|
+
windows.set(name, definition);
|
|
15
|
+
}
|
|
16
|
+
return windows;
|
|
17
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
|
|
2
|
+
/** Preserve existing cache keys; add execution dependencies for windows and shifts. */
|
|
3
|
+
export declare function windowQuerySignature(ds: AnyDatasetInstance, query: DatasetQuery): Record<string, unknown>;
|
|
4
|
+
//# sourceMappingURL=window-query-signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window-query-signature.d.ts","sourceRoot":"","sources":["../../src/utils/window-query-signature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMpE,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiBzG"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getBaseMeasure, getDerivedMeasure } from './dataset-measures.js';
|
|
2
|
+
import { selectedTimeMeasures } from './time-query-measures.js';
|
|
3
|
+
import { windowCatalogMetadata } from './window-catalog-metadata.js';
|
|
4
|
+
import { measureToAggregationSpec } from './dataset-normalization.js';
|
|
5
|
+
/** Preserve existing cache keys; add execution dependencies for windows and shifts. */
|
|
6
|
+
export function windowQuerySignature(ds, query) {
|
|
7
|
+
const timeMeasures = selectedTimeMeasures(ds, query);
|
|
8
|
+
if (!timeMeasures.size)
|
|
9
|
+
return {};
|
|
10
|
+
return {
|
|
11
|
+
windowTimeKey: ds.timeKey,
|
|
12
|
+
windowTimeDimension: ds.timeKey ? ds.dimensions[ds.timeKey] : undefined,
|
|
13
|
+
windows: [...timeMeasures].sort(([a], [b]) => a.localeCompare(b)).map(([name, definition]) => {
|
|
14
|
+
const base = getBaseMeasure(ds.measures, definition.measure);
|
|
15
|
+
return { name, ...windowCatalogMetadata(definition), base: base ? measureToAggregationSpec(definition.measure, base) : undefined };
|
|
16
|
+
}),
|
|
17
|
+
windowFormulas: (query.measures ?? []).flatMap(name => {
|
|
18
|
+
const derived = getDerivedMeasure(ds.measures, name);
|
|
19
|
+
if (!derived)
|
|
20
|
+
return [];
|
|
21
|
+
const aliases = Object.fromEntries(Object.keys(derived.uses).map(alias => [alias, alias]));
|
|
22
|
+
return [{ name, uses: derived.uses, expression: derived.formula(aliases).expression }];
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/datasets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Code-first TypeScript semantic layer for ClickHouse datasets, metrics, multi-tenancy, and AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"semantic-layer",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@hypequery/protocol": "^0.
|
|
45
|
+
"@hypequery/protocol": "^0.17.0",
|
|
46
46
|
"@noble/hashes": "^1.8.0",
|
|
47
47
|
"zod": "^3.22.4",
|
|
48
48
|
"zod-to-json-schema": "^3.23.5"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typescript": "^5.7.3",
|
|
61
61
|
"@vitest/coverage-v8": "^3.2.6",
|
|
62
62
|
"vitest": "^3.2.6",
|
|
63
|
-
"@hypequery/protocol-conformance": "^0.
|
|
63
|
+
"@hypequery/protocol-conformance": "^0.14.0"
|
|
64
64
|
},
|
|
65
65
|
"repository": {
|
|
66
66
|
"type": "git",
|