@objectstack/service-analytics 17.0.0-rc.6 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4574 -1
- package/dist/index.cjs +239 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +234 -7
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/dist/index.d.cts
CHANGED
|
@@ -522,6 +522,42 @@ interface AnalyticsServiceConfig {
|
|
|
522
522
|
* across the publish boundary. Reads only; never touches physical tables.
|
|
523
523
|
*/
|
|
524
524
|
draftRowsResolver?: (objectName: string, context?: ExecutionContext) => Promise<Record<string, unknown>[] | null>;
|
|
525
|
+
/**
|
|
526
|
+
* [#8286] Echo the executed statement back to the CALLER in
|
|
527
|
+
* `AnalyticsResult.sql`. **Off unless this host opts in.**
|
|
528
|
+
*
|
|
529
|
+
* The contract has always declared the echo debug-only —
|
|
530
|
+
* `AnalyticsResultResponseSchema.data.sql` (`spec/api/analytics.zod.ts`) is
|
|
531
|
+
* `optional()` and describes itself as "Executed SQL (if debug enabled)" —
|
|
532
|
+
* but no implementation ever read a debug switch, so every `/analytics/query`
|
|
533
|
+
* response carried the statement, on production deployments included. This
|
|
534
|
+
* field is that switch: declared = enforced, restored at the one seam the
|
|
535
|
+
* response leaves through ({@link AnalyticsService.query}).
|
|
536
|
+
*
|
|
537
|
+
* **Default** — `NODE_ENV === 'development'`, and nothing else. In
|
|
538
|
+
* particular an UNSET `NODE_ENV` counts as production and the echo stays
|
|
539
|
+
* off: that is the maintainer's 2026-08-06 ruling for machine-readable
|
|
540
|
+
* environment answers (see `resolveDiscoveryEnvironment` and the note at
|
|
541
|
+
* `runtime/src/http-dispatcher.ts`), and of the two ways to be wrong,
|
|
542
|
+
* disclosing on a production deployment whose operator forgot the variable
|
|
543
|
+
* is the dangerous one. `os start` forces `NODE_ENV='production'` when
|
|
544
|
+
* unset, `os serve` resolves `NODE_ENV || 'production'`, `os doctor` derives
|
|
545
|
+
* the same expression — this switch now reads the absence the same way.
|
|
546
|
+
*
|
|
547
|
+
* **Why not a request field.** There is none, deliberately: a caller-set
|
|
548
|
+
* debug flag would let any tenant re-open the disclosure on demand, which is
|
|
549
|
+
* the shape of the defect rather than a fix for it. The echo is a HOST
|
|
550
|
+
* decision, and the caller-facing surface for "show me the SQL" already
|
|
551
|
+
* exists as the dedicated dry-run route `/api/v1/analytics/sql`
|
|
552
|
+
* (`generateSql`), which this switch does not touch.
|
|
553
|
+
*
|
|
554
|
+
* **Why not the plugin's `debug` (log) option.** Server-side log verbosity
|
|
555
|
+
* and what travels to a caller are different decisions with different blast
|
|
556
|
+
* radii; folding them together means a support engineer raising log level on
|
|
557
|
+
* a live deployment silently reopens the disclosure. Two switches, named for
|
|
558
|
+
* what they open.
|
|
559
|
+
*/
|
|
560
|
+
debugSql?: boolean;
|
|
525
561
|
}
|
|
526
562
|
/**
|
|
527
563
|
* AnalyticsService — Multi-driver analytics orchestrator.
|
|
@@ -569,6 +605,11 @@ declare class AnalyticsService implements IAnalyticsService {
|
|
|
569
605
|
private readonly isExternalObject?;
|
|
570
606
|
/** [#3867] One-shot flag for the {@link assertInferableCube} stand-down warning. */
|
|
571
607
|
private warnedNoObjectRegistry;
|
|
608
|
+
/**
|
|
609
|
+
* [#8286] Does the executed statement travel back to the caller?
|
|
610
|
+
* See {@link AnalyticsServiceConfig.debugSql} for the switch and its default.
|
|
611
|
+
*/
|
|
612
|
+
private readonly debugSql;
|
|
572
613
|
readonly cubeRegistry: CubeRegistry;
|
|
573
614
|
private readonly logger;
|
|
574
615
|
constructor(config?: AnalyticsServiceConfig);
|
|
@@ -604,6 +645,25 @@ declare class AnalyticsService implements IAnalyticsService {
|
|
|
604
645
|
* Any other error propagates untouched.
|
|
605
646
|
*/
|
|
606
647
|
query(query: AnalyticsQuery, context?: ExecutionContext): Promise<AnalyticsResult>;
|
|
648
|
+
/**
|
|
649
|
+
* [#8286] Withhold the executed statement unless this host enabled the echo.
|
|
650
|
+
*
|
|
651
|
+
* Applied at {@link query}, which is the response-assembly seam for BOTH
|
|
652
|
+
* faces that serve callers: `/api/v1/analytics/query` calls it directly, and
|
|
653
|
+
* `queryDataset` reaches it through `DatasetExecutor`, so a dataset response
|
|
654
|
+
* inherits the same verdict without a second gate to keep in step.
|
|
655
|
+
* `generateSql` — the dedicated `/api/v1/analytics/sql` dry-run route — is
|
|
656
|
+
* deliberately NOT gated: asking for the statement is that route's entire
|
|
657
|
+
* purpose, and it is the surface a debugging author is meant to use.
|
|
658
|
+
*
|
|
659
|
+
* What the echo disclosed, and why "it is only a table name" understates it:
|
|
660
|
+
* the statement carries the compiled read scope, i.e. the SHAPE of the
|
|
661
|
+
* isolation predicate (`"sys_user"."id" IN ($2, $3, …)` rather than an
|
|
662
|
+
* `organization_id` comparison) plus its bound-parameter arity, which counts
|
|
663
|
+
* the caller's own org membership. No wall was breached by it — the echo is
|
|
664
|
+
* information disclosure, and this is the disclosure closing.
|
|
665
|
+
*/
|
|
666
|
+
private applySqlEchoPolicy;
|
|
607
667
|
/**
|
|
608
668
|
* Compile a `dataset` (ADR-0021) and register its Cube + join allowlist so it
|
|
609
669
|
* can be queried by name. Idempotent (re-registering overwrites). Returns the
|
|
@@ -893,6 +953,17 @@ interface AnalyticsServicePluginOptions {
|
|
|
893
953
|
getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;
|
|
894
954
|
/** Enable debug logging. */
|
|
895
955
|
debug?: boolean;
|
|
956
|
+
/**
|
|
957
|
+
* [#8286] Echo the executed statement back to CALLERS in
|
|
958
|
+
* `AnalyticsResult.sql` (`/api/v1/analytics/query`).
|
|
959
|
+
*
|
|
960
|
+
* Distinct from {@link AnalyticsServicePluginOptions.debug} above, which is
|
|
961
|
+
* server-side log verbosity only: raising log level must never widen what
|
|
962
|
+
* travels to a tenant. Default and rationale live with the service config —
|
|
963
|
+
* see `AnalyticsServiceConfig.debugSql`. Undefined here means "no host
|
|
964
|
+
* choice", which the service resolves to development-only.
|
|
965
|
+
*/
|
|
966
|
+
debugSql?: boolean;
|
|
896
967
|
}
|
|
897
968
|
/**
|
|
898
969
|
* AnalyticsServicePlugin — Kernel plugin for multi-driver analytics.
|
|
@@ -1115,6 +1186,47 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
|
|
|
1115
1186
|
readonly name = "NativeSQLStrategy";
|
|
1116
1187
|
readonly priority = 10;
|
|
1117
1188
|
canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean;
|
|
1189
|
+
/**
|
|
1190
|
+
* [#7598] Does serving this query require the cross-field capability this
|
|
1191
|
+
* strategy declines? See the ruling recorded at {@link canHandle}.
|
|
1192
|
+
*
|
|
1193
|
+
* ⚠️ This and {@link assertNoCrossFieldComparison} read the SAME inputs
|
|
1194
|
+
* through the SAME detector, which is what makes the decline and the
|
|
1195
|
+
* fail-closed backstop unable to drift: a shape one of them recognises is a
|
|
1196
|
+
* shape the other recognises.
|
|
1197
|
+
*/
|
|
1198
|
+
private carriesCrossFieldComparison;
|
|
1199
|
+
private crossFieldComparisonIn;
|
|
1200
|
+
/**
|
|
1201
|
+
* [#7598] The fail-closed backstop at the door that BINDS.
|
|
1202
|
+
*
|
|
1203
|
+
* ⚠️ **Unreachable by construction, and kept deliberately** — saying so
|
|
1204
|
+
* because #7598's brief asks that a refusal arm which has become unreachable
|
|
1205
|
+
* be named rather than left to be re-discovered. {@link canHandle} declines
|
|
1206
|
+
* every query this would fire on, and it declines using
|
|
1207
|
+
* {@link crossFieldComparisonIn} — the same walk over the same two inputs —
|
|
1208
|
+
* so `resolveStrategy` cannot hand this strategy a query carrying one.
|
|
1209
|
+
*
|
|
1210
|
+
* It is kept because of what the failure mode is if that ever stops being
|
|
1211
|
+
* true. The defect #7598 measured was not a missing error: it was a SILENT
|
|
1212
|
+
* BIND — `toSqlBindValue` JSON-stringifies the reference object, so the
|
|
1213
|
+
* statement compiled perfectly and compared a column against the text
|
|
1214
|
+
* `{"$field":"budget"}`, a value no row can hold. A routing gate that misses
|
|
1215
|
+
* a shape therefore degrades to a wrong ANSWER rather than to an error, and
|
|
1216
|
+
* that is the one class this package refuses to leave to a single guard
|
|
1217
|
+
* (Prime Directive #12 — refuse at the door, do not tolerate at the
|
|
1218
|
+
* consumer). One line, no measurable cost, and it turns a routing regression
|
|
1219
|
+
* into a loud refusal instead of an empty chart.
|
|
1220
|
+
*
|
|
1221
|
+
* Deliberately BARE — an undeclared 500, not `INVALID_FILTER` / 400 — for the
|
|
1222
|
+
* reason `buildFilterClauseSql`'s #5333 exit in `objectql-strategy.ts` gives
|
|
1223
|
+
* for the same class: the caller's filter is legal and is served on the
|
|
1224
|
+
* engine path, so an arrival here is drift between our own routing gate and
|
|
1225
|
+
* our own emitter. Billing the caller 400 for that would hide a platform bug
|
|
1226
|
+
* from 5xx alerting and tell a dashboard user to fix a filter that is fine.
|
|
1227
|
+
* Same tier as `resolveMeasureSql`'s unrecognised-`Metric.type` throw below.
|
|
1228
|
+
*/
|
|
1229
|
+
private assertNoCrossFieldComparison;
|
|
1118
1230
|
execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult>;
|
|
1119
1231
|
generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{
|
|
1120
1232
|
sql: string;
|
|
@@ -1513,6 +1625,18 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
|
|
|
1513
1625
|
*/
|
|
1514
1626
|
private convertFilter;
|
|
1515
1627
|
private extractObjectName;
|
|
1628
|
+
/**
|
|
1629
|
+
* [#7598] The query's `where`, lowered — the same input
|
|
1630
|
+
* `NativeSQLStrategy.canHandle` scans, so the strategy that DECLINED and the
|
|
1631
|
+
* echo that refuses read one shape rather than two.
|
|
1632
|
+
*
|
|
1633
|
+
* A throw from the lowering is swallowed for the same reason it is there: the
|
|
1634
|
+
* `where` is malformed either way and `normalizeAnalyticsFilterTree` below
|
|
1635
|
+
* refuses it with the message and envelope it has always had. This helper's
|
|
1636
|
+
* only job is finding a reference, and there is none to find in a filter that
|
|
1637
|
+
* does not lower.
|
|
1638
|
+
*/
|
|
1639
|
+
private loweredWhere;
|
|
1516
1640
|
/**
|
|
1517
1641
|
* The dimensions this query PROJECTS, in the order the result carries them:
|
|
1518
1642
|
* every `dimensions` entry, then every granular `timeDimensions` entry that
|
package/dist/index.d.ts
CHANGED
|
@@ -522,6 +522,42 @@ interface AnalyticsServiceConfig {
|
|
|
522
522
|
* across the publish boundary. Reads only; never touches physical tables.
|
|
523
523
|
*/
|
|
524
524
|
draftRowsResolver?: (objectName: string, context?: ExecutionContext) => Promise<Record<string, unknown>[] | null>;
|
|
525
|
+
/**
|
|
526
|
+
* [#8286] Echo the executed statement back to the CALLER in
|
|
527
|
+
* `AnalyticsResult.sql`. **Off unless this host opts in.**
|
|
528
|
+
*
|
|
529
|
+
* The contract has always declared the echo debug-only —
|
|
530
|
+
* `AnalyticsResultResponseSchema.data.sql` (`spec/api/analytics.zod.ts`) is
|
|
531
|
+
* `optional()` and describes itself as "Executed SQL (if debug enabled)" —
|
|
532
|
+
* but no implementation ever read a debug switch, so every `/analytics/query`
|
|
533
|
+
* response carried the statement, on production deployments included. This
|
|
534
|
+
* field is that switch: declared = enforced, restored at the one seam the
|
|
535
|
+
* response leaves through ({@link AnalyticsService.query}).
|
|
536
|
+
*
|
|
537
|
+
* **Default** — `NODE_ENV === 'development'`, and nothing else. In
|
|
538
|
+
* particular an UNSET `NODE_ENV` counts as production and the echo stays
|
|
539
|
+
* off: that is the maintainer's 2026-08-06 ruling for machine-readable
|
|
540
|
+
* environment answers (see `resolveDiscoveryEnvironment` and the note at
|
|
541
|
+
* `runtime/src/http-dispatcher.ts`), and of the two ways to be wrong,
|
|
542
|
+
* disclosing on a production deployment whose operator forgot the variable
|
|
543
|
+
* is the dangerous one. `os start` forces `NODE_ENV='production'` when
|
|
544
|
+
* unset, `os serve` resolves `NODE_ENV || 'production'`, `os doctor` derives
|
|
545
|
+
* the same expression — this switch now reads the absence the same way.
|
|
546
|
+
*
|
|
547
|
+
* **Why not a request field.** There is none, deliberately: a caller-set
|
|
548
|
+
* debug flag would let any tenant re-open the disclosure on demand, which is
|
|
549
|
+
* the shape of the defect rather than a fix for it. The echo is a HOST
|
|
550
|
+
* decision, and the caller-facing surface for "show me the SQL" already
|
|
551
|
+
* exists as the dedicated dry-run route `/api/v1/analytics/sql`
|
|
552
|
+
* (`generateSql`), which this switch does not touch.
|
|
553
|
+
*
|
|
554
|
+
* **Why not the plugin's `debug` (log) option.** Server-side log verbosity
|
|
555
|
+
* and what travels to a caller are different decisions with different blast
|
|
556
|
+
* radii; folding them together means a support engineer raising log level on
|
|
557
|
+
* a live deployment silently reopens the disclosure. Two switches, named for
|
|
558
|
+
* what they open.
|
|
559
|
+
*/
|
|
560
|
+
debugSql?: boolean;
|
|
525
561
|
}
|
|
526
562
|
/**
|
|
527
563
|
* AnalyticsService — Multi-driver analytics orchestrator.
|
|
@@ -569,6 +605,11 @@ declare class AnalyticsService implements IAnalyticsService {
|
|
|
569
605
|
private readonly isExternalObject?;
|
|
570
606
|
/** [#3867] One-shot flag for the {@link assertInferableCube} stand-down warning. */
|
|
571
607
|
private warnedNoObjectRegistry;
|
|
608
|
+
/**
|
|
609
|
+
* [#8286] Does the executed statement travel back to the caller?
|
|
610
|
+
* See {@link AnalyticsServiceConfig.debugSql} for the switch and its default.
|
|
611
|
+
*/
|
|
612
|
+
private readonly debugSql;
|
|
572
613
|
readonly cubeRegistry: CubeRegistry;
|
|
573
614
|
private readonly logger;
|
|
574
615
|
constructor(config?: AnalyticsServiceConfig);
|
|
@@ -604,6 +645,25 @@ declare class AnalyticsService implements IAnalyticsService {
|
|
|
604
645
|
* Any other error propagates untouched.
|
|
605
646
|
*/
|
|
606
647
|
query(query: AnalyticsQuery, context?: ExecutionContext): Promise<AnalyticsResult>;
|
|
648
|
+
/**
|
|
649
|
+
* [#8286] Withhold the executed statement unless this host enabled the echo.
|
|
650
|
+
*
|
|
651
|
+
* Applied at {@link query}, which is the response-assembly seam for BOTH
|
|
652
|
+
* faces that serve callers: `/api/v1/analytics/query` calls it directly, and
|
|
653
|
+
* `queryDataset` reaches it through `DatasetExecutor`, so a dataset response
|
|
654
|
+
* inherits the same verdict without a second gate to keep in step.
|
|
655
|
+
* `generateSql` — the dedicated `/api/v1/analytics/sql` dry-run route — is
|
|
656
|
+
* deliberately NOT gated: asking for the statement is that route's entire
|
|
657
|
+
* purpose, and it is the surface a debugging author is meant to use.
|
|
658
|
+
*
|
|
659
|
+
* What the echo disclosed, and why "it is only a table name" understates it:
|
|
660
|
+
* the statement carries the compiled read scope, i.e. the SHAPE of the
|
|
661
|
+
* isolation predicate (`"sys_user"."id" IN ($2, $3, …)` rather than an
|
|
662
|
+
* `organization_id` comparison) plus its bound-parameter arity, which counts
|
|
663
|
+
* the caller's own org membership. No wall was breached by it — the echo is
|
|
664
|
+
* information disclosure, and this is the disclosure closing.
|
|
665
|
+
*/
|
|
666
|
+
private applySqlEchoPolicy;
|
|
607
667
|
/**
|
|
608
668
|
* Compile a `dataset` (ADR-0021) and register its Cube + join allowlist so it
|
|
609
669
|
* can be queried by name. Idempotent (re-registering overwrites). Returns the
|
|
@@ -893,6 +953,17 @@ interface AnalyticsServicePluginOptions {
|
|
|
893
953
|
getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;
|
|
894
954
|
/** Enable debug logging. */
|
|
895
955
|
debug?: boolean;
|
|
956
|
+
/**
|
|
957
|
+
* [#8286] Echo the executed statement back to CALLERS in
|
|
958
|
+
* `AnalyticsResult.sql` (`/api/v1/analytics/query`).
|
|
959
|
+
*
|
|
960
|
+
* Distinct from {@link AnalyticsServicePluginOptions.debug} above, which is
|
|
961
|
+
* server-side log verbosity only: raising log level must never widen what
|
|
962
|
+
* travels to a tenant. Default and rationale live with the service config —
|
|
963
|
+
* see `AnalyticsServiceConfig.debugSql`. Undefined here means "no host
|
|
964
|
+
* choice", which the service resolves to development-only.
|
|
965
|
+
*/
|
|
966
|
+
debugSql?: boolean;
|
|
896
967
|
}
|
|
897
968
|
/**
|
|
898
969
|
* AnalyticsServicePlugin — Kernel plugin for multi-driver analytics.
|
|
@@ -1115,6 +1186,47 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
|
|
|
1115
1186
|
readonly name = "NativeSQLStrategy";
|
|
1116
1187
|
readonly priority = 10;
|
|
1117
1188
|
canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean;
|
|
1189
|
+
/**
|
|
1190
|
+
* [#7598] Does serving this query require the cross-field capability this
|
|
1191
|
+
* strategy declines? See the ruling recorded at {@link canHandle}.
|
|
1192
|
+
*
|
|
1193
|
+
* ⚠️ This and {@link assertNoCrossFieldComparison} read the SAME inputs
|
|
1194
|
+
* through the SAME detector, which is what makes the decline and the
|
|
1195
|
+
* fail-closed backstop unable to drift: a shape one of them recognises is a
|
|
1196
|
+
* shape the other recognises.
|
|
1197
|
+
*/
|
|
1198
|
+
private carriesCrossFieldComparison;
|
|
1199
|
+
private crossFieldComparisonIn;
|
|
1200
|
+
/**
|
|
1201
|
+
* [#7598] The fail-closed backstop at the door that BINDS.
|
|
1202
|
+
*
|
|
1203
|
+
* ⚠️ **Unreachable by construction, and kept deliberately** — saying so
|
|
1204
|
+
* because #7598's brief asks that a refusal arm which has become unreachable
|
|
1205
|
+
* be named rather than left to be re-discovered. {@link canHandle} declines
|
|
1206
|
+
* every query this would fire on, and it declines using
|
|
1207
|
+
* {@link crossFieldComparisonIn} — the same walk over the same two inputs —
|
|
1208
|
+
* so `resolveStrategy` cannot hand this strategy a query carrying one.
|
|
1209
|
+
*
|
|
1210
|
+
* It is kept because of what the failure mode is if that ever stops being
|
|
1211
|
+
* true. The defect #7598 measured was not a missing error: it was a SILENT
|
|
1212
|
+
* BIND — `toSqlBindValue` JSON-stringifies the reference object, so the
|
|
1213
|
+
* statement compiled perfectly and compared a column against the text
|
|
1214
|
+
* `{"$field":"budget"}`, a value no row can hold. A routing gate that misses
|
|
1215
|
+
* a shape therefore degrades to a wrong ANSWER rather than to an error, and
|
|
1216
|
+
* that is the one class this package refuses to leave to a single guard
|
|
1217
|
+
* (Prime Directive #12 — refuse at the door, do not tolerate at the
|
|
1218
|
+
* consumer). One line, no measurable cost, and it turns a routing regression
|
|
1219
|
+
* into a loud refusal instead of an empty chart.
|
|
1220
|
+
*
|
|
1221
|
+
* Deliberately BARE — an undeclared 500, not `INVALID_FILTER` / 400 — for the
|
|
1222
|
+
* reason `buildFilterClauseSql`'s #5333 exit in `objectql-strategy.ts` gives
|
|
1223
|
+
* for the same class: the caller's filter is legal and is served on the
|
|
1224
|
+
* engine path, so an arrival here is drift between our own routing gate and
|
|
1225
|
+
* our own emitter. Billing the caller 400 for that would hide a platform bug
|
|
1226
|
+
* from 5xx alerting and tell a dashboard user to fix a filter that is fine.
|
|
1227
|
+
* Same tier as `resolveMeasureSql`'s unrecognised-`Metric.type` throw below.
|
|
1228
|
+
*/
|
|
1229
|
+
private assertNoCrossFieldComparison;
|
|
1118
1230
|
execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult>;
|
|
1119
1231
|
generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{
|
|
1120
1232
|
sql: string;
|
|
@@ -1513,6 +1625,18 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
|
|
|
1513
1625
|
*/
|
|
1514
1626
|
private convertFilter;
|
|
1515
1627
|
private extractObjectName;
|
|
1628
|
+
/**
|
|
1629
|
+
* [#7598] The query's `where`, lowered — the same input
|
|
1630
|
+
* `NativeSQLStrategy.canHandle` scans, so the strategy that DECLINED and the
|
|
1631
|
+
* echo that refuses read one shape rather than two.
|
|
1632
|
+
*
|
|
1633
|
+
* A throw from the lowering is swallowed for the same reason it is there: the
|
|
1634
|
+
* `where` is malformed either way and `normalizeAnalyticsFilterTree` below
|
|
1635
|
+
* refuses it with the message and envelope it has always had. This helper's
|
|
1636
|
+
* only job is finding a reference, and there is none to find in a filter that
|
|
1637
|
+
* does not lower.
|
|
1638
|
+
*/
|
|
1639
|
+
private loweredWhere;
|
|
1516
1640
|
/**
|
|
1517
1641
|
* The dimensions this query PROJECTS, in the order the result carries them:
|
|
1518
1642
|
* every `dimensions` entry, then every granular `timeDimensions` entry that
|