@hypequery/datasets 0.21.0 → 0.22.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 +6 -0
- package/dist/api.type-test.js +7 -0
- package/dist/catalog.d.ts +4 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +10 -1
- package/dist/dataset-compilation.d.ts +45 -0
- package/dist/dataset-compilation.d.ts.map +1 -0
- package/dist/dataset-compilation.js +1 -0
- package/dist/dataset-query.d.ts +3 -3
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +7 -71
- package/dist/dataset-sql-dialect.d.ts +11 -0
- package/dist/dataset-sql-dialect.d.ts.map +1 -0
- package/dist/dataset-sql-dialect.js +1 -0
- package/dist/dataset.d.ts +3 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/execution-capabilities.d.ts +28 -0
- package/dist/execution-capabilities.d.ts.map +1 -0
- package/dist/execution-capabilities.js +17 -0
- package/dist/execution-capabilities.json +13 -0
- package/dist/executor.d.ts +17 -38
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +37 -400
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/measure.d.ts +54 -17
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +3 -63
- package/dist/metric-query-engine.d.ts +29 -0
- package/dist/metric-query-engine.d.ts.map +1 -0
- package/dist/metric-query-engine.js +106 -0
- package/dist/portable-executor.d.ts +20 -6
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +29 -9
- package/dist/query-builder-protocol.d.ts +4 -0
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-helpers.d.ts +10 -10
- package/dist/query-helpers.d.ts.map +1 -1
- package/dist/query-helpers.js +1 -3
- package/dist/query-planner.d.ts +7 -3
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +10 -12
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +2 -0
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +3 -3
- package/dist/types.d.ts +18 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +1 -0
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +2 -1
- package/dist/utils/base-measure-definition.d.ts +23 -0
- package/dist/utils/base-measure-definition.d.ts.map +1 -0
- package/dist/utils/base-measure-definition.js +65 -0
- package/dist/utils/build-dataset-query-builder.d.ts +5 -0
- package/dist/utils/build-dataset-query-builder.d.ts.map +1 -0
- package/dist/utils/build-dataset-query-builder.js +62 -0
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +4 -0
- package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -0
- package/dist/utils/clickhouse-dataset-sql-dialect.js +6 -0
- package/dist/utils/compile-dataset-query.d.ts +10 -0
- package/dist/utils/compile-dataset-query.d.ts.map +1 -0
- package/dist/utils/compile-dataset-query.js +71 -0
- package/dist/utils/composite-time-measure-sql.d.ts.map +1 -1
- package/dist/utils/composite-time-measure-sql.js +2 -1
- package/dist/utils/dataset-derived-query.d.ts +1 -2
- package/dist/utils/dataset-derived-query.d.ts.map +1 -1
- package/dist/utils/dataset-derived-query.js +12 -26
- package/dist/utils/dataset-filter-types.d.ts +28 -0
- package/dist/utils/dataset-filter-types.d.ts.map +1 -0
- package/dist/utils/dataset-filter-types.js +1 -0
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +15 -3
- package/dist/utils/dataset-sql-dialect.d.ts +5 -0
- package/dist/utils/dataset-sql-dialect.d.ts.map +1 -0
- package/dist/utils/dataset-sql-dialect.js +5 -0
- package/dist/utils/dataset-target.d.ts +3 -0
- package/dist/utils/dataset-target.d.ts.map +1 -0
- package/dist/utils/dataset-target.js +3 -0
- package/dist/utils/filter-definition.d.ts +3 -0
- package/dist/utils/filter-definition.d.ts.map +1 -0
- package/dist/utils/filter-definition.js +3 -0
- package/dist/utils/metric-derived-query.d.ts +8 -0
- package/dist/utils/metric-derived-query.d.ts.map +1 -0
- package/dist/utils/metric-derived-query.js +106 -0
- package/dist/utils/metric-query-builder.d.ts +5 -0
- package/dist/utils/metric-query-builder.d.ts.map +1 -0
- package/dist/utils/metric-query-builder.js +50 -0
- package/dist/utils/metric-query-result.d.ts +10 -0
- package/dist/utils/metric-query-result.d.ts.map +1 -0
- package/dist/utils/metric-query-result.js +18 -0
- package/dist/utils/metric-query-validation.d.ts +6 -0
- package/dist/utils/metric-query-validation.d.ts.map +1 -0
- package/dist/utils/metric-query-validation.js +116 -0
- package/dist/utils/relationship-builder-plan.d.ts +4 -2
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +10 -13
- package/dist/utils/relationship-measure-sql.d.ts +14 -0
- package/dist/utils/relationship-measure-sql.d.ts.map +1 -0
- package/dist/utils/relationship-measure-sql.js +44 -0
- package/dist/utils/relationship-measure-types.d.ts +36 -0
- package/dist/utils/relationship-measure-types.d.ts.map +1 -0
- package/dist/utils/relationship-measure-types.js +1 -0
- package/dist/utils/relationship-measures.d.ts +23 -0
- package/dist/utils/relationship-measures.d.ts.map +1 -0
- package/dist/utils/relationship-measures.js +62 -0
- package/dist/utils/relationship-validation.d.ts +1 -1
- package/dist/utils/relationship-validation.d.ts.map +1 -1
- package/dist/utils/relationship-validation.js +3 -5
- package/dist/utils/semantic-query-syntax.d.ts +6 -0
- package/dist/utils/semantic-query-syntax.d.ts.map +1 -0
- package/dist/utils/semantic-query-syntax.js +25 -0
- package/dist/utils/snapshot-execution-context.d.ts +4 -0
- package/dist/utils/snapshot-execution-context.d.ts.map +1 -0
- package/dist/utils/snapshot-execution-context.js +16 -0
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-dataset-sql.js +3 -2
- package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-result-sql.js +2 -1
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
- package/dist/utils/time-measure-source-sql.js +8 -4
- package/dist/validation.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { queryMeasureDefinitions } from './relationship-measures.js';
|
|
2
|
+
import { buildDatasetQueryBuilder } from './build-dataset-query-builder.js';
|
|
3
|
+
import { baseMeasureNames } from './dataset-measures.js';
|
|
4
|
+
import { buildDerivedDatasetSql, hasSelectedDerivedMeasure } from './dataset-derived-query.js';
|
|
5
|
+
import { measureDependencyNames } from './measure-dependencies.js';
|
|
6
|
+
import { overfetchLimit } from './pagination.js';
|
|
7
|
+
import { resolveResultLimit } from './result-limits.js';
|
|
8
|
+
import { buildTimeMeasureDatasetSql } from './time-measure-dataset-sql.js';
|
|
9
|
+
import { selectedTimeMeasures } from './time-query-measures.js';
|
|
10
|
+
/** The one dispatch point for dataset preview and query-builder execution. */
|
|
11
|
+
export function prepareDatasetQuery(dataset, input, options, mode = 'execution') {
|
|
12
|
+
const query = structuredClone({
|
|
13
|
+
...input,
|
|
14
|
+
limit: mode === 'execution' ? resolveResultLimit(input.limit, dataset.limits).limit : input.limit,
|
|
15
|
+
});
|
|
16
|
+
// Preserve omission while validating/planning: maxMeasures constrains an
|
|
17
|
+
// explicit selection, not the dataset's default set of base measures.
|
|
18
|
+
const measures = query.measures ?? baseMeasureNames(dataset.measures);
|
|
19
|
+
const executionLimit = mode === 'execution' ? overfetchLimit(query.limit) : query.limit;
|
|
20
|
+
const executionOptions = { ...options, executionLimit };
|
|
21
|
+
const preflightStatements = [];
|
|
22
|
+
let statement;
|
|
23
|
+
let execute;
|
|
24
|
+
let plan;
|
|
25
|
+
const abortSignal = options.context?.abortSignal;
|
|
26
|
+
if (selectedTimeMeasures(dataset, query).size) {
|
|
27
|
+
plan = 'time';
|
|
28
|
+
const compiled = buildTimeMeasureDatasetSql(dataset, query, executionOptions);
|
|
29
|
+
statement = compiled;
|
|
30
|
+
preflightStatements.push({ sql: compiled.timeAxisSql, parameters: compiled.parameters });
|
|
31
|
+
execute = async () => {
|
|
32
|
+
await options.builderFactory.rawQuery(compiled.timeAxisSql, compiled.parameters, { abortSignal });
|
|
33
|
+
return options.builderFactory.rawQuery(compiled.sql, compiled.parameters, { abortSignal });
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
else if (hasSelectedDerivedMeasure(dataset, query)) {
|
|
37
|
+
plan = 'derived';
|
|
38
|
+
statement = buildDerivedDatasetSql(dataset, query, executionOptions, buildDatasetQueryBuilder);
|
|
39
|
+
execute = () => options.builderFactory.rawQuery(statement.sql, statement.parameters, { abortSignal });
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
plan = 'aggregate';
|
|
43
|
+
const builder = buildDatasetQueryBuilder(dataset, query, executionOptions);
|
|
44
|
+
statement = builder.toSQLWithParams();
|
|
45
|
+
// Retain builder execution: custom builders may carry adapter settings.
|
|
46
|
+
execute = () => builder.execute({ abortSignal });
|
|
47
|
+
}
|
|
48
|
+
const description = Object.freeze({
|
|
49
|
+
kind: 'dataset-compilation', version: 1, dataset: dataset.name, plan,
|
|
50
|
+
dimensions: Object.freeze([...(query.dimensions ?? [])]),
|
|
51
|
+
measures: Object.freeze([...measures]),
|
|
52
|
+
measureDependencies: Object.freeze(measureDependencyNames(queryMeasureDefinitions(dataset, measures), measures)),
|
|
53
|
+
filters: Object.freeze((query.filters ?? []).map(({ field, operator }) => Object.freeze({ field, operator }))),
|
|
54
|
+
segments: Object.freeze([...(query.segments ?? [])]),
|
|
55
|
+
by: query.by, timezone: query.timezone,
|
|
56
|
+
effectiveLimit: query.limit, executionLimit, offset: query.offset ?? 0,
|
|
57
|
+
parameterCount: statement.parameters.length,
|
|
58
|
+
preflightStatementCount: preflightStatements.length,
|
|
59
|
+
});
|
|
60
|
+
const compilation = Object.freeze({
|
|
61
|
+
sql: statement.sql,
|
|
62
|
+
parameters: Object.freeze(structuredClone(statement.parameters)),
|
|
63
|
+
query: Object.freeze(structuredClone({ ...query, measures })),
|
|
64
|
+
preflightStatements: Object.freeze(preflightStatements.map(preflight => Object.freeze({
|
|
65
|
+
sql: preflight.sql, parameters: Object.freeze(structuredClone(preflight.parameters)),
|
|
66
|
+
}))),
|
|
67
|
+
describe: () => description,
|
|
68
|
+
toJSON: () => description,
|
|
69
|
+
});
|
|
70
|
+
return { compilation, execute };
|
|
71
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite-time-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/composite-time-measure-sql.ts"],"names":[],"mappings":"
|
|
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;AAW9D,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,4BAA4B,EAAE,IAAI,EAAE,eAAe;;;;EAgDrJ"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { queryMeasureDefinitions } from './relationship-measures.js';
|
|
1
2
|
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
3
|
import { isDerivedMeasure, isWindowMeasure } from './dataset-measures.js';
|
|
3
4
|
import { buildMeasureEvaluationGraph, evaluationFormula } from './measure-evaluation-graph.js';
|
|
@@ -9,7 +10,7 @@ import { derivedProjection } from './derived-measure-sql.js';
|
|
|
9
10
|
import { buildTimeMeasureResultSql } from './time-measure-result-sql.js';
|
|
10
11
|
/** Aggregate each dependency in its evaluation context, then evaluate formulas. */
|
|
11
12
|
export function buildCompositeTimeMeasureSql(ds, query, options, axis) {
|
|
12
|
-
const graph = buildMeasureEvaluationGraph(ds.measures, query.measures ?? []);
|
|
13
|
+
const graph = buildMeasureEvaluationGraph(queryMeasureDefinitions(ds, query.measures ?? []), query.measures ?? []);
|
|
13
14
|
const leaves = graph.nodes.filter(node => !isDerivedMeasure(node.definition));
|
|
14
15
|
const baseNames = new Set(leaves.map(node => isWindowMeasure(node.definition) ? node.definition.measure : node.name));
|
|
15
16
|
const source = buildTimeMeasureSourceSql(ds, query, baseNames, axis.filters, options);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyDatasetInstance, DatasetQuery
|
|
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
4
|
type BuildBaseQuery = (dataset: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions) => QueryBuilderLike;
|
|
@@ -7,6 +7,5 @@ export declare function buildDerivedDatasetSql(ds: AnyDatasetInstance, query: Da
|
|
|
7
7
|
sql: string;
|
|
8
8
|
parameters: unknown[];
|
|
9
9
|
};
|
|
10
|
-
export declare function runDerivedDatasetQuery(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions, buildBaseQuery: BuildBaseQuery): Promise<DatasetQueryResult>;
|
|
11
10
|
export {};
|
|
12
11
|
//# 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":"
|
|
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,KAAK,cAAc,GAAG,CACpB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,KAClC,gBAAgB,CAAC;AAEtB,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAE9F;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,cAAc,EAAE,cAAc,GAC7B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,EAAE,CAAA;CAAE,CA4CxC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { queryMeasureDefinitions } from './relationship-measures.js';
|
|
2
|
+
import { semanticOrderDirection } from './semantic-query-syntax.js';
|
|
1
3
|
import { measureDependencyNames } from './measure-dependencies.js';
|
|
2
4
|
import { derivedExpressionSql } from './derived-measure-sql.js';
|
|
3
5
|
import { baseMeasureNames, getBaseMeasure, getDerivedMeasure } from './dataset-measures.js';
|
|
4
|
-
import {
|
|
6
|
+
import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
|
|
5
7
|
import { validateDatasetQueryInput } from './dataset-query-validation.js';
|
|
6
|
-
import { overfetchLimit } from './pagination.js';
|
|
7
|
-
import { toDatasetQueryResult } from './dataset-query-result.js';
|
|
8
8
|
export function hasSelectedDerivedMeasure(ds, query) {
|
|
9
9
|
return (query.measures ?? []).some(name => getDerivedMeasure(ds.measures, name) !== undefined);
|
|
10
10
|
}
|
|
@@ -13,11 +13,12 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
13
13
|
if (!validation.valid) {
|
|
14
14
|
throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
|
|
15
15
|
}
|
|
16
|
+
const dialect = resolveDatasetSqlDialect(options.builderFactory);
|
|
16
17
|
const selected = query.measures ?? baseMeasureNames(ds.measures);
|
|
17
|
-
const baseMeasures = measureDependencyNames(ds
|
|
18
|
+
const baseMeasures = measureDependencyNames(queryMeasureDefinitions(ds, selected), selected).filter(name => name.includes('.') || getBaseMeasure(ds.measures, name));
|
|
18
19
|
const resolve = (name) => {
|
|
19
20
|
const derived = getDerivedMeasure(ds.measures, name);
|
|
20
|
-
return derived ? `(${derivedExpressionSql(derived, resolve)})` :
|
|
21
|
+
return derived ? `(${derivedExpressionSql(derived, resolve)})` : dialect.quoteIdentifier(name);
|
|
21
22
|
};
|
|
22
23
|
const inner = buildBaseQuery(ds, {
|
|
23
24
|
...query,
|
|
@@ -29,21 +30,21 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
29
30
|
const { sql: innerSql, parameters } = inner.toSQLWithParams();
|
|
30
31
|
const projections = [];
|
|
31
32
|
if (query.by)
|
|
32
|
-
projections.push(
|
|
33
|
+
projections.push(dialect.quoteIdentifier('period'));
|
|
33
34
|
for (const dimension of query.dimensions ?? [])
|
|
34
|
-
projections.push(
|
|
35
|
+
projections.push(dialect.quoteIdentifier(dimension));
|
|
35
36
|
for (const name of selected) {
|
|
36
37
|
const derived = getDerivedMeasure(ds.measures, name);
|
|
37
38
|
projections.push(derived
|
|
38
|
-
? `${derivedExpressionSql(derived, resolve)} AS ${
|
|
39
|
-
:
|
|
39
|
+
? `${derivedExpressionSql(derived, resolve)} AS ${dialect.quoteIdentifier(name)}`
|
|
40
|
+
: dialect.quoteIdentifier(name));
|
|
40
41
|
}
|
|
41
42
|
let sql = `WITH base AS (${innerSql}) SELECT ${projections.join(', ')} FROM base`;
|
|
42
43
|
if (query.orderBy?.length) {
|
|
43
|
-
sql += ` ORDER BY ${query.orderBy.map(order => (`${
|
|
44
|
+
sql += ` ORDER BY ${query.orderBy.map(order => (`${dialect.quoteIdentifier(order.field)} ${semanticOrderDirection(order.direction)}`)).join(', ')}`;
|
|
44
45
|
}
|
|
45
46
|
else if (query.by) {
|
|
46
|
-
sql += ` ORDER BY ${
|
|
47
|
+
sql += ` ORDER BY ${dialect.quoteIdentifier('period')} ASC`;
|
|
47
48
|
}
|
|
48
49
|
const limit = options.executionLimit ?? query.limit;
|
|
49
50
|
if (limit !== undefined)
|
|
@@ -52,18 +53,3 @@ export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
|
52
53
|
sql += ` OFFSET ${query.offset}`;
|
|
53
54
|
return { sql, parameters };
|
|
54
55
|
}
|
|
55
|
-
export async function runDerivedDatasetQuery(ds, query, options, buildBaseQuery) {
|
|
56
|
-
const start = Date.now();
|
|
57
|
-
const { sql, parameters } = buildDerivedDatasetSql(ds, query, {
|
|
58
|
-
...options,
|
|
59
|
-
executionLimit: overfetchLimit(query.limit),
|
|
60
|
-
}, buildBaseQuery);
|
|
61
|
-
const rows = await options.builderFactory.rawQuery(sql, parameters, { abortSignal: options.context?.abortSignal });
|
|
62
|
-
return toDatasetQueryResult(rows, {
|
|
63
|
-
dataset: ds,
|
|
64
|
-
query,
|
|
65
|
-
sql,
|
|
66
|
-
timingMs: Date.now() - start,
|
|
67
|
-
context: options.context,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { DatasetInstance, DimensionDefinition, MetricFilter, MetricFilterOperator, RelationshipDefinition, SemanticFilterDefinition, SemanticFiltersDefinition } from '../types.js';
|
|
2
|
+
/** The generated allowlist mirrors normalizeFilters; explicit maps replace it. */
|
|
3
|
+
export type NormalizedDatasetFilters<TDimensions extends Record<string, DimensionDefinition>, TFilters extends SemanticFiltersDefinition | undefined> = TFilters extends SemanticFiltersDefinition ? TFilters : {
|
|
4
|
+
[K in keyof TDimensions as TDimensions[K] extends {
|
|
5
|
+
filterable: false;
|
|
6
|
+
} ? never : K]: SemanticFilterDefinition & {
|
|
7
|
+
field: K & string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
type FilterOperators<TDefinition> = TDefinition extends {
|
|
11
|
+
operators: readonly (infer TOperator extends MetricFilterOperator)[];
|
|
12
|
+
} ? TOperator : MetricFilterOperator;
|
|
13
|
+
type LocalFilterFor<TFilters extends SemanticFiltersDefinition> = {
|
|
14
|
+
[K in keyof TFilters & string]: K extends `${string}.${string}` ? never : MetricFilter<K, unknown, FilterOperators<TFilters[K]>>;
|
|
15
|
+
}[keyof TFilters & string];
|
|
16
|
+
type RelationshipFilterFor<TRelationships extends Record<string, RelationshipDefinition>> = {
|
|
17
|
+
[R in keyof TRelationships & string]: TRelationships[R] extends RelationshipDefinition<infer TTarget, infer TKind> ? TKind extends 'hasMany' ? never : TTarget extends DatasetInstance<any, any, any, any> ? {
|
|
18
|
+
[K in keyof TTarget['filters'] & keyof TTarget['dimensions'] & string]: TTarget['dimensions'][K] extends {
|
|
19
|
+
sql: string;
|
|
20
|
+
} ? never : K extends TTarget['filters'][K]['field'] ? MetricFilter<`${R}.${K}`, unknown, FilterOperators<TTarget['filters'][K]>> : never;
|
|
21
|
+
}[keyof TTarget['filters'] & keyof TTarget['dimensions'] & string] : never : never;
|
|
22
|
+
}[keyof TRelationships & string];
|
|
23
|
+
/** Filter names and operator policies exposed by a dataset and its to-one targets. */
|
|
24
|
+
export type DatasetFilterFor<TDataset extends DatasetInstance<any, any, any, any>> = LocalFilterFor<TDataset['filters']> | RelationshipFilterFor<TDataset['relationships']>;
|
|
25
|
+
/** Names in the normalized filter allowlist, including one-hop relationship filters. */
|
|
26
|
+
export type DatasetFilterNames<TDataset extends DatasetInstance<any, any, any, any>> = DatasetFilterFor<TDataset>['field'];
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=dataset-filter-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-filter-types.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-filter-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAErB,kFAAkF;AAClF,MAAM,MAAM,wBAAwB,CAClC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACvD,QAAQ,SAAS,yBAAyB,GAAG,SAAS,IACpD,QAAQ,SAAS,yBAAyB,GAAG,QAAQ,GAAG;KACzD,CAAC,IAAI,MAAM,WAAW,IAAI,WAAW,CAAC,CAAC,CAAC,SAAS;QAAE,UAAU,EAAE,KAAK,CAAA;KAAE,GAAG,KAAK,GAAG,CAAC,GAAG,wBAAwB,GAAG;QAC/G,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC;KACnB;CACF,CAAC;AAEF,KAAK,eAAe,CAAC,WAAW,IAAI,WAAW,SAAS;IACtD,SAAS,EAAE,SAAS,CAAC,MAAM,SAAS,SAAS,oBAAoB,CAAC,EAAE,CAAC;CACtE,GAAG,SAAS,GAAG,oBAAoB,CAAC;AAErC,KAAK,cAAc,CAAC,QAAQ,SAAS,yBAAyB,IAAI;KAC/D,CAAC,IAAI,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,SAAS,GAAG,MAAM,IAAI,MAAM,EAAE,GAC3D,KAAK,GACL,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3D,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC;AAE3B,KAAK,qBAAqB,CAAC,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,IAAI;KACzF,CAAC,IAAI,MAAM,cAAc,GAAG,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,SAAS,sBAAsB,CAAC,MAAM,OAAO,EAAE,MAAM,KAAK,CAAC,GAC9G,KAAK,SAAS,SAAS,GAAG,KAAK,GAC7B,OAAO,SAAS,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GACjD;SACC,CAAC,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,MAAM,GACnE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,KAAK,GACpD,CAAC,SAAS,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GACtC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC1E,KAAK;KACd,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,GAChE,KAAK,GACT,KAAK;CACV,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC;AAEjC,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,CAAC,QAAQ,SAAS,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAC7E,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,GACnC,qBAAqB,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;AAErD,wFAAwF;AACxF,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IACjF,gBAAgB,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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":"AAIA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAiB9E,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAuKlB"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveRelationshipMeasure } from './relationship-measures.js';
|
|
2
|
+
import { semanticQuerySyntaxErrors } from './semantic-query-syntax.js';
|
|
1
3
|
import { queryTimezoneErrors } from './query-timezone.js';
|
|
2
4
|
import { baseMeasureNames } from './dataset-measures.js';
|
|
3
5
|
import { validateFilterValue } from '../validation.js';
|
|
@@ -8,11 +10,16 @@ import { validateQualifiedFilter, validateRelationshipTenantRuntime, } from './r
|
|
|
8
10
|
import { segmentSelectionErrors } from './segments.js';
|
|
9
11
|
import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
|
|
10
12
|
export function validateDatasetQueryInput(ds, query, context) {
|
|
11
|
-
const errors = queryTimezoneErrors(query.timezone);
|
|
13
|
+
const errors = [...semanticQuerySyntaxErrors(query), ...queryTimezoneErrors(query.timezone)];
|
|
12
14
|
const dimensionNames = Object.keys(ds.dimensions);
|
|
13
15
|
const measureNames = Object.keys(ds.measures);
|
|
14
16
|
const selectedDimensions = query.dimensions ?? [];
|
|
15
17
|
const selectedMeasures = query.measures ?? baseMeasureNames(ds.measures);
|
|
18
|
+
for (const name of selectedDimensions) {
|
|
19
|
+
if (selectedMeasures.includes(name)) {
|
|
20
|
+
errors.push(`Output "${name}" cannot be selected as both a dimension and a measure. Select one or give the definitions distinct names.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
16
23
|
const filterNames = Object.keys(ds.filters);
|
|
17
24
|
const orderableFields = new Set([
|
|
18
25
|
...selectedDimensions,
|
|
@@ -47,7 +54,12 @@ export function validateDatasetQueryInput(ds, query, context) {
|
|
|
47
54
|
if (query.measures) {
|
|
48
55
|
for (const measure of query.measures) {
|
|
49
56
|
if (isQualifiedField(measure)) {
|
|
50
|
-
|
|
57
|
+
try {
|
|
58
|
+
resolveRelationshipMeasure(ds, measure);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
errors.push(error.message);
|
|
62
|
+
}
|
|
51
63
|
continue;
|
|
52
64
|
}
|
|
53
65
|
if (!measureNames.includes(measure)) {
|
|
@@ -95,7 +107,7 @@ export function validateDatasetQueryInput(ds, query, context) {
|
|
|
95
107
|
if (query.orderBy) {
|
|
96
108
|
const invalid = [];
|
|
97
109
|
for (const order of query.orderBy) {
|
|
98
|
-
if (isQualifiedField(order.field)) {
|
|
110
|
+
if (isQualifiedField(order.field) && !selectedMeasures.includes(order.field)) {
|
|
99
111
|
const resolution = resolveQualifiedField(ds, order.field);
|
|
100
112
|
if (resolution?.error) {
|
|
101
113
|
errors.push(resolution.error);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DatasetSqlDialect } from '../dataset-sql-dialect.js';
|
|
2
|
+
import type { QueryBuilderFactoryInput } from '../query-builder-protocol.js';
|
|
3
|
+
/** Resolve from the active factory, so a runtime builder override carries its dialect. */
|
|
4
|
+
export declare function resolveDatasetSqlDialect(factory: QueryBuilderFactoryInput): DatasetSqlDialect;
|
|
5
|
+
//# sourceMappingURL=dataset-sql-dialect.d.ts.map
|
|
@@ -0,0 +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;AACnE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAG7E,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAE7F"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { clickhouseDatasetSqlDialect } from './clickhouse-dataset-sql-dialect.js';
|
|
2
|
+
/** Resolve from the active factory, so a runtime builder override carries its dialect. */
|
|
3
|
+
export function resolveDatasetSqlDialect(factory) {
|
|
4
|
+
return factory.datasetSqlDialect ?? clickhouseDatasetSqlDialect;
|
|
5
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-target.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-target.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,kBAAkB,CAE/E"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { MetricFilter, MetricFilterOperator } from '../types.js';
|
|
2
|
+
export declare function createFilter<const TField extends string, const TValue, TOperator extends MetricFilterOperator>(field: TField, operator: TOperator, value: TValue): MetricFilter<TField, TValue, TOperator>;
|
|
3
|
+
//# sourceMappingURL=filter-definition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-definition.d.ts","sourceRoot":"","sources":["../../src/utils/filter-definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEtE,wBAAgB,YAAY,CAC1B,KAAK,CAAC,MAAM,SAAS,MAAM,EAC3B,KAAK,CAAC,MAAM,EACZ,SAAS,SAAS,oBAAoB,EACtC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAE5F"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MetricRef, MetricQuery, ExecutionContext, TimeGrain } from '../types.js';
|
|
2
|
+
import type { QueryBuilderFactoryLike } from '../query-builder-protocol.js';
|
|
3
|
+
import type { DerivedMetricSpec } from '../types.js';
|
|
4
|
+
export declare function buildDerivedMetricSql(ref: MetricRef, spec: DerivedMetricSpec, query: MetricQuery, grain: TimeGrain | undefined, builderFactory: QueryBuilderFactoryLike, context?: ExecutionContext): {
|
|
5
|
+
sql: string;
|
|
6
|
+
params: unknown[];
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=metric-derived-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-derived-query.d.ts","sourceRoot":"","sources":["../../src/utils/metric-derived-query.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAoB,MAAM,8BAA8B,CAAC;AAiB9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,cAAc,EAAE,uBAAuB,EACvC,OAAO,CAAC,EAAE,gBAAgB,GACzB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAiHpC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { semanticQuerySyntaxErrors, semanticOrderDirection } from './semantic-query-syntax.js';
|
|
2
|
+
/** Metric SQL compilation; factory selection and execution belong to the client. */
|
|
3
|
+
import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
|
|
4
|
+
import { applyAggregationSpec, buildDimensionSelectionPlan, resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn, } from '../query-planner.js';
|
|
5
|
+
import { queryTimeFilterSql } from './query-timezone.js';
|
|
6
|
+
import { isTenantScopedFilter } from './metric-handle.js';
|
|
7
|
+
import { getRuntimeTenantPredicate } from './tenant-runtime.js';
|
|
8
|
+
import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn, } from './relationship-builder-plan.js';
|
|
9
|
+
import { segmentFilters } from './segments.js';
|
|
10
|
+
import { validateSQLIdentifier } from '../sql-utils.js';
|
|
11
|
+
import { isQualifiedField } from './relationship-fields.js';
|
|
12
|
+
import { validateDerivedCteGrouping } from './derived-cte-validation.js';
|
|
13
|
+
export function buildDerivedMetricSql(ref, spec, query, grain, builderFactory, context) {
|
|
14
|
+
const syntaxErrors = semanticQuerySyntaxErrors(query);
|
|
15
|
+
if (syntaxErrors.length)
|
|
16
|
+
throw new Error(syntaxErrors.join('; '));
|
|
17
|
+
const dialect = resolveDatasetSqlDialect(builderFactory);
|
|
18
|
+
const ds = ref.dataset;
|
|
19
|
+
const joinCtx = buildRelationshipBuilderContext(ds, query, context);
|
|
20
|
+
// Build the CTE inner query using the builder
|
|
21
|
+
let cteBuilder = builderFactory.table(ds.source);
|
|
22
|
+
cteBuilder = applyRelationshipJoins(cteBuilder, joinCtx);
|
|
23
|
+
const { selectParts, groupByParts } = buildDimensionSelectionPlan(ds, query.dimensions ?? [], grain, joinCtx, query.timezone, dialect);
|
|
24
|
+
if (selectParts.length > 0) {
|
|
25
|
+
cteBuilder = cteBuilder.select(selectParts);
|
|
26
|
+
}
|
|
27
|
+
// Base aggregations
|
|
28
|
+
const refAliases = {};
|
|
29
|
+
const aggregateAliases = [];
|
|
30
|
+
for (const [alias, baseMetric] of Object.entries(spec.uses)) {
|
|
31
|
+
const baseSpec = baseMetric.spec;
|
|
32
|
+
if (baseSpec.__type !== 'aggregation_spec') {
|
|
33
|
+
throw new Error(`Derived metric "${ref.name}" references non-base metric "${alias}".`);
|
|
34
|
+
}
|
|
35
|
+
cteBuilder = applyAggregationSpec(cteBuilder, ds, baseSpec, alias, joinCtx);
|
|
36
|
+
refAliases[alias] = alias;
|
|
37
|
+
aggregateAliases.push(alias);
|
|
38
|
+
}
|
|
39
|
+
if (groupByParts.length > 0) {
|
|
40
|
+
cteBuilder = cteBuilder.groupBy(groupByParts);
|
|
41
|
+
}
|
|
42
|
+
// Filters on CTE
|
|
43
|
+
const tenantColumn = resolveTenantFilterColumn(ds, context);
|
|
44
|
+
const tenantPredicate = getRuntimeTenantPredicate(context);
|
|
45
|
+
if (tenantPredicate && tenantColumn) {
|
|
46
|
+
cteBuilder = cteBuilder.where(qualifyBaseColumn(joinCtx, tenantColumn), tenantPredicate.operator, tenantPredicate.value);
|
|
47
|
+
}
|
|
48
|
+
for (const filter of query.filters ?? []) {
|
|
49
|
+
if (isTenantScopedFilter(ds, filter, context)) {
|
|
50
|
+
throw new Error(`Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`);
|
|
51
|
+
}
|
|
52
|
+
const resolvedField = resolveFilterField(ds, filter.field, joinCtx);
|
|
53
|
+
cteBuilder = cteBuilder.where(queryTimeFilterSql(ds, filter.field, resolvedField, query.timezone), filter.operator, filter.value);
|
|
54
|
+
}
|
|
55
|
+
for (const filter of segmentFilters(ds, query.segments)) {
|
|
56
|
+
cteBuilder = cteBuilder.where(resolveDimensionExpression(ds, filter.field, joinCtx), filter.operator, filter.value);
|
|
57
|
+
}
|
|
58
|
+
const { sql: cteSql, parameters: cteParams } = cteBuilder.toSQLWithParams();
|
|
59
|
+
const groupingErrors = validateDerivedCteGrouping(cteSql, aggregateAliases, groupByParts);
|
|
60
|
+
if (groupingErrors.length > 0) {
|
|
61
|
+
throw new Error(groupingErrors.join('; '));
|
|
62
|
+
}
|
|
63
|
+
// Outer query: trivial SELECT from the CTE — stays as string concat
|
|
64
|
+
// because table('base') would fail schema typing
|
|
65
|
+
const outerSelectParts = [];
|
|
66
|
+
if (grain)
|
|
67
|
+
outerSelectParts.push('period');
|
|
68
|
+
for (const dim of query.dimensions ?? []) {
|
|
69
|
+
// Joined dimensions surface from the CTE under their quoted qualified alias.
|
|
70
|
+
if (joinCtx && isQualifiedField(dim)) {
|
|
71
|
+
outerSelectParts.push(dialect.quoteIdentifier(dim));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
validateSQLIdentifier(dim, 'dimension name');
|
|
75
|
+
outerSelectParts.push(dim);
|
|
76
|
+
}
|
|
77
|
+
const formulaExpr = spec.formula(refAliases);
|
|
78
|
+
validateSQLIdentifier(ref.name, 'metric name');
|
|
79
|
+
outerSelectParts.push(`${formulaExpr.toSQL()} AS ${ref.name}`);
|
|
80
|
+
let sql = `WITH base AS (${cteSql}) SELECT ${outerSelectParts.join(', ')} FROM base`;
|
|
81
|
+
// ORDER BY
|
|
82
|
+
if (query.orderBy && query.orderBy.length > 0) {
|
|
83
|
+
const orderParts = query.orderBy.map(o => {
|
|
84
|
+
if (joinCtx && isQualifiedField(o.field)) {
|
|
85
|
+
return `${dialect.quoteIdentifier(o.field)} ${semanticOrderDirection(o.direction)}`;
|
|
86
|
+
}
|
|
87
|
+
validateSQLIdentifier(o.field, 'order by field');
|
|
88
|
+
return `${o.field} ${semanticOrderDirection(o.direction)}`;
|
|
89
|
+
});
|
|
90
|
+
sql += ` ORDER BY ${orderParts.join(', ')}`;
|
|
91
|
+
}
|
|
92
|
+
else if (grain) {
|
|
93
|
+
sql += ' ORDER BY period';
|
|
94
|
+
}
|
|
95
|
+
if (query.limit != null) {
|
|
96
|
+
// Ensure limit is a safe integer
|
|
97
|
+
const limit = Math.floor(Math.abs(query.limit));
|
|
98
|
+
sql += ` LIMIT ${limit}`;
|
|
99
|
+
}
|
|
100
|
+
if (query.offset != null) {
|
|
101
|
+
// Ensure offset is a safe integer
|
|
102
|
+
const offset = Math.floor(Math.abs(query.offset));
|
|
103
|
+
sql += ` OFFSET ${offset}`;
|
|
104
|
+
}
|
|
105
|
+
return { sql, params: cteParams };
|
|
106
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MetricRef, MetricQuery, ExecutionContext, TimeGrain } from '../types.js';
|
|
2
|
+
import type { QueryBuilderFactoryLike, QueryBuilderLike } from '../query-builder-protocol.js';
|
|
3
|
+
import type { AggregationSpec, AnyDatasetInstance } from '../types.js';
|
|
4
|
+
export declare function buildMetricQueryBuilder(ref: MetricRef, spec: AggregationSpec, ds: AnyDatasetInstance, query: MetricQuery, grain: TimeGrain | undefined, builderFactory: QueryBuilderFactoryLike, context?: ExecutionContext): QueryBuilderLike;
|
|
5
|
+
//# sourceMappingURL=metric-query-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-query-builder.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-builder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAkB9F,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEvE,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,cAAc,EAAE,uBAAuB,EACvC,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAwDlB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { semanticQuerySyntaxErrors } from './semantic-query-syntax.js';
|
|
2
|
+
/** Metric SQL compilation; factory selection and execution belong to the client. */
|
|
3
|
+
import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
|
|
4
|
+
import { applyAggregationSpec, appendOrderLimitOffset, buildDimensionSelectionPlan, resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn, } from '../query-planner.js';
|
|
5
|
+
import { queryTimeFilterSql } from './query-timezone.js';
|
|
6
|
+
import { isTenantScopedFilter } from './metric-handle.js';
|
|
7
|
+
import { getRuntimeTenantPredicate } from './tenant-runtime.js';
|
|
8
|
+
import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn, } from './relationship-builder-plan.js';
|
|
9
|
+
import { segmentFilters } from './segments.js';
|
|
10
|
+
export function buildMetricQueryBuilder(ref, spec, ds, query, grain, builderFactory, context) {
|
|
11
|
+
const syntaxErrors = semanticQuerySyntaxErrors(query);
|
|
12
|
+
if (syntaxErrors.length)
|
|
13
|
+
throw new Error(syntaxErrors.join('; '));
|
|
14
|
+
const dialect = resolveDatasetSqlDialect(builderFactory);
|
|
15
|
+
const joinCtx = buildRelationshipBuilderContext(ds, query, context);
|
|
16
|
+
let qb = builderFactory.table(ds.source);
|
|
17
|
+
qb = applyRelationshipJoins(qb, joinCtx);
|
|
18
|
+
const { selectParts, groupByParts } = buildDimensionSelectionPlan(ds, query.dimensions ?? [], grain, joinCtx, query.timezone, dialect);
|
|
19
|
+
if (selectParts.length > 0) {
|
|
20
|
+
qb = qb.select(selectParts);
|
|
21
|
+
}
|
|
22
|
+
// Aggregation (appends to select, auto-sets groupBy on non-agg columns)
|
|
23
|
+
qb = applyAggregationSpec(qb, ds, spec, ref.name, joinCtx);
|
|
24
|
+
// Explicit groupBy (ensures period + dims are grouped even if aggregation auto-groupBy misses them)
|
|
25
|
+
if (groupByParts.length > 0) {
|
|
26
|
+
qb = qb.groupBy(groupByParts);
|
|
27
|
+
}
|
|
28
|
+
// Tenant auto-injection
|
|
29
|
+
const tenantColumn = resolveTenantFilterColumn(ds, context);
|
|
30
|
+
const tenantPredicate = getRuntimeTenantPredicate(context);
|
|
31
|
+
if (tenantPredicate && tenantColumn) {
|
|
32
|
+
qb = qb.where(qualifyBaseColumn(joinCtx, tenantColumn), tenantPredicate.operator, tenantPredicate.value);
|
|
33
|
+
}
|
|
34
|
+
// User filters
|
|
35
|
+
for (const filter of query.filters ?? []) {
|
|
36
|
+
if (isTenantScopedFilter(ds, filter, context)) {
|
|
37
|
+
throw new Error(`Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`);
|
|
38
|
+
}
|
|
39
|
+
const resolvedField = resolveFilterField(ds, filter.field, joinCtx);
|
|
40
|
+
qb = qb.where(queryTimeFilterSql(ds, filter.field, resolvedField, query.timezone), filter.operator, filter.value);
|
|
41
|
+
}
|
|
42
|
+
// Segments: author-defined, so they resolve dimensions directly rather
|
|
43
|
+
// than through the caller-facing filter allow-list.
|
|
44
|
+
for (const filter of segmentFilters(ds, query.segments)) {
|
|
45
|
+
qb = qb.where(resolveDimensionExpression(ds, filter.field, joinCtx), filter.operator, filter.value);
|
|
46
|
+
}
|
|
47
|
+
// Order, limit, offset
|
|
48
|
+
qb = appendOrderLimitOffset(qb, query.orderBy, grain, query.limit, query.offset, joinCtx, dialect);
|
|
49
|
+
return qb;
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ExecutionContext, MetricQuery, MetricResult } from '../types.js';
|
|
2
|
+
/** Keep base and derived metric execution on the same result contract. */
|
|
3
|
+
export declare function toMetricQueryResult<T>(rows: T[], options: {
|
|
4
|
+
metric: string;
|
|
5
|
+
query: MetricQuery;
|
|
6
|
+
sql: string;
|
|
7
|
+
timingMs: number;
|
|
8
|
+
context?: ExecutionContext;
|
|
9
|
+
}): MetricResult<T>;
|
|
10
|
+
//# sourceMappingURL=metric-query-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-query-result.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK/E,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE,CAAC,EAAE,EACT,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B,GACA,YAAY,CAAC,CAAC,CAAC,CAajB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { applyPagination } from './pagination.js';
|
|
2
|
+
import { serializeSemanticMeasureValues } from './semantic-result-serialization.js';
|
|
3
|
+
import { getRuntimeTenantId } from './tenant-runtime.js';
|
|
4
|
+
/** Keep base and derived metric execution on the same result contract. */
|
|
5
|
+
export function toMetricQueryResult(rows, options) {
|
|
6
|
+
const { data, pagination } = applyPagination(rows, options.query.limit, options.query.offset);
|
|
7
|
+
const serializedData = serializeSemanticMeasureValues(data, [options.metric]);
|
|
8
|
+
return {
|
|
9
|
+
data: serializedData,
|
|
10
|
+
meta: {
|
|
11
|
+
sql: options.sql,
|
|
12
|
+
timingMs: options.timingMs,
|
|
13
|
+
tenant: getRuntimeTenantId(options.context),
|
|
14
|
+
rowCount: serializedData.length,
|
|
15
|
+
pagination,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Semantic metric validation; independent of SQL compilation and execution. */
|
|
2
|
+
import type { MetricQuery, ExecutionContext } from '../types.js';
|
|
3
|
+
import { type ValidationResult } from '../validation.js';
|
|
4
|
+
import { type MetricHandle } from './metric-handle.js';
|
|
5
|
+
export declare function validateMetricQueryInput(metric: MetricHandle, query: MetricQuery, context?: ExecutionContext): ValidationResult;
|
|
6
|
+
//# sourceMappingURL=metric-query-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"AACA,gFAAgF;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI9E,OAAO,EAAsD,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAM3G,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAgIlB"}
|