@hypequery/datasets 0.18.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 +11 -1
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/aggregations.d.ts +2 -0
- package/dist/aggregations.d.ts.map +1 -1
- package/dist/aggregations.js +4 -0
- package/dist/api.type-test.js +57 -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 +27 -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 +14 -2
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +37 -10
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/contract.d.ts +7 -2
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +9 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +27 -5
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +12 -4
- package/dist/executor.d.ts +7 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +75 -22
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +34 -7
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/measure.d.ts +17 -2
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +16 -1
- package/dist/portable-execution-errors.d.ts +4 -0
- package/dist/portable-execution-errors.d.ts.map +1 -1
- package/dist/portable-execution-errors.js +4 -1
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +6 -3
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +25 -8
- package/dist/protocol-deployment-adapter.d.ts.map +1 -1
- package/dist/protocol-deployment-adapter.js +2 -1
- package/dist/protocol-rehydrate.d.ts +5 -1
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +20 -10
- package/dist/query-builder-protocol.d.ts +7 -3
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-planner.d.ts +1 -1
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +10 -17
- package/dist/relationship-check.d.ts +47 -0
- package/dist/relationship-check.d.ts.map +1 -0
- package/dist/relationship-check.js +59 -0
- package/dist/relationships.d.ts +10 -10
- package/dist/relationships.js +10 -10
- 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 +15 -4
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +7 -0
- package/dist/types.d.ts +99 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +9 -2
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +6 -10
- package/dist/utils/approximate-measures.d.ts +10 -0
- package/dist/utils/approximate-measures.d.ts.map +1 -0
- package/dist/utils/approximate-measures.js +12 -0
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +4 -3
- 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-metric-ref.js +5 -0
- 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 +13 -6
- package/dist/utils/dataset-time-grains.d.ts +15 -0
- package/dist/utils/dataset-time-grains.d.ts.map +1 -0
- package/dist/utils/dataset-time-grains.js +17 -0
- 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/filtered-aggregation-sql.d.ts.map +1 -1
- package/dist/utils/filtered-aggregation-sql.js +1 -0
- 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/portable-aggregations.d.ts +14 -0
- package/dist/utils/portable-aggregations.d.ts.map +1 -0
- package/dist/utils/portable-aggregations.js +15 -0
- package/dist/utils/portable-grains.d.ts +22 -0
- package/dist/utils/portable-grains.d.ts.map +1 -0
- package/dist/utils/portable-grains.js +35 -0
- package/dist/utils/portable-semantic-query.d.ts.map +1 -1
- package/dist/utils/portable-semantic-query.js +2 -1
- package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
- package/dist/utils/protocol-metric-expressions.js +2 -1
- 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/relationship-builder-plan.d.ts +6 -3
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +13 -5
- package/dist/utils/relationship-key-check.d.ts +43 -0
- package/dist/utils/relationship-key-check.d.ts.map +1 -0
- package/dist/utils/relationship-key-check.js +72 -0
- package/dist/utils/segments.d.ts +31 -0
- package/dist/utils/segments.d.ts.map +1 -0
- package/dist/utils/segments.js +91 -0
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
- package/dist/utils/semantic-metadata-validation.js +23 -2
- 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/unsupported-contract-reasons.d.ts +32 -0
- package/dist/utils/unsupported-contract-reasons.d.ts.map +1 -0
- package/dist/utils/unsupported-contract-reasons.js +30 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-definition-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-definition-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-definition-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-definition-validation.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAOrB,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACzD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAC5D,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AA+M/D;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,CAAC,aAAa,EAAE,WAAW,EAAE,gBAAgB,CAAC,GAClE,IAAI,CA0DN"}
|
|
@@ -1,28 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
* Definition-time structural validation for `dataset()`.
|
|
3
|
-
*
|
|
4
|
-
* Every check here runs when the dataset is *constructed*, not when it is
|
|
5
|
-
* queried. A dataset is normally defined at module scope, so a malformed model
|
|
6
|
-
* fails at import — in the build, in CI, and in the first test that touches the
|
|
7
|
-
* module — instead of on the first request that happens to exercise the broken
|
|
8
|
-
* part. The alternative is a typo in `tenantKey` that constructs cleanly, passes
|
|
9
|
-
* review, and is discovered by a query that quietly returns another tenant's
|
|
10
|
-
* rows.
|
|
11
|
-
*
|
|
12
|
-
* Two categories of check live here:
|
|
13
|
-
*
|
|
14
|
-
* - **Identifier safety.** `source`, `tenantKey`, `timeKey`, dimension columns
|
|
15
|
-
* and measure fields are all interpolated into SQL as identifiers. They are
|
|
16
|
-
* validated once here rather than trusted at every call site that builds a
|
|
17
|
-
* predicate from them.
|
|
18
|
-
* - **Raw SQL shape.** The `sql` escape hatch on dimensions and measures is an
|
|
19
|
-
* expression, never a statement. A value carrying a statement terminator or a
|
|
20
|
-
* comment is rejected structurally, so it cannot become a second statement or
|
|
21
|
-
* comment out the rest of a clause.
|
|
22
|
-
*/
|
|
1
|
+
import { validateShiftMeasures } from './shift-measure-validation.js';
|
|
23
2
|
import { escapeRegExp, isSafeSQLIdentifier, stripSqlLiterals } from '../sql-utils.js';
|
|
24
3
|
import { validateDerivedMeasures } from './derived-measure-validation.js';
|
|
25
|
-
import { isDerivedMeasure } from './dataset-measures.js';
|
|
4
|
+
import { isDerivedMeasure, isWindowMeasure, isShiftMeasure } from './dataset-measures.js';
|
|
5
|
+
import { validateWindowMeasures } from './window-measure-validation.js';
|
|
26
6
|
import { validateDatasetAgentMetadata } from './semantic-metadata-validation.js';
|
|
27
7
|
/**
|
|
28
8
|
* Statement terminators and comment openers.
|
|
@@ -139,7 +119,7 @@ function validateMeasures(datasetName, measures, dimensions) {
|
|
|
139
119
|
if (typeof definition !== 'object' || definition === null) {
|
|
140
120
|
fail(datasetName, `measure "${name}" must be created with measure.*().`);
|
|
141
121
|
}
|
|
142
|
-
if (isDerivedMeasure(definition))
|
|
122
|
+
if (isDerivedMeasure(definition) || isWindowMeasure(definition) || isShiftMeasure(definition))
|
|
143
123
|
continue;
|
|
144
124
|
if (definition.__type !== 'measure_definition') {
|
|
145
125
|
fail(datasetName, `measure "${name}" must be created with measure.*().`);
|
|
@@ -220,6 +200,8 @@ export function validateDatasetDefinition(name, config) {
|
|
|
220
200
|
const dimensions = config.dimensions ?? {};
|
|
221
201
|
validateDimensions(name, dimensions);
|
|
222
202
|
const measures = config.measures ?? {};
|
|
203
|
+
validateWindowMeasures(name, config.timeKey, measures);
|
|
204
|
+
validateShiftMeasures(name, config.timeKey, measures);
|
|
223
205
|
validateDerivedMeasures(name, measures);
|
|
224
206
|
validateMeasures(name, measures, dimensions);
|
|
225
207
|
validateLimits(name, config.limits);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-derived-query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-derived-query.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,KAAK,cAAc,GAAG,CACpB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,KAClC,gBAAgB,CAAC;AAEtB,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAE9F;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,cAAc,EAAE,cAAc,GAC7B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,EAAE,CAAA;CAAE,CA2CxC;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CAgB7B"}
|
|
@@ -1,64 +1,21 @@
|
|
|
1
|
+
import { derivedProjection } from './derived-measure-sql.js';
|
|
2
|
+
import { baseMeasureNames, getBaseMeasure, getDerivedMeasure } from './dataset-measures.js';
|
|
1
3
|
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
4
|
import { validateDatasetQueryInput } from './dataset-query-validation.js';
|
|
3
5
|
import { overfetchLimit } from './pagination.js';
|
|
4
6
|
import { toDatasetQueryResult } from './dataset-query-result.js';
|
|
5
7
|
export function hasSelectedDerivedMeasure(ds, query) {
|
|
6
|
-
return (query.measures ?? []).some(name =>
|
|
7
|
-
}
|
|
8
|
-
function expressionSql(expression, uses) {
|
|
9
|
-
switch (expression.kind) {
|
|
10
|
-
case 'ref': {
|
|
11
|
-
if (!Object.hasOwn(uses, expression.name)) {
|
|
12
|
-
throw new Error(`Formula references undeclared input "${expression.name}".`);
|
|
13
|
-
}
|
|
14
|
-
const measureName = uses[expression.name];
|
|
15
|
-
return quoteSQLIdentifier(measureName);
|
|
16
|
-
}
|
|
17
|
-
case 'literal': {
|
|
18
|
-
if (expression.value === null)
|
|
19
|
-
return 'NULL';
|
|
20
|
-
if (typeof expression.value === 'number') {
|
|
21
|
-
if (!Number.isFinite(expression.value))
|
|
22
|
-
throw new Error('Formula contains a non-finite number.');
|
|
23
|
-
return String(expression.value);
|
|
24
|
-
}
|
|
25
|
-
if (typeof expression.value === 'boolean')
|
|
26
|
-
return expression.value ? 'true' : 'false';
|
|
27
|
-
return `'${expression.value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
28
|
-
}
|
|
29
|
-
case 'binary': {
|
|
30
|
-
const operators = { add: '+', subtract: '-', multiply: '*', divide: '/' };
|
|
31
|
-
const operator = operators[expression.operator];
|
|
32
|
-
if (!operator)
|
|
33
|
-
throw new Error('Formula contains an unsupported binary operator.');
|
|
34
|
-
return `(${expressionSql(expression.left, uses)} ${operator} ${expressionSql(expression.right, uses)})`;
|
|
35
|
-
}
|
|
36
|
-
case 'function': {
|
|
37
|
-
const args = expression.args.map(arg => expressionSql(arg, uses));
|
|
38
|
-
switch (expression.name) {
|
|
39
|
-
case 'nullIfZero': return `NULLIF(${args[0]}, 0)`;
|
|
40
|
-
case 'coalesce': return `COALESCE(${args.join(', ')})`;
|
|
41
|
-
case 'round': return `ROUND(${args.join(', ')})`;
|
|
42
|
-
case 'floor': return `FLOOR(${args[0]})`;
|
|
43
|
-
case 'ceil': return `CEIL(${args[0]})`;
|
|
44
|
-
default: throw new Error('Formula contains an unsupported function.');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function derivedProjection(name, definition) {
|
|
50
|
-
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
51
|
-
return `${expressionSql(definition.formula(aliases).expression, definition.uses)} AS ${quoteSQLIdentifier(name)}`;
|
|
8
|
+
return (query.measures ?? []).some(name => getDerivedMeasure(ds.measures, name) !== undefined);
|
|
52
9
|
}
|
|
53
10
|
export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
54
11
|
const validation = validateDatasetQueryInput(ds, query, options.context);
|
|
55
12
|
if (!validation.valid) {
|
|
56
13
|
throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
|
|
57
14
|
}
|
|
58
|
-
const selected = query.measures ??
|
|
59
|
-
const baseMeasures = new Set(selected.filter(name =>
|
|
15
|
+
const selected = query.measures ?? baseMeasureNames(ds.measures);
|
|
16
|
+
const baseMeasures = new Set(selected.filter(name => getBaseMeasure(ds.measures, name) !== undefined));
|
|
60
17
|
for (const name of selected) {
|
|
61
|
-
const derived =
|
|
18
|
+
const derived = getDerivedMeasure(ds.measures, name);
|
|
62
19
|
if (derived)
|
|
63
20
|
Object.values(derived.uses).forEach(base => baseMeasures.add(base));
|
|
64
21
|
}
|
|
@@ -76,7 +33,7 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
76
33
|
for (const dimension of query.dimensions ?? [])
|
|
77
34
|
projections.push(quoteSQLIdentifier(dimension));
|
|
78
35
|
for (const name of selected) {
|
|
79
|
-
const derived =
|
|
36
|
+
const derived = getDerivedMeasure(ds.measures, name);
|
|
80
37
|
projections.push(derived
|
|
81
38
|
? derivedProjection(name, derived)
|
|
82
39
|
: quoteSQLIdentifier(name));
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function isDerivedMeasure(definition: DatasetMeasureDefinition): definition is DerivedMeasureDefinition;
|
|
3
|
-
export declare function isBaseMeasure(definition: DatasetMeasureDefinition): definition is MeasureDefinition;
|
|
1
|
+
import type { DatasetMeasureDefinition, DerivedMeasureDefinition, DerivedMeasures, MeasureDefinition, WindowMeasureDefinition, ShiftMeasureDefinition } from '../types.js';
|
|
2
|
+
export declare function isDerivedMeasure(definition: DatasetMeasureDefinition | null | undefined): definition is DerivedMeasureDefinition;
|
|
3
|
+
export declare function isBaseMeasure(definition: DatasetMeasureDefinition | null | undefined): definition is MeasureDefinition;
|
|
4
|
+
export declare function isWindowMeasure(definition: DatasetMeasureDefinition | null | undefined): definition is WindowMeasureDefinition;
|
|
5
|
+
export declare function isShiftMeasure(definition: DatasetMeasureDefinition | null | undefined): definition is ShiftMeasureDefinition;
|
|
4
6
|
export declare function baseMeasureNames(measures: Record<string, DatasetMeasureDefinition>): string[];
|
|
5
|
-
export declare function
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
export declare function getBaseMeasure(measures: Record<string, DatasetMeasureDefinition>, name: string): MeasureDefinition | undefined;
|
|
8
|
+
export declare function splitDatasetMeasures(measures: Record<string, DatasetMeasureDefinition> | undefined): {
|
|
9
|
+
base: Record<string, MeasureDefinition>;
|
|
10
|
+
derived: Record<string, DerivedMeasureDefinition>;
|
|
11
|
+
windows: Record<string, WindowMeasureDefinition>;
|
|
12
|
+
shifts: Record<string, ShiftMeasureDefinition>;
|
|
8
13
|
};
|
|
14
|
+
/** Preserve the literal keys and formula aliases of the deprecated typed view. */
|
|
15
|
+
export declare function derivedMeasuresView<TMeasures extends Record<string, DatasetMeasureDefinition>>(measures: TMeasures): DerivedMeasures<TMeasures>;
|
|
9
16
|
export declare function getDerivedMeasure(measures: Record<string, DatasetMeasureDefinition>, name: string): DerivedMeasureDefinition | undefined;
|
|
10
17
|
//# sourceMappingURL=dataset-measures.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-measures.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-measures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"dataset-measures.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-measures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,GACtD,UAAU,IAAI,wBAAwB,CAExC;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,GACtD,UAAU,IAAI,iBAAiB,CAEjC;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,GACtD,UAAU,IAAI,uBAAuB,CAEvC;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,wBAAwB,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,IAAI,sBAAsB,CAE5H;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,MAAM,EAAE,CAI7F;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAClD,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,SAAS,CAI/B;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,GAC7D;IACD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CAChD,CAQA;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC5F,QAAQ,EAAE,SAAS,GAClB,eAAe,CAAC,SAAS,CAAC,CAK5B;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAClD,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,SAAS,CAItC"}
|
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
export function isDerivedMeasure(definition) {
|
|
2
|
-
return definition
|
|
2
|
+
return definition?.__type === 'derived_measure_definition';
|
|
3
3
|
}
|
|
4
4
|
export function isBaseMeasure(definition) {
|
|
5
|
-
return definition
|
|
5
|
+
return definition?.__type === 'measure_definition';
|
|
6
|
+
}
|
|
7
|
+
export function isWindowMeasure(definition) {
|
|
8
|
+
return definition?.__type === 'window_measure_definition';
|
|
9
|
+
}
|
|
10
|
+
export function isShiftMeasure(definition) {
|
|
11
|
+
return definition?.__type === 'shift_measure_definition';
|
|
6
12
|
}
|
|
7
13
|
export function baseMeasureNames(measures) {
|
|
8
14
|
return Object.entries(measures)
|
|
9
15
|
.filter(([, definition]) => isBaseMeasure(definition))
|
|
10
16
|
.map(([name]) => name);
|
|
11
17
|
}
|
|
18
|
+
export function getBaseMeasure(measures, name) {
|
|
19
|
+
if (!Object.hasOwn(measures, name))
|
|
20
|
+
return undefined;
|
|
21
|
+
const definition = measures[name];
|
|
22
|
+
return isBaseMeasure(definition) ? definition : undefined;
|
|
23
|
+
}
|
|
12
24
|
export function splitDatasetMeasures(measures) {
|
|
13
25
|
const entries = Object.entries(measures ?? {});
|
|
14
26
|
return {
|
|
15
|
-
base: Object.fromEntries(entries.filter((
|
|
16
|
-
derived: Object.fromEntries(entries.filter((
|
|
27
|
+
base: Object.fromEntries(entries.filter((entry) => isBaseMeasure(entry[1]))),
|
|
28
|
+
derived: Object.fromEntries(entries.filter((entry) => isDerivedMeasure(entry[1]))),
|
|
29
|
+
shifts: Object.fromEntries(entries.filter((entry) => isShiftMeasure(entry[1]))),
|
|
30
|
+
windows: Object.fromEntries(entries.filter((entry) => isWindowMeasure(entry[1]))),
|
|
17
31
|
};
|
|
18
32
|
}
|
|
33
|
+
/** Preserve the literal keys and formula aliases of the deprecated typed view. */
|
|
34
|
+
export function derivedMeasuresView(measures) {
|
|
35
|
+
// TypeScript narrows each entry's value, but cannot reconstruct the mapped
|
|
36
|
+
// subset of generic keys after Object.fromEntries. The filtering above
|
|
37
|
+
// establishes that correspondence; preserve it at this single boundary.
|
|
38
|
+
return splitDatasetMeasures(measures).derived;
|
|
39
|
+
}
|
|
19
40
|
export function getDerivedMeasure(measures, name) {
|
|
20
41
|
if (!Object.hasOwn(measures, name))
|
|
21
42
|
return undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AggregationSpec, BaseMetricConfig, BaseMetricRef, DatasetInstance, DerivedMetricConfig, DerivedMetricRef, DerivedMetricSpec, DimensionDefinition, MeasureDefinition, MetricRef, RelationshipDefinition, SemanticMetadata } from '../types.js';
|
|
1
|
+
import type { AggregationSpec, BaseMetricConfig, BaseMetricRef, DatasetInstance, DerivedMetricConfig, DerivedMetricRef, DerivedMetricSpec, DimensionDefinition, DatasetMeasureDefinition, MeasureDefinition, MetricRef, RelationshipDefinition, SemanticMetadata } from '../types.js';
|
|
2
2
|
type AnyDimensions = Record<string, DimensionDefinition>;
|
|
3
|
-
type AnyMeasures = Record<string,
|
|
3
|
+
type AnyMeasures = Record<string, DatasetMeasureDefinition>;
|
|
4
4
|
type AnyRelationships = Record<string, RelationshipDefinition>;
|
|
5
5
|
export declare function isDerivedMetricConfig<TMeasures extends Record<string, MeasureDefinition>, TDatasetName extends string>(config: BaseMetricConfig<TMeasures> | DerivedMetricConfig<TDatasetName>): config is DerivedMetricConfig<TDatasetName>;
|
|
6
6
|
export declare function createMetricRef<TDatasetName extends string, TMetricName extends string, TSpec extends AggregationSpec | DerivedMetricSpec<TDatasetName>, TDataset extends DatasetInstance<AnyDimensions, AnyMeasures, AnyRelationships, TDatasetName>>(ds: TDataset, name: TMetricName, spec: TSpec, label?: string, description?: string, metadata?: SemanticMetadata): MetricRef<TDatasetName, TMetricName, TSpec, TDataset>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-metric-ref.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-metric-ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEnB,iBAAiB,EACjB,SAAS,EACT,sBAAsB,EACtB,gBAAgB,EAEjB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"dataset-metric-ref.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-metric-ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEnB,wBAAwB,EACxB,iBAAiB,EACjB,SAAS,EACT,sBAAsB,EACtB,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAKrB,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACzD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAC5D,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAE/D,wBAAgB,qBAAqB,CACnC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnD,YAAY,SAAS,MAAM,EAE3B,MAAM,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,mBAAmB,CAAC,YAAY,CAAC,GACtE,MAAM,IAAI,mBAAmB,CAAC,YAAY,CAAC,CAE7C;AAED,wBAAgB,eAAe,CAC7B,YAAY,SAAS,MAAM,EAC3B,WAAW,SAAS,MAAM,EAC1B,KAAK,SAAS,eAAe,GAAG,iBAAiB,CAAC,YAAY,CAAC,EAC/D,QAAQ,SAAS,eAAe,CAAC,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,CAAC,EAE5F,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,KAAK,EACX,KAAK,CAAC,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,GAAE,gBAAqB,GAC9B,SAAS,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,CAsCvD;AAED,wBAAgB,uBAAuB,CAAC,YAAY,SAAS,MAAM,EACjE,MAAM,EAAE,mBAAmB,CAAC,YAAY,CAAC,GACxC,iBAAiB,CAAC,YAAY,CAAC,CAMjC;AAED,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildMetricContract } from './dataset-contract.js';
|
|
2
2
|
import { snapshotSemanticMetadata } from './semantic-metadata.js';
|
|
3
|
+
import { unsupportedTimeGrainError } from './dataset-time-grains.js';
|
|
3
4
|
export function isDerivedMetricConfig(config) {
|
|
4
5
|
return 'uses' in config && 'formula' in config;
|
|
5
6
|
}
|
|
@@ -17,6 +18,10 @@ export function createMetricRef(ds, name, spec, label, description, metadata = {
|
|
|
17
18
|
if (!ds.timeKey) {
|
|
18
19
|
throw new Error(`Cannot apply .by("${grain}") to metric "${name}" — dataset "${ds.name}" has no timeKey defined.`);
|
|
19
20
|
}
|
|
21
|
+
const grainError = unsupportedTimeGrainError(ds, grain);
|
|
22
|
+
if (grainError) {
|
|
23
|
+
throw new Error(`Cannot apply .by("${grain}") to metric "${name}": ${grainError}.`);
|
|
24
|
+
}
|
|
20
25
|
return {
|
|
21
26
|
__type: 'grained_metric_ref',
|
|
22
27
|
metric: ref,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-query-result.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-result.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-query-result.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-result.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAKrB,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,OAAO,EAAE;IACP,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B,GACA,kBAAkB,CAgBpB"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { baseMeasureNames } from './dataset-measures.js';
|
|
1
2
|
import { applyPagination } from './pagination.js';
|
|
2
3
|
import { serializeSemanticMeasureValues } from './semantic-result-serialization.js';
|
|
3
4
|
import { getRuntimeTenantId } from './tenant-runtime.js';
|
|
4
5
|
/** Apply the same result contract to builder-backed and derived dataset queries. */
|
|
5
6
|
export function toDatasetQueryResult(rows, options) {
|
|
6
7
|
const { dataset, query, sql, timingMs, context } = options;
|
|
7
|
-
const selectedMeasures = query.measures ??
|
|
8
|
+
const selectedMeasures = query.measures ?? baseMeasureNames(dataset.measures);
|
|
8
9
|
const { data, pagination } = applyPagination(rows, query.limit, query.offset);
|
|
9
10
|
const serializedData = serializeSemanticMeasureValues(data, selectedMeasures);
|
|
10
11
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAiB9E,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAmKlB"}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { queryTimezoneErrors } from './query-timezone.js';
|
|
2
|
+
import { baseMeasureNames } from './dataset-measures.js';
|
|
1
3
|
import { validateFilterValue } from '../validation.js';
|
|
2
|
-
import {
|
|
4
|
+
import { unsupportedTimeGrainError } from './dataset-time-grains.js';
|
|
3
5
|
import { getRuntimeTenantPredicate, validateTenantRuntime, } from './tenant-runtime.js';
|
|
4
6
|
import { isQualifiedField, resolveQualifiedField, } from './relationship-fields.js';
|
|
5
7
|
import { validateQualifiedFilter, validateRelationshipTenantRuntime, } from './relationship-validation.js';
|
|
8
|
+
import { segmentSelectionErrors } from './segments.js';
|
|
9
|
+
import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
|
|
6
10
|
export function validateDatasetQueryInput(ds, query, context) {
|
|
7
|
-
const errors =
|
|
11
|
+
const errors = queryTimezoneErrors(query.timezone);
|
|
8
12
|
const dimensionNames = Object.keys(ds.dimensions);
|
|
9
|
-
const measureNames =
|
|
13
|
+
const measureNames = Object.keys(ds.measures);
|
|
10
14
|
const selectedDimensions = query.dimensions ?? [];
|
|
11
|
-
const selectedMeasures = query.measures ??
|
|
15
|
+
const selectedMeasures = query.measures ?? baseMeasureNames(ds.measures);
|
|
12
16
|
const filterNames = Object.keys(ds.filters);
|
|
13
17
|
const orderableFields = new Set([
|
|
14
18
|
...selectedDimensions,
|
|
@@ -113,8 +117,11 @@ export function validateDatasetQueryInput(ds, query, context) {
|
|
|
113
117
|
if (query.by && !ds.timeKey) {
|
|
114
118
|
errors.push(`Cannot use "by" grain — dataset "${ds.name}" has no timeKey.`);
|
|
115
119
|
}
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
errors.push(...analyzeTimeMeasureAxis(ds, query).errors);
|
|
121
|
+
errors.push(...segmentSelectionErrors(ds, query.segments));
|
|
122
|
+
const grainError = query.by && ds.timeKey ? unsupportedTimeGrainError(ds, query.by) : undefined;
|
|
123
|
+
if (grainError) {
|
|
124
|
+
errors.push(grainError);
|
|
118
125
|
}
|
|
119
126
|
if (query.limit != null && (!Number.isInteger(query.limit) || query.limit < 0)) {
|
|
120
127
|
errors.push(`Invalid limit: expected a non-negative integer.`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The time grains a dataset supports: none without a `timeKey`, otherwise its
|
|
3
|
+
* declared `timeGrains`, or every grain the planner can bucket on.
|
|
4
|
+
*/
|
|
5
|
+
import type { TimeGrain } from '../types.js';
|
|
6
|
+
export declare function datasetTimeGrains(dataset: {
|
|
7
|
+
readonly timeKey?: string;
|
|
8
|
+
readonly timeGrains?: readonly TimeGrain[];
|
|
9
|
+
}): readonly TimeGrain[];
|
|
10
|
+
/** Validation message for a grain the dataset does not support, or undefined. */
|
|
11
|
+
export declare function unsupportedTimeGrainError(dataset: {
|
|
12
|
+
readonly timeKey?: string;
|
|
13
|
+
readonly timeGrains?: readonly TimeGrain[];
|
|
14
|
+
}, grain: unknown): string | undefined;
|
|
15
|
+
//# sourceMappingURL=dataset-time-grains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-time-grains.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-time-grains.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;CAAE,GACjF,SAAS,SAAS,EAAE,CAGtB;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CACvC,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;CAAE,EAClF,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,SAAS,CAIpB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The time grains a dataset supports: none without a `timeKey`, otherwise its
|
|
3
|
+
* declared `timeGrains`, or every grain the planner can bucket on.
|
|
4
|
+
*/
|
|
5
|
+
import { SUPPORTED_TIME_GRAINS } from '../constants.js';
|
|
6
|
+
export function datasetTimeGrains(dataset) {
|
|
7
|
+
if (!dataset.timeKey)
|
|
8
|
+
return [];
|
|
9
|
+
return dataset.timeGrains ?? SUPPORTED_TIME_GRAINS;
|
|
10
|
+
}
|
|
11
|
+
/** Validation message for a grain the dataset does not support, or undefined. */
|
|
12
|
+
export function unsupportedTimeGrainError(dataset, grain) {
|
|
13
|
+
const supported = datasetTimeGrains(dataset);
|
|
14
|
+
if (typeof grain === 'string' && supported.includes(grain))
|
|
15
|
+
return undefined;
|
|
16
|
+
return `Unsupported time grain "${String(grain)}". Supported: ${supported.join(', ')}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAyC5D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,wBAAwB,GAAG,MAAM,CAG5F"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
|
+
function expressionSql(expression, uses) {
|
|
3
|
+
switch (expression.kind) {
|
|
4
|
+
case 'ref': {
|
|
5
|
+
if (!Object.hasOwn(uses, expression.name)) {
|
|
6
|
+
throw new Error(`Formula references undeclared input "${expression.name}".`);
|
|
7
|
+
}
|
|
8
|
+
const measureName = uses[expression.name];
|
|
9
|
+
return quoteSQLIdentifier(measureName);
|
|
10
|
+
}
|
|
11
|
+
case 'literal': {
|
|
12
|
+
if (expression.value === null)
|
|
13
|
+
return 'NULL';
|
|
14
|
+
if (typeof expression.value === 'number') {
|
|
15
|
+
if (!Number.isFinite(expression.value))
|
|
16
|
+
throw new Error('Formula contains a non-finite number.');
|
|
17
|
+
return String(expression.value);
|
|
18
|
+
}
|
|
19
|
+
if (typeof expression.value === 'boolean')
|
|
20
|
+
return expression.value ? 'true' : 'false';
|
|
21
|
+
return `'${expression.value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
22
|
+
}
|
|
23
|
+
case 'binary': {
|
|
24
|
+
const operators = { add: '+', subtract: '-', multiply: '*', divide: '/' };
|
|
25
|
+
const operator = operators[expression.operator];
|
|
26
|
+
if (!operator)
|
|
27
|
+
throw new Error('Formula contains an unsupported binary operator.');
|
|
28
|
+
return `(${expressionSql(expression.left, uses)} ${operator} ${expressionSql(expression.right, uses)})`;
|
|
29
|
+
}
|
|
30
|
+
case 'function': {
|
|
31
|
+
const args = expression.args.map(arg => expressionSql(arg, uses));
|
|
32
|
+
switch (expression.name) {
|
|
33
|
+
case 'nullIfZero': return `NULLIF(${args[0]}, 0)`;
|
|
34
|
+
case 'coalesce': return `COALESCE(${args.join(', ')})`;
|
|
35
|
+
case 'round': return `ROUND(${args.join(', ')})`;
|
|
36
|
+
case 'floor': return `FLOOR(${args[0]})`;
|
|
37
|
+
case 'ceil': return `CEIL(${args[0]})`;
|
|
38
|
+
default: throw new Error('Formula contains an unsupported function.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function derivedProjection(name, definition) {
|
|
44
|
+
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
45
|
+
return `${expressionSql(definition.formula(aliases).expression, definition.uses)} AS ${quoteSQLIdentifier(name)}`;
|
|
46
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derived-measure-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,
|
|
1
|
+
{"version":3,"file":"derived-measure-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EAGzB,MAAM,aAAa,CAAC;AAmHrB,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GACjD,IAAI,CAmDN"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isSafeSQLIdentifier } from '../sql-utils.js';
|
|
2
|
-
import {
|
|
2
|
+
import { splitDatasetMeasures } from './dataset-measures.js';
|
|
3
3
|
function fail(datasetName, measureName, detail) {
|
|
4
4
|
throw new Error(`Invalid dataset "${datasetName}": derived measure "${measureName}" ${detail}`);
|
|
5
5
|
}
|
|
@@ -90,8 +90,8 @@ function assertAcyclic(datasetName, derivedMeasures) {
|
|
|
90
90
|
Object.keys(derivedMeasures).forEach(visit);
|
|
91
91
|
}
|
|
92
92
|
export function validateDerivedMeasures(datasetName, measures) {
|
|
93
|
-
const baseMeasures
|
|
94
|
-
const
|
|
93
|
+
const { base: baseMeasures, derived: derivedMeasures, windows, shifts } = splitDatasetMeasures(measures);
|
|
94
|
+
const timeMeasureNames = new Set([...Object.keys(windows), ...Object.keys(shifts)]);
|
|
95
95
|
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
96
96
|
if (!isSafeSQLIdentifier(measureName)) {
|
|
97
97
|
fail(datasetName, measureName, 'name is not a safe identifier.');
|
|
@@ -107,7 +107,7 @@ export function validateDerivedMeasures(datasetName, measures) {
|
|
|
107
107
|
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
108
108
|
const uses = Object.entries(definition.uses);
|
|
109
109
|
if (uses.length === 0)
|
|
110
|
-
fail(datasetName, measureName, 'must reference at least one base measure.');
|
|
110
|
+
fail(datasetName, measureName, 'must reference at least one base, window, or shift measure.');
|
|
111
111
|
if (typeof definition.formula !== 'function')
|
|
112
112
|
fail(datasetName, measureName, 'must define a formula function.');
|
|
113
113
|
for (const [alias, dependencyName] of uses) {
|
|
@@ -119,8 +119,9 @@ export function validateDerivedMeasures(datasetName, measures) {
|
|
|
119
119
|
if (Object.hasOwn(derivedMeasures, dependencyName)) {
|
|
120
120
|
fail(datasetName, measureName, `references derived measure "${dependencyName}"; v1 inputs must be base measures.`);
|
|
121
121
|
}
|
|
122
|
-
if (!Object.hasOwn(baseMeasures, dependencyName))
|
|
122
|
+
if (!Object.hasOwn(baseMeasures, dependencyName) && !timeMeasureNames.has(dependencyName)) {
|
|
123
123
|
fail(datasetName, measureName, `references missing measure "${dependencyName}".`);
|
|
124
|
+
}
|
|
124
125
|
}
|
|
125
126
|
const references = new Set();
|
|
126
127
|
collectReferences(datasetName, measureName, evaluateFormula(datasetName, measureName, definition).expression, references, { nodes: 0 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filtered-aggregation-sql.d.ts","sourceRoot":"","sources":["../../src/utils/filtered-aggregation-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAgB,MAAM,aAAa,CAAC;AAErF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAEjF,KAAK,YAAY,GAAG,kBAAkB,CAAC;AAqEvC,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,0BAA0B,GACnC,MAAM,
|
|
1
|
+
{"version":3,"file":"filtered-aggregation-sql.d.ts","sourceRoot":"","sources":["../../src/utils/filtered-aggregation-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAgB,MAAM,aAAa,CAAC;AAErF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAEjF,KAAK,YAAY,GAAG,kBAAkB,CAAC;AAqEvC,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,0BAA0B,GACnC,MAAM,CAgCR"}
|
|
@@ -74,6 +74,7 @@ export function applyFilteredAggregationExpression(ds, spec, fieldOrExpr, joinCt
|
|
|
74
74
|
case 'percentile':
|
|
75
75
|
case 'stddev':
|
|
76
76
|
case 'variance':
|
|
77
|
+
case 'approxCountDistinct':
|
|
77
78
|
return `if(${combinedCondition}, ${fieldOrExpr}, NULL)`;
|
|
78
79
|
case 'argMax':
|
|
79
80
|
case 'argMin':
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatasetMeasureDefinition, TimeGrain } from '../types.js';
|
|
2
|
+
/** Intersect time-measure requirements with the dataset's declared grains. */
|
|
3
|
+
export declare function measureSupportedGrains(measures: Readonly<Record<string, DatasetMeasureDefinition>>, name: string, grains: readonly TimeGrain[]): TimeGrain[];
|
|
4
|
+
//# sourceMappingURL=measure-time-grains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-time-grains.d.ts","sourceRoot":"","sources":["../../src/utils/measure-time-grains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIvE,8EAA8E;AAC9E,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,EAAE,GACvG,SAAS,EAAE,CAUb"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getDerivedMeasure, isShiftMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
2
|
+
import { intervalBuckets, windowGrainErrors } from './time-axis-intervals.js';
|
|
3
|
+
/** Intersect time-measure requirements with the dataset's declared grains. */
|
|
4
|
+
export function measureSupportedGrains(measures, name, grains) {
|
|
5
|
+
const definition = measures[name];
|
|
6
|
+
if (isShiftMeasure(definition)) {
|
|
7
|
+
return grains.filter(grain => intervalBuckets(definition.interval.amount, definition.interval.unit, grain) !== undefined);
|
|
8
|
+
}
|
|
9
|
+
if (isWindowMeasure(definition))
|
|
10
|
+
return grains.filter(grain => !windowGrainErrors(name, definition, grain).length);
|
|
11
|
+
const derived = getDerivedMeasure(measures, name);
|
|
12
|
+
if (!derived)
|
|
13
|
+
return [...grains];
|
|
14
|
+
const inputs = Object.values(derived.uses).map(input => measureSupportedGrains(measures, input, grains));
|
|
15
|
+
return grains.filter(grain => inputs.every(supported => supported.includes(grain)));
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregations that the published deployment contract can carry.
|
|
3
|
+
*
|
|
4
|
+
* `approxCountDistinct` runs locally, but it is an expression extension 2 and
|
|
5
|
+
* deployment contract 3 feature (RFC 0015). Publishing emits contract 2 until
|
|
6
|
+
* it can emit contract 3, so it refuses the measure with an error naming it.
|
|
7
|
+
*/
|
|
8
|
+
import type { ProtocolAggregation } from '@hypequery/protocol';
|
|
9
|
+
import type { AggregationType } from '../types.js';
|
|
10
|
+
type PortableAggregation = Exclude<ProtocolAggregation, 'approxCountDistinct'>;
|
|
11
|
+
/** Returns `aggregation` when publishing can carry it; otherwise throws naming `owner`. */
|
|
12
|
+
export declare function requirePortableAggregation(aggregation: AggregationType, owner: string): PortableAggregation;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=portable-aggregations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-aggregations.d.ts","sourceRoot":"","sources":["../../src/utils/portable-aggregations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,CAAC;AAE/E,2FAA2F;AAC3F,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,eAAe,EAC5B,KAAK,EAAE,MAAM,GACZ,mBAAmB,CAQrB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregations that the published deployment contract can carry.
|
|
3
|
+
*
|
|
4
|
+
* `approxCountDistinct` runs locally, but it is an expression extension 2 and
|
|
5
|
+
* deployment contract 3 feature (RFC 0015). Publishing emits contract 2 until
|
|
6
|
+
* it can emit contract 3, so it refuses the measure with an error naming it.
|
|
7
|
+
*/
|
|
8
|
+
/** Returns `aggregation` when publishing can carry it; otherwise throws naming `owner`. */
|
|
9
|
+
export function requirePortableAggregation(aggregation, owner) {
|
|
10
|
+
if (aggregation === 'approxCountDistinct') {
|
|
11
|
+
throw new Error(`${owner} uses approxCountDistinct, which cannot be published yet: approximate aggregations ` +
|
|
12
|
+
'need deployment contract 3 (RFC 0015). Use it for local queries, or publish countDistinct instead.');
|
|
13
|
+
}
|
|
14
|
+
return aggregation;
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time grains that the published deployment contract can carry.
|
|
3
|
+
*
|
|
4
|
+
* `minute` and `hour` run locally, but they are expression extension 2 and
|
|
5
|
+
* deployment contract 3 features (RFC 0015). Publishing emits contract 2 until
|
|
6
|
+
* it can emit contract 3, so a dataset that pins a sub-day grain is refused
|
|
7
|
+
* with an error naming what needs it, rather than failing later as an opaque
|
|
8
|
+
* contract validation error.
|
|
9
|
+
*/
|
|
10
|
+
import type { ProtocolTimeGrain } from '@hypequery/protocol';
|
|
11
|
+
import type { TimeGrain } from '../types.js';
|
|
12
|
+
/** Grains deployment contract 2 and invocation 1 carry, in ascending order. */
|
|
13
|
+
export declare const PORTABLE_TIME_GRAINS: readonly ProtocolTimeGrain[];
|
|
14
|
+
export declare function isPortableTimeGrain(grain: TimeGrain): grain is ProtocolTimeGrain;
|
|
15
|
+
/** Returns `grain` when publishing can carry it; otherwise throws naming `owner`. */
|
|
16
|
+
export declare function requirePortableTimeGrain(grain: TimeGrain, owner: string): ProtocolTimeGrain;
|
|
17
|
+
/** Contract 2 cannot represent a dataset-level restriction on portable grains. */
|
|
18
|
+
export declare function assertPublishableTimeGrains(dataset: {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly timeGrains?: readonly TimeGrain[];
|
|
21
|
+
}): void;
|
|
22
|
+
//# sourceMappingURL=portable-grains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-grains.d.ts","sourceRoot":"","sources":["../../src/utils/portable-grains.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EAE3D,CAAC;AAIH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,IAAI,iBAAiB,CAEhF;AAED,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAQ3F;AAED,kFAAkF;AAClF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;CAAE,GAC7E,IAAI,CASN"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time grains that the published deployment contract can carry.
|
|
3
|
+
*
|
|
4
|
+
* `minute` and `hour` run locally, but they are expression extension 2 and
|
|
5
|
+
* deployment contract 3 features (RFC 0015). Publishing emits contract 2 until
|
|
6
|
+
* it can emit contract 3, so a dataset that pins a sub-day grain is refused
|
|
7
|
+
* with an error naming what needs it, rather than failing later as an opaque
|
|
8
|
+
* contract validation error.
|
|
9
|
+
*/
|
|
10
|
+
/** Grains deployment contract 2 and invocation 1 carry, in ascending order. */
|
|
11
|
+
export const PORTABLE_TIME_GRAINS = Object.freeze([
|
|
12
|
+
'day', 'week', 'month', 'quarter', 'year',
|
|
13
|
+
]);
|
|
14
|
+
const CONTRACT_2_GRAINS = new Set(PORTABLE_TIME_GRAINS);
|
|
15
|
+
export function isPortableTimeGrain(grain) {
|
|
16
|
+
return CONTRACT_2_GRAINS.has(grain);
|
|
17
|
+
}
|
|
18
|
+
/** Returns `grain` when publishing can carry it; otherwise throws naming `owner`. */
|
|
19
|
+
export function requirePortableTimeGrain(grain, owner) {
|
|
20
|
+
if (!isPortableTimeGrain(grain)) {
|
|
21
|
+
throw new Error(`${owner} uses the "${grain}" time grain, which cannot be published yet: sub-day grains ` +
|
|
22
|
+
'need deployment contract 3 (RFC 0015). Use it for local queries, or pin a grain of a day or longer.');
|
|
23
|
+
}
|
|
24
|
+
return grain;
|
|
25
|
+
}
|
|
26
|
+
/** Contract 2 cannot represent a dataset-level restriction on portable grains. */
|
|
27
|
+
export function assertPublishableTimeGrains(dataset) {
|
|
28
|
+
if (dataset.timeGrains === undefined)
|
|
29
|
+
return;
|
|
30
|
+
const missing = PORTABLE_TIME_GRAINS.filter(grain => !dataset.timeGrains?.includes(grain));
|
|
31
|
+
if (missing.length > 0) {
|
|
32
|
+
throw new Error(`Dataset "${dataset.name}" timeGrains excludes ${missing.join(', ')}, but deployment contract 2 ` +
|
|
33
|
+
'cannot preserve dataset-level grain restrictions. Use all day-through-year grains or keep this dataset local.');
|
|
34
|
+
}
|
|
35
|
+
}
|