@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
package/dist/query-planner.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { semanticOrderDirection } from './utils/semantic-query-syntax.js';
|
|
2
2
|
import { queryTimeSql } from './utils/query-timezone.js';
|
|
3
3
|
import { assertNoRawSqlUnderJoins } from './utils/sql-under-joins.js';
|
|
4
|
-
import {
|
|
4
|
+
import { isSupportedTimeGrain } from "./constants.js";
|
|
5
5
|
import { applyFilteredAggregationExpression } from './utils/filtered-aggregation-sql.js';
|
|
6
6
|
import { getRuntimeTenantPredicate } from './utils/tenant-runtime.js';
|
|
7
|
-
import { clickhouseDatasetSqlDialect } from './utils/clickhouse-dataset-sql-dialect.js';
|
|
8
7
|
import { isQualifiedField } from './utils/relationship-fields.js';
|
|
9
|
-
import { qualifyBaseColumn,
|
|
8
|
+
import { qualifyBaseColumn, resolveQualifiedExpression, } from './utils/relationship-builder-plan.js';
|
|
10
9
|
import { measureToAggregationSpec } from './utils/dataset-normalization.js';
|
|
11
10
|
import { validatePercentileLevel } from './measure.js';
|
|
11
|
+
import { assertAggregateStateFunction } from './utils/aggregate-state-functions.js';
|
|
12
12
|
export function resolveDimensionExpression(ds, dimensionName, joinCtx) {
|
|
13
13
|
if (joinCtx && isQualifiedField(dimensionName)) {
|
|
14
|
-
return
|
|
14
|
+
return resolveQualifiedExpression(ds, dimensionName, joinCtx);
|
|
15
15
|
}
|
|
16
16
|
const definition = ds.dimensions[dimensionName];
|
|
17
17
|
if (definition?.sql) {
|
|
@@ -22,50 +22,51 @@ export function resolveDimensionExpression(ds, dimensionName, joinCtx) {
|
|
|
22
22
|
}
|
|
23
23
|
export function resolveFilterField(ds, filterField, joinCtx) {
|
|
24
24
|
if (joinCtx && isQualifiedField(filterField)) {
|
|
25
|
-
return
|
|
25
|
+
return resolveQualifiedExpression(ds, filterField, joinCtx);
|
|
26
26
|
}
|
|
27
27
|
const resolvedField = ds.filters[filterField]?.field ?? filterField;
|
|
28
28
|
return resolveDimensionExpression(ds, resolvedField, joinCtx);
|
|
29
29
|
}
|
|
30
|
-
export function buildDimensionSelectionPlan(ds, dimensions, grain, joinCtx, timezone, dialect
|
|
30
|
+
export function buildDimensionSelectionPlan(ds, dimensions, grain, joinCtx, timezone, dialect) {
|
|
31
31
|
const selectParts = [];
|
|
32
32
|
const groupByParts = new Set();
|
|
33
33
|
if (grain) {
|
|
34
|
-
|
|
35
|
-
if (!fn) {
|
|
34
|
+
if (!isSupportedTimeGrain(grain)) {
|
|
36
35
|
throw new Error(`Unsupported time grain "${grain}".`);
|
|
37
36
|
}
|
|
38
37
|
const time = resolveDimensionExpression(ds, String(ds.timeKey), joinCtx);
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const period = dialect.quoteIdentifier('period');
|
|
39
|
+
selectParts.push(`${dialect.truncateTime(queryTimeSql(dialect, time, timezone), grain)} AS ${period}`);
|
|
40
|
+
groupByParts.add(period);
|
|
41
41
|
}
|
|
42
42
|
for (const dimensionName of dimensions) {
|
|
43
43
|
const expression = resolveDimensionExpression(ds, dimensionName, joinCtx);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
? dialect.quoteIdentifier(dimensionName)
|
|
49
|
-
: dimensionName;
|
|
50
|
-
selectParts.push(`${expression} AS ${alias}`);
|
|
51
|
-
groupByParts.add(alias);
|
|
52
|
-
}
|
|
53
|
-
else if (expression === dimensionName) {
|
|
44
|
+
// Every generated alias is quoted: reserved words and engines that fold
|
|
45
|
+
// unquoted case (Postgres) must keep the semantic name. With joins in
|
|
46
|
+
// scope every selection is table-qualified, so it is always aliased.
|
|
47
|
+
if (!joinCtx && expression === dimensionName) {
|
|
54
48
|
selectParts.push(dimensionName);
|
|
55
49
|
groupByParts.add(dimensionName);
|
|
56
50
|
}
|
|
57
51
|
else {
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
const alias = dialect.quoteIdentifier(dimensionName);
|
|
53
|
+
selectParts.push(`${expression} AS ${alias}`);
|
|
54
|
+
// A qualified alias (`customer.tier`) can resolve to the physical joined
|
|
55
|
+
// column, so group on its guarded expression: the match marker stays
|
|
56
|
+
// part of the key whatever the engine's alias-resolution rules.
|
|
57
|
+
groupByParts.add(joinCtx && isQualifiedField(dimensionName) ? expression : alias);
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
return { selectParts, groupByParts: Array.from(groupByParts) };
|
|
63
61
|
}
|
|
64
|
-
export function applyAggregationSpec(qb, ds, spec,
|
|
62
|
+
export function applyAggregationSpec(qb, ds, spec, name, joinCtx, dialect, expressions) {
|
|
63
|
+
// The semantic name is the output alias, quoted so it survives reserved
|
|
64
|
+
// words and case folding; messages keep the unquoted name.
|
|
65
|
+
const alias = dialect.quoteIdentifier(name);
|
|
65
66
|
if (spec.sql) {
|
|
66
|
-
assertNoRawSqlUnderJoins('measure',
|
|
67
|
+
assertNoRawSqlUnderJoins('measure', name, spec.sql, joinCtx);
|
|
67
68
|
}
|
|
68
|
-
const fieldOrExpr = expressions?.field ?? applyFilteredAggregationExpression(ds, spec, spec.sql ?? resolveDimensionExpression(ds, spec.field, joinCtx), joinCtx);
|
|
69
|
+
const fieldOrExpr = expressions?.field ?? applyFilteredAggregationExpression(ds, spec, spec.sql ?? resolveDimensionExpression(ds, spec.field, joinCtx), joinCtx, dialect);
|
|
69
70
|
switch (spec.aggregation) {
|
|
70
71
|
case "sum":
|
|
71
72
|
return qb.sum(fieldOrExpr, alias);
|
|
@@ -82,7 +83,7 @@ export function applyAggregationSpec(qb, ds, spec, alias, joinCtx, expressions)
|
|
|
82
83
|
case "argMax":
|
|
83
84
|
case "argMin": {
|
|
84
85
|
if (!spec.argField) {
|
|
85
|
-
throw new Error(`Aggregation "${spec.aggregation}" for "${
|
|
86
|
+
throw new Error(`Aggregation "${spec.aggregation}" for "${name}" requires an argField ("by" column).`);
|
|
86
87
|
}
|
|
87
88
|
const argExpr = expressions?.arg ?? resolveDimensionExpression(ds, spec.argField, joinCtx);
|
|
88
89
|
if (spec.aggregation === "argMax") {
|
|
@@ -98,7 +99,7 @@ export function applyAggregationSpec(qb, ds, spec, alias, joinCtx, expressions)
|
|
|
98
99
|
}
|
|
99
100
|
case "percentile": {
|
|
100
101
|
if (spec.level == null) {
|
|
101
|
-
throw new Error(`Aggregation "percentile" for "${
|
|
102
|
+
throw new Error(`Aggregation "percentile" for "${name}" requires a level.`);
|
|
102
103
|
}
|
|
103
104
|
validatePercentileLevel(spec.level);
|
|
104
105
|
if (!qb.quantile) {
|
|
@@ -111,6 +112,16 @@ export function applyAggregationSpec(qb, ds, spec, alias, joinCtx, expressions)
|
|
|
111
112
|
throw new Error('Query builder does not support approxCountDistinct aggregations.');
|
|
112
113
|
}
|
|
113
114
|
return qb.approxCountDistinct(fieldOrExpr, alias);
|
|
115
|
+
case "merge": {
|
|
116
|
+
assertAggregateStateFunction(spec.stateFunction, `Measure "${name}"`);
|
|
117
|
+
if (spec.filters?.length) {
|
|
118
|
+
throw new Error(`Measure filters are not supported on merge aggregations ("${name}").`);
|
|
119
|
+
}
|
|
120
|
+
if (!qb.mergeState) {
|
|
121
|
+
throw new Error('Query builder does not support merge aggregations (mergeState).');
|
|
122
|
+
}
|
|
123
|
+
return qb.mergeState(spec.stateFunction, fieldOrExpr, alias);
|
|
124
|
+
}
|
|
114
125
|
case "stddev":
|
|
115
126
|
if (!qb.stddev) {
|
|
116
127
|
throw new Error('Query builder does not support stddev aggregations.');
|
|
@@ -125,20 +136,28 @@ export function applyAggregationSpec(qb, ds, spec, alias, joinCtx, expressions)
|
|
|
125
136
|
throw new Error(`Unknown aggregation type: ${spec.aggregation}`);
|
|
126
137
|
}
|
|
127
138
|
}
|
|
128
|
-
export function applyMeasureDefinition(qb, ds, name, definition, joinCtx) {
|
|
129
|
-
return applyAggregationSpec(qb, ds, measureToAggregationSpec(name, definition), name, joinCtx);
|
|
139
|
+
export function applyMeasureDefinition(qb, ds, name, definition, joinCtx, dialect) {
|
|
140
|
+
return applyAggregationSpec(qb, ds, measureToAggregationSpec(name, definition), name, joinCtx, dialect);
|
|
130
141
|
}
|
|
131
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Appends ORDER BY, LIMIT and OFFSET. Under joins, an order on a selected
|
|
144
|
+
* qualified dimension repeats its guarded expression (the GROUP BY key) rather
|
|
145
|
+
* than naming the alias, which the engine may resolve to the raw joined column;
|
|
146
|
+
* any other qualified order (a related measure) sorts by its quoted alias.
|
|
147
|
+
*/
|
|
148
|
+
export function appendOrderLimitOffset(qb, orderBy, grain, limit, offset, joinCtx, dialect, selection) {
|
|
132
149
|
if (orderBy && orderBy.length > 0) {
|
|
133
150
|
for (const order of orderBy) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
// A selected related dimension orders by its guarded expression, for the
|
|
152
|
+
// same alias-resolution reason it groups by it.
|
|
153
|
+
const column = joinCtx && isQualifiedField(order.field) && selection?.dimensions.includes(order.field)
|
|
154
|
+
? resolveDimensionExpression(selection.ds, order.field, joinCtx)
|
|
155
|
+
: dialect.quoteIdentifier(order.field);
|
|
137
156
|
qb = qb.orderBy(column, semanticOrderDirection(order.direction));
|
|
138
157
|
}
|
|
139
158
|
}
|
|
140
159
|
else if (grain) {
|
|
141
|
-
qb = qb.orderBy("period", "ASC");
|
|
160
|
+
qb = qb.orderBy(dialect.quoteIdentifier("period"), "ASC");
|
|
142
161
|
}
|
|
143
162
|
if (limit != null) {
|
|
144
163
|
qb = qb.limit(limit);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-check.d.ts","sourceRoot":"","sources":["../src/relationship-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"relationship-check.d.ts","sourceRoot":"","sources":["../src/relationship-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAyB,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAInG,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,YAAY,EAAE,wBAAwB,CAAC;IACvC;;;OAGG;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,EAAE,EAAE,OAAO,CAAC;IACZ,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAKD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAkCnC"}
|
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
* }
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
+
import { datasetSourceBuilder } from './utils/dataset-source.js';
|
|
19
20
|
import { toQueryBuilderFactory } from './query-builder-protocol.js';
|
|
20
|
-
import {
|
|
21
|
+
import { validateTenantRuntime } from './utils/tenant-runtime.js';
|
|
22
|
+
import { applyRowScope, datasetRowScope } from './utils/row-scope.js';
|
|
23
|
+
import { resolveDatasetSqlDialect } from './utils/dataset-sql-dialect.js';
|
|
21
24
|
import { listToOneRelationships, readCount, relationshipKeyIssue, } from './utils/relationship-key-check.js';
|
|
22
25
|
const ROWS_ALIAS = '__hq_rows';
|
|
23
26
|
const KEYS_ALIAS = '__hq_keys';
|
|
@@ -29,7 +32,7 @@ const KEYS_ALIAS = '__hq_keys';
|
|
|
29
32
|
*/
|
|
30
33
|
export async function checkRelationships(ds, options) {
|
|
31
34
|
const factory = toQueryBuilderFactory(options.queryBuilder);
|
|
32
|
-
const
|
|
35
|
+
const dialect = resolveDatasetSqlDialect(factory);
|
|
33
36
|
const entries = listToOneRelationships(ds, options.relationships);
|
|
34
37
|
const issues = [];
|
|
35
38
|
for (const entry of entries) {
|
|
@@ -37,17 +40,14 @@ export async function checkRelationships(ds, options) {
|
|
|
37
40
|
if (tenantError) {
|
|
38
41
|
throw new Error(`Cannot check relationship "${entry.relationship}": ${tenantError}`);
|
|
39
42
|
}
|
|
40
|
-
let qb = factory
|
|
41
|
-
.table(entry.target.source)
|
|
43
|
+
let qb = datasetSourceBuilder(factory, entry.target)
|
|
42
44
|
.count(entry.column, ROWS_ALIAS)
|
|
43
|
-
.countDistinct(entry.columns ?
|
|
45
|
+
.countDistinct(entry.columns ? dialect.packRow(entry.columns) : entry.column, KEYS_ALIAS);
|
|
44
46
|
if (entry.columns) {
|
|
45
47
|
for (const column of entry.columns)
|
|
46
48
|
qb = qb.where(column, 'isNotNull', undefined);
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
qb = qb.where(entry.target.tenantKey, tenantPredicate.operator, tenantPredicate.value);
|
|
50
|
-
}
|
|
50
|
+
qb = applyRowScope(qb, datasetRowScope(entry.target, options.context));
|
|
51
51
|
const [row] = await qb.execute({
|
|
52
52
|
abortSignal: options.context?.abortSignal,
|
|
53
53
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hypequery/datasets/schemas`: Zod-backed query schemas and agent tools.
|
|
3
|
+
*
|
|
4
|
+
* Everything here builds or reads Zod schemas. It lives apart from the root
|
|
5
|
+
* entry so that defining and querying datasets never loads Zod. In 1.0 the
|
|
6
|
+
* root entry stops re-exporting these and Zod becomes an optional peer
|
|
7
|
+
* dependency needed only by this entry.
|
|
8
|
+
*/
|
|
9
|
+
export { generateDatasetTools, toOpenAITools, toAISDKTools, toMcpTools, } from './tools.js';
|
|
10
|
+
export type { AISDKToolDefinition, DatasetToolAnalytics, DatasetToolMode, GenerateDatasetToolsOptions, JsonSchema, McpToolDefinition, OpenAIToolDefinition, SemanticToolDefinition, } from './tools.js';
|
|
11
|
+
export { buildDatasetInputSchema, buildMetricInputSchema, buildCanonicalSemanticQuerySchemas, toSemanticJsonSchema, DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS, buildDatasetInputProtocolSchema, buildMetricInputProtocolSchema, } from './semantic-query-schema.js';
|
|
12
|
+
export type { CanonicalSemanticQuerySchemas, SemanticMetricQueryContract, SemanticQuerySchemaLimits, SemanticQuerySchemaOptions, SemanticQuerySchemaSource, } from './semantic-query-schema.js';
|
|
13
|
+
export { ProtocolSchemaAdapterError, zodToProtocolSchema } from './protocol-schema-adapter.js';
|
|
14
|
+
export type { AnyZodSchema } from './utils/zod-schema-version.js';
|
|
15
|
+
export { buildClientContractQuerySchemas } from './client-contract/query-schema.js';
|
|
16
|
+
export type { ClientContractQuerySchemas, ClientEndpointQuerySchema } from './client-contract/query-schema.js';
|
|
17
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,2BAA2B,EAC3B,UAAU,EACV,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,kCAAkC,EAClC,oBAAoB,EACpB,oCAAoC,EACpC,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,6BAA6B,EAC7B,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAGlE,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,YAAY,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC"}
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hypequery/datasets/schemas`: Zod-backed query schemas and agent tools.
|
|
3
|
+
*
|
|
4
|
+
* Everything here builds or reads Zod schemas. It lives apart from the root
|
|
5
|
+
* entry so that defining and querying datasets never loads Zod. In 1.0 the
|
|
6
|
+
* root entry stops re-exporting these and Zod becomes an optional peer
|
|
7
|
+
* dependency needed only by this entry.
|
|
8
|
+
*/
|
|
9
|
+
// Agent/tool metadata
|
|
10
|
+
export { generateDatasetTools, toOpenAITools, toAISDKTools, toMcpTools, } from './tools.js';
|
|
11
|
+
// Canonical semantic query schemas shared by Serve, MCP, and agent adapters.
|
|
12
|
+
export { buildDatasetInputSchema, buildMetricInputSchema, buildCanonicalSemanticQuerySchemas, toSemanticJsonSchema, DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS, buildDatasetInputProtocolSchema, buildMetricInputProtocolSchema, } from './semantic-query-schema.js';
|
|
13
|
+
// Zod to RFC 0004 protocol schemas, for Zod 3 or Zod 4 input.
|
|
14
|
+
export { ProtocolSchemaAdapterError, zodToProtocolSchema } from './protocol-schema-adapter.js';
|
|
15
|
+
// Client-contract query schemas.
|
|
16
|
+
export { buildClientContractQuerySchemas } from './client-contract/query-schema.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-planner.d.ts","sourceRoot":"","sources":["../src/semantic-planner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,QAAQ,
|
|
1
|
+
{"version":3,"file":"semantic-planner.d.ts","sourceRoot":"","sources":["../src/semantic-planner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,QAAQ,EAKT,MAAM,oBAAoB,CAAC;AAgO5B,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,kBAAkB,EACtB,KAAK,GAAE,YAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAsBV;AA2FD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,KAAK,GAAE,WAAgB,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAgBV"}
|
package/dist/semantic-planner.js
CHANGED
|
@@ -4,9 +4,10 @@ import { protocolMetricCapabilityErrors } from './utils/protocol-metric-capabili
|
|
|
4
4
|
import { getMetricGrain, getMetricRef, } from './utils/metric-handle.js';
|
|
5
5
|
import { validateDatasetQuery } from './dataset-query.js';
|
|
6
6
|
import { isSupportedTimeGrain } from './constants.js';
|
|
7
|
-
import {
|
|
7
|
+
import { datasetRowScope, runtimeConstraints } from './utils/row-scope.js';
|
|
8
8
|
import { isQualifiedField, resolveQualifiedField } from './utils/relationship-fields.js';
|
|
9
9
|
import { segmentFilters } from './utils/segments.js';
|
|
10
|
+
import { selectsAlternateTimeKey } from './utils/dataset-time-keys.js';
|
|
10
11
|
function resolveField(ds, field) {
|
|
11
12
|
const dimension = ds.dimensions[field];
|
|
12
13
|
if (dimension?.sql) {
|
|
@@ -62,6 +63,38 @@ function normalizeFilters(ds, filters = []) {
|
|
|
62
63
|
* query's dimensions, filters, or orderBy, scoping each joined target with the
|
|
63
64
|
* runtime tenant predicate when the target declares a `tenantKey`.
|
|
64
65
|
*/
|
|
66
|
+
/**
|
|
67
|
+
* Semantic backends receive a source name, not SQL, so they cannot apply
|
|
68
|
+
* `FINAL`. Running a final dataset there would count unmerged row versions.
|
|
69
|
+
*/
|
|
70
|
+
function rejectFinalOnBackend(ds) {
|
|
71
|
+
if (ds.rowResolution) {
|
|
72
|
+
throw new Error(`Dataset "${ds.name}" reads its source with FINAL, which requires the queryBuilder execution path.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The frozen plan protocol has no field for enforced constraints. Dropping
|
|
77
|
+
* them would run the call unrestricted, so it is refused instead.
|
|
78
|
+
*/
|
|
79
|
+
function rejectConstraintsOnBackend(context) {
|
|
80
|
+
if (runtimeConstraints(context).length > 0) {
|
|
81
|
+
throw new Error('Enforced constraints require the queryBuilder execution path.');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The dataset's row scope as the plan's single tenant predicate. The plan
|
|
86
|
+
* protocol carries at most one, so a wider scope is refused, never truncated.
|
|
87
|
+
*/
|
|
88
|
+
function planRowScope(ds, context) {
|
|
89
|
+
const scope = datasetRowScope(ds, context);
|
|
90
|
+
if (scope.length > 1) {
|
|
91
|
+
throw new Error(`Dataset "${ds.name}" has more than one row predicate, which requires the queryBuilder execution path.`);
|
|
92
|
+
}
|
|
93
|
+
if (scope.length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
const { column, ...predicate } = scope[0];
|
|
96
|
+
return { field: column, ...predicate };
|
|
97
|
+
}
|
|
65
98
|
function collectJoins(ds, dimensions = [], filters = [], orderBy = [], context) {
|
|
66
99
|
const referenced = [
|
|
67
100
|
...dimensions,
|
|
@@ -71,12 +104,12 @@ function collectJoins(ds, dimensions = [], filters = [], orderBy = [], context)
|
|
|
71
104
|
if (referenced.length === 0) {
|
|
72
105
|
return undefined;
|
|
73
106
|
}
|
|
74
|
-
const tenantPredicate = getRuntimeTenantPredicate(context);
|
|
75
107
|
const joins = new Map();
|
|
76
108
|
for (const name of referenced) {
|
|
77
109
|
const { relationshipName, relationship, target } = resolveQualifiedPlanField(ds, name);
|
|
78
110
|
if (relationship.keys && relationship.keys.length > 1)
|
|
79
111
|
throw new Error('Composite relationships require the queryBuilder execution path.');
|
|
112
|
+
rejectFinalOnBackend(target);
|
|
80
113
|
if (joins.has(relationshipName)) {
|
|
81
114
|
continue;
|
|
82
115
|
}
|
|
@@ -86,9 +119,7 @@ function collectJoins(ds, dimensions = [], filters = [], orderBy = [], context)
|
|
|
86
119
|
from: relationship.from,
|
|
87
120
|
to: relationship.to,
|
|
88
121
|
type: 'left',
|
|
89
|
-
tenant:
|
|
90
|
-
? { field: target.tenantKey, ...tenantPredicate }
|
|
91
|
-
: undefined,
|
|
122
|
+
tenant: planRowScope(target, context),
|
|
92
123
|
});
|
|
93
124
|
}
|
|
94
125
|
return Array.from(joins.values());
|
|
@@ -98,6 +129,10 @@ function aggregationForMeasure(ds, name) {
|
|
|
98
129
|
if (!measure) {
|
|
99
130
|
throw new Error(`Unknown measure "${name}" on dataset "${ds.name}".`);
|
|
100
131
|
}
|
|
132
|
+
if (measure.aggregation === 'merge') {
|
|
133
|
+
throw new Error(`Semantic backend plans do not support merge measure "${name}": it reads ClickHouse aggregate states. ` +
|
|
134
|
+
'Query it through createDatasetClient({ queryBuilder }).');
|
|
135
|
+
}
|
|
101
136
|
if (measure.sql) {
|
|
102
137
|
throw new Error(`Semantic backend plans do not support SQL-backed measure "${name}". ` +
|
|
103
138
|
'Use a backend-specific query builder for raw SQL expressions.');
|
|
@@ -111,13 +146,6 @@ function aggregationForMeasure(ds, name) {
|
|
|
111
146
|
filters: normalizeFilters(ds, measure.filters),
|
|
112
147
|
};
|
|
113
148
|
}
|
|
114
|
-
function tenantForContext(ds, context) {
|
|
115
|
-
const tenantPredicate = getRuntimeTenantPredicate(context);
|
|
116
|
-
if (!tenantPredicate || !ds.tenantKey) {
|
|
117
|
-
return undefined;
|
|
118
|
-
}
|
|
119
|
-
return { field: ds.tenantKey, ...tenantPredicate };
|
|
120
|
-
}
|
|
121
149
|
function grainForQuery(ds, unit) {
|
|
122
150
|
if (!unit) {
|
|
123
151
|
return undefined;
|
|
@@ -148,15 +176,19 @@ function aggregatePlan(ds, query, aggregations, context) {
|
|
|
148
176
|
orderBy: query.orderBy,
|
|
149
177
|
limit: query.limit,
|
|
150
178
|
offset: query.offset,
|
|
151
|
-
tenant:
|
|
179
|
+
tenant: planRowScope(ds, context),
|
|
152
180
|
joins: collectJoins(ds, query.dimensions, query.filters, query.orderBy, context),
|
|
153
181
|
};
|
|
154
182
|
}
|
|
155
183
|
export function buildDatasetPlan(ds, query = {}, context) {
|
|
184
|
+
rejectFinalOnBackend(ds);
|
|
185
|
+
rejectConstraintsOnBackend(context);
|
|
156
186
|
if ((query.measures ?? []).some(name => name.includes('.')))
|
|
157
187
|
throw new Error('Relationship measures require the queryBuilder execution path.');
|
|
158
188
|
if (query.timezone !== undefined)
|
|
159
189
|
throw new Error('Execution timezone requires the queryBuilder execution path.');
|
|
190
|
+
if (selectsAlternateTimeKey(ds, query.timeKey))
|
|
191
|
+
throw new Error('Alternate time keys require the queryBuilder execution path.');
|
|
160
192
|
rejectTimeMeasuresOnBackend(ds, query);
|
|
161
193
|
if ((query.measures ?? []).some(name => getDerivedMeasure(ds.measures, name) !== undefined)) {
|
|
162
194
|
throw new Error('Derived dataset measures require the queryBuilder execution path.');
|
|
@@ -236,6 +268,10 @@ export function buildMetricPlan(metric, query = {}, context) {
|
|
|
236
268
|
if (errors.length)
|
|
237
269
|
throw new Error(`Invalid metric query: ${errors.join('; ')}`);
|
|
238
270
|
const ref = getMetricRef(metric);
|
|
271
|
+
if (selectsAlternateTimeKey(ref.dataset, query.timeKey))
|
|
272
|
+
throw new Error('Alternate time keys require the queryBuilder execution path.');
|
|
273
|
+
rejectFinalOnBackend(ref.dataset);
|
|
274
|
+
rejectConstraintsOnBackend(context);
|
|
239
275
|
const grain = getMetricGrain(metric, query);
|
|
240
276
|
const plannedQuery = { ...query, by: grain };
|
|
241
277
|
if (ref.spec.__type === 'derived_metric_spec') {
|
|
@@ -2,6 +2,7 @@ import { type ZodTypeAny } from 'zod';
|
|
|
2
2
|
import { type DatasetCatalog, type DatasetCatalogSource, type MetricCatalogEntry } from './catalog.js';
|
|
3
3
|
import type { JsonSchema } from './tools.js';
|
|
4
4
|
import type { ProtocolSchema } from '@hypequery/protocol';
|
|
5
|
+
import type { AnyZodSchema } from './utils/zod-schema-version.js';
|
|
5
6
|
export interface SemanticQuerySchemaLimits {
|
|
6
7
|
defaultResultSize?: number;
|
|
7
8
|
maxResultSize?: number;
|
|
@@ -20,6 +21,12 @@ export interface SemanticQuerySchemaOptions extends SemanticQuerySchemaLimits {
|
|
|
20
21
|
* protocol does not carry post-aggregation conditions yet.
|
|
21
22
|
*/
|
|
22
23
|
having?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Accept a `timeKey` selection on datasets that declare alternate time axes.
|
|
26
|
+
* Off by default, like `having`: enable it only on surfaces that pass the
|
|
27
|
+
* field through to the dataset client.
|
|
28
|
+
*/
|
|
29
|
+
timeKey?: boolean;
|
|
23
30
|
/** Include Serve's opt-in result metadata flag. */
|
|
24
31
|
includeMeta?: boolean;
|
|
25
32
|
/** Require at least one dimension or measure for dataset queries. */
|
|
@@ -60,7 +67,7 @@ export interface CanonicalSemanticQuerySchemas {
|
|
|
60
67
|
export type SemanticQuerySchemaSource = DatasetCatalogSource | DatasetCatalog;
|
|
61
68
|
export declare function buildDatasetInputSchema(dataset: SemanticQuerySchemaSource, options?: SemanticQuerySchemaOptions): ZodTypeAny;
|
|
62
69
|
export declare function buildMetricInputSchema(dataset: SemanticQuerySchemaSource, metricName: string, options?: SemanticQuerySchemaOptions, metricContract?: SemanticMetricQueryContract): ZodTypeAny;
|
|
63
|
-
export declare function toSemanticJsonSchema(schema:
|
|
70
|
+
export declare function toSemanticJsonSchema(schema: AnyZodSchema, options?: {
|
|
64
71
|
requireSelection?: boolean;
|
|
65
72
|
}): JsonSchema;
|
|
66
73
|
export declare function buildCanonicalSemanticQuerySchemas(datasets: Record<string, SemanticQuerySchemaSource>, options?: SemanticQuerySchemaOptions): CanonicalSemanticQuerySchemas;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { isQueryTimezone } from './utils/query-timezone.js';
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha2';
|
|
3
|
-
import { bytesToHex } from '@noble/hashes/utils';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
|
+
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
6
5
|
import { getDatasetCatalog, getGroupableRelationshipFields, getQueryableRelationshipFields, getQueryableRelationshipMeasures, } from './catalog.js';
|
|
7
6
|
import { SEMANTIC_FILTER_OPERATORS, SEMANTIC_HAVING_OPERATORS } from './constants.js';
|
|
8
7
|
import { zodToProtocolSchema } from './protocol-schema-adapter.js';
|
|
9
8
|
import { compareStrings, stableStringify, uniqueSorted } from './utils/canonical-json.js';
|
|
9
|
+
import { zodToDraft7JsonSchema } from './utils/zod-json-schema.js';
|
|
10
|
+
import { datasetTimeKeys } from './utils/dataset-time-keys.js';
|
|
10
11
|
export const DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS = Object.freeze({
|
|
11
12
|
maxResultSize: 10_000,
|
|
12
13
|
maxOffset: 10_000,
|
|
@@ -182,6 +183,11 @@ function queryShape(catalog, metricName, options, relationshipOperators, metric,
|
|
|
182
183
|
? z.number().int().nonnegative()
|
|
183
184
|
: z.number().int().nonnegative().max(limits.maxOffset)).optional(),
|
|
184
185
|
[grainField]: fieldEnum(grains).optional(),
|
|
186
|
+
// Only datasets with more than one axis accept the field, so every other
|
|
187
|
+
// schema and manifest hash is unchanged.
|
|
188
|
+
...(options.timeKey && grains.length > 0 && datasetTimeKeys(catalog).length > 1 ? {
|
|
189
|
+
timeKey: fieldEnum(datasetTimeKeys(catalog)).optional().describe(`Time axis for the grain and time-based measures. Defaults to ${catalog.timeKey}.`),
|
|
190
|
+
} : {}),
|
|
185
191
|
timezone: z.string().max(100).refine(isQueryTimezone, 'Use an IANA timezone such as UTC or America/New_York.').optional(),
|
|
186
192
|
...(options.includeMeta ? { includeMeta: z.boolean().optional() } : {}),
|
|
187
193
|
};
|
|
@@ -190,7 +196,7 @@ export function buildDatasetInputSchema(dataset, options = {}) {
|
|
|
190
196
|
const schema = z.object(queryShape(resolveCatalog(dataset), undefined, options, relationshipFilterPolicy(dataset))).strict();
|
|
191
197
|
if (options.requireSelection === false)
|
|
192
198
|
return schema;
|
|
193
|
-
return schema
|
|
199
|
+
return requireSelection(schema);
|
|
194
200
|
}
|
|
195
201
|
export function buildMetricInputSchema(dataset, metricName, options = {}, metricContract) {
|
|
196
202
|
const catalog = resolveCatalog(dataset);
|
|
@@ -207,16 +213,16 @@ function unionSchemas(schemas, emptyShape) {
|
|
|
207
213
|
return schemas[0];
|
|
208
214
|
return z.union(schemas);
|
|
209
215
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
function requireSelection(schema) {
|
|
217
|
+
return schema.refine((input) => (input.dimensions?.length ?? 0) > 0 || (input.measures?.length ?? 0) > 0, { message: 'At least one dimension or measure must be specified' });
|
|
218
|
+
}
|
|
219
|
+
function withSelectors(schema, selectors, selectionRequired) {
|
|
220
|
+
// Zod 4 keeps refinements on the object itself, so its shape is readable.
|
|
221
|
+
const selected = z.object({ ...selectors, ...schema.shape }).strict();
|
|
222
|
+
return selectionRequired ? requireSelection(selected) : selected;
|
|
216
223
|
}
|
|
217
224
|
export function toSemanticJsonSchema(schema, options = {}) {
|
|
218
|
-
const
|
|
219
|
-
const { $schema: _schema, ...jsonSchema } = converted;
|
|
225
|
+
const { $schema: _schema, ...jsonSchema } = zodToDraft7JsonSchema(schema);
|
|
220
226
|
const result = jsonSchema;
|
|
221
227
|
const objectSchema = result.anyOf && !result.type ? { ...result, type: 'object' } : result;
|
|
222
228
|
return options.requireSelection ? addSelectionRequirement(objectSchema) : objectSchema;
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AAatB,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,WAAW,sBAAsB,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,aAAa,GAAG,YAAY,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACnC,OAAO,CACL,MAAM,EAAE,kBAAkB,GAAG,YAAY,EACzC,KAAK,CAAC,EAAE,YAAY,GAAG,WAAW,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC/C,SAAS,EAAE,oBAAoB,CAAC;IAChC,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,UAAU,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -2,7 +2,8 @@ import { getDatasetCatalog, } from './catalog.js';
|
|
|
2
2
|
import { buildCanonicalSemanticQuerySchemas, buildDatasetInputSchema, buildMetricInputSchema, toSemanticJsonSchema, } from './semantic-query-schema.js';
|
|
3
3
|
import { parseCanonicalToolInput, redactSemanticToolSql, semanticToolNamePart, } from './utils/semantic-tool.js';
|
|
4
4
|
function buildCatalogTool(datasets, analytics, includeSql) {
|
|
5
|
-
|
|
5
|
+
// Parsed input reaches the client whole, so it can carry a selected time axis.
|
|
6
|
+
const schemas = buildCanonicalSemanticQuerySchemas(datasets, { timeKey: true });
|
|
6
7
|
return {
|
|
7
8
|
name: 'query_dataset',
|
|
8
9
|
description: 'Query governed analytics datasets by selecting dimensions, measures, filters, and time grains.',
|
|
@@ -17,7 +18,7 @@ function buildCatalogTool(datasets, analytics, includeSql) {
|
|
|
17
18
|
}
|
|
18
19
|
function buildDatasetTools(datasets, analytics, includeSql) {
|
|
19
20
|
return Object.entries(datasets).map(([datasetName, dataset]) => {
|
|
20
|
-
const schema = buildDatasetInputSchema(dataset);
|
|
21
|
+
const schema = buildDatasetInputSchema(dataset, { timeKey: true });
|
|
21
22
|
return {
|
|
22
23
|
name: `query_${semanticToolNamePart(datasetName)}`,
|
|
23
24
|
description: `Query the ${datasetName} analytics dataset.`,
|
|
@@ -34,7 +35,7 @@ function buildMetricTools(datasets, analytics, includeSql) {
|
|
|
34
35
|
const tools = [];
|
|
35
36
|
for (const [datasetName, dataset] of Object.entries(datasets)) {
|
|
36
37
|
for (const [metricName, metric] of Object.entries(dataset.metrics ?? {})) {
|
|
37
|
-
const schema = buildMetricInputSchema(dataset, metricName, {}, metric.contract());
|
|
38
|
+
const schema = buildMetricInputSchema(dataset, metricName, { timeKey: true }, metric.contract());
|
|
38
39
|
tools.push({
|
|
39
40
|
name: `query_${semanticToolNamePart(metricName)}`,
|
|
40
41
|
description: `Query the ${metricName} metric from the ${datasetName} dataset.`,
|