@hypequery/datasets 0.19.0 → 0.21.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 -1
- 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 +6 -5
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +29 -9
- package/dist/contract.d.ts +3 -2
- 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 +61 -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 +3 -2
- package/dist/utils/approximate-measures.d.ts.map +1 -1
- package/dist/utils/approximate-measures.js +11 -8
- package/dist/utils/composite-time-measure-sql.d.ts +10 -0
- package/dist/utils/composite-time-measure-sql.d.ts.map +1 -0
- package/dist/utils/composite-time-measure-sql.js +60 -0
- 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 +12 -55
- 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 +4 -0
- package/dist/utils/derived-measure-sql.d.ts.map +1 -0
- package/dist/utils/derived-measure-sql.js +49 -0
- package/dist/utils/derived-measure-validation.d.ts.map +1 -1
- package/dist/utils/derived-measure-validation.js +7 -27
- package/dist/utils/measure-dependencies.d.ts +8 -0
- package/dist/utils/measure-dependencies.d.ts.map +1 -0
- package/dist/utils/measure-dependencies.js +44 -0
- package/dist/utils/measure-evaluation-graph.d.ts +23 -0
- package/dist/utils/measure-evaluation-graph.d.ts.map +1 -0
- package/dist/utils/measure-evaluation-graph.js +38 -0
- package/dist/utils/measure-time-grains.d.ts +4 -0
- package/dist/utils/measure-time-grains.d.ts.map +1 -0
- package/dist/utils/measure-time-grains.js +18 -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-grains.d.ts +5 -0
- package/dist/utils/shift-measure-grains.d.ts.map +1 -0
- package/dist/utils/shift-measure-grains.js +10 -0
- package/dist/utils/shift-measure-sql.d.ts +21 -0
- package/dist/utils/shift-measure-sql.d.ts.map +1 -0
- package/dist/utils/shift-measure-sql.js +63 -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/shift-wall-time-sql.d.ts +4 -0
- package/dist/utils/shift-wall-time-sql.d.ts.map +1 -0
- package/dist/utils/shift-wall-time-sql.js +17 -0
- package/dist/utils/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 +13 -0
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-axis-sql.js +48 -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 +46 -0
- package/dist/utils/time-measure-context-sql.d.ts +6 -0
- package/dist/utils/time-measure-context-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-context-sql.js +21 -0
- package/dist/utils/time-measure-dataset-sql.d.ts +9 -0
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-dataset-sql.js +80 -0
- package/dist/utils/time-measure-leaf-sql.d.ts +14 -0
- package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-leaf-sql.js +52 -0
- package/dist/utils/time-measure-result-sql.d.ts +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 +24 -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 +35 -0
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approximate-measures.d.ts","sourceRoot":"","sources":["../../src/utils/approximate-measures.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"approximate-measures.d.ts","sourceRoot":"","sources":["../../src/utils/approximate-measures.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEvG,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,eAAe,GAAG,OAAO,CAE9E;AAED,oEAAoE;AACpE,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,EAC5D,UAAU,EAAE,wBAAwB,GACnC,OAAO,CAKT;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxH"}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* consumer, and in particular an agent, can say a value is an estimate
|
|
4
|
-
* (RFC 0015).
|
|
5
|
-
*/
|
|
1
|
+
import { measureDependencyNames } from './measure-dependencies.js';
|
|
2
|
+
import { getBaseMeasure } from './dataset-measures.js';
|
|
6
3
|
export function isApproximateAggregation(aggregation) {
|
|
7
4
|
return aggregation === 'approxCountDistinct';
|
|
8
5
|
}
|
|
9
6
|
/** A derived measure is approximate when any measure it uses is. */
|
|
10
7
|
export function isApproximateDerivedMeasure(measures, definition) {
|
|
11
|
-
return Object.values(definition.uses).some(name => {
|
|
12
|
-
const
|
|
13
|
-
return
|
|
8
|
+
return measureDependencyNames(measures, Object.values(definition.uses)).some(name => {
|
|
9
|
+
const base = getBaseMeasure(measures, name);
|
|
10
|
+
return base !== undefined && isApproximateAggregation(base.aggregation);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function isApproximateMeasure(measures, name) {
|
|
14
|
+
return measureDependencyNames(measures, [name]).some(input => {
|
|
15
|
+
const base = getBaseMeasure(measures, input);
|
|
16
|
+
return base !== undefined && isApproximateAggregation(base.aggregation);
|
|
14
17
|
});
|
|
15
18
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
|
|
2
|
+
import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
|
|
3
|
+
import type { TimeMeasureAxis } from './time-measure-axis.js';
|
|
4
|
+
/** Aggregate each dependency in its evaluation context, then evaluate formulas. */
|
|
5
|
+
export declare function buildCompositeTimeMeasureSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, axis: TimeMeasureAxis): {
|
|
6
|
+
sql: string;
|
|
7
|
+
parameters: unknown[];
|
|
8
|
+
timeAxisSql: string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=composite-time-measure-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite-time-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/composite-time-measure-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAW9D,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,4BAA4B,EAAE,IAAI,EAAE,eAAe;;;;EAgDrJ"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
|
+
import { isDerivedMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
3
|
+
import { buildMeasureEvaluationGraph, evaluationFormula } from './measure-evaluation-graph.js';
|
|
4
|
+
import { buildTimeMeasureSourceSql } from './time-measure-source-sql.js';
|
|
5
|
+
import { buildTimeMeasureAxisSql } from './time-measure-axis-sql.js';
|
|
6
|
+
import { timeMeasureContextCtes, timeMeasureContextGuard } from './time-measure-context-sql.js';
|
|
7
|
+
import { timeMeasureLeafSql } from './time-measure-leaf-sql.js';
|
|
8
|
+
import { derivedProjection } from './derived-measure-sql.js';
|
|
9
|
+
import { buildTimeMeasureResultSql } from './time-measure-result-sql.js';
|
|
10
|
+
/** Aggregate each dependency in its evaluation context, then evaluate formulas. */
|
|
11
|
+
export function buildCompositeTimeMeasureSql(ds, query, options, axis) {
|
|
12
|
+
const graph = buildMeasureEvaluationGraph(ds.measures, query.measures ?? []);
|
|
13
|
+
const leaves = graph.nodes.filter(node => !isDerivedMeasure(node.definition));
|
|
14
|
+
const baseNames = new Set(leaves.map(node => isWindowMeasure(node.definition) ? node.definition.measure : node.name));
|
|
15
|
+
const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options);
|
|
16
|
+
const timeAxis = buildTimeMeasureAxisSql(source, axis);
|
|
17
|
+
const contexts = timeMeasureContextCtes(graph.contexts, axis);
|
|
18
|
+
const inputs = leaves.map(node => {
|
|
19
|
+
const window = isWindowMeasure(node.definition) ? node.definition : undefined;
|
|
20
|
+
const base = source.bases.find(base => base.name === (window?.measure ?? node.name));
|
|
21
|
+
return { node, ...timeMeasureLeafSql(node.id, node.context, base, window, axis, source.dimensions) };
|
|
22
|
+
});
|
|
23
|
+
const keys = source.dimensions.map(dim => dim.alias);
|
|
24
|
+
const combinations = keys.length ? ` CROSS JOIN (SELECT DISTINCT ${keys.join(', ')} FROM (${inputs.map(input => `SELECT ${keys.join(', ')} FROM ${input.population}`).join(' UNION ALL ')})) AS _hq_combinations` : '';
|
|
25
|
+
const guardCtes = [...source.ctes, ...timeAxis.ctes, ...contexts, ...inputs.flatMap(input => input.ctes)];
|
|
26
|
+
const ctes = [...guardCtes,
|
|
27
|
+
`_hq_skeleton AS (SELECT * FROM _hq_series${combinations})`];
|
|
28
|
+
let previous = '_hq_skeleton';
|
|
29
|
+
for (const input of inputs) {
|
|
30
|
+
const { node } = input;
|
|
31
|
+
const output = `_hq_value${node.id}`;
|
|
32
|
+
const next = `_hq_evaluated${node.id}`;
|
|
33
|
+
const value = input.count ? `coalesce(m.${output}, 0)` : `m.${output}`;
|
|
34
|
+
if (input.cumulative) {
|
|
35
|
+
const partition = keys.length ? `tuple(${keys.map(key => `s.${key}`).join(', ')})` : 'tuple(0)';
|
|
36
|
+
const lookup = `_hq_lookup${node.id}`;
|
|
37
|
+
ctes.push(`${lookup} AS (SELECT s.*, c._hq_eval_end, ${partition} AS _hq_partition FROM ${previous} AS s INNER JOIN _hq_context${input.context} AS c ON s._hq_period = c._hq_period)`);
|
|
38
|
+
ctes.push(`${next} AS (SELECT s.* EXCEPT (_hq_eval_end, _hq_partition), ${value} AS ${output} FROM ${lookup} AS s LEFT ASOF JOIN ${input.aggregate} AS m ON s._hq_partition = m._hq_partition AND s._hq_eval_end > m._hq_time)`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const condition = ['s._hq_period = m._hq_period', ...keys.map(key => `isNotDistinctFrom(s.${key}, m.${key})`)];
|
|
42
|
+
ctes.push(`${next} AS (SELECT s.*, ${value} AS ${output} FROM ${previous} AS s LEFT JOIN ${input.aggregate} AS m ON ${condition.join(' AND ')})`);
|
|
43
|
+
}
|
|
44
|
+
previous = next;
|
|
45
|
+
}
|
|
46
|
+
for (const node of graph.nodes.filter(node => isDerivedMeasure(node.definition))) {
|
|
47
|
+
const next = `_hq_formula${node.id}`;
|
|
48
|
+
ctes.push(`${next} AS (SELECT *, ${derivedProjection(`_hq_value${node.id}`, evaluationFormula(node))} FROM ${previous})`);
|
|
49
|
+
previous = next;
|
|
50
|
+
}
|
|
51
|
+
const projections = ['v._hq_period AS period', ...source.dimensions.map(dim => `v.${dim.alias} AS ${quoteSQLIdentifier(dim.name)}`),
|
|
52
|
+
...Object.entries(graph.outputs).map(([name, id]) => `v._hq_value${id} AS ${quoteSQLIdentifier(name)}`)];
|
|
53
|
+
ctes.push(`_hq_values AS (SELECT ${projections.join(', ')} FROM ${previous} AS v)`);
|
|
54
|
+
const guard = [timeAxis.guard, timeAxis.rangeGuard, ...graph.contexts.map(context => timeMeasureContextGuard(context, axis)), ...inputs.map(input => input.guard)].join(' + ');
|
|
55
|
+
return {
|
|
56
|
+
sql: buildTimeMeasureResultSql(ds, query, options, source.dimensions, ctes, guard, true),
|
|
57
|
+
parameters: source.parameters,
|
|
58
|
+
timeAxisSql: `WITH ${guardCtes.join(',\n')} SELECT ${guard} AS _hq_validated FROM _hq_bounds`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-contract.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-contract.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-contract.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-contract.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,SAAS,EACV,MAAM,aAAa,CAAC;AAIrB,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,eAAe,GAAG,iBAAiB,EACzC,KAAK,CAAC,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,GAAE,gBAAqB,EAC/B,KAAK,CAAC,EAAE,SAAS,GAChB,cAAc,CA8BhB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { baseMeasureNames } from './dataset-measures.js';
|
|
1
2
|
import { datasetTimeGrains } from './dataset-time-grains.js';
|
|
2
3
|
import { snapshotSemanticMetadata } from './semantic-metadata.js';
|
|
3
4
|
export function buildMetricContract(metricName, ds, spec, label, description, metadata = {}, grain) {
|
|
4
5
|
const dimensionNames = Object.keys(ds.dimensions);
|
|
5
|
-
const measureNames =
|
|
6
|
+
const measureNames = baseMeasureNames(ds.measures);
|
|
6
7
|
const filterNames = Object.keys(ds.filters).length > 0
|
|
7
8
|
? Object.keys(ds.filters)
|
|
8
9
|
: dimensionNames.filter(name => ds.dimensions[name]?.filterable !== false);
|
|
@@ -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":"AAGA,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,67 +1,24 @@
|
|
|
1
|
+
import { measureDependencyNames } from './measure-dependencies.js';
|
|
2
|
+
import { derivedExpressionSql } from './derived-measure-sql.js';
|
|
3
|
+
import { baseMeasureNames, getBaseMeasure, getDerivedMeasure } from './dataset-measures.js';
|
|
1
4
|
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
5
|
import { validateDatasetQueryInput } from './dataset-query-validation.js';
|
|
3
6
|
import { overfetchLimit } from './pagination.js';
|
|
4
7
|
import { toDatasetQueryResult } from './dataset-query-result.js';
|
|
5
8
|
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)}`;
|
|
9
|
+
return (query.measures ?? []).some(name => getDerivedMeasure(ds.measures, name) !== undefined);
|
|
52
10
|
}
|
|
53
11
|
export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
54
12
|
const validation = validateDatasetQueryInput(ds, query, options.context);
|
|
55
13
|
if (!validation.valid) {
|
|
56
14
|
throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
|
|
57
15
|
}
|
|
58
|
-
const selected = query.measures ??
|
|
59
|
-
const baseMeasures =
|
|
60
|
-
|
|
61
|
-
const derived =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
16
|
+
const selected = query.measures ?? baseMeasureNames(ds.measures);
|
|
17
|
+
const baseMeasures = measureDependencyNames(ds.measures, selected).filter(name => getBaseMeasure(ds.measures, name));
|
|
18
|
+
const resolve = (name) => {
|
|
19
|
+
const derived = getDerivedMeasure(ds.measures, name);
|
|
20
|
+
return derived ? `(${derivedExpressionSql(derived, resolve)})` : quoteSQLIdentifier(name);
|
|
21
|
+
};
|
|
65
22
|
const inner = buildBaseQuery(ds, {
|
|
66
23
|
...query,
|
|
67
24
|
measures: [...baseMeasures],
|
|
@@ -76,9 +33,9 @@ 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
|
+
? `${derivedExpressionSql(derived, resolve)} AS ${quoteSQLIdentifier(name)}`
|
|
82
39
|
: quoteSQLIdentifier(name));
|
|
83
40
|
}
|
|
84
41
|
let sql = `WITH base AS (${innerSql}) SELECT ${projections.join(', ')} FROM base`;
|
|
@@ -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;AAKrB,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACzD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,
|
|
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 +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,15 +1,18 @@
|
|
|
1
|
+
import { queryTimezoneErrors } from './query-timezone.js';
|
|
2
|
+
import { baseMeasureNames } from './dataset-measures.js';
|
|
1
3
|
import { validateFilterValue } from '../validation.js';
|
|
2
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';
|
|
6
8
|
import { segmentSelectionErrors } from './segments.js';
|
|
9
|
+
import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
|
|
7
10
|
export function validateDatasetQueryInput(ds, query, context) {
|
|
8
|
-
const errors =
|
|
11
|
+
const errors = queryTimezoneErrors(query.timezone);
|
|
9
12
|
const dimensionNames = Object.keys(ds.dimensions);
|
|
10
|
-
const measureNames =
|
|
13
|
+
const measureNames = Object.keys(ds.measures);
|
|
11
14
|
const selectedDimensions = query.dimensions ?? [];
|
|
12
|
-
const selectedMeasures = query.measures ??
|
|
15
|
+
const selectedMeasures = query.measures ?? baseMeasureNames(ds.measures);
|
|
13
16
|
const filterNames = Object.keys(ds.filters);
|
|
14
17
|
const orderableFields = new Set([
|
|
15
18
|
...selectedDimensions,
|
|
@@ -114,6 +117,7 @@ export function validateDatasetQueryInput(ds, query, context) {
|
|
|
114
117
|
if (query.by && !ds.timeKey) {
|
|
115
118
|
errors.push(`Cannot use "by" grain — dataset "${ds.name}" has no timeKey.`);
|
|
116
119
|
}
|
|
120
|
+
errors.push(...analyzeTimeMeasureAxis(ds, query).errors);
|
|
117
121
|
errors.push(...segmentSelectionErrors(ds, query.segments));
|
|
118
122
|
const grainError = query.by && ds.timeKey ? unsupportedTimeGrainError(ds, query.by) : undefined;
|
|
119
123
|
if (grainError) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DerivedMeasureDefinition } from '../types.js';
|
|
2
|
+
export declare function derivedExpressionSql(definition: DerivedMeasureDefinition, resolve?: (name: string) => string): string;
|
|
3
|
+
export declare function derivedProjection(name: string, definition: DerivedMeasureDefinition): string;
|
|
4
|
+
//# sourceMappingURL=derived-measure-sql.d.ts.map
|
|
@@ -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,oBAAoB,CAAC,UAAU,EAAE,wBAAwB,EAAE,OAAO,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAA2B,GAAG,MAAM,CAGzI;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,wBAAwB,GAAG,MAAM,CAE5F"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
|
+
function expressionSql(expression, uses, resolve) {
|
|
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 resolve(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, resolve)} ${operator} ${expressionSql(expression.right, uses, resolve)})`;
|
|
29
|
+
}
|
|
30
|
+
case 'function': {
|
|
31
|
+
const args = expression.args.map(arg => expressionSql(arg, uses, resolve));
|
|
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 derivedExpressionSql(definition, resolve = quoteSQLIdentifier) {
|
|
44
|
+
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
45
|
+
return expressionSql(definition.formula(aliases).expression, definition.uses, resolve);
|
|
46
|
+
}
|
|
47
|
+
export function derivedProjection(name, definition) {
|
|
48
|
+
return `${derivedExpressionSql(definition)} AS ${quoteSQLIdentifier(name)}`;
|
|
49
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derived-measure-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"derived-measure-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,wBAAwB,EAGzB,MAAM,aAAa,CAAC;AA8FrB,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GACjD,IAAI,CAgDN"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { assertMeasureDependenciesAcyclic } from './measure-dependencies.js';
|
|
1
2
|
import { isSafeSQLIdentifier } from '../sql-utils.js';
|
|
2
|
-
import {
|
|
3
|
+
import { splitDatasetMeasures } from './dataset-measures.js';
|
|
3
4
|
function fail(datasetName, measureName, detail) {
|
|
4
5
|
throw new Error(`Invalid dataset "${datasetName}": derived measure "${measureName}" ${detail}`);
|
|
5
6
|
}
|
|
@@ -71,27 +72,8 @@ function evaluateFormula(datasetName, measureName, definition) {
|
|
|
71
72
|
}
|
|
72
73
|
return formula;
|
|
73
74
|
}
|
|
74
|
-
function assertAcyclic(datasetName, derivedMeasures) {
|
|
75
|
-
const visiting = new Set();
|
|
76
|
-
const visited = new Set();
|
|
77
|
-
const visit = (name) => {
|
|
78
|
-
if (visiting.has(name))
|
|
79
|
-
fail(datasetName, name, 'contains a dependency cycle.');
|
|
80
|
-
if (visited.has(name))
|
|
81
|
-
return;
|
|
82
|
-
if (!Object.hasOwn(derivedMeasures, name))
|
|
83
|
-
return;
|
|
84
|
-
const definition = derivedMeasures[name];
|
|
85
|
-
visiting.add(name);
|
|
86
|
-
Object.values(definition.uses).forEach(dependency => visit(dependency));
|
|
87
|
-
visiting.delete(name);
|
|
88
|
-
visited.add(name);
|
|
89
|
-
};
|
|
90
|
-
Object.keys(derivedMeasures).forEach(visit);
|
|
91
|
-
}
|
|
92
75
|
export function validateDerivedMeasures(datasetName, measures) {
|
|
93
|
-
const
|
|
94
|
-
const derivedMeasures = Object.fromEntries(Object.entries(measures).filter(([, definition]) => definition && isDerivedMeasure(definition)));
|
|
76
|
+
const { derived: derivedMeasures } = splitDatasetMeasures(measures);
|
|
95
77
|
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
96
78
|
if (!isSafeSQLIdentifier(measureName)) {
|
|
97
79
|
fail(datasetName, measureName, 'name is not a safe identifier.');
|
|
@@ -103,11 +85,11 @@ export function validateDerivedMeasures(datasetName, measures) {
|
|
|
103
85
|
fail(datasetName, measureName, 'must declare an input map.');
|
|
104
86
|
}
|
|
105
87
|
}
|
|
106
|
-
|
|
88
|
+
assertMeasureDependenciesAcyclic(datasetName, measures);
|
|
107
89
|
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
108
90
|
const uses = Object.entries(definition.uses);
|
|
109
91
|
if (uses.length === 0)
|
|
110
|
-
fail(datasetName, measureName, 'must reference at least one base measure.');
|
|
92
|
+
fail(datasetName, measureName, 'must reference at least one base, window, or shift measure.');
|
|
111
93
|
if (typeof definition.formula !== 'function')
|
|
112
94
|
fail(datasetName, measureName, 'must define a formula function.');
|
|
113
95
|
for (const [alias, dependencyName] of uses) {
|
|
@@ -116,11 +98,9 @@ export function validateDerivedMeasures(datasetName, measures) {
|
|
|
116
98
|
if (typeof dependencyName !== 'string' || dependencyName.includes('.')) {
|
|
117
99
|
fail(datasetName, measureName, `references cross-dataset measure "${String(dependencyName)}".`);
|
|
118
100
|
}
|
|
119
|
-
if (Object.hasOwn(
|
|
120
|
-
fail(datasetName, measureName, `references derived measure "${dependencyName}"; v1 inputs must be base measures.`);
|
|
121
|
-
}
|
|
122
|
-
if (!Object.hasOwn(baseMeasures, dependencyName))
|
|
101
|
+
if (!Object.hasOwn(measures, dependencyName)) {
|
|
123
102
|
fail(datasetName, measureName, `references missing measure "${dependencyName}".`);
|
|
103
|
+
}
|
|
124
104
|
}
|
|
125
105
|
const references = new Set();
|
|
126
106
|
collectReferences(datasetName, measureName, evaluateFormula(datasetName, measureName, definition).expression, references, { nodes: 0 });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DatasetMeasureDefinition, MeasureDefinition } from '../types.js';
|
|
2
|
+
export declare function measureDependencies(definition: DatasetMeasureDefinition): string[];
|
|
3
|
+
/** Dependency order, with each shared input visited once. */
|
|
4
|
+
export declare function measureDependencyNames(measures: Readonly<Record<string, DatasetMeasureDefinition>>, selected: readonly string[]): string[];
|
|
5
|
+
export declare function assertMeasureDependenciesAcyclic(dataset: string, measures: Readonly<Record<string, DatasetMeasureDefinition>>): void;
|
|
6
|
+
/** Wrappers inherit aggregation metadata only when their input has one. */
|
|
7
|
+
export declare function inheritedBaseMeasure(measures: Readonly<Record<string, DatasetMeasureDefinition>>, name: string): MeasureDefinition | undefined;
|
|
8
|
+
//# sourceMappingURL=measure-dependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-dependencies.d.ts","sourceRoot":"","sources":["../../src/utils/measure-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG/E,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,wBAAwB,GAAG,MAAM,EAAE,CAIlF;AAED,6DAA6D;AAC7D,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAe1I;AAED,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,GAAG,IAAI,CAGpI;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAK9I"}
|