@hypequery/datasets 0.23.1 → 0.24.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.js +2 -2
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +3 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +3 -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 +2 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +4 -3
- package/dist/dataset-sql-dialect.d.ts +117 -0
- package/dist/dataset-sql-dialect.d.ts.map +1 -1
- package/dist/dataset.d.ts +1 -1
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +2 -1
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +2 -0
- package/dist/index.d.ts +48 -5
- 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.map +1 -1
- package/dist/metric-query-engine.js +4 -3
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +5 -3
- package/dist/protocol-deployment-adapter.d.ts.map +1 -1
- package/dist/protocol-deployment-adapter.js +7 -1
- 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 +32 -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 +5 -3
- 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 +16 -0
- package/dist/semantic-query-schema.d.ts +2 -1
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +12 -12
- package/dist/types.d.ts +22 -1
- 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.map +1 -1
- package/dist/utils/build-dataset-query-builder.js +16 -8
- 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 +140 -1
- package/dist/utils/compile-dataset-query.d.ts.map +1 -1
- package/dist/utils/compile-dataset-query.js +9 -6
- 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.map +1 -1
- package/dist/utils/dataset-definition-validation.js +36 -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.map +1 -1
- package/dist/utils/dataset-query-validation.js +7 -0
- package/dist/utils/dataset-source.d.ts +9 -0
- package/dist/utils/dataset-source.d.ts.map +1 -0
- package/dist/utils/dataset-source.js +16 -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-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/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 +54 -41
- package/dist/utils/metric-query-builder.d.ts.map +1 -1
- package/dist/utils/metric-query-builder.js +17 -9
- package/dist/utils/metric-query-validation.d.ts.map +1 -1
- package/dist/utils/metric-query-validation.js +7 -0
- 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 +75 -8
- 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 +25 -8
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +23 -9
- 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 +33 -10
- package/dist/utils/relationship-measures.d.ts.map +1 -1
- package/dist/utils/relationship-measures.js +3 -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/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 +2 -1
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-axis-sql.js +19 -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 +2 -1
- package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-leaf-sql.js +17 -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 +2 -2
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-source-sql.js +25 -16
- 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,4 +1,5 @@
|
|
|
1
1
|
import type { AnyDatasetInstance, DatasetHavingCondition, DatasetQuery } from '../types.js';
|
|
2
|
+
import type { StatementParameters } from './statement-parameters.js';
|
|
2
3
|
/**
|
|
3
4
|
* Validates `having` conditions against the query they belong to.
|
|
4
5
|
*
|
|
@@ -8,14 +9,11 @@ import type { AnyDatasetInstance, DatasetHavingCondition, DatasetQuery } from '.
|
|
|
8
9
|
*/
|
|
9
10
|
export declare function datasetHavingErrors(ds: AnyDatasetInstance, query: DatasetQuery, selectedMeasures: readonly string[]): string[];
|
|
10
11
|
/**
|
|
11
|
-
* Renders validated `having` conditions as one
|
|
12
|
+
* Renders validated `having` conditions as one predicate.
|
|
12
13
|
*
|
|
13
14
|
* `measureSql` maps a selected measure to the expression that reads its
|
|
14
|
-
* aggregated value in the outer query. Values are only ever bound
|
|
15
|
-
*
|
|
15
|
+
* aggregated value in the outer query. Values are only ever bound through
|
|
16
|
+
* `statement`, never written into the SQL text.
|
|
16
17
|
*/
|
|
17
|
-
export declare function datasetHavingSql(conditions: readonly DatasetHavingCondition[], measureSql: (measure: string) => string):
|
|
18
|
-
sql: string;
|
|
19
|
-
parameters: unknown[];
|
|
20
|
-
};
|
|
18
|
+
export declare function datasetHavingSql(conditions: readonly DatasetHavingCondition[], measureSql: (measure: string) => string, statement: StatementParameters): string;
|
|
21
19
|
//# sourceMappingURL=dataset-having.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-having.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-having.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"dataset-having.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-having.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAkCrE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAClC,MAAM,EAAE,CA0CV;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,SAAS,sBAAsB,EAAE,EAC7C,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EACvC,SAAS,EAAE,mBAAmB,GAC7B,MAAM,CAgBR"}
|
|
@@ -77,30 +77,27 @@ export function datasetHavingErrors(ds, query, selectedMeasures) {
|
|
|
77
77
|
return errors;
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* Renders validated `having` conditions as one
|
|
80
|
+
* Renders validated `having` conditions as one predicate.
|
|
81
81
|
*
|
|
82
82
|
* `measureSql` maps a selected measure to the expression that reads its
|
|
83
|
-
* aggregated value in the outer query. Values are only ever bound
|
|
84
|
-
*
|
|
83
|
+
* aggregated value in the outer query. Values are only ever bound through
|
|
84
|
+
* `statement`, never written into the SQL text.
|
|
85
85
|
*/
|
|
86
|
-
export function datasetHavingSql(conditions, measureSql) {
|
|
87
|
-
const
|
|
86
|
+
export function datasetHavingSql(conditions, measureSql, statement) {
|
|
87
|
+
const bind = (value) => statement.bind(value, 'number');
|
|
88
88
|
const predicates = conditions.map((condition) => {
|
|
89
89
|
const column = measureSql(condition.measure);
|
|
90
90
|
if (condition.operator === 'between') {
|
|
91
|
-
|
|
92
|
-
return `${column} BETWEEN ? AND ?`;
|
|
91
|
+
return `${column} BETWEEN ${bind(condition.value[0])} AND ${bind(condition.value[1])}`;
|
|
93
92
|
}
|
|
94
93
|
if (condition.operator === 'in' || condition.operator === 'notIn') {
|
|
95
|
-
|
|
96
|
-
const placeholders = condition.value.map(() => '?').join(', ');
|
|
94
|
+
const placeholders = condition.value.map(bind).join(', ');
|
|
97
95
|
return `${column} ${condition.operator === 'in' ? 'IN' : 'NOT IN'} (${placeholders})`;
|
|
98
96
|
}
|
|
99
97
|
const comparison = COMPARISONS[condition.operator];
|
|
100
98
|
if (!comparison)
|
|
101
99
|
throw new Error(`Unsupported having operator "${String(condition.operator)}".`);
|
|
102
|
-
|
|
103
|
-
return `${column} ${comparison} ?`;
|
|
100
|
+
return `${column} ${comparison} ${bind(condition.value)}`;
|
|
104
101
|
});
|
|
105
|
-
return
|
|
102
|
+
return predicates.join(' AND ');
|
|
106
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-normalization.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-normalization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAGrB,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAC5D,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzF,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,CAAC,GAChE,WAAW,CAEb;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC/C,OAAO,CAAC,EAAE,yBAAyB,GAClC,yBAAyB,CAgB3B;AAED,wBAAgB,iBAAiB,CAAC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC1F,QAAQ,EAAE,SAAS,GAAG,SAAS,GAC9B,SAAS,CAEX;AAED,wBAAgB,sBAAsB,CAAC,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAClG,aAAa,EAAE,cAAc,GAAG,SAAS,EACzC,MAAM,EAAE,MAAM,GACb,cAAc,CAkBhB;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,GAC5B,eAAe,
|
|
1
|
+
{"version":3,"file":"dataset-normalization.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-normalization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAGrB,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAC5D,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzF,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,CAAC,GAChE,WAAW,CAEb;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC/C,OAAO,CAAC,EAAE,yBAAyB,GAClC,yBAAyB,CAgB3B;AAED,wBAAgB,iBAAiB,CAAC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC1F,QAAQ,EAAE,SAAS,GAAG,SAAS,GAC9B,SAAS,CAEX;AAED,wBAAgB,sBAAsB,CAAC,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAClG,aAAa,EAAE,cAAc,GAAG,SAAS,EACzC,MAAM,EAAE,MAAM,GACb,cAAc,CAkBhB;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,GAC5B,eAAe,CAejB"}
|
|
@@ -45,6 +45,7 @@ export function measureToAggregationSpec(measureName, definition) {
|
|
|
45
45
|
field: definition.field,
|
|
46
46
|
argField: definition.argField,
|
|
47
47
|
level: definition.level,
|
|
48
|
+
...(definition.stateFunction !== undefined ? { stateFunction: definition.stateFunction } : {}),
|
|
48
49
|
sql: definition.sql,
|
|
49
50
|
filters: definition.filters,
|
|
50
51
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataset-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-validation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAkB9E,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CA6KlB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { notGroupableError } from './groupable.js';
|
|
1
2
|
import { resolveRelationshipMeasure } from './relationship-measures.js';
|
|
2
3
|
import { semanticQuerySyntaxErrors } from './semantic-query-syntax.js';
|
|
3
4
|
import { queryTimezoneErrors } from './query-timezone.js';
|
|
@@ -45,11 +46,17 @@ export function validateDatasetQueryInput(ds, query, context) {
|
|
|
45
46
|
if (resolution?.error) {
|
|
46
47
|
errors.push(resolution.error);
|
|
47
48
|
}
|
|
49
|
+
else if (resolution?.resolved?.targetDimension.groupable === false) {
|
|
50
|
+
errors.push(notGroupableError(dimension));
|
|
51
|
+
}
|
|
48
52
|
continue;
|
|
49
53
|
}
|
|
50
54
|
if (!dimensionNames.includes(dimension)) {
|
|
51
55
|
errors.push(`Unknown dimensions: ${dimension}. Available: ${dimensionNames.join(', ')}`);
|
|
52
56
|
}
|
|
57
|
+
else if (ds.dimensions[dimension]?.groupable === false) {
|
|
58
|
+
errors.push(notGroupableError(dimension));
|
|
59
|
+
}
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
if (query.measures) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnyDatasetInstance } from '../types.js';
|
|
2
|
+
import type { QueryBuilderFactoryLike, QueryBuilderLike } from '../query-builder-protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* The dataset's source table as a builder, read with `FINAL` when the dataset
|
|
5
|
+
* is declared `final: true`. Every builder-path read of a dataset source goes
|
|
6
|
+
* through here so no query can skip row-version resolution.
|
|
7
|
+
*/
|
|
8
|
+
export declare function datasetSourceBuilder(builderFactory: Pick<QueryBuilderFactoryLike, 'table'>, ds: Pick<AnyDatasetInstance, 'name' | 'source' | 'final'>): QueryBuilderLike;
|
|
9
|
+
//# sourceMappingURL=dataset-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-source.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAE9F;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,IAAI,CAAC,uBAAuB,EAAE,OAAO,CAAC,EACtD,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,GACxD,gBAAgB,CAYlB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dataset's source table as a builder, read with `FINAL` when the dataset
|
|
3
|
+
* is declared `final: true`. Every builder-path read of a dataset source goes
|
|
4
|
+
* through here so no query can skip row-version resolution.
|
|
5
|
+
*/
|
|
6
|
+
export function datasetSourceBuilder(builderFactory, ds) {
|
|
7
|
+
const qb = builderFactory.table(ds.source);
|
|
8
|
+
if (!ds.final) {
|
|
9
|
+
return qb;
|
|
10
|
+
}
|
|
11
|
+
if (typeof qb.final !== 'function') {
|
|
12
|
+
throw new Error(`Dataset "${ds.name}" is declared final: true, but the query builder does not implement final(). ` +
|
|
13
|
+
'Reading it without FINAL would count every unmerged row version, so the query is refused.');
|
|
14
|
+
}
|
|
15
|
+
return qb.final();
|
|
16
|
+
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import type { DatasetSqlDialect } from '../dataset-sql-dialect.js';
|
|
1
|
+
import type { DatasetSqlDialect, DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
|
|
2
2
|
import type { QueryBuilderFactoryInput } from '../query-builder-protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* Register a complete engine rule set, so a factory can carry it as its
|
|
5
|
+
* `datasetSqlDialect` and have it used as-is rather than completed with the
|
|
6
|
+
* ClickHouse rules. Internal: for engine packages and tests.
|
|
7
|
+
*/
|
|
8
|
+
export declare function defineDatasetSqlDialectRules<TRules extends DatasetSqlDialectRules>(rules: TRules): Readonly<TRules>;
|
|
9
|
+
/**
|
|
10
|
+
* Complete a public dialect hook with the ClickHouse rules it does not
|
|
11
|
+
* override. Memoized, so one hook always resolves to one rule set.
|
|
12
|
+
*/
|
|
13
|
+
export declare function completeDatasetSqlDialect(dialect: DatasetSqlDialect): DatasetSqlDialectRules;
|
|
3
14
|
/** Resolve from the active factory, so a runtime builder override carries its dialect. */
|
|
4
|
-
export declare function resolveDatasetSqlDialect(factory: QueryBuilderFactoryInput):
|
|
15
|
+
export declare function resolveDatasetSqlDialect(factory: QueryBuilderFactoryInput): DatasetSqlDialectRules;
|
|
5
16
|
//# sourceMappingURL=dataset-sql-dialect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-sql-dialect.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-sql-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"dataset-sql-dialect.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-sql-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAM7E;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,SAAS,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAInH;AAsCD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,iBAAiB,GAAG,sBAAsB,CAgB5F;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,wBAAwB,GAAG,sBAAsB,CAIlG"}
|
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
import { clickhouseDatasetSqlDialect } from './clickhouse-dataset-sql-dialect.js';
|
|
2
|
+
const resolved = new WeakMap();
|
|
3
|
+
const complete = new WeakSet();
|
|
4
|
+
/**
|
|
5
|
+
* Register a complete engine rule set, so a factory can carry it as its
|
|
6
|
+
* `datasetSqlDialect` and have it used as-is rather than completed with the
|
|
7
|
+
* ClickHouse rules. Internal: for engine packages and tests.
|
|
8
|
+
*/
|
|
9
|
+
export function defineDatasetSqlDialectRules(rules) {
|
|
10
|
+
const frozen = Object.isFrozen(rules) ? rules : receiverBoundSnapshot(rules);
|
|
11
|
+
complete.add(frozen);
|
|
12
|
+
return frozen;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A frozen snapshot of a rule set that leaves the caller's object unfrozen.
|
|
16
|
+
*
|
|
17
|
+
* Own members are copied as they are, so a rule shared with another rule set
|
|
18
|
+
* (spread from the ClickHouse rules, say) keeps its identity. Members a class
|
|
19
|
+
* declares are read through the original instance, methods bound to it and
|
|
20
|
+
* getters evaluated on it: its `#private` fields exist only on the object its
|
|
21
|
+
* constructor ran on, so a copy must not become their receiver. Members
|
|
22
|
+
* inherited from a plain object stay inherited.
|
|
23
|
+
*/
|
|
24
|
+
function receiverBoundSnapshot(rules) {
|
|
25
|
+
const snapshot = Object.create(Object.getPrototypeOf(rules), Object.getOwnPropertyDescriptors(rules));
|
|
26
|
+
for (let owner = Object.getPrototypeOf(rules); owner && isClassPrototype(owner); owner = Object.getPrototypeOf(owner)) {
|
|
27
|
+
for (const key of Reflect.ownKeys(owner)) {
|
|
28
|
+
if (key === 'constructor' || Object.hasOwn(snapshot, key))
|
|
29
|
+
continue;
|
|
30
|
+
// Read through the original, so a getter sees its own private fields;
|
|
31
|
+
// a method is bound to it for the same reason.
|
|
32
|
+
const value = Reflect.get(rules, key);
|
|
33
|
+
// Define rather than assign: assignment cannot shadow a read-only
|
|
34
|
+
// member inherited from a frozen prototype such as the ClickHouse rules.
|
|
35
|
+
Object.defineProperty(snapshot, key, {
|
|
36
|
+
value: typeof value === 'function' ? value.bind(rules) : value,
|
|
37
|
+
enumerable: true,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return Object.freeze(snapshot);
|
|
42
|
+
}
|
|
43
|
+
/** A prototype a class declaration created: its constructor points back at it. */
|
|
44
|
+
function isClassPrototype(owner) {
|
|
45
|
+
if (owner === Object.prototype || !Object.hasOwn(owner, 'constructor'))
|
|
46
|
+
return false;
|
|
47
|
+
const constructor = owner.constructor;
|
|
48
|
+
return typeof constructor === 'function' && constructor.prototype === owner;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Complete a public dialect hook with the ClickHouse rules it does not
|
|
52
|
+
* override. Memoized, so one hook always resolves to one rule set.
|
|
53
|
+
*/
|
|
54
|
+
export function completeDatasetSqlDialect(dialect) {
|
|
55
|
+
if (dialect === clickhouseDatasetSqlDialect)
|
|
56
|
+
return clickhouseDatasetSqlDialect;
|
|
57
|
+
if (complete.has(dialect))
|
|
58
|
+
return dialect;
|
|
59
|
+
let rules = resolved.get(dialect);
|
|
60
|
+
if (!rules) {
|
|
61
|
+
// Read the public members explicitly: a class-based hook keeps its methods
|
|
62
|
+
// on the prototype, which spreading would silently drop.
|
|
63
|
+
rules = Object.freeze({
|
|
64
|
+
...clickhouseDatasetSqlDialect,
|
|
65
|
+
name: dialect.name,
|
|
66
|
+
quoteIdentifier: (identifier) => dialect.quoteIdentifier(identifier),
|
|
67
|
+
quoteQualifiedName: (name) => name.split('.').map(part => dialect.quoteIdentifier(part)).join('.'),
|
|
68
|
+
});
|
|
69
|
+
resolved.set(dialect, rules);
|
|
70
|
+
}
|
|
71
|
+
return rules;
|
|
72
|
+
}
|
|
2
73
|
/** Resolve from the active factory, so a runtime builder override carries its dialect. */
|
|
3
74
|
export function resolveDatasetSqlDialect(factory) {
|
|
4
|
-
return factory.datasetSqlDialect
|
|
75
|
+
return factory.datasetSqlDialect
|
|
76
|
+
? completeDatasetSqlDialect(factory.datasetSqlDialect)
|
|
77
|
+
: clickhouseDatasetSqlDialect;
|
|
5
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"dataset-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAarB,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,IAAI,CAiEN;AAED,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC,GAClC,IAAI,CAmBN"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validateFilterValue } from '../validation.js';
|
|
2
2
|
import { validatePercentileLevel } from '../measure.js';
|
|
3
|
+
import { assertAggregateStateFunction } from './aggregate-state-functions.js';
|
|
3
4
|
const NUMERIC_FIELD_TYPES = new Set(['number']);
|
|
4
5
|
// Aggregations whose output tracks the input field's type, so a metric
|
|
5
6
|
// (contract valueType: 'number') needs a numeric input dimension.
|
|
@@ -15,6 +16,12 @@ export function validateBaseMetric(ds, metricName, spec, options) {
|
|
|
15
16
|
throw new Error(`Invalid metric "${metricName}": measure filters are not supported on ${spec.aggregation} aggregations.`);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
if (spec.aggregation === 'merge') {
|
|
20
|
+
assertAggregateStateFunction(spec.stateFunction, `Invalid metric "${metricName}":`);
|
|
21
|
+
if (spec.filters?.length) {
|
|
22
|
+
throw new Error(`Invalid metric "${metricName}": measure filters are not supported on merge aggregations.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
18
25
|
if (spec.aggregation === 'percentile') {
|
|
19
26
|
if (spec.level == null) {
|
|
20
27
|
throw new Error(`Invalid metric "${metricName}": percentile() requires a level.`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subset of derived-measure formulas a deployment contract accepts.
|
|
3
|
+
*
|
|
4
|
+
* Local planning accepts every formula the helpers can build. The RFC 0006
|
|
5
|
+
* deployment grammar is narrower: a derived measure may reference only base
|
|
6
|
+
* measures, and a literal may appear only as `round` decimals or as a
|
|
7
|
+
* `coalesce` fallback. Checking that subset against the definition, rather
|
|
8
|
+
* than the emitted contract, lets the failure name the dataset and measure
|
|
9
|
+
* instead of a JSON path such as `$.datasets[2].measures[19].expression.right`.
|
|
10
|
+
*/
|
|
11
|
+
import type { AnyDatasetInstance, DatasetMeasureDefinition, DerivedMeasureDefinition } from '../types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Why `definition` cannot be deployed, or `undefined` when it can.
|
|
14
|
+
*
|
|
15
|
+
* `measures` are the measures of the dataset that owns the definition. The
|
|
16
|
+
* dataset has already validated references, aliases and cycles, so only the
|
|
17
|
+
* deployment-specific restrictions are reported here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function deployableFormulaIssue(definition: DerivedMeasureDefinition, measures: Readonly<Record<string, DatasetMeasureDefinition>>): string | undefined;
|
|
20
|
+
/** Every `[measure name, issue]` pair among a dataset's derived measures, in definition order. */
|
|
21
|
+
export declare function deployableMeasureIssues(measures: Readonly<Record<string, DatasetMeasureDefinition>> | undefined): Array<[string, string]>;
|
|
22
|
+
/**
|
|
23
|
+
* Refuse a dataset whose derived measures the deployment contract cannot carry.
|
|
24
|
+
*
|
|
25
|
+
* Local queries accept every formula the helpers build. A deployment contract
|
|
26
|
+
* accepts a narrower subset: derived measures may reference only base measures,
|
|
27
|
+
* and a literal may appear only as `round` decimals or as a `coalesce`
|
|
28
|
+
* fallback. `buildProtocolDeploymentContract` (and so `hypequery deploy`) runs
|
|
29
|
+
* this check first, so the error names the dataset and measure
|
|
30
|
+
* (`orders.doubled: ...`) instead of a contract path.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertDeployable(dataset: AnyDatasetInstance): void;
|
|
33
|
+
//# sourceMappingURL=deployable-formulas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployable-formulas.d.ts","sourceRoot":"","sources":["../../src/utils/deployable-formulas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AA6C1G;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,wBAAwB,EACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,GAC3D,MAAM,GAAG,SAAS,CAYpB;AAED,kGAAkG;AAClG,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,GAAG,SAAS,GACvE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAOzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAMlE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subset of derived-measure formulas a deployment contract accepts.
|
|
3
|
+
*
|
|
4
|
+
* Local planning accepts every formula the helpers can build. The RFC 0006
|
|
5
|
+
* deployment grammar is narrower: a derived measure may reference only base
|
|
6
|
+
* measures, and a literal may appear only as `round` decimals or as a
|
|
7
|
+
* `coalesce` fallback. Checking that subset against the definition, rather
|
|
8
|
+
* than the emitted contract, lets the failure name the dataset and measure
|
|
9
|
+
* instead of a JSON path such as `$.datasets[2].measures[19].expression.right`.
|
|
10
|
+
*/
|
|
11
|
+
import { isDerivedMeasure, splitDatasetMeasures } from './dataset-measures.js';
|
|
12
|
+
const LITERAL_POSITIONS = 'literals are only accepted as round decimals or a coalesce fallback';
|
|
13
|
+
function literalIssue(expression, where) {
|
|
14
|
+
if (expression.kind !== 'literal')
|
|
15
|
+
return undefined;
|
|
16
|
+
return `a numeric literal cannot be ${where} in a deployable formula; ${LITERAL_POSITIONS}`;
|
|
17
|
+
}
|
|
18
|
+
/** Why `expression` cannot be deployed, or `undefined` when it can. */
|
|
19
|
+
function expressionIssue(expression) {
|
|
20
|
+
switch (expression.kind) {
|
|
21
|
+
case 'ref':
|
|
22
|
+
return undefined;
|
|
23
|
+
case 'literal':
|
|
24
|
+
return `a literal cannot be a deployable formula on its own; ${LITERAL_POSITIONS}`;
|
|
25
|
+
case 'binary':
|
|
26
|
+
for (const operand of [expression.left, expression.right]) {
|
|
27
|
+
const issue = literalIssue(operand, 'an arithmetic operand') ?? expressionIssue(operand);
|
|
28
|
+
if (issue !== undefined)
|
|
29
|
+
return issue;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
case 'function': {
|
|
33
|
+
const [first, second] = expression.args;
|
|
34
|
+
if (first === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const issue = literalIssue(first, `the value passed to ${expression.name}`) ?? expressionIssue(first);
|
|
37
|
+
if (issue !== undefined || second === undefined)
|
|
38
|
+
return issue;
|
|
39
|
+
if (expression.name === 'round') {
|
|
40
|
+
return second.kind === 'literal' && typeof second.value === 'number'
|
|
41
|
+
? undefined
|
|
42
|
+
: 'round decimals must be a numeric literal in a deployable formula';
|
|
43
|
+
}
|
|
44
|
+
if (expression.name === 'coalesce' && second.kind === 'literal') {
|
|
45
|
+
return typeof second.value === 'number'
|
|
46
|
+
? undefined
|
|
47
|
+
: 'a coalesce fallback must be a number in a deployable formula; null, booleans and strings are not accepted';
|
|
48
|
+
}
|
|
49
|
+
return expressionIssue(second);
|
|
50
|
+
}
|
|
51
|
+
default:
|
|
52
|
+
return 'formula contains an unsupported expression kind';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Why `definition` cannot be deployed, or `undefined` when it can.
|
|
57
|
+
*
|
|
58
|
+
* `measures` are the measures of the dataset that owns the definition. The
|
|
59
|
+
* dataset has already validated references, aliases and cycles, so only the
|
|
60
|
+
* deployment-specific restrictions are reported here.
|
|
61
|
+
*/
|
|
62
|
+
export function deployableFormulaIssue(definition, measures) {
|
|
63
|
+
// Window and shift inputs are refused by the contract adapter itself, with
|
|
64
|
+
// the contract version as the reason; only a derived input is reported here.
|
|
65
|
+
for (const [alias, measureName] of Object.entries(definition.uses)) {
|
|
66
|
+
const input = Object.hasOwn(measures, measureName) ? measures[measureName] : undefined;
|
|
67
|
+
if (!isDerivedMeasure(input))
|
|
68
|
+
continue;
|
|
69
|
+
const spelled = alias === measureName ? `"${measureName}"` : `"${measureName}" (as "${alias}")`;
|
|
70
|
+
return `references derived measure ${spelled}; a deployable formula may only reference base measures, `
|
|
71
|
+
+ 'so inline that formula or reference the base measures directly';
|
|
72
|
+
}
|
|
73
|
+
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
74
|
+
return expressionIssue(definition.formula(aliases).expression);
|
|
75
|
+
}
|
|
76
|
+
/** Every `[measure name, issue]` pair among a dataset's derived measures, in definition order. */
|
|
77
|
+
export function deployableMeasureIssues(measures) {
|
|
78
|
+
const issues = [];
|
|
79
|
+
for (const [name, definition] of Object.entries(splitDatasetMeasures(measures).derived)) {
|
|
80
|
+
const issue = deployableFormulaIssue(definition, measures ?? {});
|
|
81
|
+
if (issue !== undefined)
|
|
82
|
+
issues.push([name, issue]);
|
|
83
|
+
}
|
|
84
|
+
return issues;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Refuse a dataset whose derived measures the deployment contract cannot carry.
|
|
88
|
+
*
|
|
89
|
+
* Local queries accept every formula the helpers build. A deployment contract
|
|
90
|
+
* accepts a narrower subset: derived measures may reference only base measures,
|
|
91
|
+
* and a literal may appear only as `round` decimals or as a `coalesce`
|
|
92
|
+
* fallback. `buildProtocolDeploymentContract` (and so `hypequery deploy`) runs
|
|
93
|
+
* this check first, so the error names the dataset and measure
|
|
94
|
+
* (`orders.doubled: ...`) instead of a contract path.
|
|
95
|
+
*/
|
|
96
|
+
export function assertDeployable(dataset) {
|
|
97
|
+
const [first] = deployableMeasureIssues(dataset.measures);
|
|
98
|
+
if (first !== undefined) {
|
|
99
|
+
const [name, issue] = first;
|
|
100
|
+
throw new Error(`${dataset.name}.${name}: ${issue}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The top-level GROUP BY terms of a statement. Scans rather than matching a
|
|
3
|
+
* regex, so quoted names (`limit`), string literals and nested calls with
|
|
4
|
+
* commas neither end the clause nor split a term.
|
|
5
|
+
*/
|
|
1
6
|
export declare function extractGroupByExpressions(sql: string): string[];
|
|
2
7
|
export declare function validateDerivedCteGrouping(sql: string, aggregateAliases: string[], intendedGroupBy: string[]): string[];
|
|
3
8
|
//# sourceMappingURL=derived-cte-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derived-cte-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-cte-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"derived-cte-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-cte-validation.ts"],"names":[],"mappings":"AA6BA;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA4B/D;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,gBAAgB,EAAE,MAAM,EAAE,EAC1B,eAAe,EAAE,MAAM,EAAE,GACxB,MAAM,EAAE,CAkCV"}
|
|
@@ -1,16 +1,85 @@
|
|
|
1
|
+
/** Compare identifiers by name, whether or not the planner or builder quoted them. */
|
|
2
|
+
function identifierName(expression) {
|
|
3
|
+
const quote = expression[0];
|
|
4
|
+
if ((quote === '`' || quote === '"') && expression.length > 1 && expression.endsWith(quote)) {
|
|
5
|
+
return expression.slice(1, -1).split(quote + quote).join(quote);
|
|
6
|
+
}
|
|
7
|
+
return expression;
|
|
8
|
+
}
|
|
9
|
+
const GROUP_BY_START = /^GROUP\s+BY\b/i;
|
|
10
|
+
const GROUP_BY_END = /^(?:HAVING|ORDER\s+BY|LIMIT|OFFSET)\b/i;
|
|
11
|
+
/** Index just past the quoted identifier or string at `index`, or `index` when none starts there. */
|
|
12
|
+
function skipQuoted(sql, index) {
|
|
13
|
+
const quote = sql[index];
|
|
14
|
+
if (quote !== '`' && quote !== '"' && quote !== "'")
|
|
15
|
+
return index;
|
|
16
|
+
let i = index + 1;
|
|
17
|
+
while (i < sql.length) {
|
|
18
|
+
if (sql[i] === '\\') {
|
|
19
|
+
i += 2;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (sql[i] === quote) {
|
|
23
|
+
if (sql[i + 1] !== quote)
|
|
24
|
+
return i + 1;
|
|
25
|
+
i += 2;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
return i;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The top-level GROUP BY terms of a statement. Scans rather than matching a
|
|
34
|
+
* regex, so quoted names (`limit`), string literals and nested calls with
|
|
35
|
+
* commas neither end the clause nor split a term.
|
|
36
|
+
*/
|
|
1
37
|
export function extractGroupByExpressions(sql) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
38
|
+
const terms = [];
|
|
39
|
+
let depth = 0;
|
|
40
|
+
let termStart = -1;
|
|
41
|
+
let i = 0;
|
|
42
|
+
while (i < sql.length) {
|
|
43
|
+
const quotedEnd = skipQuoted(sql, i);
|
|
44
|
+
if (quotedEnd > i) {
|
|
45
|
+
i = quotedEnd;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const char = sql[i];
|
|
49
|
+
if (char === '(')
|
|
50
|
+
depth++;
|
|
51
|
+
else if (char === ')')
|
|
52
|
+
depth--;
|
|
53
|
+
else if (depth === 0) {
|
|
54
|
+
const atWord = /[A-Za-z]/.test(char) && (i === 0 || !/[\w$]/.test(sql[i - 1]));
|
|
55
|
+
if (termStart < 0 && atWord) {
|
|
56
|
+
const start = sql.slice(i).match(GROUP_BY_START);
|
|
57
|
+
if (start) {
|
|
58
|
+
i += start[0].length;
|
|
59
|
+
termStart = i;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (termStart >= 0 && atWord && GROUP_BY_END.test(sql.slice(i))) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
else if (termStart >= 0 && char === ',') {
|
|
67
|
+
terms.push(sql.slice(termStart, i));
|
|
68
|
+
termStart = i + 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
i++;
|
|
5
72
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
73
|
+
if (termStart < 0)
|
|
74
|
+
return [];
|
|
75
|
+
terms.push(sql.slice(termStart, i));
|
|
76
|
+
return terms.map(term => term.trim()).filter(Boolean);
|
|
10
77
|
}
|
|
11
78
|
export function validateDerivedCteGrouping(sql, aggregateAliases, intendedGroupBy) {
|
|
12
79
|
const errors = [];
|
|
13
|
-
const actualGroupBy = extractGroupByExpressions(sql);
|
|
80
|
+
const actualGroupBy = extractGroupByExpressions(sql).map(identifierName);
|
|
81
|
+
aggregateAliases = aggregateAliases.map(identifierName);
|
|
82
|
+
intendedGroupBy = intendedGroupBy.map(identifierName);
|
|
14
83
|
if (intendedGroupBy.length === 0 && actualGroupBy.length > 0) {
|
|
15
84
|
errors.push('Derived metric planner emitted GROUP BY for an ungrouped query.');
|
|
16
85
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
|
|
2
|
+
import type { SemanticExpression } from '../semantic-plan.js';
|
|
1
3
|
import type { DerivedMeasureDefinition } from '../types.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Render a formula through the dialect. `resolveRef` maps each formula input
|
|
6
|
+
* to the SQL that reads it, and rejects inputs the formula did not declare.
|
|
7
|
+
* The one formula renderer for derived measures and derived metrics.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formulaExpressionSql(dialect: DatasetSqlDialectRules, expression: SemanticExpression, resolveRef: (name: string) => string): string;
|
|
10
|
+
export declare function derivedExpressionSql(dialect: DatasetSqlDialectRules, definition: DerivedMeasureDefinition, resolve?: (name: string) => string): string;
|
|
11
|
+
export declare function derivedProjection(dialect: DatasetSqlDialectRules, name: string, definition: DerivedMeasureDefinition): string;
|
|
4
12
|
//# sourceMappingURL=derived-measure-sql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derived-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-sql.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"derived-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACnC,MAAM,CAeR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,UAAU,EAAE,wBAAwB,EACpC,OAAO,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAgC,GAC1D,MAAM,CAQR;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,wBAAwB,GAAG,MAAM,CAE7H"}
|
|
@@ -1,49 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Render a formula through the dialect. `resolveRef` maps each formula input
|
|
3
|
+
* to the SQL that reads it, and rejects inputs the formula did not declare.
|
|
4
|
+
* The one formula renderer for derived measures and derived metrics.
|
|
5
|
+
*/
|
|
6
|
+
export function formulaExpressionSql(dialect, expression, resolveRef) {
|
|
3
7
|
switch (expression.kind) {
|
|
4
|
-
case 'ref':
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (expression.value === null)
|
|
13
|
-
return 'NULL';
|
|
14
|
-
if (typeof expression.value === 'number') {
|
|
15
|
-
if (!Number.isFinite(expression.value))
|
|
16
|
-
throw new Error('Formula contains a non-finite number.');
|
|
17
|
-
return String(expression.value);
|
|
18
|
-
}
|
|
19
|
-
if (typeof expression.value === 'boolean')
|
|
20
|
-
return expression.value ? 'true' : 'false';
|
|
21
|
-
return `'${expression.value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
22
|
-
}
|
|
23
|
-
case 'binary': {
|
|
24
|
-
const operators = { add: '+', subtract: '-', multiply: '*', divide: '/' };
|
|
25
|
-
const operator = operators[expression.operator];
|
|
26
|
-
if (!operator)
|
|
27
|
-
throw new Error('Formula contains an unsupported binary operator.');
|
|
28
|
-
return `(${expressionSql(expression.left, uses, resolve)} ${operator} ${expressionSql(expression.right, uses, resolve)})`;
|
|
29
|
-
}
|
|
30
|
-
case 'function': {
|
|
31
|
-
const args = expression.args.map(arg => expressionSql(arg, uses, resolve));
|
|
32
|
-
switch (expression.name) {
|
|
33
|
-
case 'nullIfZero': return `NULLIF(${args[0]}, 0)`;
|
|
34
|
-
case 'coalesce': return `COALESCE(${args.join(', ')})`;
|
|
35
|
-
case 'round': return `ROUND(${args.join(', ')})`;
|
|
36
|
-
case 'floor': return `FLOOR(${args[0]})`;
|
|
37
|
-
case 'ceil': return `CEIL(${args[0]})`;
|
|
38
|
-
default: throw new Error('Formula contains an unsupported function.');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
8
|
+
case 'ref':
|
|
9
|
+
return resolveRef(expression.name);
|
|
10
|
+
case 'literal':
|
|
11
|
+
return dialect.formulaLiteral(expression.value);
|
|
12
|
+
case 'binary':
|
|
13
|
+
return dialect.formulaBinary(expression.operator, formulaExpressionSql(dialect, expression.left, resolveRef), formulaExpressionSql(dialect, expression.right, resolveRef));
|
|
14
|
+
case 'function':
|
|
15
|
+
return dialect.formulaFunction(expression.name, expression.args.map(arg => formulaExpressionSql(dialect, arg, resolveRef)));
|
|
41
16
|
}
|
|
42
17
|
}
|
|
43
|
-
export function derivedExpressionSql(definition, resolve =
|
|
18
|
+
export function derivedExpressionSql(dialect, definition, resolve = dialect.quoteIdentifier) {
|
|
44
19
|
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
45
|
-
return
|
|
20
|
+
return formulaExpressionSql(dialect, definition.formula(aliases).expression, name => {
|
|
21
|
+
if (!Object.hasOwn(definition.uses, name)) {
|
|
22
|
+
throw new Error(`Formula references undeclared input "${name}".`);
|
|
23
|
+
}
|
|
24
|
+
return resolve(definition.uses[name]);
|
|
25
|
+
});
|
|
46
26
|
}
|
|
47
|
-
export function derivedProjection(name, definition) {
|
|
48
|
-
return `${derivedExpressionSql(definition)} AS ${
|
|
27
|
+
export function derivedProjection(dialect, name, definition) {
|
|
28
|
+
return `${derivedExpressionSql(dialect, definition)} AS ${dialect.quoteIdentifier(name)}`;
|
|
49
29
|
}
|