@hypequery/datasets 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/cache/definition-identity.d.ts +9 -6
  2. package/dist/cache/definition-identity.d.ts.map +1 -1
  3. package/dist/cache/definition-identity.js +12 -6
  4. package/dist/cache/protocol-cache-keys.d.ts.map +1 -1
  5. package/dist/cache/protocol-cache-keys.js +29 -6
  6. package/dist/cache/query-signature.d.ts.map +1 -1
  7. package/dist/cache/query-signature.js +11 -3
  8. package/dist/catalog.d.ts +4 -0
  9. package/dist/catalog.d.ts.map +1 -1
  10. package/dist/catalog.js +2 -0
  11. package/dist/contract.d.ts +3 -0
  12. package/dist/contract.d.ts.map +1 -1
  13. package/dist/contract.js +2 -0
  14. package/dist/dataset-compilation.d.ts +2 -0
  15. package/dist/dataset-compilation.d.ts.map +1 -1
  16. package/dist/dataset-sql-dialect.d.ts +3 -1
  17. package/dist/dataset-sql-dialect.d.ts.map +1 -1
  18. package/dist/dataset.d.ts +3 -1
  19. package/dist/dataset.d.ts.map +1 -1
  20. package/dist/dataset.js +23 -32
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/metric-query-engine.d.ts +0 -1
  24. package/dist/metric-query-engine.d.ts.map +1 -1
  25. package/dist/metric-query-engine.js +8 -1
  26. package/dist/protocol-adapter.d.ts.map +1 -1
  27. package/dist/protocol-adapter.js +1 -4
  28. package/dist/protocol-rehydrate.d.ts.map +1 -1
  29. package/dist/protocol-rehydrate.js +1 -0
  30. package/dist/query-builder-protocol.d.ts +3 -1
  31. package/dist/query-builder-protocol.d.ts.map +1 -1
  32. package/dist/relationship-check.d.ts.map +1 -1
  33. package/dist/relationship-check.js +3 -5
  34. package/dist/semantic-planner.d.ts.map +1 -1
  35. package/dist/semantic-planner.js +35 -15
  36. package/dist/semantic-query-schema.d.ts +6 -0
  37. package/dist/semantic-query-schema.d.ts.map +1 -1
  38. package/dist/semantic-query-schema.js +6 -0
  39. package/dist/tools.d.ts.map +1 -1
  40. package/dist/tools.js +4 -3
  41. package/dist/types.d.ts +84 -7
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/utils/build-dataset-query-builder.d.ts +1 -1
  44. package/dist/utils/build-dataset-query-builder.d.ts.map +1 -1
  45. package/dist/utils/build-dataset-query-builder.js +12 -15
  46. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -1
  47. package/dist/utils/clickhouse-dataset-sql-dialect.js +3 -0
  48. package/dist/utils/compile-dataset-query.d.ts +1 -1
  49. package/dist/utils/compile-dataset-query.d.ts.map +1 -1
  50. package/dist/utils/compile-dataset-query.js +16 -5
  51. package/dist/utils/dataset-definition-validation.d.ts +1 -1
  52. package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
  53. package/dist/utils/dataset-definition-validation.js +19 -0
  54. package/dist/utils/dataset-query-validation.d.ts +1 -1
  55. package/dist/utils/dataset-query-validation.d.ts.map +1 -1
  56. package/dist/utils/dataset-query-validation.js +12 -2
  57. package/dist/utils/dataset-source.d.ts +35 -2
  58. package/dist/utils/dataset-source.d.ts.map +1 -1
  59. package/dist/utils/dataset-source.js +47 -4
  60. package/dist/utils/dataset-time-keys.d.ts +32 -0
  61. package/dist/utils/dataset-time-keys.d.ts.map +1 -0
  62. package/dist/utils/dataset-time-keys.js +55 -0
  63. package/dist/utils/enforced-constraints.d.ts +14 -0
  64. package/dist/utils/enforced-constraints.d.ts.map +1 -0
  65. package/dist/utils/enforced-constraints.js +57 -0
  66. package/dist/utils/metric-derived-query.d.ts.map +1 -1
  67. package/dist/utils/metric-derived-query.js +6 -12
  68. package/dist/utils/metric-query-builder.d.ts +1 -1
  69. package/dist/utils/metric-query-builder.d.ts.map +1 -1
  70. package/dist/utils/metric-query-builder.js +7 -13
  71. package/dist/utils/metric-query-validation.d.ts.map +1 -1
  72. package/dist/utils/metric-query-validation.js +7 -1
  73. package/dist/utils/query-timezone.d.ts +1 -1
  74. package/dist/utils/query-timezone.d.ts.map +1 -1
  75. package/dist/utils/query-timezone.js +3 -3
  76. package/dist/utils/relationship-builder-plan.d.ts +12 -10
  77. package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
  78. package/dist/utils/relationship-builder-plan.js +20 -19
  79. package/dist/utils/relationship-measure-sql.d.ts.map +1 -1
  80. package/dist/utils/relationship-measure-sql.js +6 -3
  81. package/dist/utils/relationship-validation.d.ts.map +1 -1
  82. package/dist/utils/relationship-validation.js +2 -0
  83. package/dist/utils/row-scope.d.ts +23 -0
  84. package/dist/utils/row-scope.d.ts.map +1 -0
  85. package/dist/utils/row-scope.js +26 -0
  86. package/dist/utils/scoped-dataset-source.d.ts +23 -0
  87. package/dist/utils/scoped-dataset-source.d.ts.map +1 -0
  88. package/dist/utils/scoped-dataset-source.js +34 -0
  89. package/dist/utils/snapshot-execution-context.d.ts.map +1 -1
  90. package/dist/utils/snapshot-execution-context.js +3 -0
  91. package/dist/utils/time-measure-axis-sql.d.ts +1 -1
  92. package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
  93. package/dist/utils/time-measure-axis-sql.js +2 -1
  94. package/dist/utils/time-measure-leaf-sql.d.ts +1 -1
  95. package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -1
  96. package/dist/utils/time-measure-leaf-sql.js +3 -1
  97. package/dist/utils/time-measure-source-sql.d.ts +3 -0
  98. package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
  99. package/dist/utils/time-measure-source-sql.js +10 -9
  100. package/package.json +3 -3
@@ -1,23 +1,23 @@
1
- import { datasetSourceBuilder } from './dataset-source.js';
1
+ import { scopedDatasetSource } from './scoped-dataset-source.js';
2
2
  import { semanticQuerySyntaxErrors } from './semantic-query-syntax.js';
3
3
  /** Metric SQL compilation; factory selection and execution belong to the client. */
4
4
  import { resolveDatasetSqlDialect } from './dataset-sql-dialect.js';
5
5
  import { assertRawSqlForDialect } from './raw-sql-dialect-check.js';
6
- import { applyAggregationSpec, appendOrderLimitOffset, buildDimensionSelectionPlan, resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn, } from '../query-planner.js';
6
+ import { applyAggregationSpec, appendOrderLimitOffset, buildDimensionSelectionPlan, resolveDimensionExpression, resolveFilterField, } from '../query-planner.js';
7
7
  import { queryTimeFilter } from './query-timezone.js';
8
8
  import { isTenantScopedFilter } from './metric-handle.js';
9
- import { getRuntimeTenantPredicate } from './tenant-runtime.js';
10
- import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn, } from './relationship-builder-plan.js';
9
+ import { buildRelationshipBuilderContext } from './relationship-builder-plan.js';
11
10
  import { segmentFilters } from './segments.js';
12
- export function buildMetricQueryBuilder(ref, spec, ds, query, grain, builderFactory, context) {
11
+ import { datasetOnTimeKey } from './dataset-time-keys.js';
12
+ export function buildMetricQueryBuilder(ref, spec, dataset, query, grain, builderFactory, context) {
13
13
  const syntaxErrors = semanticQuerySyntaxErrors(query);
14
14
  if (syntaxErrors.length)
15
15
  throw new Error(syntaxErrors.join('; '));
16
+ const ds = datasetOnTimeKey(dataset, query.timeKey);
16
17
  const dialect = resolveDatasetSqlDialect(builderFactory);
17
18
  assertRawSqlForDialect(ds, dialect);
18
19
  const joinCtx = buildRelationshipBuilderContext(ds, query, context, dialect);
19
- let qb = datasetSourceBuilder(builderFactory, ds);
20
- qb = applyRelationshipJoins(qb, joinCtx);
20
+ let qb = scopedDatasetSource({ builderFactory, ds, context, joinCtx, dialect, timezone: query.timezone });
21
21
  const { selectParts, groupByParts } = buildDimensionSelectionPlan(ds, query.dimensions ?? [], grain, joinCtx, query.timezone, dialect);
22
22
  if (selectParts.length > 0) {
23
23
  qb = qb.select(selectParts);
@@ -28,12 +28,6 @@ export function buildMetricQueryBuilder(ref, spec, ds, query, grain, builderFact
28
28
  qb = qb.groupBy(groupByParts);
29
29
  }
30
30
  qb = applyAggregationSpec(qb, ds, spec, ref.name, joinCtx, dialect);
31
- // Tenant auto-injection
32
- const tenantColumn = resolveTenantFilterColumn(ds, context);
33
- const tenantPredicate = getRuntimeTenantPredicate(context);
34
- if (tenantPredicate && tenantColumn) {
35
- qb = qb.where(qualifyBaseColumn(joinCtx, tenantColumn), tenantPredicate.operator, tenantPredicate.value);
36
- }
37
31
  // User filters
38
32
  for (const filter of query.filters ?? []) {
39
33
  if (isTenantScopedFilter(ds, filter, context)) {
@@ -1 +1 @@
1
- {"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"AAEA,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,CAoIlB"}
1
+ {"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"AAEA,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;AAQ3G,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAuIlB"}
@@ -9,10 +9,15 @@ import { validateTenantRuntime } from './tenant-runtime.js';
9
9
  import { isQualifiedField, resolveQualifiedField } from './relationship-fields.js';
10
10
  import { validateQualifiedFilter, validateRelationshipTenantRuntime } from './relationship-validation.js';
11
11
  import { segmentSelectionErrors } from './segments.js';
12
+ import { datasetOnTimeKey, selectedTimeKeyError } from './dataset-time-keys.js';
13
+ import { enforcedConstraintErrors } from './enforced-constraints.js';
12
14
  export function validateMetricQueryInput(metric, query, context) {
13
15
  const errors = [...semanticQuerySyntaxErrors(query), ...protocolMetricCapabilityErrors(metric, query), ...queryTimezoneErrors(query.timezone)];
14
16
  const ref = getMetricRef(metric);
15
- const ds = ref.dataset;
17
+ const timeKeyError = selectedTimeKeyError(ref.dataset, query.timeKey);
18
+ if (timeKeyError)
19
+ errors.push(timeKeyError);
20
+ const ds = timeKeyError ? ref.dataset : datasetOnTimeKey(ref.dataset, query.timeKey);
16
21
  const dimensionNames = Object.keys(ds.dimensions);
17
22
  const filterNames = Object.keys(ds.filters);
18
23
  const grain = getMetricGrain(metric, query);
@@ -29,6 +34,7 @@ export function validateMetricQueryInput(metric, query, context) {
29
34
  if (relationshipTenantError) {
30
35
  errors.push(relationshipTenantError);
31
36
  }
37
+ errors.push(...enforcedConstraintErrors(ds, context, query.filters?.length ?? 0));
32
38
  if (metric.__type === 'grained_metric_ref' && query.by && query.by !== metric.grain) {
33
39
  errors.push(`Metric "${ref.name}" is already grained by "${metric.grain}" and cannot be queried with by="${query.by}".`);
34
40
  }
@@ -5,7 +5,7 @@ export declare function isQueryTimezone(value: unknown): value is string;
5
5
  export declare function queryTimezoneErrors(timezone: unknown): string[];
6
6
  /** Conversion preserves timestamp instants and interprets Date values as local dates. */
7
7
  export declare function queryTimeSql(dialect: DatasetSqlDialectRules, expression: string, timezone: string | undefined): string;
8
- /** True when a local filter field reads the dataset's time-key column. */
8
+ /** True when a local filter field reads one of the dataset's time-axis columns. */
9
9
  export declare function isTimeKeyFilter(ds: AnyDatasetInstance, field: string): boolean;
10
10
  /** Apply the query zone to time-key predicates, including semantic filter aliases. */
11
11
  export declare function queryTimeFilterSql(dialect: DatasetSqlDialectRules, ds: AnyDatasetInstance, field: string, expression: string, timezone: string | undefined): string;
@@ -1 +1 @@
1
- {"version":3,"file":"query-timezone.d.ts","sourceRoot":"","sources":["../../src/utils/query-timezone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAK1F,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAS/D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAG/D;AASD,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAEtH;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAM9E;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,sBAAsB,EAC/B,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GACtF,MAAM,CAER;AAED,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,sFAAsF;IACtF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CACzC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,EAC/B,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,GAAE,sBAA2B,GACnC,kBAAkB,EAAE,CAwCtB"}
1
+ {"version":3,"file":"query-timezone.d.ts","sourceRoot":"","sources":["../../src/utils/query-timezone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAM1F,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAS/D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAG/D;AASD,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAEtH;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAK9E;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,sBAAsB,EAC/B,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GACtF,MAAM,CAER;AAED,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,sFAAsF;IACtF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CACzC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,EAC/B,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,GAAE,sBAA2B,GACnC,kBAAkB,EAAE,CAwCtB"}
@@ -1,6 +1,7 @@
1
1
  import { resolveDimensionFieldType, resolveFilterFieldType } from './filter-field-type.js';
2
2
  import { instantWallClock, parseTimestampInstant } from './timestamp-instants.js';
3
3
  import { mixedInstantListError } from '../validation.js';
4
+ import { datasetTimeKeys } from './dataset-time-keys.js';
4
5
  /** Named zones only: reject fixed offsets, invalid zones, and SQL fragments. */
5
6
  export function isQueryTimezone(value) {
6
7
  if (typeof value !== 'string' || value.length > 100
@@ -29,14 +30,13 @@ function canonicalTimezone(timezone) {
29
30
  export function queryTimeSql(dialect, expression, timezone) {
30
31
  return dialect.toQueryTimezone(expression, canonicalTimezone(timezone));
31
32
  }
32
- /** True when a local filter field reads the dataset's time-key column. */
33
+ /** True when a local filter field reads one of the dataset's time-axis columns. */
33
34
  export function isTimeKeyFilter(ds, field) {
34
35
  if (!ds.timeKey || field.includes('.'))
35
36
  return false;
36
37
  const resolved = ds.filters[field]?.field ?? field;
37
38
  const column = ds.dimensions[resolved]?.column ?? resolved;
38
- const timeColumn = ds.dimensions[ds.timeKey]?.column ?? ds.timeKey;
39
- return column === timeColumn;
39
+ return datasetTimeKeys(ds).some(timeKey => (ds.dimensions[timeKey]?.column ?? timeKey) === column);
40
40
  }
41
41
  /** Apply the query zone to time-key predicates, including semantic filter aliases. */
42
42
  export function queryTimeFilterSql(dialect, ds, field, expression, timezone) {
@@ -19,7 +19,7 @@ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
19
19
  */
20
20
  import type { AnyDatasetInstance, DatasetQuery, ExecutionContext, MetricQuery } from '../types.js';
21
21
  import type { QueryBuilderLike } from '../query-builder-protocol.js';
22
- import { type TenantPredicate } from './tenant-runtime.js';
22
+ import { type RowScopePredicate } from './row-scope.js';
23
23
  export interface ResolvedBuilderJoin {
24
24
  /** Relationship name, used as the joined table's SQL alias. */
25
25
  relationship: string;
@@ -32,10 +32,10 @@ export interface ResolvedBuilderJoin {
32
32
  from: string;
33
33
  /** Target join column (unqualified). */
34
34
  to: string;
35
- /** Tenant predicate applied to the joined target, when active. */
36
- tenant?: TenantPredicate & {
37
- field: string;
38
- };
35
+ /** The target's own row scope (RFC 0020), applied in the join condition. */
36
+ scope: RowScopePredicate[];
37
+ /** The target is read with `FINAL` (RFC 0021), so the query needs the PREWHERE pin. */
38
+ final: boolean;
39
39
  }
40
40
  export interface RelationshipBuilderContext {
41
41
  baseSource: string;
@@ -46,9 +46,10 @@ export interface RelationshipBuilderContext {
46
46
  }
47
47
  type QueryLike = Pick<DatasetQuery & MetricQuery, 'dimensions' | 'filters' | 'orderBy'> & Pick<DatasetQuery, 'measures'>;
48
48
  /**
49
- * Builds the join context for a query, or returns undefined when the query
50
- * references no relationship-qualified fields. Validation runs first, so any
51
- * qualified name that fails to resolve here is skipped defensively.
49
+ * Builds the join context for a query, or returns undefined when neither the
50
+ * query nor the call's enforced constraints reference a relationship-qualified
51
+ * field. Validation runs first, so any qualified name that fails to resolve
52
+ * here is skipped defensively.
52
53
  */
53
54
  export declare function buildRelationshipBuilderContext(ds: AnyDatasetInstance, query: QueryLike, context?: ExecutionContext, dialect?: DatasetSqlDialectRules): RelationshipBuilderContext | undefined;
54
55
  /** Qualifies a base physical column with the base source when joins are active. */
@@ -68,8 +69,9 @@ export declare function qualifyBaseColumn(ctx: RelationshipBuilderContext | unde
68
69
  export declare function resolveQualifiedExpression(ds: AnyDatasetInstance, name: string, ctx: RelationshipBuilderContext): string;
69
70
  /**
70
71
  * Applies each relationship join to the builder as a single-match LEFT JOIN
71
- * (`leftAnyJoin`, e.g. ClickHouse `LEFT ANY JOIN`) and, when runtime tenancy
72
- * is active on a target, scopes the joined rows with the tenant predicate.
72
+ * (`leftAnyJoin`, e.g. ClickHouse `LEFT ANY JOIN`), scoping the joined rows
73
+ * with the target's own row scope: an out-of-scope target row behaves exactly
74
+ * like a missing one.
73
75
  *
74
76
  * A builder without `leftAnyJoin` is refused rather than downgraded to a plain
75
77
  * `leftJoin`: duplicate target keys would fan out base rows and silently
@@ -1 +1 @@
1
- {"version":3,"file":"relationship-builder-plan.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-builder-plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAGxE;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtF,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAClC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,MAAM,CAAC,EAAE,eAAe,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACrD,oEAAoE;IACpE,OAAO,EAAE,sBAAsB,CAAC;CACjC;AAED,KAAK,SAAS,GAAG,IAAI,CAAC,YAAY,GAAG,WAAW,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEzH;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,SAAS,EAChB,OAAO,CAAC,EAAE,gBAAgB,EAC1B,OAAO,GAAE,sBAAoD,GAC5D,0BAA0B,GAAG,SAAS,CAgDxC;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,0BAA0B,GAAG,SAAS,EAC3C,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,0BAA0B,GAC9B,MAAM,CAaR;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,0BAA0B,GAAG,SAAS,GAC1C,gBAAgB,CAoClB"}
1
+ {"version":3,"file":"relationship-builder-plan.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-builder-plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAGxE;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAuC,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAG7F,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAClC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,uFAAuF;IACvF,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACrD,oEAAoE;IACpE,OAAO,EAAE,sBAAsB,CAAC;CACjC;AAED,KAAK,SAAS,GAAG,IAAI,CAAC,YAAY,GAAG,WAAW,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEzH;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,SAAS,EAChB,OAAO,CAAC,EAAE,gBAAgB,EAC1B,OAAO,GAAE,sBAAoD,GAC5D,0BAA0B,GAAG,SAAS,CA+CxC;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,0BAA0B,GAAG,SAAS,EAC3C,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,0BAA0B,GAC9B,MAAM,CAaR;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,gBAAgB,EACpB,GAAG,EAAE,0BAA0B,GAAG,SAAS,GAC1C,gBAAgB,CAoClB"}
@@ -2,12 +2,13 @@ import { relationshipKeys } from './relationship-keys.js';
2
2
  import { relationshipJoinSource } from './relationship-measure-sql.js';
3
3
  import { clickhouseDatasetSqlDialect } from './clickhouse-dataset-sql-dialect.js';
4
4
  import { resolveRelationshipReference } from './relationship-measures.js';
5
- import { getRuntimeTenantPredicate } from './tenant-runtime.js';
5
+ import { datasetRowScope, runtimeConstraints } from './row-scope.js';
6
6
  import { isQualifiedField, resolveQualifiedField } from './relationship-fields.js';
7
7
  /**
8
- * Builds the join context for a query, or returns undefined when the query
9
- * references no relationship-qualified fields. Validation runs first, so any
10
- * qualified name that fails to resolve here is skipped defensively.
8
+ * Builds the join context for a query, or returns undefined when neither the
9
+ * query nor the call's enforced constraints reference a relationship-qualified
10
+ * field. Validation runs first, so any qualified name that fails to resolve
11
+ * here is skipped defensively.
11
12
  */
12
13
  export function buildRelationshipBuilderContext(ds, query, context, dialect = clickhouseDatasetSqlDialect) {
13
14
  const referenced = [
@@ -15,11 +16,11 @@ export function buildRelationshipBuilderContext(ds, query, context, dialect = cl
15
16
  ...(query.measures ?? []),
16
17
  ...(query.filters ?? []).map((filter) => filter.field),
17
18
  ...(query.orderBy ?? []).map((order) => order.field),
19
+ ...runtimeConstraints(context).map((constraint) => constraint.field),
18
20
  ].filter(isQualifiedField);
19
21
  if (referenced.length === 0) {
20
22
  return undefined;
21
23
  }
22
- const tenantPredicate = getRuntimeTenantPredicate(context);
23
24
  const joinByRelationship = new Map();
24
25
  for (const name of referenced) {
25
26
  const resolution = resolveRelationshipReference(ds, name, query.measures);
@@ -42,9 +43,8 @@ export function buildRelationshipBuilderContext(ds, query, context, dialect = cl
42
43
  from: relationship.from,
43
44
  to: relationship.to,
44
45
  keys,
45
- tenant: tenantPredicate && target.tenantKey
46
- ? { field: target.tenantKey, ...tenantPredicate }
47
- : undefined,
46
+ scope: datasetRowScope(target, context),
47
+ final: Boolean(target.rowResolution),
48
48
  });
49
49
  }
50
50
  return {
@@ -86,8 +86,9 @@ export function resolveQualifiedExpression(ds, name, ctx) {
86
86
  }
87
87
  /**
88
88
  * Applies each relationship join to the builder as a single-match LEFT JOIN
89
- * (`leftAnyJoin`, e.g. ClickHouse `LEFT ANY JOIN`) and, when runtime tenancy
90
- * is active on a target, scopes the joined rows with the tenant predicate.
89
+ * (`leftAnyJoin`, e.g. ClickHouse `LEFT ANY JOIN`), scoping the joined rows
90
+ * with the target's own row scope: an out-of-scope target row behaves exactly
91
+ * like a missing one.
91
92
  *
92
93
  * A builder without `leftAnyJoin` is refused rather than downgraded to a plain
93
94
  * `leftJoin`: duplicate target keys would fan out base rows and silently
@@ -104,20 +105,20 @@ export function applyRelationshipJoins(qb, ctx) {
104
105
  'would fan out duplicate target keys and inflate aggregates, so relationship-qualified fields ' +
105
106
  'are unavailable with this builder.');
106
107
  }
107
- const tenant = join.tenant
108
- ? {
109
- column: `${join.relationship}.${join.tenant.field}`,
110
- operator: join.tenant.operator,
111
- value: join.tenant.value,
112
- }
113
- : undefined;
108
+ const conditions = join.scope.map(predicate => ({
109
+ column: `${join.relationship}.${predicate.column}`,
110
+ operator: predicate.operator,
111
+ value: predicate.value,
112
+ }));
113
+ // A single condition keeps the object form older builders accept.
114
+ const scope = conditions.length > 1 ? conditions : conditions[0];
114
115
  if (join.keys && join.keys.length > 1) {
115
116
  // Key-pair form; @hypequery/clickhouse >= 2.13.0.
116
117
  const pairs = join.keys.map(key => [`${ctx.baseSource}.${key.from}`, `${join.relationship}.${key.to}`]);
117
- qb = qb.leftAnyJoin(join.source, [pairs[0], ...pairs.slice(1)], join.relationship, tenant);
118
+ qb = qb.leftAnyJoin(join.source, [pairs[0], ...pairs.slice(1)], join.relationship, scope);
118
119
  continue;
119
120
  }
120
- qb = qb.leftAnyJoin(join.source, `${ctx.baseSource}.${join.from}`, `${join.relationship}.${join.to}`, join.relationship, tenant);
121
+ qb = qb.leftAnyJoin(join.source, `${ctx.baseSource}.${join.from}`, `${join.relationship}.${join.to}`, join.relationship, scope);
121
122
  }
122
123
  return qb;
123
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"relationship-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-measure-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,aAAa,CAAC;AAEvE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAIjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAExE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,sBAAsB;;;EAkBlJ;AAeD,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,0BAA0B,EAAE,OAAO,EAAE,sBAAsB;;;;EAexJ"}
1
+ {"version":3,"file":"relationship-measure-sql.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-measure-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,aAAa,CAAC;AAEvE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAIjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAExE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,sBAAsB;;;EAoBlJ;AAeD,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,0BAA0B,EAAE,OAAO,EAAE,sBAAsB;;;;EAexJ"}
@@ -1,3 +1,4 @@
1
+ import { currentRowPredicate } from './dataset-source.js';
1
2
  import { resolveRelationshipMeasure } from './relationship-measures.js';
2
3
  import { resolveDimensionExpression } from '../query-planner.js';
3
4
  import { measureToAggregationSpec } from './dataset-normalization.js';
@@ -35,15 +36,17 @@ export function relationshipJoinSource(target, to, names, dialect) {
35
36
  // Sorted so the SQL is byte-identical however the target's dimensions are
36
37
  // ordered (authored vs. rehydrated catalogs declare them differently).
37
38
  const projection = [...columns].sort().map(quote);
38
- return { source: `(SELECT ${[...projection, `${dialect.toNullable('1')} AS ${quote(marker)}`].join(', ')} FROM ${source})`, marker };
39
+ const current = currentRowPredicate(target);
40
+ const where = current ? ` WHERE ${quote(current.column)} = ${current.value}` : '';
41
+ return { source: `(SELECT ${[...projection, `${dialect.toNullable('1')} AS ${quote(marker)}`].join(', ')} FROM ${source}${where})`, marker };
39
42
  }
40
43
  /** The target's quoted table, read with `FINAL` when the target dataset requires it. */
41
44
  function relationshipTargetTable(target, dialect) {
42
45
  const quoted = dialect.quoteQualifiedName(target.source);
43
- if (!target.final)
46
+ if (!target.rowResolution)
44
47
  return quoted;
45
48
  if (!dialect.finalTable) {
46
- throw new Error(`Dataset "${target.name}" is declared final: true, but the "${dialect.name}" SQL dialect has no FINAL ` +
49
+ throw new Error(`Dataset "${target.name}" reads its source with FINAL, but the "${dialect.name}" SQL dialect has no FINAL ` +
47
50
  'modifier, so it cannot be joined without counting unmerged row versions.');
48
51
  }
49
52
  return dialect.finalTable(quoted);
@@ -1 +1 @@
1
- {"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAWrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAoBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,CA4Bf"}
1
+ {"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAYrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAqBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,CA4Bf"}
@@ -1,6 +1,7 @@
1
1
  import { resolveRelationshipReference } from './relationship-measures.js';
2
2
  import { validateFilterValue } from '../validation.js';
3
3
  import { getRuntimeTenantPredicate, hasTenantRuntime, } from './tenant-runtime.js';
4
+ import { runtimeConstraints } from './row-scope.js';
4
5
  import { isQualifiedField, resolveQualifiedField, } from './relationship-fields.js';
5
6
  /**
6
7
  * Tenant-less base datasets may still reach tenant-scoped data through a
@@ -16,6 +17,7 @@ export function validateRelationshipTenantRuntime(ds, query, context) {
16
17
  ...(query.measures ?? []),
17
18
  ...(query.filters ?? []).map((filter) => filter.field),
18
19
  ...(query.orderBy ?? []).map((order) => order.field),
20
+ ...runtimeConstraints(context).map((constraint) => constraint.field),
19
21
  ].filter(isQualifiedField);
20
22
  for (const name of referenced) {
21
23
  const resolution = resolveRelationshipReference(ds, name, query.measures);
@@ -0,0 +1,23 @@
1
+ import type { AnyDatasetInstance, ExecutionContext, MetricFilter } from '../types.js';
2
+ import type { QueryBuilderLike } from '../query-builder-protocol.js';
3
+ import { type TenantPredicate } from './tenant-runtime.js';
4
+ /** A trusted predicate on one of a dataset's own physical columns. */
5
+ export type RowScopePredicate = TenantPredicate & {
6
+ readonly column: string;
7
+ };
8
+ /**
9
+ * The trusted predicates restricting which source rows of `ds` any query may
10
+ * read (RFC 0020). They apply to the queried dataset and to every joined
11
+ * relationship target alike. `tenantKey` with a runtime tenant lowers to one
12
+ * predicate; the trusted cross-tenant scope (`scope: 'all'`) lowers to none.
13
+ */
14
+ export declare function datasetRowScope(ds: Pick<AnyDatasetInstance, 'tenantKey'>, context?: ExecutionContext): RowScopePredicate[];
15
+ /**
16
+ * The call's RFC 0019 enforced constraints, from trusted context only. They
17
+ * apply to the queried dataset, in its filter vocabulary, never to joined
18
+ * targets.
19
+ */
20
+ export declare function runtimeConstraints(context?: ExecutionContext): readonly MetricFilter[];
21
+ /** ANDs each row-scope predicate onto `qb`, with columns qualified by `qualify`. */
22
+ export declare function applyRowScope(qb: QueryBuilderLike, scope: readonly RowScopePredicate[], qualify?: (column: string) => string): QueryBuilderLike;
23
+ //# sourceMappingURL=row-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-scope.d.ts","sourceRoot":"","sources":["../../src/utils/row-scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtF,sEAAsE;AACtE,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,CAAC,EACzC,OAAO,CAAC,EAAE,gBAAgB,GACzB,iBAAiB,EAAE,CAGrB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,YAAY,EAAE,CAEtF;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAC3B,EAAE,EAAE,gBAAgB,EACpB,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,OAAO,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAyB,GACrD,gBAAgB,CAKlB"}
@@ -0,0 +1,26 @@
1
+ import { getRuntimeTenantPredicate } from './tenant-runtime.js';
2
+ /**
3
+ * The trusted predicates restricting which source rows of `ds` any query may
4
+ * read (RFC 0020). They apply to the queried dataset and to every joined
5
+ * relationship target alike. `tenantKey` with a runtime tenant lowers to one
6
+ * predicate; the trusted cross-tenant scope (`scope: 'all'`) lowers to none.
7
+ */
8
+ export function datasetRowScope(ds, context) {
9
+ const tenant = getRuntimeTenantPredicate(context);
10
+ return tenant && ds.tenantKey ? [{ column: ds.tenantKey, ...tenant }] : [];
11
+ }
12
+ /**
13
+ * The call's RFC 0019 enforced constraints, from trusted context only. They
14
+ * apply to the queried dataset, in its filter vocabulary, never to joined
15
+ * targets.
16
+ */
17
+ export function runtimeConstraints(context) {
18
+ return context?.runtime?.constraints ?? [];
19
+ }
20
+ /** ANDs each row-scope predicate onto `qb`, with columns qualified by `qualify`. */
21
+ export function applyRowScope(qb, scope, qualify = column => column) {
22
+ for (const predicate of scope) {
23
+ qb = qb.where(qualify(predicate.column), predicate.operator, predicate.value);
24
+ }
25
+ return qb;
26
+ }
@@ -0,0 +1,23 @@
1
+ import type { AnyDatasetInstance, ExecutionContext } from '../types.js';
2
+ import type { QueryBuilderFactoryLike, QueryBuilderLike } from '../query-builder-protocol.js';
3
+ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
4
+ import { type RelationshipBuilderContext } from './relationship-builder-plan.js';
5
+ export interface ScopedDatasetSourceOptions {
6
+ builderFactory: Pick<QueryBuilderFactoryLike, 'table'>;
7
+ ds: AnyDatasetInstance;
8
+ context?: ExecutionContext;
9
+ joinCtx: RelationshipBuilderContext | undefined;
10
+ dialect: DatasetSqlDialectRules;
11
+ timezone: string | undefined;
12
+ }
13
+ /**
14
+ * The dataset's source as a builder with every trusted row restriction
15
+ * applied (RFC 0020): `FINAL`, relationship joins scoped by each target's own
16
+ * row scope, the dataset's row scope, and the call's enforced constraints.
17
+ *
18
+ * Every builder-path query shape reads its source through here, so none can
19
+ * read rows the context does not allow. Callers add their own selections,
20
+ * filters and aggregations on top; every predicate here is ANDed with them.
21
+ */
22
+ export declare function scopedDatasetSource(options: ScopedDatasetSourceOptions): QueryBuilderLike;
23
+ //# sourceMappingURL=scoped-dataset-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoped-dataset-source.d.ts","sourceRoot":"","sources":["../../src/utils/scoped-dataset-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAC9F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAIxE,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,gCAAgC,CAAC;AAIxC,MAAM,WAAW,0BAA0B;IACzC,cAAc,EAAE,IAAI,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IACvD,EAAE,EAAE,kBAAkB,CAAC;IACvB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,0BAA0B,GAAG,SAAS,CAAC;IAChD,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,gBAAgB,CAiBzF"}
@@ -0,0 +1,34 @@
1
+ import { resolveFilterField } from '../query-planner.js';
2
+ import { FINAL_SAFETY_SETTINGS, datasetSourceBuilder, queryReadsFinal } from './dataset-source.js';
3
+ import { queryTimeFilter } from './query-timezone.js';
4
+ import { applyRelationshipJoins, qualifyBaseColumn, } from './relationship-builder-plan.js';
5
+ import { assertEnforcedConstraints } from './enforced-constraints.js';
6
+ import { applyRowScope, datasetRowScope, runtimeConstraints } from './row-scope.js';
7
+ /**
8
+ * The dataset's source as a builder with every trusted row restriction
9
+ * applied (RFC 0020): `FINAL`, relationship joins scoped by each target's own
10
+ * row scope, the dataset's row scope, and the call's enforced constraints.
11
+ *
12
+ * Every builder-path query shape reads its source through here, so none can
13
+ * read rows the context does not allow. Callers add their own selections,
14
+ * filters and aggregations on top; every predicate here is ANDed with them.
15
+ */
16
+ export function scopedDatasetSource(options) {
17
+ const { ds, context, joinCtx, dialect, timezone } = options;
18
+ // Validators report this too; checked here so no path can skip it.
19
+ assertEnforcedConstraints(ds, context);
20
+ let qb = applyRelationshipJoins(datasetSourceBuilder(options.builderFactory, ds), joinCtx);
21
+ // A resolved join target is read with FINAL even when this dataset isn't.
22
+ if (queryReadsFinal(ds, joinCtx) && qb.settings)
23
+ qb = qb.settings(FINAL_SAFETY_SETTINGS);
24
+ qb = applyRowScope(qb, datasetRowScope(ds, context), column => qualifyBaseColumn(joinCtx, column));
25
+ // Constraints are filters in the dataset's vocabulary, applied exactly as a
26
+ // caller filter on the same field would be.
27
+ for (const constraint of runtimeConstraints(context)) {
28
+ const expression = resolveFilterField(ds, constraint.field, joinCtx);
29
+ for (const predicate of queryTimeFilter(dialect, ds, constraint, expression, timezone)) {
30
+ qb = qb.where(predicate.expression, predicate.operator, predicate.value);
31
+ }
32
+ }
33
+ return qb;
34
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-execution-context.d.ts","sourceRoot":"","sources":["../../src/utils/snapshot-execution-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,SAAS,CAajG"}
1
+ {"version":3,"file":"snapshot-execution-context.d.ts","sourceRoot":"","sources":["../../src/utils/snapshot-execution-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,SAAS,CAgBjG"}
@@ -9,6 +9,9 @@ export function snapshotExecutionContext(context) {
9
9
  ...context.runtime,
10
10
  // Copy tenant objects and ID lists; retain adapter and AbortSignal identity.
11
11
  tenant: structuredClone(context.runtime.tenant),
12
+ ...(context.runtime.constraints === undefined
13
+ ? {}
14
+ : { constraints: structuredClone(context.runtime.constraints) }),
12
15
  },
13
16
  }),
14
17
  ...(context.cache && { cache: { ...context.cache } }),
@@ -1,6 +1,6 @@
1
1
  import type { StatementParameters } from './statement-parameters.js';
2
2
  import type { TimeMeasureAxis } from './time-measure-axis.js';
3
- import type { TimeMeasureSqlSource } from './time-measure-source-sql.js';
3
+ import { type TimeMeasureSqlSource } from './time-measure-source-sql.js';
4
4
  import type { ShiftMeasureDefinition } from '../types.js';
5
5
  export interface TimeMeasureSqlAxis {
6
6
  ctes: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"time-measure-axis-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,6DAA6D;AAC7D,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,mBAAmB,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,SAAS,sBAAsB,EAAO,GAC7C,kBAAkB,CA2CpB"}
1
+ {"version":3,"file":"time-measure-axis-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-axis-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAE5F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,6DAA6D;AAC7D,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,mBAAmB,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,SAAS,sBAAsB,EAAO,GAC7C,kBAAkB,CA2CpB"}
@@ -1,3 +1,4 @@
1
+ import { sourceTimezoneSql } from './time-measure-source-sql.js';
1
2
  import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
2
3
  import { shiftCalendarGuardSql, shiftWallTimeSeriesGuardSql } from './shift-measure-sql.js';
3
4
  /** Build bounded output buckets using the query timezone. */
@@ -24,7 +25,7 @@ export function buildTimeMeasureAxisSql(statement, source, axis, shifts = []) {
24
25
  const postSeriesGuard = shifts.map(shift => shiftWallTimeSeriesGuardSql(dialect, shift, axis.grain)).join(' + ') || '0';
25
26
  // A zero-row scalar retains the converted timestamp type and query timezone.
26
27
  // Reading any(time) here would scan the entire population just for its type.
27
- const timezone = dialect.timezoneOf(dialect.assumeNotNull('(SELECT _hq_time FROM _hq_source LIMIT 0)'));
28
+ const timezone = sourceTimezoneSql(dialect);
28
29
  const bounds = [
29
30
  // The bounds come from the caller's time filter: bind them, never inline them.
30
31
  `${dialect.parseTimestamp(statement.bind(axis.lower, 'string'), timezone)} AS _hq_lower`,
@@ -1,7 +1,7 @@
1
1
  import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
2
2
  import type { WindowMeasureDefinition } from '../types.js';
3
3
  import type { TimeMeasureAxis } from './time-measure-axis.js';
4
- import type { TimeMeasureSqlBase, TimeMeasureSqlDimension } from './time-measure-source-sql.js';
4
+ import { type TimeMeasureSqlBase, type TimeMeasureSqlDimension } from './time-measure-source-sql.js';
5
5
  /** Keyed source membership retains distinct/average semantics and row multiplicity. */
6
6
  export declare function timeMeasureLeafSql(dialect: DatasetSqlDialectRules, id: number, context: number, base: TimeMeasureSqlBase, window: WindowMeasureDefinition | undefined, axis: TimeMeasureAxis, dims: readonly TimeMeasureSqlDimension[]): {
7
7
  ctes: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"time-measure-leaf-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-leaf-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAKhG,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,uBAAuB,EAAE;;;;;;;;EA8CtO"}
1
+ {"version":3,"file":"time-measure-leaf-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-leaf-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAqB,KAAK,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAKxH,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,sBAAsB,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,uBAAuB,EAAE;;;;;;;;EA+CtO"}
@@ -1,3 +1,4 @@
1
+ import { sourceTimezoneSql } from './time-measure-source-sql.js';
1
2
  import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
2
3
  import { shiftRangePredicateSql } from './shift-measure-sql.js';
3
4
  import { isCountAggregation } from './count-aggregations.js';
@@ -27,7 +28,8 @@ export function timeMeasureLeafSql(dialect, id, context, base, window, axis, dim
27
28
  };
28
29
  }
29
30
  const lower = window?.trailing ? dialect.subtractTime('_hq_eval_end', window.trailing.amount, window.trailing.unit)
30
- : window?.toDate ? dialect.truncateTime('_hq_eval_period', window.toDate) : '_hq_eval_lower';
31
+ // Keep the to-date start zoned: a calendar Date would bucket in server time.
32
+ : window?.toDate ? dialect.truncateTimeInZone('_hq_eval_period', window.toDate, sourceTimezoneSql(dialect)) : '_hq_eval_lower';
31
33
  const upper = window ? '_hq_eval_end' : '_hq_eval_upper';
32
34
  const lowerInclusive = window ? '1' : '_hq_lower_inclusive';
33
35
  const upperInclusive = window ? '0' : '_hq_upper_inclusive';
@@ -1,6 +1,7 @@
1
1
  import type { AnyDatasetInstance, DatasetQuery, MeasureDefinition, MetricFilter } from '../types.js';
2
2
  import type { DatasetQueryExecutionOptions } from '../dataset-query.js';
3
3
  import type { StatementParameters } from './statement-parameters.js';
4
+ import type { DatasetSqlDialectRules } from '../dataset-sql-dialect.js';
4
5
  export interface TimeMeasureSqlDimension {
5
6
  name: string;
6
7
  alias: string;
@@ -17,6 +18,8 @@ export interface TimeMeasureSqlSource {
17
18
  bases: TimeMeasureSqlBase[];
18
19
  rowAlias: string;
19
20
  }
21
+ /** The query timezone carried by `_hq_source` time, read from a zero-row scalar without scanning. */
22
+ export declare function sourceTimezoneSql(dialect: DatasetSqlDialectRules): string;
20
23
  /** Isolate authored expressions, joins, tenancy and filters from internal SQL aliases. */
21
24
  export declare function buildTimeMeasureSourceSql(ds: AnyDatasetInstance, query: DatasetQuery, baseNames: Iterable<string>, filters: readonly MetricFilter[], options: DatasetQueryExecutionOptions, statement: StatementParameters): TimeMeasureSqlSource;
22
25
  //# sourceMappingURL=time-measure-source-sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"time-measure-source-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-source-sql.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AASxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,0FAA0F;AAC1F,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,EAAE,4BAA4B,EACrC,SAAS,EAAE,mBAAmB,GAC7B,oBAAoB,CAqEtB"}
1
+ {"version":3,"file":"time-measure-source-sql.d.ts","sourceRoot":"","sources":["../../src/utils/time-measure-source-sql.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAQxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAEzE;AAED,0FAA0F;AAC1F,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,EAAE,4BAA4B,EACrC,SAAS,EAAE,mBAAmB,GAC7B,oBAAoB,CAoEtB"}
@@ -1,15 +1,18 @@
1
- import { datasetSourceBuilder } from './dataset-source.js';
1
+ import { scopedDatasetSource } from './scoped-dataset-source.js';
2
2
  import { resolveRelationshipMeasure } from './relationship-measures.js';
3
3
  import { relationshipMeasureExpressions } from './relationship-measure-sql.js';
4
4
  import { queryTimeFilter, queryTimeSql } from './query-timezone.js';
5
- import { resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn } from '../query-planner.js';
6
- import { applyRelationshipJoins, buildRelationshipBuilderContext, qualifyBaseColumn } from './relationship-builder-plan.js';
7
- import { getRuntimeTenantPredicate } from './tenant-runtime.js';
5
+ import { resolveDimensionExpression, resolveFilterField } from '../query-planner.js';
6
+ import { buildRelationshipBuilderContext } from './relationship-builder-plan.js';
8
7
  import { segmentFilters } from './segments.js';
9
8
  import { getBaseMeasure } from './dataset-measures.js';
10
9
  import { measureToAggregationSpec } from './dataset-normalization.js';
11
10
  import { applyFilteredAggregationExpression } from './filtered-aggregation-sql.js';
12
11
  import { assertNoRawSqlUnderJoins } from './sql-under-joins.js';
12
+ /** The query timezone carried by `_hq_source` time, read from a zero-row scalar without scanning. */
13
+ export function sourceTimezoneSql(dialect) {
14
+ return dialect.timezoneOf(dialect.assumeNotNull('(SELECT _hq_time FROM _hq_source LIMIT 0)'));
15
+ }
13
16
  /** Isolate authored expressions, joins, tenancy and filters from internal SQL aliases. */
14
17
  export function buildTimeMeasureSourceSql(ds, query, baseNames, filters, options, statement) {
15
18
  const { dialect } = statement;
@@ -17,7 +20,9 @@ export function buildTimeMeasureSourceSql(ds, query, baseNames, filters, options
17
20
  const joinCtx = buildRelationshipBuilderContext(ds, query, options.context, dialect);
18
21
  // Keep source expressions in their own SELECT scope: internal aliases cannot
19
22
  // capture authored SQL identifiers, even if a physical column shares a name.
20
- let raw = applyRelationshipJoins(datasetSourceBuilder(options.builderFactory, ds), joinCtx);
23
+ let raw = scopedDatasetSource({
24
+ builderFactory: options.builderFactory, ds, context: options.context, joinCtx, dialect, timezone: query.timezone,
25
+ });
21
26
  const dims = (query.dimensions ?? []).map((name, i) => ({ name, alias: `_hq_d${i}` }));
22
27
  const sourceParts = [resolveDimensionExpression(ds, ds.timeKey, joinCtx)];
23
28
  for (const dimension of dims)
@@ -50,10 +55,6 @@ export function buildTimeMeasureSourceSql(ds, query, baseNames, filters, options
50
55
  while (sourceIdentifiers.some(text => text.includes(rowAlias)))
51
56
  rowAlias += '_';
52
57
  raw = raw.select([`${dialect.packRow(sourceParts)} AS ${rowAlias}`]);
53
- const tenant = getRuntimeTenantPredicate(options.context);
54
- const tenantColumn = resolveTenantFilterColumn(ds, options.context);
55
- if (tenant && tenantColumn)
56
- raw = raw.where(qualifyBaseColumn(joinCtx, tenantColumn), tenant.operator, tenant.value);
57
58
  // The time axis already consumed every time-key bound, so these are the
58
59
  // remaining caller filters.
59
60
  for (const filter of filters) {