@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.
Files changed (127) hide show
  1. package/README.md +6 -0
  2. package/dist/api.type-test.js +7 -0
  3. package/dist/catalog.d.ts +4 -0
  4. package/dist/catalog.d.ts.map +1 -1
  5. package/dist/catalog.js +10 -1
  6. package/dist/dataset-compilation.d.ts +45 -0
  7. package/dist/dataset-compilation.d.ts.map +1 -0
  8. package/dist/dataset-compilation.js +1 -0
  9. package/dist/dataset-query.d.ts +3 -3
  10. package/dist/dataset-query.d.ts.map +1 -1
  11. package/dist/dataset-query.js +7 -71
  12. package/dist/dataset-sql-dialect.d.ts +11 -0
  13. package/dist/dataset-sql-dialect.d.ts.map +1 -0
  14. package/dist/dataset-sql-dialect.js +1 -0
  15. package/dist/dataset.d.ts +3 -2
  16. package/dist/dataset.d.ts.map +1 -1
  17. package/dist/execution-capabilities.d.ts +28 -0
  18. package/dist/execution-capabilities.d.ts.map +1 -0
  19. package/dist/execution-capabilities.js +17 -0
  20. package/dist/execution-capabilities.json +13 -0
  21. package/dist/executor.d.ts +17 -38
  22. package/dist/executor.d.ts.map +1 -1
  23. package/dist/executor.js +37 -400
  24. package/dist/index.d.ts +9 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -2
  27. package/dist/internal.d.ts +3 -0
  28. package/dist/internal.d.ts.map +1 -1
  29. package/dist/internal.js +2 -0
  30. package/dist/measure.d.ts +54 -17
  31. package/dist/measure.d.ts.map +1 -1
  32. package/dist/measure.js +3 -63
  33. package/dist/metric-query-engine.d.ts +29 -0
  34. package/dist/metric-query-engine.d.ts.map +1 -0
  35. package/dist/metric-query-engine.js +106 -0
  36. package/dist/portable-executor.d.ts +20 -6
  37. package/dist/portable-executor.d.ts.map +1 -1
  38. package/dist/portable-executor.js +29 -9
  39. package/dist/query-builder-protocol.d.ts +4 -0
  40. package/dist/query-builder-protocol.d.ts.map +1 -1
  41. package/dist/query-helpers.d.ts +10 -10
  42. package/dist/query-helpers.d.ts.map +1 -1
  43. package/dist/query-helpers.js +1 -3
  44. package/dist/query-planner.d.ts +7 -3
  45. package/dist/query-planner.d.ts.map +1 -1
  46. package/dist/query-planner.js +10 -12
  47. package/dist/semantic-planner.d.ts.map +1 -1
  48. package/dist/semantic-planner.js +2 -0
  49. package/dist/semantic-query-schema.d.ts.map +1 -1
  50. package/dist/semantic-query-schema.js +3 -3
  51. package/dist/types.d.ts +18 -11
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/utils/agent-catalog-projection.d.ts +1 -0
  54. package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
  55. package/dist/utils/agent-catalog-projection.js +2 -1
  56. package/dist/utils/base-measure-definition.d.ts +23 -0
  57. package/dist/utils/base-measure-definition.d.ts.map +1 -0
  58. package/dist/utils/base-measure-definition.js +65 -0
  59. package/dist/utils/build-dataset-query-builder.d.ts +5 -0
  60. package/dist/utils/build-dataset-query-builder.d.ts.map +1 -0
  61. package/dist/utils/build-dataset-query-builder.js +62 -0
  62. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +4 -0
  63. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -0
  64. package/dist/utils/clickhouse-dataset-sql-dialect.js +6 -0
  65. package/dist/utils/compile-dataset-query.d.ts +10 -0
  66. package/dist/utils/compile-dataset-query.d.ts.map +1 -0
  67. package/dist/utils/compile-dataset-query.js +71 -0
  68. package/dist/utils/composite-time-measure-sql.d.ts.map +1 -1
  69. package/dist/utils/composite-time-measure-sql.js +2 -1
  70. package/dist/utils/dataset-derived-query.d.ts +1 -2
  71. package/dist/utils/dataset-derived-query.d.ts.map +1 -1
  72. package/dist/utils/dataset-derived-query.js +12 -26
  73. package/dist/utils/dataset-filter-types.d.ts +28 -0
  74. package/dist/utils/dataset-filter-types.d.ts.map +1 -0
  75. package/dist/utils/dataset-filter-types.js +1 -0
  76. package/dist/utils/dataset-query-validation.d.ts.map +1 -1
  77. package/dist/utils/dataset-query-validation.js +15 -3
  78. package/dist/utils/dataset-sql-dialect.d.ts +5 -0
  79. package/dist/utils/dataset-sql-dialect.d.ts.map +1 -0
  80. package/dist/utils/dataset-sql-dialect.js +5 -0
  81. package/dist/utils/dataset-target.d.ts +3 -0
  82. package/dist/utils/dataset-target.d.ts.map +1 -0
  83. package/dist/utils/dataset-target.js +3 -0
  84. package/dist/utils/filter-definition.d.ts +3 -0
  85. package/dist/utils/filter-definition.d.ts.map +1 -0
  86. package/dist/utils/filter-definition.js +3 -0
  87. package/dist/utils/metric-derived-query.d.ts +8 -0
  88. package/dist/utils/metric-derived-query.d.ts.map +1 -0
  89. package/dist/utils/metric-derived-query.js +106 -0
  90. package/dist/utils/metric-query-builder.d.ts +5 -0
  91. package/dist/utils/metric-query-builder.d.ts.map +1 -0
  92. package/dist/utils/metric-query-builder.js +50 -0
  93. package/dist/utils/metric-query-result.d.ts +10 -0
  94. package/dist/utils/metric-query-result.d.ts.map +1 -0
  95. package/dist/utils/metric-query-result.js +18 -0
  96. package/dist/utils/metric-query-validation.d.ts +6 -0
  97. package/dist/utils/metric-query-validation.d.ts.map +1 -0
  98. package/dist/utils/metric-query-validation.js +116 -0
  99. package/dist/utils/relationship-builder-plan.d.ts +4 -2
  100. package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
  101. package/dist/utils/relationship-builder-plan.js +10 -13
  102. package/dist/utils/relationship-measure-sql.d.ts +14 -0
  103. package/dist/utils/relationship-measure-sql.d.ts.map +1 -0
  104. package/dist/utils/relationship-measure-sql.js +44 -0
  105. package/dist/utils/relationship-measure-types.d.ts +36 -0
  106. package/dist/utils/relationship-measure-types.d.ts.map +1 -0
  107. package/dist/utils/relationship-measure-types.js +1 -0
  108. package/dist/utils/relationship-measures.d.ts +23 -0
  109. package/dist/utils/relationship-measures.d.ts.map +1 -0
  110. package/dist/utils/relationship-measures.js +62 -0
  111. package/dist/utils/relationship-validation.d.ts +1 -1
  112. package/dist/utils/relationship-validation.d.ts.map +1 -1
  113. package/dist/utils/relationship-validation.js +3 -5
  114. package/dist/utils/semantic-query-syntax.d.ts +6 -0
  115. package/dist/utils/semantic-query-syntax.d.ts.map +1 -0
  116. package/dist/utils/semantic-query-syntax.js +25 -0
  117. package/dist/utils/snapshot-execution-context.d.ts +4 -0
  118. package/dist/utils/snapshot-execution-context.d.ts.map +1 -0
  119. package/dist/utils/snapshot-execution-context.js +16 -0
  120. package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
  121. package/dist/utils/time-measure-dataset-sql.js +3 -2
  122. package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
  123. package/dist/utils/time-measure-result-sql.js +2 -1
  124. package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
  125. package/dist/utils/time-measure-source-sql.js +8 -4
  126. package/dist/validation.js +1 -1
  127. 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":"AAAA,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
+ {"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, DatasetQueryResult } from '../types.js';
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":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrE,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,CA2CxC;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,EACrC,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CAgB7B"}
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 { quoteSQLIdentifier } from '../sql-utils.js';
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.measures, selected).filter(name => getBaseMeasure(ds.measures, name));
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)})` : quoteSQLIdentifier(name);
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(quoteSQLIdentifier('period'));
33
+ projections.push(dialect.quoteIdentifier('period'));
33
34
  for (const dimension of query.dimensions ?? [])
34
- projections.push(quoteSQLIdentifier(dimension));
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 ${quoteSQLIdentifier(name)}`
39
- : quoteSQLIdentifier(name));
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 => (`${quoteSQLIdentifier(order.field)} ${order.direction === 'asc' ? 'ASC' : 'DESC'}`)).join(', ')}`;
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 ${quoteSQLIdentifier('period')} ASC`;
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":"AAEA,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,CAmKlB"}
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
- errors.push(`Measure "${measure}" is relationship-qualified. Measures can only be defined on the base dataset "${ds.name}", not traversed through relationships.`);
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,3 @@
1
+ import type { AnyDatasetInstance } from '../types.js';
2
+ export declare function isDatasetInstance(target: unknown): target is AnyDatasetInstance;
3
+ //# sourceMappingURL=dataset-target.d.ts.map
@@ -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
+ export function isDatasetInstance(target) {
2
+ return !!target && typeof target === 'object' && '__type' in target && target.__type === 'dataset';
3
+ }
@@ -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,3 @@
1
+ export function createFilter(field, operator, value) {
2
+ return { field, operator, value };
3
+ }
@@ -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"}