@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The weights a name sorts by: underscore, then digits, then letters
|
|
3
|
+
* compared without case; ties then go lowercase first. This is what
|
|
4
|
+
* `localeCompare` gives RFC 0002 names (ASCII letters, digits, underscores)
|
|
5
|
+
* under the root collation, fixed so it cannot change with the host locale.
|
|
6
|
+
* Python's `portable_name_key` is the same collation.
|
|
7
|
+
*/
|
|
8
|
+
function nameWeights(value) {
|
|
9
|
+
const base = [];
|
|
10
|
+
const caseWeights = [];
|
|
11
|
+
for (const char of value) {
|
|
12
|
+
if (char === '_') {
|
|
13
|
+
base.push(0);
|
|
14
|
+
caseWeights.push(0);
|
|
15
|
+
}
|
|
16
|
+
else if (char >= '0' && char <= '9') {
|
|
17
|
+
base.push(char.charCodeAt(0) - 48 + 1);
|
|
18
|
+
caseWeights.push(0);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const lower = char.toLowerCase();
|
|
22
|
+
base.push(lower.charCodeAt(0) - 97 + 11);
|
|
23
|
+
caseWeights.push(char === lower ? 0 : 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { base, caseWeights };
|
|
27
|
+
}
|
|
28
|
+
function compareWeights(left, right) {
|
|
29
|
+
const length = Math.min(left.length, right.length);
|
|
30
|
+
for (let index = 0; index < length; index += 1) {
|
|
31
|
+
if (left[index] !== right[index])
|
|
32
|
+
return left[index] - right[index];
|
|
33
|
+
}
|
|
34
|
+
return left.length - right.length;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The order protocol artifacts list named things in, so the same definitions
|
|
38
|
+
* produce the same bytes whatever order they were supplied in, on any host.
|
|
39
|
+
*/
|
|
40
|
+
export function byName(left, right) {
|
|
41
|
+
const a = nameWeights(left.name);
|
|
42
|
+
const b = nameWeights(right.name);
|
|
43
|
+
return compareWeights(a.base, b.base) || compareWeights(a.caseWeights, b.caseWeights);
|
|
44
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
|
|
1
2
|
import type { MeasureTimeInterval } from '../types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Shift a point inside a bucket by a calendar interval, relative to the
|
|
@@ -10,9 +11,9 @@ import type { MeasureTimeInterval } from '../types.js';
|
|
|
10
11
|
* the 2024-01-26 week, so a partial bucket's range came out empty or
|
|
11
12
|
* overlapping the next week.
|
|
12
13
|
*
|
|
13
|
-
* The point keeps its own wall-clock time (
|
|
14
|
-
* preserve it) and moves by whole calendar days to the anchor's
|
|
15
|
-
* point's day offset within its bucket.
|
|
14
|
+
* The point keeps its own wall-clock time (calendar subtraction and day
|
|
15
|
+
* addition both preserve it) and moves by whole calendar days to the anchor's
|
|
16
|
+
* day plus the point's day offset within its bucket.
|
|
16
17
|
*/
|
|
17
|
-
export declare function anchoredCalendarShiftSql(point: string, anchor: string, interval: MeasureTimeInterval): string;
|
|
18
|
+
export declare function anchoredCalendarShiftSql(dialect: DatasetSqlDialectRules, point: string, anchor: string, interval: MeasureTimeInterval): string;
|
|
18
19
|
//# sourceMappingURL=calendar-shift-sql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar-shift-sql.d.ts","sourceRoot":"","sources":["../../src/utils/calendar-shift-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"calendar-shift-sql.d.ts","sourceRoot":"","sources":["../../src/utils/calendar-shift-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CAK9I"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { subtractTimeSql } from './time-arithmetic-sql.js';
|
|
2
1
|
/**
|
|
3
2
|
* Shift a point inside a bucket by a calendar interval, relative to the
|
|
4
3
|
* bucket's shifted anchor rather than on its own.
|
|
@@ -10,12 +9,13 @@ import { subtractTimeSql } from './time-arithmetic-sql.js';
|
|
|
10
9
|
* the 2024-01-26 week, so a partial bucket's range came out empty or
|
|
11
10
|
* overlapping the next week.
|
|
12
11
|
*
|
|
13
|
-
* The point keeps its own wall-clock time (
|
|
14
|
-
* preserve it) and moves by whole calendar days to the anchor's
|
|
15
|
-
* point's day offset within its bucket.
|
|
12
|
+
* The point keeps its own wall-clock time (calendar subtraction and day
|
|
13
|
+
* addition both preserve it) and moves by whole calendar days to the anchor's
|
|
14
|
+
* day plus the point's day offset within its bucket.
|
|
16
15
|
*/
|
|
17
|
-
export function anchoredCalendarShiftSql(point, anchor, interval) {
|
|
18
|
-
const shiftedPoint =
|
|
19
|
-
const shiftedAnchor =
|
|
20
|
-
|
|
16
|
+
export function anchoredCalendarShiftSql(dialect, point, anchor, interval) {
|
|
17
|
+
const shiftedPoint = dialect.subtractTime(point, interval.amount, interval.unit);
|
|
18
|
+
const shiftedAnchor = dialect.subtractTime(anchor, interval.amount, interval.unit);
|
|
19
|
+
const days = `${dialect.timeDiff('day', shiftedPoint, shiftedAnchor)} + ${dialect.timeDiff('day', anchor, point)}`;
|
|
20
|
+
return dialect.addTime(shiftedPoint, days, 'day');
|
|
21
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
|
|
2
2
|
/** Preserve the existing ClickHouse spelling and escaping byte for byte. */
|
|
3
|
-
export declare const clickhouseDatasetSqlDialect:
|
|
3
|
+
export declare const clickhouseDatasetSqlDialect: DatasetSqlDialectRules;
|
|
4
4
|
//# sourceMappingURL=clickhouse-dataset-sql-dialect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse-dataset-sql-dialect.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-dataset-sql-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"clickhouse-dataset-sql-dialect.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-dataset-sql-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA4B,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAwClG,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,EAAE,sBA8GP,CAAC"}
|
|
@@ -1,6 +1,145 @@
|
|
|
1
|
-
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
1
|
+
import { quoteSQLIdentifier, stripSqlLiterals } from '../sql-utils.js';
|
|
2
|
+
import { assertAggregateStateFunction, mergeStateExpression } from './aggregate-state-functions.js';
|
|
3
|
+
const TRUNCATE_FUNCTIONS = {
|
|
4
|
+
minute: 'toStartOfMinute',
|
|
5
|
+
hour: 'toStartOfHour',
|
|
6
|
+
day: 'toStartOfDay',
|
|
7
|
+
// Mode 0 (Sunday) is the shipped behaviour; Monday weeks wait for a major.
|
|
8
|
+
week: 'toStartOfWeek',
|
|
9
|
+
month: 'toStartOfMonth',
|
|
10
|
+
quarter: 'toStartOfQuarter',
|
|
11
|
+
year: 'toStartOfYear',
|
|
12
|
+
};
|
|
13
|
+
const INTERVAL_UNITS = {
|
|
14
|
+
minute: 'Minutes', hour: 'Hours', day: 'Days', week: 'Weeks', month: 'Months', quarter: 'Quarters', year: 'Years',
|
|
15
|
+
};
|
|
16
|
+
const BINARY_OPERATORS = {
|
|
17
|
+
add: '+', subtract: '-', multiply: '*', divide: '/',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Statement terminators and comment openers. A raw `sql` value is spliced into
|
|
21
|
+
* a larger expression, so any of these turns one expression into something
|
|
22
|
+
* else: `;` starts a second statement, `--` and `/*` comment out whatever the
|
|
23
|
+
* builder appends after it.
|
|
24
|
+
*/
|
|
25
|
+
const STATEMENT_BREAKERS = /;|--|\/\*/;
|
|
26
|
+
// ClickHouse also treats backslash as a string escape. Doubling quotes without
|
|
27
|
+
// escaping backslashes lets a value ending in \\ before a quote break out of
|
|
28
|
+
// the literal and change the predicate.
|
|
29
|
+
function stringLiteral(value) {
|
|
30
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
31
|
+
}
|
|
2
32
|
/** Preserve the existing ClickHouse spelling and escaping byte for byte. */
|
|
3
33
|
export const clickhouseDatasetSqlDialect = Object.freeze({
|
|
4
34
|
name: 'clickhouse',
|
|
5
35
|
quoteIdentifier: quoteSQLIdentifier,
|
|
36
|
+
// The adapter substitutes positional `?` on the client, one value per marker.
|
|
37
|
+
placeholder: () => '?',
|
|
38
|
+
reusesPlaceholders: false,
|
|
39
|
+
embedsPositionalPieces: true,
|
|
40
|
+
orderTerm: (expression, direction) => direction ? `${expression} ${direction}` : expression,
|
|
41
|
+
paginate: (limit, offset) => `${limit === undefined ? '' : ` LIMIT ${limit}`}${offset === undefined ? '' : ` OFFSET ${offset}`}`,
|
|
42
|
+
quoteQualifiedName: (name) => name.split('.').map(quoteSQLIdentifier).join('.'),
|
|
43
|
+
finalTable: (quotedSource) => `${quotedSource} FINAL`,
|
|
44
|
+
rawSqlExpressionError(sql) {
|
|
45
|
+
let code;
|
|
46
|
+
try {
|
|
47
|
+
// Quoted spans are blanked first, so a terminator inside a literal is data.
|
|
48
|
+
code = stripSqlLiterals(sql);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return 'unterminated-literal';
|
|
52
|
+
}
|
|
53
|
+
return STATEMENT_BREAKERS.test(code) ? 'statement-break' : undefined;
|
|
54
|
+
},
|
|
55
|
+
stringLiteral,
|
|
56
|
+
measureFilterLiteral(value) {
|
|
57
|
+
if (value === null)
|
|
58
|
+
return 'NULL';
|
|
59
|
+
if (typeof value === 'string')
|
|
60
|
+
return stringLiteral(value);
|
|
61
|
+
if (typeof value === 'number') {
|
|
62
|
+
if (!Number.isFinite(value)) {
|
|
63
|
+
throw new Error(`Invalid non-finite numeric literal in measure filter: ${value}`);
|
|
64
|
+
}
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === 'boolean')
|
|
68
|
+
return value ? '1' : '0';
|
|
69
|
+
throw new Error(`Unsupported literal type in measure filter: ${typeof value}`);
|
|
70
|
+
},
|
|
71
|
+
truncateTime: (time, grain) => `${TRUNCATE_FUNCTIONS[grain]}(${time})`,
|
|
72
|
+
addTime: (time, amount, grain) => `add${INTERVAL_UNITS[grain]}(${time}, ${amount})`,
|
|
73
|
+
subtractTime: (time, amount, grain) => `subtract${INTERVAL_UNITS[grain]}(${time}, ${amount})`,
|
|
74
|
+
timeDiff: (grain, start, end) => `dateDiff('${grain}', ${start}, ${end})`,
|
|
75
|
+
toQueryTimezone: (time, timezone) => `toDateTime64(${time}, 9, '${timezone}')`,
|
|
76
|
+
parseTimestamp: (value, timezone) => `parseDateTime64BestEffort(${value}, 9, ${timezone})`,
|
|
77
|
+
timezoneOf: (time) => `timezoneOf(${time})`,
|
|
78
|
+
clockPart: (part, time) => `to${part}(${time})`,
|
|
79
|
+
isDateOnlyType: (value) => `position(toTypeName(${value}), 'Date') > 0 AND position(toTypeName(${value}), 'DateTime') = 0`,
|
|
80
|
+
guard: (condition, message) => `throwIf(${condition}, ${stringLiteral(message)})`,
|
|
81
|
+
ifElse: (condition, then, otherwise) => `if(${condition}, ${then}, ${otherwise})`,
|
|
82
|
+
greatest: (...values) => `greatest(${values.join(', ')})`,
|
|
83
|
+
least: (...values) => `least(${values.join(', ')})`,
|
|
84
|
+
isNotDistinctFrom: (left, right) => `isNotDistinctFrom(${left}, ${right})`,
|
|
85
|
+
isNotNull: (value) => `isNotNull(${value})`,
|
|
86
|
+
assumeNotNull: (value) => `assumeNotNull(${value})`,
|
|
87
|
+
toNullable: (value) => `toNullable(${value})`,
|
|
88
|
+
countIf: (condition) => `countIf(${condition})`,
|
|
89
|
+
seriesIndex: (count) => `arrayJoin(range(toUInt64(${count})))`,
|
|
90
|
+
anyIndex: (index, count, predicate) => `arrayExists(${index} -> ${predicate}, range(toUInt64(${count})))`,
|
|
91
|
+
packRow: (values) => `tuple(${values.join(', ')})`,
|
|
92
|
+
rowElement: (row, position) => `tupleElement(${row}, ${position})`,
|
|
93
|
+
selectAllExcept: (relation, columns) => `${relation}.* EXCEPT (${columns.join(', ')})`,
|
|
94
|
+
asofLeftJoin: 'LEFT ASOF JOIN',
|
|
95
|
+
filteredAggregateInput: (condition, value, fallback) => `if(${condition}, ${value}, ${fallback})`,
|
|
96
|
+
windowAggregate(definition, value, arg) {
|
|
97
|
+
switch (definition.aggregation) {
|
|
98
|
+
case 'count': return `count(${value})`;
|
|
99
|
+
case 'countDistinct': return `uniqExact(${value})`;
|
|
100
|
+
case 'approxCountDistinct': return `uniq(${value})`;
|
|
101
|
+
// -Merge cannot take -OrNull (the state type names the bare function); a
|
|
102
|
+
// window group always has rows, and counts coalesce to 0 downstream.
|
|
103
|
+
case 'merge': return mergeStateExpression(requireStateFunction(definition), value);
|
|
104
|
+
case 'percentile': return `quantileOrNull(${definition.level})(${value})`;
|
|
105
|
+
case 'argMax': return `argMaxOrNull(${value}, ${arg})`;
|
|
106
|
+
case 'argMin': return `argMinOrNull(${value}, ${arg})`;
|
|
107
|
+
case 'stddev': return `stddevSampOrNull(${value})`;
|
|
108
|
+
case 'variance': return `varSampOrNull(${value})`;
|
|
109
|
+
default: return `${definition.aggregation}OrNull(${value})`;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
reaggregate: (aggregation, value) => `${aggregation}OrNull(${value})`,
|
|
113
|
+
formulaLiteral(value) {
|
|
114
|
+
if (value === null)
|
|
115
|
+
return 'NULL';
|
|
116
|
+
if (typeof value === 'number') {
|
|
117
|
+
if (!Number.isFinite(value))
|
|
118
|
+
throw new Error('Formula contains a non-finite number.');
|
|
119
|
+
return String(value);
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'boolean')
|
|
122
|
+
return value ? 'true' : 'false';
|
|
123
|
+
return stringLiteral(value);
|
|
124
|
+
},
|
|
125
|
+
formulaBinary(operator, left, right) {
|
|
126
|
+
const symbol = BINARY_OPERATORS[operator];
|
|
127
|
+
if (!symbol)
|
|
128
|
+
throw new Error('Formula contains an unsupported binary operator.');
|
|
129
|
+
return `(${left} ${symbol} ${right})`;
|
|
130
|
+
},
|
|
131
|
+
formulaFunction(name, args) {
|
|
132
|
+
switch (name) {
|
|
133
|
+
case 'nullIfZero': return `NULLIF(${args[0]}, 0)`;
|
|
134
|
+
case 'coalesce': return `COALESCE(${args.join(', ')})`;
|
|
135
|
+
case 'round': return `ROUND(${args.join(', ')})`;
|
|
136
|
+
case 'floor': return `FLOOR(${args[0]})`;
|
|
137
|
+
case 'ceil': return `CEIL(${args[0]})`;
|
|
138
|
+
default: throw new Error('Formula contains an unsupported function.');
|
|
139
|
+
}
|
|
140
|
+
},
|
|
6
141
|
});
|
|
142
|
+
function requireStateFunction(definition) {
|
|
143
|
+
assertAggregateStateFunction(definition.stateFunction, 'A merge measure');
|
|
144
|
+
return definition.stateFunction;
|
|
145
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile-dataset-query.d.ts","sourceRoot":"","sources":["../../src/utils/compile-dataset-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAA2D,MAAM,2BAA2B,CAAC;AAC7H,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"compile-dataset-query.d.ts","sourceRoot":"","sources":["../../src/utils/compile-dataset-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAA2D,MAAM,2BAA2B,CAAC;AAC7H,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACzC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;CAC/C;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,IAAI,GAAE,WAAW,GAAG,SAAuB,GAC1C,oBAAoB,CA+DtB"}
|
|
@@ -7,6 +7,7 @@ import { overfetchLimit } from './pagination.js';
|
|
|
7
7
|
import { resolveResultLimit } from './result-limits.js';
|
|
8
8
|
import { buildTimeMeasureDatasetSql } from './time-measure-dataset-sql.js';
|
|
9
9
|
import { selectedTimeMeasures } from './time-query-measures.js';
|
|
10
|
+
import { renderStatement } from './statement-parameters.js';
|
|
10
11
|
/** The one dispatch point for dataset preview and query-builder execution. */
|
|
11
12
|
export function prepareDatasetQuery(dataset, input, options, mode = 'execution') {
|
|
12
13
|
const query = structuredClone({
|
|
@@ -25,17 +26,19 @@ export function prepareDatasetQuery(dataset, input, options, mode = 'execution')
|
|
|
25
26
|
const abortSignal = options.context?.abortSignal;
|
|
26
27
|
if (selectedTimeMeasures(dataset, query).size) {
|
|
27
28
|
plan = 'time';
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const planned = buildTimeMeasureDatasetSql(dataset, query, executionOptions);
|
|
30
|
+
const main = renderStatement(planned.statement);
|
|
31
|
+
const preflight = renderStatement(planned.preflight);
|
|
32
|
+
statement = main;
|
|
33
|
+
preflightStatements.push(preflight);
|
|
31
34
|
execute = async () => {
|
|
32
|
-
await options.builderFactory.rawQuery(
|
|
33
|
-
return options.builderFactory.rawQuery(
|
|
35
|
+
await options.builderFactory.rawQuery(preflight.sql, preflight.parameters, { abortSignal });
|
|
36
|
+
return options.builderFactory.rawQuery(main.sql, main.parameters, { abortSignal });
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
else if (needsOuterDatasetQuery(dataset, query)) {
|
|
37
40
|
plan = hasSelectedDerivedMeasure(dataset, query) ? 'derived' : 'aggregate';
|
|
38
|
-
statement = buildDerivedDatasetSql(dataset, query, executionOptions, buildDatasetQueryBuilder);
|
|
41
|
+
statement = renderStatement(buildDerivedDatasetSql(dataset, query, executionOptions, buildDatasetQueryBuilder));
|
|
39
42
|
execute = () => options.builderFactory.rawQuery(statement.sql, statement.parameters, { abortSignal });
|
|
40
43
|
}
|
|
41
44
|
else {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
|
|
2
2
|
import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
|
|
3
3
|
import type { TimeMeasureAxis } from './time-measure-axis.js';
|
|
4
|
+
import type { StatementParameters } from './statement-parameters.js';
|
|
5
|
+
import type { PlannedTimeMeasureStatements } from './time-measure-dataset-sql.js';
|
|
4
6
|
/** Aggregate each dependency in its evaluation context, then evaluate formulas. */
|
|
5
|
-
export declare function buildCompositeTimeMeasureSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, axis: TimeMeasureAxis):
|
|
6
|
-
sql: string;
|
|
7
|
-
parameters: unknown[];
|
|
8
|
-
timeAxisSql: string;
|
|
9
|
-
};
|
|
7
|
+
export declare function buildCompositeTimeMeasureSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, axis: TimeMeasureAxis, parameters: StatementParameters): PlannedTimeMeasureStatements;
|
|
10
8
|
//# sourceMappingURL=composite-time-measure-sql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite-time-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/composite-time-measure-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"composite-time-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/composite-time-measure-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAS9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAElF,mFAAmF;AACnF,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,mBAAmB,GAC9B,4BAA4B,CAgD9B"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { queryMeasureDefinitions } from './relationship-measures.js';
|
|
2
|
-
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
3
2
|
import { isDerivedMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
4
3
|
import { buildMeasureEvaluationGraph, evaluationFormula } from './measure-evaluation-graph.js';
|
|
5
4
|
import { buildTimeMeasureSourceSql } from './time-measure-source-sql.js';
|
|
@@ -9,17 +8,18 @@ import { timeMeasureLeafSql } from './time-measure-leaf-sql.js';
|
|
|
9
8
|
import { derivedProjection } from './derived-measure-sql.js';
|
|
10
9
|
import { buildTimeMeasureResultSql } from './time-measure-result-sql.js';
|
|
11
10
|
/** Aggregate each dependency in its evaluation context, then evaluate formulas. */
|
|
12
|
-
export function buildCompositeTimeMeasureSql(ds, query, options, axis) {
|
|
11
|
+
export function buildCompositeTimeMeasureSql(ds, query, options, axis, parameters) {
|
|
12
|
+
const { dialect } = parameters;
|
|
13
13
|
const graph = buildMeasureEvaluationGraph(queryMeasureDefinitions(ds, query.measures ?? []), query.measures ?? []);
|
|
14
14
|
const leaves = graph.nodes.filter(node => !isDerivedMeasure(node.definition));
|
|
15
15
|
const baseNames = new Set(leaves.map(node => isWindowMeasure(node.definition) ? node.definition.measure : node.name));
|
|
16
|
-
const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options);
|
|
17
|
-
const timeAxis = buildTimeMeasureAxisSql(source, axis);
|
|
18
|
-
const contexts = timeMeasureContextCtes(graph.contexts, axis);
|
|
16
|
+
const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options, parameters);
|
|
17
|
+
const timeAxis = buildTimeMeasureAxisSql(parameters, source, axis);
|
|
18
|
+
const contexts = timeMeasureContextCtes(dialect, graph.contexts, axis);
|
|
19
19
|
const inputs = leaves.map(node => {
|
|
20
20
|
const window = isWindowMeasure(node.definition) ? node.definition : undefined;
|
|
21
21
|
const base = source.bases.find(base => base.name === (window?.measure ?? node.name));
|
|
22
|
-
return { node, ...timeMeasureLeafSql(node.id, node.context, base, window, axis, source.dimensions) };
|
|
22
|
+
return { node, ...timeMeasureLeafSql(dialect, node.id, node.context, base, window, axis, source.dimensions) };
|
|
23
23
|
});
|
|
24
24
|
const keys = source.dimensions.map(dim => dim.alias);
|
|
25
25
|
const combinations = keys.length ? ` CROSS JOIN (SELECT DISTINCT ${keys.join(', ')} FROM (${inputs.map(input => `SELECT ${keys.join(', ')} FROM ${input.population}`).join(' UNION ALL ')})) AS _hq_combinations` : '';
|
|
@@ -33,29 +33,28 @@ export function buildCompositeTimeMeasureSql(ds, query, options, axis) {
|
|
|
33
33
|
const next = `_hq_evaluated${node.id}`;
|
|
34
34
|
const value = input.count ? `coalesce(m.${output}, 0)` : `m.${output}`;
|
|
35
35
|
if (input.cumulative) {
|
|
36
|
-
const partition = keys.length ?
|
|
36
|
+
const partition = dialect.packRow(keys.length ? keys.map(key => `s.${key}`) : ['0']);
|
|
37
37
|
const lookup = `_hq_lookup${node.id}`;
|
|
38
38
|
ctes.push(`${lookup} AS (SELECT s.*, c._hq_eval_end, ${partition} AS _hq_partition FROM ${previous} AS s INNER JOIN _hq_context${input.context} AS c ON s._hq_period = c._hq_period)`);
|
|
39
|
-
ctes.push(`${next} AS (SELECT s
|
|
39
|
+
ctes.push(`${next} AS (SELECT ${dialect.selectAllExcept('s', ['_hq_eval_end', '_hq_partition'])}, ${value} AS ${output} FROM ${lookup} AS s ${dialect.asofLeftJoin} ${input.aggregate} AS m ON s._hq_partition = m._hq_partition AND s._hq_eval_end > m._hq_time)`);
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
const condition = ['s._hq_period = m._hq_period', ...keys.map(key =>
|
|
42
|
+
const condition = ['s._hq_period = m._hq_period', ...keys.map(key => dialect.isNotDistinctFrom(`s.${key}`, `m.${key}`))];
|
|
43
43
|
ctes.push(`${next} AS (SELECT s.*, ${value} AS ${output} FROM ${previous} AS s LEFT JOIN ${input.aggregate} AS m ON ${condition.join(' AND ')})`);
|
|
44
44
|
}
|
|
45
45
|
previous = next;
|
|
46
46
|
}
|
|
47
47
|
for (const node of graph.nodes.filter(node => isDerivedMeasure(node.definition))) {
|
|
48
48
|
const next = `_hq_formula${node.id}`;
|
|
49
|
-
ctes.push(`${next} AS (SELECT *, ${derivedProjection(`_hq_value${node.id}`, evaluationFormula(node))} FROM ${previous})`);
|
|
49
|
+
ctes.push(`${next} AS (SELECT *, ${derivedProjection(dialect, `_hq_value${node.id}`, evaluationFormula(node))} FROM ${previous})`);
|
|
50
50
|
previous = next;
|
|
51
51
|
}
|
|
52
|
-
const projections = ['v._hq_period AS period', ...source.dimensions.map(dim => `v.${dim.alias} AS ${
|
|
53
|
-
...Object.entries(graph.outputs).map(([name, id]) => `v._hq_value${id} AS ${
|
|
52
|
+
const projections = ['v._hq_period AS period', ...source.dimensions.map(dim => `v.${dim.alias} AS ${dialect.quoteIdentifier(dim.name)}`),
|
|
53
|
+
...Object.entries(graph.outputs).map(([name, id]) => `v._hq_value${id} AS ${dialect.quoteIdentifier(name)}`)];
|
|
54
54
|
ctes.push(`_hq_values AS (SELECT ${projections.join(', ')} FROM ${previous} AS v)`);
|
|
55
|
-
const guard = [timeAxis.guard, timeAxis.rangeGuard, ...graph.contexts.map(context => timeMeasureContextGuard(context, axis)), ...inputs.map(input => input.guard)].join(' + ');
|
|
55
|
+
const guard = [timeAxis.guard, timeAxis.rangeGuard, ...graph.contexts.map(context => timeMeasureContextGuard(dialect, context, axis)), ...inputs.map(input => input.guard)].join(' + ');
|
|
56
56
|
return {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
timeAxisSql: `WITH ${guardCtes.join(',\n')} SELECT ${guard} AS _hq_validated FROM _hq_bounds`,
|
|
57
|
+
statement: { text: buildTimeMeasureResultSql(ds, query, options, source.dimensions, ctes, guard, true), parameters },
|
|
58
|
+
preflight: { text: `WITH ${guardCtes.join(',\n')} SELECT ${guard} AS _hq_validated FROM _hq_bounds`, parameters },
|
|
60
59
|
};
|
|
61
60
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"count-aggregations.d.ts","sourceRoot":"","sources":["../../src/utils/count-aggregations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAGzE"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { isCountStateMerge } from './aggregate-state-functions.js';
|
|
2
|
+
/** Counts are 0, not NULL, over an empty population. */
|
|
3
|
+
export function isCountAggregation(definition) {
|
|
4
|
+
return ['count', 'countDistinct', 'approxCountDistinct'].includes(definition.aggregation)
|
|
5
|
+
|| isCountStateMerge(definition);
|
|
6
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-definition-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-definition-validation.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,wBAAwB,EACxB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"dataset-definition-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-definition-validation.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EAGnB,sBAAsB,EAEvB,MAAM,aAAa,CAAC;AAWrB,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACzD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAC5D,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AA2R/D;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,CAAC,aAAa,EAAE,WAAW,EAAE,gBAAgB,CAAC,GAClE,IAAI,CA+DN"}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { validateShiftMeasures } from './shift-measure-validation.js';
|
|
2
2
|
import { escapeRegExp, isSafeSQLIdentifier, stripSqlLiterals } from '../sql-utils.js';
|
|
3
|
+
import { clickhouseDatasetSqlDialect } from './clickhouse-dataset-sql-dialect.js';
|
|
4
|
+
import { rawSqlErrorMessage } from './raw-sql-dialect-check.js';
|
|
3
5
|
import { SEMANTIC_FILTER_OPERATORS, isSemanticFilterOperator } from '../constants.js';
|
|
4
6
|
import { validateDerivedMeasures } from './derived-measure-validation.js';
|
|
5
7
|
import { isDerivedMeasure, isWindowMeasure, isShiftMeasure } from './dataset-measures.js';
|
|
6
8
|
import { validateWindowMeasures } from './window-measure-validation.js';
|
|
7
9
|
import { validateDatasetAgentMetadata } from './semantic-metadata-validation.js';
|
|
8
|
-
|
|
9
|
-
* Statement terminators and comment openers.
|
|
10
|
-
*
|
|
11
|
-
* A raw `sql` value is spliced into a larger expression, so any of these turns
|
|
12
|
-
* one expression into something else: `;` starts a second statement, `--` and
|
|
13
|
-
* `/*` comment out whatever the builder appends after it.
|
|
14
|
-
*/
|
|
15
|
-
const STATEMENT_BREAKERS = /;|--|\/\*/;
|
|
10
|
+
import { AGGREGATE_STATE_FUNCTIONS, isAggregateStateFunction } from './aggregate-state-functions.js';
|
|
16
11
|
/** Dataset, dimension and measure names address fields as `<relationship>.<dimension>`. */
|
|
17
12
|
const QUALIFIED_SEPARATOR = '.';
|
|
18
13
|
function fail(datasetName, message) {
|
|
@@ -63,24 +58,16 @@ function validateRawSql(datasetName, kind, name, sql, dependencies) {
|
|
|
63
58
|
if (sql.trim().length === 0) {
|
|
64
59
|
fail(datasetName, `${kind} "${name}" declares an empty sql expression.`);
|
|
65
60
|
}
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// rejection: left as data, an open quote
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
fail(datasetName, `${kind} "${name}" sql has an unterminated quoted literal.`);
|
|
79
|
-
}
|
|
80
|
-
if (STATEMENT_BREAKERS.test(code)) {
|
|
81
|
-
fail(datasetName, `${kind} "${name}" sql must be a single expression without statement terminators or ` +
|
|
82
|
-
'comments (";", "--", "/*") outside a quoted literal.');
|
|
83
|
-
}
|
|
61
|
+
// Definitions are engine-neutral, so this checks ClickHouse lexical rules;
|
|
62
|
+
// compilation re-checks under any other engine's rules (raw-sql-dialect-check).
|
|
63
|
+
// An unterminated quote is itself a rejection: left as data, an open quote
|
|
64
|
+
// would hide everything after it.
|
|
65
|
+
const lexicalError = clickhouseDatasetSqlDialect.rawSqlExpressionError(sql);
|
|
66
|
+
if (lexicalError)
|
|
67
|
+
fail(datasetName, rawSqlErrorMessage(kind, name, lexicalError));
|
|
68
|
+
// A quoted identifier is a column reference, so the dependency check below
|
|
69
|
+
// needs its text even though the terminator check above must not see it.
|
|
70
|
+
const referenceable = stripSqlLiterals(sql, { keepQuotedIdentifiers: true });
|
|
84
71
|
for (const dependency of dependencies ?? []) {
|
|
85
72
|
// Dependencies are qualified identifiers (`analytics.orders.amount`); the
|
|
86
73
|
// expression references the column itself, so the final segment is what has
|
|
@@ -176,6 +163,9 @@ function validateMeasures(datasetName, measures, dimensions, declaredFilters) {
|
|
|
176
163
|
validateRawSql(datasetName, 'measure', name, definition.sql, definition.dependencies);
|
|
177
164
|
}
|
|
178
165
|
validateMeasureFilters(datasetName, name, definition.filters ?? [], dimensions, declaredFilters);
|
|
166
|
+
if (definition.aggregation === 'merge') {
|
|
167
|
+
validateMergeMeasure(datasetName, name, definition);
|
|
168
|
+
}
|
|
179
169
|
// `field` and `argField` name either a declared dimension or a physical
|
|
180
170
|
// column that the model deliberately does not expose (the `allowHiddenField`
|
|
181
171
|
// case in metric validation). Either way the value reaches SQL, so a name
|
|
@@ -191,6 +181,23 @@ function validateMeasures(datasetName, measures, dimensions, declaredFilters) {
|
|
|
191
181
|
}
|
|
192
182
|
}
|
|
193
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* A merge measure names its state function (interpolated into SQL as
|
|
186
|
+
* `<fn>Merge`) and reads a plain column: raw SQL or filters would wrap the
|
|
187
|
+
* state column in an expression the `-Merge` combinator rejects.
|
|
188
|
+
*/
|
|
189
|
+
function validateMergeMeasure(datasetName, name, definition) {
|
|
190
|
+
if (!isAggregateStateFunction(definition.stateFunction)) {
|
|
191
|
+
fail(datasetName, `measure "${name}" merges unsupported aggregate state "${String(definition.stateFunction)}". ` +
|
|
192
|
+
`Supported state functions: ${AGGREGATE_STATE_FUNCTIONS.join(', ')}.`);
|
|
193
|
+
}
|
|
194
|
+
if (definition.filters?.length) {
|
|
195
|
+
fail(datasetName, `measure "${name}" cannot have filters: merge measures read stored states.`);
|
|
196
|
+
}
|
|
197
|
+
if (definition.sql !== undefined) {
|
|
198
|
+
fail(datasetName, `measure "${name}" cannot use sql: merge measures read a stored state column.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
194
201
|
function validateLimits(datasetName, limits) {
|
|
195
202
|
if (!limits) {
|
|
196
203
|
return;
|
|
@@ -226,6 +233,9 @@ export function validateDatasetDefinition(name, config) {
|
|
|
226
233
|
fail(name, `source "${config.source}" is not a safe table identifier. Expected "table" or ` +
|
|
227
234
|
'"database.table", each segment containing only letters, numbers and underscores.');
|
|
228
235
|
}
|
|
236
|
+
if (config.final !== undefined && typeof config.final !== 'boolean') {
|
|
237
|
+
fail(name, 'final must be true or false.');
|
|
238
|
+
}
|
|
229
239
|
// The tenant key is the isolation boundary: it becomes a WHERE predicate on
|
|
230
240
|
// every query against a tenant-scoped dataset, so it is checked here rather
|
|
231
241
|
// than trusted wherever that predicate is assembled.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnyDatasetInstance, DatasetQuery } from '../types.js';
|
|
2
2
|
import type { QueryBuilderLike } from '../query-builder-protocol.js';
|
|
3
3
|
import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
|
|
4
|
+
import { type PlannedStatement } from './statement-parameters.js';
|
|
4
5
|
type BuildBaseQuery = (dataset: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions) => QueryBuilderLike;
|
|
5
6
|
export declare function hasSelectedDerivedMeasure(ds: AnyDatasetInstance, query: DatasetQuery): boolean;
|
|
6
7
|
/**
|
|
@@ -8,9 +9,6 @@ export declare function hasSelectedDerivedMeasure(ds: AnyDatasetInstance, query:
|
|
|
8
9
|
* measures are computed there, and `having` conditions filter there.
|
|
9
10
|
*/
|
|
10
11
|
export declare function needsOuterDatasetQuery(ds: AnyDatasetInstance, query: DatasetQuery): boolean;
|
|
11
|
-
export declare function buildDerivedDatasetSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, buildBaseQuery: BuildBaseQuery):
|
|
12
|
-
sql: string;
|
|
13
|
-
parameters: unknown[];
|
|
14
|
-
};
|
|
12
|
+
export declare function buildDerivedDatasetSql(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, buildBaseQuery: BuildBaseQuery): PlannedStatement;
|
|
15
13
|
export {};
|
|
16
14
|
//# sourceMappingURL=dataset-derived-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-derived-query.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"dataset-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-derived-query.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEvF,KAAK,cAAc,GAAG,CACpB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,KAClC,gBAAgB,CAAC;AAEtB,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAE9F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAE3F;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,cAAc,EAAE,cAAc,GAC7B,gBAAgB,CAmDlB"}
|
|
@@ -6,6 +6,7 @@ import { baseMeasureNames, getBaseMeasure, getDerivedMeasure } from './dataset-m
|
|
|
6
6
|
import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
|
|
7
7
|
import { validateDatasetQueryInput } from './dataset-query-validation.js';
|
|
8
8
|
import { datasetHavingSql } from './dataset-having.js';
|
|
9
|
+
import { StatementParameters } from './statement-parameters.js';
|
|
9
10
|
export function hasSelectedDerivedMeasure(ds, query) {
|
|
10
11
|
return (query.measures ?? []).some(name => getDerivedMeasure(ds.measures, name) !== undefined);
|
|
11
12
|
}
|
|
@@ -26,7 +27,7 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
26
27
|
const baseMeasures = measureDependencyNames(queryMeasureDefinitions(ds, selected), selected).filter(name => name.includes('.') || getBaseMeasure(ds.measures, name));
|
|
27
28
|
const resolve = (name) => {
|
|
28
29
|
const derived = getDerivedMeasure(ds.measures, name);
|
|
29
|
-
return derived ? `(${derivedExpressionSql(derived, resolve)})` : dialect.quoteIdentifier(name);
|
|
30
|
+
return derived ? `(${derivedExpressionSql(dialect, derived, resolve)})` : dialect.quoteIdentifier(name);
|
|
30
31
|
};
|
|
31
32
|
const inner = buildBaseQuery(ds, {
|
|
32
33
|
...query,
|
|
@@ -36,7 +37,8 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
36
37
|
limit: undefined,
|
|
37
38
|
offset: undefined,
|
|
38
39
|
}, { ...options, executionLimit: undefined, skipDefaultOrderBy: true });
|
|
39
|
-
const
|
|
40
|
+
const parameters = new StatementParameters(dialect);
|
|
41
|
+
const base = parameters.embedBuilder(inner);
|
|
40
42
|
const projections = [];
|
|
41
43
|
if (query.by)
|
|
42
44
|
projections.push(dialect.quoteIdentifier('period'));
|
|
@@ -45,27 +47,22 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
45
47
|
for (const name of selected) {
|
|
46
48
|
const derived = getDerivedMeasure(ds.measures, name);
|
|
47
49
|
projections.push(derived
|
|
48
|
-
? `${derivedExpressionSql(derived, resolve)} AS ${dialect.quoteIdentifier(name)}`
|
|
50
|
+
? `${derivedExpressionSql(dialect, derived, resolve)} AS ${dialect.quoteIdentifier(name)}`
|
|
49
51
|
: dialect.quoteIdentifier(name));
|
|
50
52
|
}
|
|
51
|
-
let
|
|
53
|
+
let text = `WITH base AS (${base}) SELECT ${projections.join(', ')} FROM base`;
|
|
52
54
|
// Conditions read the same expression the projection does, never the output
|
|
53
55
|
// alias, so the predicate stays valid on dialects without alias resolution.
|
|
54
56
|
if (query.having?.length) {
|
|
55
|
-
|
|
56
|
-
sql += ` WHERE ${having.sql}`;
|
|
57
|
-
parameters.push(...having.parameters);
|
|
57
|
+
text += ` WHERE ${datasetHavingSql(query.having, resolve, parameters)}`;
|
|
58
58
|
}
|
|
59
59
|
if (query.orderBy?.length) {
|
|
60
|
-
|
|
60
|
+
text += ` ORDER BY ${query.orderBy.map(order => (dialect.orderTerm(dialect.quoteIdentifier(order.field), semanticOrderDirection(order.direction)))).join(', ')}`;
|
|
61
61
|
}
|
|
62
62
|
else if (query.by) {
|
|
63
|
-
|
|
63
|
+
text += ` ORDER BY ${dialect.orderTerm(dialect.quoteIdentifier('period'), 'ASC')}`;
|
|
64
64
|
}
|
|
65
65
|
const limit = options.executionLimit ?? query.limit;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (query.offset !== undefined)
|
|
69
|
-
sql += ` OFFSET ${query.offset}`;
|
|
70
|
-
return { sql, parameters };
|
|
66
|
+
text += dialect.paginate(limit, query.offset);
|
|
67
|
+
return { text, parameters };
|
|
71
68
|
}
|