@hypequery/datasets 0.23.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/aggregations.d.ts +3 -0
- package/dist/aggregations.d.ts.map +1 -1
- package/dist/aggregations.js +6 -0
- package/dist/cache/definition-identity.d.ts +9 -6
- package/dist/cache/definition-identity.d.ts.map +1 -1
- package/dist/cache/definition-identity.js +14 -8
- package/dist/cache/protocol-cache-keys.d.ts.map +1 -1
- package/dist/cache/protocol-cache-keys.js +29 -6
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +12 -1
- package/dist/catalog.d.ts +7 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +5 -2
- package/dist/client-contract/query-schema.d.ts +19 -0
- package/dist/client-contract/query-schema.d.ts.map +1 -0
- package/dist/client-contract/query-schema.js +85 -0
- package/dist/constants.d.ts +5 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +7 -6
- package/dist/contract.d.ts +5 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +6 -3
- package/dist/dataset-compilation.d.ts +2 -0
- package/dist/dataset-compilation.d.ts.map +1 -1
- package/dist/dataset-sql-dialect.d.ts +119 -0
- package/dist/dataset-sql-dialect.d.ts.map +1 -1
- package/dist/dataset.d.ts +4 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +23 -31
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +2 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -6
- package/dist/internal.d.ts +5 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +5 -1
- package/dist/measure.d.ts +9 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +9 -1
- package/dist/metric-query-engine.d.ts +0 -1
- package/dist/metric-query-engine.d.ts.map +1 -1
- package/dist/metric-query-engine.js +11 -3
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +2 -3
- package/dist/protocol-deployment-adapter.d.ts.map +1 -1
- package/dist/protocol-deployment-adapter.js +7 -1
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +1 -0
- package/dist/protocol-schema-adapter.d.ts +7 -7
- package/dist/protocol-schema-adapter.d.ts.map +1 -1
- package/dist/protocol-schema-adapter.js +12 -245
- package/dist/query-builder-protocol.d.ts +34 -1
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-planner.d.ts +14 -5
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +53 -34
- package/dist/relationship-check.d.ts.map +1 -1
- package/dist/relationship-check.js +8 -8
- package/dist/schemas.d.ts +17 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +16 -0
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +49 -13
- package/dist/semantic-query-schema.d.ts +8 -1
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +18 -12
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +4 -3
- package/dist/types.d.ts +105 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/aggregate-state-functions.d.ts +24 -0
- package/dist/utils/aggregate-state-functions.d.ts.map +1 -0
- package/dist/utils/aggregate-state-functions.js +60 -0
- package/dist/utils/approximate-measures.d.ts +3 -1
- package/dist/utils/approximate-measures.d.ts.map +1 -1
- package/dist/utils/approximate-measures.js +8 -2
- package/dist/utils/base-measure-definition.d.ts +16 -0
- package/dist/utils/base-measure-definition.d.ts.map +1 -1
- package/dist/utils/base-measure-definition.js +31 -0
- package/dist/utils/build-dataset-query-builder.d.ts +1 -1
- package/dist/utils/build-dataset-query-builder.d.ts.map +1 -1
- package/dist/utils/build-dataset-query-builder.js +26 -21
- package/dist/utils/by-name.d.ts +8 -0
- package/dist/utils/by-name.d.ts.map +1 -0
- package/dist/utils/by-name.js +44 -0
- package/dist/utils/calendar-shift-sql.d.ts +5 -4
- package/dist/utils/calendar-shift-sql.d.ts.map +1 -1
- package/dist/utils/calendar-shift-sql.js +8 -8
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +2 -2
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -1
- package/dist/utils/clickhouse-dataset-sql-dialect.js +143 -1
- package/dist/utils/compile-dataset-query.d.ts +1 -1
- package/dist/utils/compile-dataset-query.d.ts.map +1 -1
- package/dist/utils/compile-dataset-query.js +22 -8
- package/dist/utils/composite-time-measure-sql.d.ts +3 -5
- package/dist/utils/composite-time-measure-sql.d.ts.map +1 -1
- package/dist/utils/composite-time-measure-sql.js +15 -16
- package/dist/utils/count-aggregations.d.ts +4 -0
- package/dist/utils/count-aggregations.d.ts.map +1 -0
- package/dist/utils/count-aggregations.js +6 -0
- package/dist/utils/dataset-definition-validation.d.ts +1 -1
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
- package/dist/utils/dataset-definition-validation.js +55 -26
- package/dist/utils/dataset-derived-query.d.ts +2 -4
- package/dist/utils/dataset-derived-query.d.ts.map +1 -1
- package/dist/utils/dataset-derived-query.js +11 -14
- package/dist/utils/dataset-having.d.ts +5 -7
- package/dist/utils/dataset-having.d.ts.map +1 -1
- package/dist/utils/dataset-having.js +9 -12
- package/dist/utils/dataset-normalization.d.ts.map +1 -1
- package/dist/utils/dataset-normalization.js +1 -0
- package/dist/utils/dataset-query-validation.d.ts +1 -1
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +19 -2
- package/dist/utils/dataset-source.d.ts +42 -0
- package/dist/utils/dataset-source.d.ts.map +1 -0
- package/dist/utils/dataset-source.js +59 -0
- package/dist/utils/dataset-sql-dialect.d.ts +13 -2
- package/dist/utils/dataset-sql-dialect.d.ts.map +1 -1
- package/dist/utils/dataset-sql-dialect.js +74 -1
- package/dist/utils/dataset-time-keys.d.ts +32 -0
- package/dist/utils/dataset-time-keys.d.ts.map +1 -0
- package/dist/utils/dataset-time-keys.js +55 -0
- package/dist/utils/dataset-validation.d.ts.map +1 -1
- package/dist/utils/dataset-validation.js +7 -0
- package/dist/utils/deployable-formulas.d.ts +33 -0
- package/dist/utils/deployable-formulas.d.ts.map +1 -0
- package/dist/utils/deployable-formulas.js +102 -0
- package/dist/utils/derived-cte-validation.d.ts +5 -0
- package/dist/utils/derived-cte-validation.d.ts.map +1 -1
- package/dist/utils/derived-cte-validation.js +77 -8
- package/dist/utils/derived-measure-sql.d.ts +10 -2
- package/dist/utils/derived-measure-sql.d.ts.map +1 -1
- package/dist/utils/derived-measure-sql.js +23 -43
- package/dist/utils/enforced-constraints.d.ts +14 -0
- package/dist/utils/enforced-constraints.d.ts.map +1 -0
- package/dist/utils/enforced-constraints.js +57 -0
- package/dist/utils/filter-field-type.d.ts +14 -0
- package/dist/utils/filter-field-type.d.ts.map +1 -0
- package/dist/utils/filter-field-type.js +21 -0
- package/dist/utils/filtered-aggregation-sql.d.ts +2 -1
- package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
- package/dist/utils/filtered-aggregation-sql.js +18 -35
- package/dist/utils/groupable.d.ts +7 -0
- package/dist/utils/groupable.d.ts.map +1 -0
- package/dist/utils/groupable.js +8 -0
- package/dist/utils/metric-derived-query.d.ts +2 -4
- package/dist/utils/metric-derived-query.d.ts.map +1 -1
- package/dist/utils/metric-derived-query.js +58 -51
- package/dist/utils/metric-query-builder.d.ts +1 -1
- package/dist/utils/metric-query-builder.d.ts.map +1 -1
- package/dist/utils/metric-query-builder.js +22 -20
- package/dist/utils/metric-query-validation.d.ts.map +1 -1
- package/dist/utils/metric-query-validation.js +14 -1
- package/dist/utils/portable-aggregations.d.ts +1 -0
- package/dist/utils/portable-aggregations.d.ts.map +1 -1
- package/dist/utils/portable-aggregations.js +5 -0
- package/dist/utils/protocol-schema-conversion.d.ts +12 -0
- package/dist/utils/protocol-schema-conversion.d.ts.map +1 -0
- package/dist/utils/protocol-schema-conversion.js +61 -0
- package/dist/utils/query-field-references.d.ts +10 -0
- package/dist/utils/query-field-references.d.ts.map +1 -0
- package/dist/utils/query-field-references.js +38 -0
- package/dist/utils/query-timezone.d.ts +41 -3
- package/dist/utils/query-timezone.d.ts.map +1 -1
- package/dist/utils/query-timezone.js +76 -9
- package/dist/utils/raw-sql-dialect-check.d.ts +12 -0
- package/dist/utils/raw-sql-dialect-check.d.ts.map +1 -0
- package/dist/utils/raw-sql-dialect-check.js +37 -0
- package/dist/utils/relationship-builder-plan.d.ts +37 -18
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +43 -28
- package/dist/utils/relationship-measure-sql.d.ts +14 -4
- package/dist/utils/relationship-measure-sql.d.ts.map +1 -1
- package/dist/utils/relationship-measure-sql.js +36 -10
- package/dist/utils/relationship-measures.d.ts.map +1 -1
- package/dist/utils/relationship-measures.js +3 -0
- package/dist/utils/relationship-validation.d.ts.map +1 -1
- package/dist/utils/relationship-validation.js +2 -0
- package/dist/utils/row-scope.d.ts +23 -0
- package/dist/utils/row-scope.d.ts.map +1 -0
- package/dist/utils/row-scope.js +26 -0
- package/dist/utils/scoped-dataset-source.d.ts +23 -0
- package/dist/utils/scoped-dataset-source.d.ts.map +1 -0
- package/dist/utils/scoped-dataset-source.js +34 -0
- package/dist/utils/semantic-tool.d.ts +2 -2
- package/dist/utils/semantic-tool.d.ts.map +1 -1
- package/dist/utils/semantic-tool.js +1 -1
- package/dist/utils/shift-measure-sql.d.ts +5 -4
- package/dist/utils/shift-measure-sql.d.ts.map +1 -1
- package/dist/utils/shift-measure-sql.js +25 -26
- package/dist/utils/shift-wall-time-sql.d.ts +2 -1
- package/dist/utils/shift-wall-time-sql.d.ts.map +1 -1
- package/dist/utils/shift-wall-time-sql.js +7 -7
- package/dist/utils/snapshot-execution-context.d.ts.map +1 -1
- package/dist/utils/snapshot-execution-context.js +3 -0
- package/dist/utils/statement-parameters.d.ts +58 -0
- package/dist/utils/statement-parameters.d.ts.map +1 -0
- package/dist/utils/statement-parameters.js +101 -0
- package/dist/utils/time-axis-calendar-guard.d.ts +2 -1
- package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -1
- package/dist/utils/time-axis-calendar-guard.js +5 -7
- package/dist/utils/time-measure-axis-sql.d.ts +3 -2
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-axis-sql.js +20 -22
- package/dist/utils/time-measure-context-sql.d.ts +3 -2
- package/dist/utils/time-measure-context-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-context-sql.js +11 -11
- package/dist/utils/time-measure-dataset-sql.d.ts +7 -5
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-dataset-sql.js +17 -12
- package/dist/utils/time-measure-leaf-sql.d.ts +3 -2
- package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-leaf-sql.js +19 -18
- package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-result-sql.js +10 -11
- package/dist/utils/time-measure-source-sql.d.ts +5 -2
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-source-sql.js +33 -23
- package/dist/utils/time-measure-values-sql.d.ts +4 -3
- package/dist/utils/time-measure-values-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-values-sql.js +26 -29
- package/dist/utils/timestamp-instants.d.ts +17 -0
- package/dist/utils/timestamp-instants.d.ts.map +1 -0
- package/dist/utils/timestamp-instants.js +34 -0
- package/dist/utils/timestamp-text.d.ts +12 -0
- package/dist/utils/timestamp-text.d.ts.map +1 -0
- package/dist/utils/timestamp-text.js +18 -0
- package/dist/utils/zod-json-schema.d.ts +8 -0
- package/dist/utils/zod-json-schema.d.ts.map +1 -0
- package/dist/utils/zod-json-schema.js +18 -0
- package/dist/utils/zod-schema-version.d.ts +19 -0
- package/dist/utils/zod-schema-version.d.ts.map +1 -0
- package/dist/utils/zod-schema-version.js +6 -0
- package/dist/utils/zod-v3-protocol-schema.d.ts +10 -0
- package/dist/utils/zod-v3-protocol-schema.d.ts.map +1 -0
- package/dist/utils/zod-v3-protocol-schema.js +164 -0
- package/dist/utils/zod-v4-protocol-schema.d.ts +12 -0
- package/dist/utils/zod-v4-protocol-schema.d.ts.map +1 -0
- package/dist/utils/zod-v4-protocol-schema.js +171 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +42 -8
- package/package.json +21 -12
- package/dist/api.type-test.d.ts +0 -2
- package/dist/api.type-test.d.ts.map +0 -1
- package/dist/api.type-test.js +0 -251
- package/dist/utils/time-arithmetic-sql.d.ts +0 -4
- package/dist/utils/time-arithmetic-sql.d.ts.map +0 -1
- package/dist/utils/time-arithmetic-sql.js +0 -9
- package/dist/utils/window-aggregation-sql.d.ts +0 -5
- package/dist/utils/window-aggregation-sql.d.ts.map +0 -1
- package/dist/utils/window-aggregation-sql.js +0 -17
|
@@ -1,69 +1,49 @@
|
|
|
1
1
|
import { resolveFilterField } from '../query-planner.js';
|
|
2
|
-
function
|
|
3
|
-
if (value === null) {
|
|
4
|
-
return 'NULL';
|
|
5
|
-
}
|
|
6
|
-
if (typeof value === 'string') {
|
|
7
|
-
// ClickHouse also treats backslash as a string escape. Doubling quotes
|
|
8
|
-
// without escaping backslashes lets a value ending in \\ before a quote
|
|
9
|
-
// break out of the literal and change the predicate.
|
|
10
|
-
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
11
|
-
}
|
|
12
|
-
if (typeof value === 'number') {
|
|
13
|
-
if (!Number.isFinite(value)) {
|
|
14
|
-
throw new Error(`Invalid non-finite numeric literal in measure filter: ${value}`);
|
|
15
|
-
}
|
|
16
|
-
return String(value);
|
|
17
|
-
}
|
|
18
|
-
if (typeof value === 'boolean') {
|
|
19
|
-
return value ? '1' : '0';
|
|
20
|
-
}
|
|
21
|
-
throw new Error(`Unsupported literal type in measure filter: ${typeof value}`);
|
|
22
|
-
}
|
|
23
|
-
function renderMeasureFilterCondition(ds, filter, joinCtx) {
|
|
2
|
+
function renderMeasureFilterCondition(ds, filter, joinCtx, dialect) {
|
|
24
3
|
const field = resolveFilterField(ds, filter.field, joinCtx);
|
|
4
|
+
const literal = (value) => dialect.measureFilterLiteral(value);
|
|
25
5
|
switch (filter.operator) {
|
|
26
6
|
case 'eq':
|
|
27
|
-
return `${field} = ${
|
|
7
|
+
return `${field} = ${literal(filter.value)}`;
|
|
28
8
|
case 'neq':
|
|
29
|
-
return `${field} != ${
|
|
9
|
+
return `${field} != ${literal(filter.value)}`;
|
|
30
10
|
case 'gt':
|
|
31
|
-
return `${field} > ${
|
|
11
|
+
return `${field} > ${literal(filter.value)}`;
|
|
32
12
|
case 'gte':
|
|
33
|
-
return `${field} >= ${
|
|
13
|
+
return `${field} >= ${literal(filter.value)}`;
|
|
34
14
|
case 'lt':
|
|
35
|
-
return `${field} < ${
|
|
15
|
+
return `${field} < ${literal(filter.value)}`;
|
|
36
16
|
case 'lte':
|
|
37
|
-
return `${field} <= ${
|
|
17
|
+
return `${field} <= ${literal(filter.value)}`;
|
|
38
18
|
case 'like':
|
|
39
|
-
return `${field} LIKE ${
|
|
19
|
+
return `${field} LIKE ${literal(filter.value)}`;
|
|
40
20
|
case 'in':
|
|
41
21
|
case 'notIn': {
|
|
42
22
|
if (!Array.isArray(filter.value) || filter.value.length === 0) {
|
|
43
23
|
throw new Error(`"${filter.operator}" measure filters require a non-empty array.`);
|
|
44
24
|
}
|
|
45
|
-
const values = filter.value.map(
|
|
25
|
+
const values = filter.value.map(literal).join(', ');
|
|
46
26
|
return `${field} ${filter.operator === 'in' ? 'IN' : 'NOT IN'} (${values})`;
|
|
47
27
|
}
|
|
48
28
|
case 'between': {
|
|
49
29
|
if (!Array.isArray(filter.value) || filter.value.length !== 2) {
|
|
50
30
|
throw new Error('"between" measure filters require a two-item array.');
|
|
51
31
|
}
|
|
52
|
-
return `${field} BETWEEN ${
|
|
32
|
+
return `${field} BETWEEN ${literal(filter.value[0])} AND ${literal(filter.value[1])}`;
|
|
53
33
|
}
|
|
54
34
|
}
|
|
55
35
|
}
|
|
56
|
-
export function applyFilteredAggregationExpression(ds, spec, fieldOrExpr, joinCtx) {
|
|
36
|
+
export function applyFilteredAggregationExpression(ds, spec, fieldOrExpr, joinCtx, dialect) {
|
|
57
37
|
if (!spec.filters?.length) {
|
|
58
38
|
return fieldOrExpr;
|
|
59
39
|
}
|
|
60
40
|
const combinedCondition = spec.filters
|
|
61
|
-
.map(filter => renderMeasureFilterCondition(ds, filter, joinCtx))
|
|
41
|
+
.map(filter => renderMeasureFilterCondition(ds, filter, joinCtx, dialect))
|
|
62
42
|
.map(condition => `(${condition})`)
|
|
63
43
|
.join(' AND ');
|
|
64
44
|
switch (spec.aggregation) {
|
|
65
45
|
case 'sum':
|
|
66
|
-
return
|
|
46
|
+
return dialect.filteredAggregateInput(combinedCondition, fieldOrExpr, '0');
|
|
67
47
|
// Aggregate functions skip NULLs, so the NULL fallback excludes
|
|
68
48
|
// non-matching rows from percentile/stddev/variance too.
|
|
69
49
|
case 'count':
|
|
@@ -75,7 +55,10 @@ export function applyFilteredAggregationExpression(ds, spec, fieldOrExpr, joinCt
|
|
|
75
55
|
case 'stddev':
|
|
76
56
|
case 'variance':
|
|
77
57
|
case 'approxCountDistinct':
|
|
78
|
-
return
|
|
58
|
+
return dialect.filteredAggregateInput(combinedCondition, fieldOrExpr, 'NULL');
|
|
59
|
+
case 'merge':
|
|
60
|
+
// A filtered merge needs -MergeIf; wrapping a state column in if() is a type error.
|
|
61
|
+
throw new Error('Measure filters are not supported on merge aggregations.');
|
|
79
62
|
case 'argMax':
|
|
80
63
|
case 'argMin':
|
|
81
64
|
// NULL handling under argMax/argMin varies across ClickHouse versions.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `groupable: false` declares a dimension that backs filters or measures but
|
|
3
|
+
* must not become a grouping key. The query schemas leave it out; the client
|
|
4
|
+
* refuses it too, as the Python planner does.
|
|
5
|
+
*/
|
|
6
|
+
export declare function notGroupableError(name: string): string;
|
|
7
|
+
//# sourceMappingURL=groupable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groupable.d.ts","sourceRoot":"","sources":["../../src/utils/groupable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `groupable: false` declares a dimension that backs filters or measures but
|
|
3
|
+
* must not become a grouping key. The query schemas leave it out; the client
|
|
4
|
+
* refuses it too, as the Python planner does.
|
|
5
|
+
*/
|
|
6
|
+
export function notGroupableError(name) {
|
|
7
|
+
return `Dimension "${name}" is not groupable.`;
|
|
8
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { MetricRef, MetricQuery, ExecutionContext, TimeGrain } from '../types.js';
|
|
2
2
|
import type { QueryBuilderFactoryLike } from '../query-builder-protocol.js';
|
|
3
|
+
import { type PlannedStatement } from './statement-parameters.js';
|
|
3
4
|
import type { DerivedMetricSpec } from '../types.js';
|
|
4
|
-
export declare function buildDerivedMetricSql(ref: MetricRef, spec: DerivedMetricSpec, query: MetricQuery, grain: TimeGrain | undefined, builderFactory: QueryBuilderFactoryLike, context?: ExecutionContext):
|
|
5
|
-
sql: string;
|
|
6
|
-
params: unknown[];
|
|
7
|
-
};
|
|
5
|
+
export declare function buildDerivedMetricSql(ref: MetricRef, spec: DerivedMetricSpec, query: MetricQuery, grain: TimeGrain | undefined, builderFactory: QueryBuilderFactoryLike, context?: ExecutionContext): PlannedStatement;
|
|
8
6
|
//# sourceMappingURL=metric-derived-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metric-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/metric-derived-query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metric-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/metric-derived-query.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAoB,MAAM,8BAA8B,CAAC;AAQ9F,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKvF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAMrD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,cAAc,EAAE,uBAAuB,EACvC,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAmHlB"}
|
|
@@ -1,62 +1,73 @@
|
|
|
1
|
+
import { scopedDatasetSource } from './scoped-dataset-source.js';
|
|
1
2
|
import { semanticQuerySyntaxErrors, semanticOrderDirection } from './semantic-query-syntax.js';
|
|
2
3
|
/** Metric SQL compilation; factory selection and execution belong to the client. */
|
|
3
4
|
import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { assertRawSqlForDialect } from './raw-sql-dialect-check.js';
|
|
6
|
+
import { applyAggregationSpec, buildDimensionSelectionPlan, resolveDimensionExpression, resolveFilterField, } from '../query-planner.js';
|
|
7
|
+
import { queryTimeFilter } from './query-timezone.js';
|
|
8
|
+
import { StatementParameters } from './statement-parameters.js';
|
|
9
|
+
import { formulaExpressionSql } from './derived-measure-sql.js';
|
|
6
10
|
import { isTenantScopedFilter } from './metric-handle.js';
|
|
7
|
-
import {
|
|
8
|
-
import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn, } from './relationship-builder-plan.js';
|
|
11
|
+
import { buildRelationshipBuilderContext } from './relationship-builder-plan.js';
|
|
9
12
|
import { segmentFilters } from './segments.js';
|
|
10
13
|
import { validateSQLIdentifier } from '../sql-utils.js';
|
|
11
14
|
import { isQualifiedField } from './relationship-fields.js';
|
|
12
15
|
import { validateDerivedCteGrouping } from './derived-cte-validation.js';
|
|
16
|
+
import { datasetOnTimeKey } from './dataset-time-keys.js';
|
|
13
17
|
export function buildDerivedMetricSql(ref, spec, query, grain, builderFactory, context) {
|
|
14
18
|
const syntaxErrors = semanticQuerySyntaxErrors(query);
|
|
15
19
|
if (syntaxErrors.length)
|
|
16
20
|
throw new Error(syntaxErrors.join('; '));
|
|
17
21
|
const dialect = resolveDatasetSqlDialect(builderFactory);
|
|
18
|
-
|
|
19
|
-
const
|
|
22
|
+
assertRawSqlForDialect(ref.dataset, dialect);
|
|
23
|
+
const ds = datasetOnTimeKey(ref.dataset, query.timeKey);
|
|
24
|
+
const joinCtx = buildRelationshipBuilderContext(ds, query, context, dialect);
|
|
20
25
|
// Build the CTE inner query using the builder
|
|
21
|
-
let cteBuilder = builderFactory
|
|
22
|
-
cteBuilder = applyRelationshipJoins(cteBuilder, joinCtx);
|
|
26
|
+
let cteBuilder = scopedDatasetSource({ builderFactory, ds, context, joinCtx, dialect, timezone: query.timezone });
|
|
23
27
|
const { selectParts, groupByParts } = buildDimensionSelectionPlan(ds, query.dimensions ?? [], grain, joinCtx, query.timezone, dialect);
|
|
24
28
|
if (selectParts.length > 0) {
|
|
25
29
|
cteBuilder = cteBuilder.select(selectParts);
|
|
26
30
|
}
|
|
31
|
+
// Group before aggregating: the builder only infers GROUP BY when none is
|
|
32
|
+
// set, and cannot infer it from quoted aliases.
|
|
33
|
+
if (groupByParts.length > 0) {
|
|
34
|
+
cteBuilder = cteBuilder.groupBy(groupByParts);
|
|
35
|
+
}
|
|
27
36
|
// Base aggregations
|
|
28
37
|
const refAliases = {};
|
|
38
|
+
const inputNames = {};
|
|
29
39
|
const aggregateAliases = [];
|
|
30
40
|
for (const [alias, baseMetric] of Object.entries(spec.uses)) {
|
|
31
41
|
const baseSpec = baseMetric.spec;
|
|
32
42
|
if (baseSpec.__type !== 'aggregation_spec') {
|
|
33
43
|
throw new Error(`Derived metric "${ref.name}" references non-base metric "${alias}".`);
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
cteBuilder = cteBuilder.groupBy(groupByParts);
|
|
45
|
+
const quoted = dialect.quoteIdentifier(alias);
|
|
46
|
+
cteBuilder = applyAggregationSpec(cteBuilder, ds, baseSpec, alias, joinCtx, dialect);
|
|
47
|
+
refAliases[alias] = quoted;
|
|
48
|
+
inputNames[alias] = alias;
|
|
49
|
+
aggregateAliases.push(quoted);
|
|
41
50
|
}
|
|
42
51
|
// Filters on CTE
|
|
43
|
-
const tenantColumn = resolveTenantFilterColumn(ds, context);
|
|
44
|
-
const tenantPredicate = getRuntimeTenantPredicate(context);
|
|
45
|
-
if (tenantPredicate && tenantColumn) {
|
|
46
|
-
cteBuilder = cteBuilder.where(qualifyBaseColumn(joinCtx, tenantColumn), tenantPredicate.operator, tenantPredicate.value);
|
|
47
|
-
}
|
|
48
52
|
for (const filter of query.filters ?? []) {
|
|
49
53
|
if (isTenantScopedFilter(ds, filter, context)) {
|
|
50
54
|
throw new Error(`Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`);
|
|
51
55
|
}
|
|
52
56
|
const resolvedField = resolveFilterField(ds, filter.field, joinCtx);
|
|
53
|
-
|
|
57
|
+
for (const predicate of queryTimeFilter(dialect, ds, filter, resolvedField, query.timezone)) {
|
|
58
|
+
cteBuilder = cteBuilder.where(predicate.expression, predicate.operator, predicate.value);
|
|
59
|
+
}
|
|
54
60
|
}
|
|
55
61
|
for (const filter of segmentFilters(ds, query.segments)) {
|
|
56
|
-
|
|
62
|
+
const predicates = queryTimeFilter(dialect, ds, filter, resolveDimensionExpression(ds, filter.field, joinCtx), query.timezone, { field: 'dimension' });
|
|
63
|
+
for (const predicate of predicates) {
|
|
64
|
+
cteBuilder = cteBuilder.where(predicate.expression, predicate.operator, predicate.value);
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
// Check the builder's own SQL: embedded text may be an opaque block marker.
|
|
68
|
+
// Replaced by a plan-level check in HQ-197.
|
|
69
|
+
const cteStatement = cteBuilder.toSQLWithParams();
|
|
70
|
+
const groupingErrors = validateDerivedCteGrouping(cteStatement.sql, aggregateAliases, groupByParts);
|
|
60
71
|
if (groupingErrors.length > 0) {
|
|
61
72
|
throw new Error(groupingErrors.join('; '));
|
|
62
73
|
}
|
|
@@ -64,43 +75,39 @@ export function buildDerivedMetricSql(ref, spec, query, grain, builderFactory, c
|
|
|
64
75
|
// because table('base') would fail schema typing
|
|
65
76
|
const outerSelectParts = [];
|
|
66
77
|
if (grain)
|
|
67
|
-
outerSelectParts.push('period');
|
|
78
|
+
outerSelectParts.push(dialect.quoteIdentifier('period'));
|
|
68
79
|
for (const dim of query.dimensions ?? []) {
|
|
69
80
|
// Joined dimensions surface from the CTE under their quoted qualified alias.
|
|
70
|
-
if (joinCtx && isQualifiedField(dim))
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
validateSQLIdentifier(dim, 'dimension name');
|
|
75
|
-
outerSelectParts.push(dim);
|
|
81
|
+
if (!(joinCtx && isQualifiedField(dim)))
|
|
82
|
+
validateSQLIdentifier(dim, 'dimension name');
|
|
83
|
+
outerSelectParts.push(dialect.quoteIdentifier(dim));
|
|
76
84
|
}
|
|
77
|
-
|
|
85
|
+
// Render through the dialect, like derived measures. FormulaExpr.toSQL()
|
|
86
|
+
// is kept for definition identity, not for planning.
|
|
78
87
|
validateSQLIdentifier(ref.name, 'metric name');
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
const formulaSql = formulaExpressionSql(dialect, spec.formula(inputNames).expression, name => {
|
|
89
|
+
const quoted = refAliases[name];
|
|
90
|
+
if (quoted === undefined || !Object.hasOwn(refAliases, name)) {
|
|
91
|
+
throw new Error(`Derived metric "${ref.name}" formula references undeclared input "${name}".`);
|
|
92
|
+
}
|
|
93
|
+
return quoted;
|
|
94
|
+
});
|
|
95
|
+
outerSelectParts.push(`${formulaSql} AS ${dialect.quoteIdentifier(ref.name)}`);
|
|
96
|
+
const parameters = new StatementParameters(dialect);
|
|
97
|
+
let text = `WITH base AS (${parameters.embedBuilder(cteBuilder, cteStatement)}) SELECT ${outerSelectParts.join(', ')} FROM base`;
|
|
81
98
|
// ORDER BY
|
|
82
99
|
if (query.orderBy && query.orderBy.length > 0) {
|
|
83
100
|
const orderParts = query.orderBy.map(o => {
|
|
84
|
-
if (joinCtx && isQualifiedField(o.field))
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
validateSQLIdentifier(o.field, 'order by field');
|
|
88
|
-
return `${o.field} ${semanticOrderDirection(o.direction)}`;
|
|
101
|
+
if (!(joinCtx && isQualifiedField(o.field)))
|
|
102
|
+
validateSQLIdentifier(o.field, 'order by field');
|
|
103
|
+
return dialect.orderTerm(dialect.quoteIdentifier(o.field), semanticOrderDirection(o.direction));
|
|
89
104
|
});
|
|
90
|
-
|
|
105
|
+
text += ` ORDER BY ${orderParts.join(', ')}`;
|
|
91
106
|
}
|
|
92
107
|
else if (grain) {
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
if (query.limit != null) {
|
|
96
|
-
// Ensure limit is a safe integer
|
|
97
|
-
const limit = Math.floor(Math.abs(query.limit));
|
|
98
|
-
sql += ` LIMIT ${limit}`;
|
|
99
|
-
}
|
|
100
|
-
if (query.offset != null) {
|
|
101
|
-
// Ensure offset is a safe integer
|
|
102
|
-
const offset = Math.floor(Math.abs(query.offset));
|
|
103
|
-
sql += ` OFFSET ${offset}`;
|
|
108
|
+
text += ` ORDER BY ${dialect.orderTerm(dialect.quoteIdentifier('period'))}`;
|
|
104
109
|
}
|
|
105
|
-
|
|
110
|
+
// Ensure limit and offset are safe integers.
|
|
111
|
+
text += dialect.paginate(query.limit != null ? Math.floor(Math.abs(query.limit)) : undefined, query.offset != null ? Math.floor(Math.abs(query.offset)) : undefined);
|
|
112
|
+
return { text, parameters };
|
|
106
113
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MetricRef, MetricQuery, ExecutionContext, TimeGrain } from '../types.js';
|
|
2
2
|
import type { QueryBuilderFactoryLike, QueryBuilderLike } from '../query-builder-protocol.js';
|
|
3
3
|
import type { AggregationSpec, AnyDatasetInstance } from '../types.js';
|
|
4
|
-
export declare function buildMetricQueryBuilder(ref: MetricRef, spec: AggregationSpec,
|
|
4
|
+
export declare function buildMetricQueryBuilder(ref: MetricRef, spec: AggregationSpec, dataset: AnyDatasetInstance, query: MetricQuery, grain: TimeGrain | undefined, builderFactory: QueryBuilderFactoryLike, context?: ExecutionContext): QueryBuilderLike;
|
|
5
5
|
//# sourceMappingURL=metric-query-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metric-query-builder.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-builder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metric-query-builder.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-builder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAa9F,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEvE,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,cAAc,EAAE,uBAAuB,EACvC,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAsDlB"}
|
|
@@ -1,50 +1,52 @@
|
|
|
1
|
+
import { scopedDatasetSource } from './scoped-dataset-source.js';
|
|
1
2
|
import { semanticQuerySyntaxErrors } from './semantic-query-syntax.js';
|
|
2
3
|
/** Metric SQL compilation; factory selection and execution belong to the client. */
|
|
3
4
|
import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { assertRawSqlForDialect } from './raw-sql-dialect-check.js';
|
|
6
|
+
import { applyAggregationSpec, appendOrderLimitOffset, buildDimensionSelectionPlan, resolveDimensionExpression, resolveFilterField, } from '../query-planner.js';
|
|
7
|
+
import { queryTimeFilter } from './query-timezone.js';
|
|
6
8
|
import { isTenantScopedFilter } from './metric-handle.js';
|
|
7
|
-
import {
|
|
8
|
-
import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn, } from './relationship-builder-plan.js';
|
|
9
|
+
import { buildRelationshipBuilderContext } from './relationship-builder-plan.js';
|
|
9
10
|
import { segmentFilters } from './segments.js';
|
|
10
|
-
|
|
11
|
+
import { datasetOnTimeKey } from './dataset-time-keys.js';
|
|
12
|
+
export function buildMetricQueryBuilder(ref, spec, dataset, query, grain, builderFactory, context) {
|
|
11
13
|
const syntaxErrors = semanticQuerySyntaxErrors(query);
|
|
12
14
|
if (syntaxErrors.length)
|
|
13
15
|
throw new Error(syntaxErrors.join('; '));
|
|
16
|
+
const ds = datasetOnTimeKey(dataset, query.timeKey);
|
|
14
17
|
const dialect = resolveDatasetSqlDialect(builderFactory);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
qb =
|
|
18
|
+
assertRawSqlForDialect(ds, dialect);
|
|
19
|
+
const joinCtx = buildRelationshipBuilderContext(ds, query, context, dialect);
|
|
20
|
+
let qb = scopedDatasetSource({ builderFactory, ds, context, joinCtx, dialect, timezone: query.timezone });
|
|
18
21
|
const { selectParts, groupByParts } = buildDimensionSelectionPlan(ds, query.dimensions ?? [], grain, joinCtx, query.timezone, dialect);
|
|
19
22
|
if (selectParts.length > 0) {
|
|
20
23
|
qb = qb.select(selectParts);
|
|
21
24
|
}
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
// Explicit groupBy (ensures period + dims are grouped even if aggregation auto-groupBy misses them)
|
|
25
|
+
// Group before aggregating: the builder only infers GROUP BY when none is
|
|
26
|
+
// set, and cannot infer it from quoted aliases.
|
|
25
27
|
if (groupByParts.length > 0) {
|
|
26
28
|
qb = qb.groupBy(groupByParts);
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
const tenantColumn = resolveTenantFilterColumn(ds, context);
|
|
30
|
-
const tenantPredicate = getRuntimeTenantPredicate(context);
|
|
31
|
-
if (tenantPredicate && tenantColumn) {
|
|
32
|
-
qb = qb.where(qualifyBaseColumn(joinCtx, tenantColumn), tenantPredicate.operator, tenantPredicate.value);
|
|
33
|
-
}
|
|
30
|
+
qb = applyAggregationSpec(qb, ds, spec, ref.name, joinCtx, dialect);
|
|
34
31
|
// User filters
|
|
35
32
|
for (const filter of query.filters ?? []) {
|
|
36
33
|
if (isTenantScopedFilter(ds, filter, context)) {
|
|
37
34
|
throw new Error(`Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`);
|
|
38
35
|
}
|
|
39
36
|
const resolvedField = resolveFilterField(ds, filter.field, joinCtx);
|
|
40
|
-
|
|
37
|
+
for (const predicate of queryTimeFilter(dialect, ds, filter, resolvedField, query.timezone)) {
|
|
38
|
+
qb = qb.where(predicate.expression, predicate.operator, predicate.value);
|
|
39
|
+
}
|
|
41
40
|
}
|
|
42
41
|
// Segments: author-defined, so they resolve dimensions directly rather
|
|
43
42
|
// than through the caller-facing filter allow-list.
|
|
44
43
|
for (const filter of segmentFilters(ds, query.segments)) {
|
|
45
|
-
|
|
44
|
+
const predicates = queryTimeFilter(dialect, ds, filter, resolveDimensionExpression(ds, filter.field, joinCtx), query.timezone, { field: 'dimension' });
|
|
45
|
+
for (const predicate of predicates) {
|
|
46
|
+
qb = qb.where(predicate.expression, predicate.operator, predicate.value);
|
|
47
|
+
}
|
|
46
48
|
}
|
|
47
49
|
// Order, limit, offset
|
|
48
|
-
qb = appendOrderLimitOffset(qb, query.orderBy, grain, query.limit, query.offset, joinCtx, dialect);
|
|
50
|
+
qb = appendOrderLimitOffset(qb, query.orderBy, grain, query.limit, query.offset, joinCtx, dialect, { ds, dimensions: query.dimensions ?? [] });
|
|
49
51
|
return qb;
|
|
50
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI9E,OAAO,EAAsD,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQ3G,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAuIlB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { notGroupableError } from './groupable.js';
|
|
1
2
|
import { semanticQuerySyntaxErrors } from './semantic-query-syntax.js';
|
|
2
3
|
import { validateFilterValue } from '../validation.js';
|
|
3
4
|
import { queryTimezoneErrors } from './query-timezone.js';
|
|
@@ -8,10 +9,15 @@ import { validateTenantRuntime } from './tenant-runtime.js';
|
|
|
8
9
|
import { isQualifiedField, resolveQualifiedField } from './relationship-fields.js';
|
|
9
10
|
import { validateQualifiedFilter, validateRelationshipTenantRuntime } from './relationship-validation.js';
|
|
10
11
|
import { segmentSelectionErrors } from './segments.js';
|
|
12
|
+
import { datasetOnTimeKey, selectedTimeKeyError } from './dataset-time-keys.js';
|
|
13
|
+
import { enforcedConstraintErrors } from './enforced-constraints.js';
|
|
11
14
|
export function validateMetricQueryInput(metric, query, context) {
|
|
12
15
|
const errors = [...semanticQuerySyntaxErrors(query), ...protocolMetricCapabilityErrors(metric, query), ...queryTimezoneErrors(query.timezone)];
|
|
13
16
|
const ref = getMetricRef(metric);
|
|
14
|
-
const
|
|
17
|
+
const timeKeyError = selectedTimeKeyError(ref.dataset, query.timeKey);
|
|
18
|
+
if (timeKeyError)
|
|
19
|
+
errors.push(timeKeyError);
|
|
20
|
+
const ds = timeKeyError ? ref.dataset : datasetOnTimeKey(ref.dataset, query.timeKey);
|
|
15
21
|
const dimensionNames = Object.keys(ds.dimensions);
|
|
16
22
|
const filterNames = Object.keys(ds.filters);
|
|
17
23
|
const grain = getMetricGrain(metric, query);
|
|
@@ -28,6 +34,7 @@ export function validateMetricQueryInput(metric, query, context) {
|
|
|
28
34
|
if (relationshipTenantError) {
|
|
29
35
|
errors.push(relationshipTenantError);
|
|
30
36
|
}
|
|
37
|
+
errors.push(...enforcedConstraintErrors(ds, context, query.filters?.length ?? 0));
|
|
31
38
|
if (metric.__type === 'grained_metric_ref' && query.by && query.by !== metric.grain) {
|
|
32
39
|
errors.push(`Metric "${ref.name}" is already grained by "${metric.grain}" and cannot be queried with by="${query.by}".`);
|
|
33
40
|
}
|
|
@@ -38,11 +45,17 @@ export function validateMetricQueryInput(metric, query, context) {
|
|
|
38
45
|
if (resolution?.error) {
|
|
39
46
|
errors.push(resolution.error);
|
|
40
47
|
}
|
|
48
|
+
else if (resolution?.resolved?.targetDimension.groupable === false) {
|
|
49
|
+
errors.push(notGroupableError(dim));
|
|
50
|
+
}
|
|
41
51
|
continue;
|
|
42
52
|
}
|
|
43
53
|
if (!dimensionNames.includes(dim)) {
|
|
44
54
|
errors.push(`Unknown dimension "${dim}". Available: ${dimensionNames.join(', ')}`);
|
|
45
55
|
}
|
|
56
|
+
else if (ds.dimensions[dim]?.groupable === false) {
|
|
57
|
+
errors.push(notGroupableError(dim));
|
|
58
|
+
}
|
|
46
59
|
}
|
|
47
60
|
// Validate filters
|
|
48
61
|
for (const filter of query.filters ?? []) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* `approxCountDistinct` runs locally, but it is an expression extension 2 and
|
|
5
5
|
* deployment contract 3 feature (RFC 0015). Publishing emits contract 2 until
|
|
6
6
|
* it can emit contract 3, so it refuses the measure with an error naming it.
|
|
7
|
+
* `merge` reads ClickHouse aggregate-state columns and has no contract form.
|
|
7
8
|
*/
|
|
8
9
|
import type { ProtocolAggregation } from '@hypequery/protocol';
|
|
9
10
|
import type { AggregationType } from '../types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portable-aggregations.d.ts","sourceRoot":"","sources":["../../src/utils/portable-aggregations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"portable-aggregations.d.ts","sourceRoot":"","sources":["../../src/utils/portable-aggregations.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;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,CAcrB"}
|
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
* `approxCountDistinct` runs locally, but it is an expression extension 2 and
|
|
5
5
|
* deployment contract 3 feature (RFC 0015). Publishing emits contract 2 until
|
|
6
6
|
* it can emit contract 3, so it refuses the measure with an error naming it.
|
|
7
|
+
* `merge` reads ClickHouse aggregate-state columns and has no contract form.
|
|
7
8
|
*/
|
|
8
9
|
/** Returns `aggregation` when publishing can carry it; otherwise throws naming `owner`. */
|
|
9
10
|
export function requirePortableAggregation(aggregation, owner) {
|
|
11
|
+
if (aggregation === 'merge') {
|
|
12
|
+
throw new Error(`${owner} uses merge, which cannot be published: the deployment contract has no aggregate-state ` +
|
|
13
|
+
'measures. Use it for local queries only.');
|
|
14
|
+
}
|
|
10
15
|
if (aggregation === 'approxCountDistinct') {
|
|
11
16
|
throw new Error(`${owner} uses approxCountDistinct, which cannot be published yet: approximate aggregations ` +
|
|
12
17
|
'need deployment contract 3 (RFC 0015). Use it for local queries, or publish countDistinct instead.');
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CanonicalValue } from '@hypequery/protocol';
|
|
2
|
+
export declare class ProtocolSchemaAdapterError extends TypeError {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
constructor(message: string, path?: string);
|
|
5
|
+
}
|
|
6
|
+
export type ProtocolSchemaDraft = Record<string, unknown>;
|
|
7
|
+
export declare function canonicalValue(input: unknown, path: string): CanonicalValue;
|
|
8
|
+
/** Values of a TypeScript enum object, without the reverse mappings of numeric members. */
|
|
9
|
+
export declare function nativeEnumValues(input: Record<string, string | number>): readonly (string | number)[];
|
|
10
|
+
export declare function annotate(description: unknown, result: ProtocolSchemaDraft): ProtocolSchemaDraft;
|
|
11
|
+
export declare function withDefault(inner: ProtocolSchemaDraft, value: unknown, path: string): ProtocolSchemaDraft;
|
|
12
|
+
//# sourceMappingURL=protocol-schema-conversion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-schema-conversion.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-schema-conversion.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAElF,qBAAa,0BAA2B,SAAQ,SAAS;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,IAAI,SAAM;CAKxC;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAgC3E;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAKrG;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAE/F;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,GACX,mBAAmB,CAKrB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { validateCanonicalValue } from '@hypequery/protocol';
|
|
2
|
+
export class ProtocolSchemaAdapterError extends TypeError {
|
|
3
|
+
path;
|
|
4
|
+
constructor(message, path = '$') {
|
|
5
|
+
super(`${message} at ${path}`);
|
|
6
|
+
this.name = 'ProtocolSchemaAdapterError';
|
|
7
|
+
this.path = path;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function canonicalValue(input, path) {
|
|
11
|
+
try {
|
|
12
|
+
if (Array.isArray(input)) {
|
|
13
|
+
return validateCanonicalValue({
|
|
14
|
+
$hypequery: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
version: 1,
|
|
17
|
+
values: input.map((item, index) => canonicalValue(item, `${path}[${index}]`)),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (typeof input === 'object' && input !== null) {
|
|
22
|
+
if ('$hypequery' in input)
|
|
23
|
+
return validateCanonicalValue(input);
|
|
24
|
+
const prototype = Object.getPrototypeOf(input);
|
|
25
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
26
|
+
throw new ProtocolSchemaAdapterError('Default is not portable plain data', path);
|
|
27
|
+
}
|
|
28
|
+
return validateCanonicalValue({
|
|
29
|
+
$hypequery: {
|
|
30
|
+
type: 'map',
|
|
31
|
+
version: 1,
|
|
32
|
+
entries: Object.entries(input)
|
|
33
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
34
|
+
.map(([key, value]) => [key, canonicalValue(value, `${path}.${key}`)]),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return validateCanonicalValue(input);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof ProtocolSchemaAdapterError)
|
|
42
|
+
throw error;
|
|
43
|
+
throw new ProtocolSchemaAdapterError('Default is not a canonical protocol value', path);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Values of a TypeScript enum object, without the reverse mappings of numeric members. */
|
|
47
|
+
export function nativeEnumValues(input) {
|
|
48
|
+
const values = Object.keys(input)
|
|
49
|
+
.filter(key => typeof input[String(input[key])] !== 'number')
|
|
50
|
+
.map(key => input[key]);
|
|
51
|
+
return [...new Set(values)];
|
|
52
|
+
}
|
|
53
|
+
export function annotate(description, result) {
|
|
54
|
+
return typeof description === 'string' ? { ...result, description } : result;
|
|
55
|
+
}
|
|
56
|
+
export function withDefault(inner, value, path) {
|
|
57
|
+
if (inner.kind === 'void') {
|
|
58
|
+
throw new ProtocolSchemaAdapterError('Void schemas cannot carry defaults', path);
|
|
59
|
+
}
|
|
60
|
+
return { ...inner, default: canonicalValue(value, `${path}.default`) };
|
|
61
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every field a query reads, as the planner will: requested names, the fields
|
|
4
|
+
* named filters and segments stand for, and, for each selected measure (every
|
|
5
|
+
* base measure when `measures` is omitted), its dependencies and the fields
|
|
6
|
+
* its fixed filters read. A measure filter can name `<relationship>.<field>`,
|
|
7
|
+
* so selecting a measure can join a relationship the request never names.
|
|
8
|
+
*/
|
|
9
|
+
export declare function queryFieldReferences(dataset: AnyDatasetInstance, query: DatasetQuery): string[];
|
|
10
|
+
//# sourceMappingURL=query-field-references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-field-references.d.ts","sourceRoot":"","sources":["../../src/utils/query-field-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAgB,MAAM,aAAa,CAAC;AASlF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE,CAsB/F"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { baseMeasureNames, isBaseMeasure } from './dataset-measures.js';
|
|
2
|
+
import { measureDependencies } from './measure-dependencies.js';
|
|
3
|
+
import { segmentFilters } from './segments.js';
|
|
4
|
+
function filterField(dataset, filter) {
|
|
5
|
+
return dataset.filters?.[filter.field]?.field ?? filter.field;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Every field a query reads, as the planner will: requested names, the fields
|
|
9
|
+
* named filters and segments stand for, and, for each selected measure (every
|
|
10
|
+
* base measure when `measures` is omitted), its dependencies and the fields
|
|
11
|
+
* its fixed filters read. A measure filter can name `<relationship>.<field>`,
|
|
12
|
+
* so selecting a measure can join a relationship the request never names.
|
|
13
|
+
*/
|
|
14
|
+
export function queryFieldReferences(dataset, query) {
|
|
15
|
+
const names = [
|
|
16
|
+
...(query.dimensions ?? []),
|
|
17
|
+
...(query.filters ?? []).map(filter => filterField(dataset, filter)),
|
|
18
|
+
...segmentFilters(dataset, query.segments).map(filter => filterField(dataset, filter)),
|
|
19
|
+
...(query.orderBy ?? []).map(order => order.field),
|
|
20
|
+
...(query.having ?? []).map(condition => condition.measure),
|
|
21
|
+
];
|
|
22
|
+
const visited = new Set();
|
|
23
|
+
const visit = (name) => {
|
|
24
|
+
if (visited.has(name))
|
|
25
|
+
return;
|
|
26
|
+
visited.add(name);
|
|
27
|
+
names.push(name);
|
|
28
|
+
if (!Object.hasOwn(dataset.measures, name))
|
|
29
|
+
return;
|
|
30
|
+
const definition = dataset.measures[name];
|
|
31
|
+
measureDependencies(definition).forEach(visit);
|
|
32
|
+
if (isBaseMeasure(definition)) {
|
|
33
|
+
names.push(...(definition.filters ?? []).map(filter => filterField(dataset, filter)));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
(query.measures ?? baseMeasureNames(dataset.measures)).forEach(visit);
|
|
37
|
+
return names;
|
|
38
|
+
}
|