@hypequery/datasets 0.17.1 → 0.19.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/dist/agent-catalog.d.ts +9 -0
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/aggregations.d.ts +2 -0
- package/dist/aggregations.d.ts.map +1 -1
- package/dist/aggregations.js +4 -0
- package/dist/api.type-test.js +40 -1
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +17 -0
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +12 -2
- package/dist/cloud-publishing.d.ts +23 -0
- package/dist/cloud-publishing.d.ts.map +1 -0
- package/dist/cloud-publishing.js +57 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/contract.d.ts +5 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +7 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +6 -1
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +3 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +15 -4
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +34 -7
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/measure.d.ts +5 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +5 -0
- package/dist/portable-execution-errors.d.ts +4 -0
- package/dist/portable-execution-errors.d.ts.map +1 -1
- package/dist/portable-execution-errors.js +4 -1
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +6 -3
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +15 -7
- package/dist/protocol-rehydrate.d.ts +5 -1
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +20 -10
- package/dist/query-builder-protocol.d.ts +7 -3
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +5 -0
- package/dist/relationship-check.d.ts +47 -0
- package/dist/relationship-check.d.ts.map +1 -0
- package/dist/relationship-check.js +59 -0
- package/dist/relationships.d.ts +10 -10
- package/dist/relationships.js +10 -10
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +5 -1
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +5 -0
- package/dist/sql-portability.d.ts.map +1 -1
- package/dist/sql-portability.js +88 -19
- package/dist/types.d.ts +39 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +6 -0
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +4 -0
- package/dist/utils/approximate-measures.d.ts +10 -0
- package/dist/utils/approximate-measures.d.ts.map +1 -0
- package/dist/utils/approximate-measures.js +15 -0
- package/dist/utils/collect-cloud-datasets.d.ts +3 -0
- package/dist/utils/collect-cloud-datasets.d.ts.map +1 -0
- package/dist/utils/collect-cloud-datasets.js +12 -0
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +2 -2
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
- package/dist/utils/dataset-metric-ref.js +5 -0
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +6 -3
- package/dist/utils/dataset-time-grains.d.ts +15 -0
- package/dist/utils/dataset-time-grains.d.ts.map +1 -0
- package/dist/utils/dataset-time-grains.js +17 -0
- package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
- package/dist/utils/filtered-aggregation-sql.js +1 -0
- package/dist/utils/portable-aggregations.d.ts +14 -0
- package/dist/utils/portable-aggregations.d.ts.map +1 -0
- package/dist/utils/portable-aggregations.js +15 -0
- package/dist/utils/portable-grains.d.ts +22 -0
- package/dist/utils/portable-grains.d.ts.map +1 -0
- package/dist/utils/portable-grains.js +35 -0
- package/dist/utils/portable-semantic-query.d.ts.map +1 -1
- package/dist/utils/portable-semantic-query.js +2 -1
- package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
- package/dist/utils/protocol-metric-expressions.js +2 -1
- package/dist/utils/relationship-builder-plan.d.ts +6 -3
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +13 -5
- package/dist/utils/relationship-key-check.d.ts +43 -0
- package/dist/utils/relationship-key-check.d.ts.map +1 -0
- package/dist/utils/relationship-key-check.js +72 -0
- package/dist/utils/segments.d.ts +31 -0
- package/dist/utils/segments.d.ts.map +1 -0
- package/dist/utils/segments.js +91 -0
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
- package/dist/utils/semantic-metadata-validation.js +22 -1
- package/dist/utils/unsupported-contract-reasons.d.ts +32 -0
- package/dist/utils/unsupported-contract-reasons.d.ts.map +1 -0
- package/dist/utils/unsupported-contract-reasons.js +30 -0
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationship-check.d.ts","sourceRoot":"","sources":["../src/relationship-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAyB,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAEnG,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,YAAY,EAAE,wBAAwB,CAAC;IACvC;;;OAGG;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,EAAE,EAAE,OAAO,CAAC;IACZ,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAKD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAkCnC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies declared to-one relationships against the data.
|
|
3
|
+
*
|
|
4
|
+
* `belongsTo` and `hasOne` are declarations: nothing at query time proves the
|
|
5
|
+
* target join column is unique. Relationship joins are single-match, so a
|
|
6
|
+
* duplicate key cannot inflate an aggregate, but the join then takes an
|
|
7
|
+
* arbitrary one of the matching rows and grouped values drift without any
|
|
8
|
+
* error. `checkRelationships` counts rows and distinct keys on each target so a
|
|
9
|
+
* mis-declaration is caught in CI or at deploy time rather than in a report.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const result = await checkRelationships(Orders, { queryBuilder: db });
|
|
14
|
+
* if (!result.ok) {
|
|
15
|
+
* throw new Error(result.issues.map((issue) => issue.message).join('\n'));
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { toQueryBuilderFactory } from './query-builder-protocol.js';
|
|
20
|
+
import { getRuntimeTenantPredicate, validateTenantRuntime } from './utils/tenant-runtime.js';
|
|
21
|
+
import { listToOneRelationships, readCount, relationshipKeyIssue, } from './utils/relationship-key-check.js';
|
|
22
|
+
const ROWS_ALIAS = '__hq_rows';
|
|
23
|
+
const KEYS_ALIAS = '__hq_keys';
|
|
24
|
+
/**
|
|
25
|
+
* Checks that the target join column of each `belongsTo` and `hasOne`
|
|
26
|
+
* relationship on `ds` is unique. NULL keys are ignored because they never
|
|
27
|
+
* match a join. `hasMany` relationships are skipped.
|
|
28
|
+
*/
|
|
29
|
+
export async function checkRelationships(ds, options) {
|
|
30
|
+
const factory = toQueryBuilderFactory(options.queryBuilder);
|
|
31
|
+
const tenantPredicate = getRuntimeTenantPredicate(options.context);
|
|
32
|
+
const entries = listToOneRelationships(ds, options.relationships);
|
|
33
|
+
const issues = [];
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const tenantError = validateTenantRuntime(entry.target, options.context);
|
|
36
|
+
if (tenantError) {
|
|
37
|
+
throw new Error(`Cannot check relationship "${entry.relationship}": ${tenantError}`);
|
|
38
|
+
}
|
|
39
|
+
let qb = factory
|
|
40
|
+
.table(entry.target.source)
|
|
41
|
+
.count(entry.column, ROWS_ALIAS)
|
|
42
|
+
.countDistinct(entry.column, KEYS_ALIAS);
|
|
43
|
+
if (entry.target.tenantKey && tenantPredicate) {
|
|
44
|
+
qb = qb.where(entry.target.tenantKey, tenantPredicate.operator, tenantPredicate.value);
|
|
45
|
+
}
|
|
46
|
+
const [row] = await qb.execute({
|
|
47
|
+
abortSignal: options.context?.abortSignal,
|
|
48
|
+
});
|
|
49
|
+
const issue = relationshipKeyIssue(entry, readCount(row?.[ROWS_ALIAS]), readCount(row?.[KEYS_ALIAS]));
|
|
50
|
+
if (issue) {
|
|
51
|
+
issues.push(issue);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
ok: issues.length === 0,
|
|
56
|
+
checked: entries.map((entry) => entry.relationship),
|
|
57
|
+
issues,
|
|
58
|
+
};
|
|
59
|
+
}
|
package/dist/relationships.d.ts
CHANGED
|
@@ -10,15 +10,14 @@
|
|
|
10
10
|
* is not addressable through a relationship, so there are no cross-dataset
|
|
11
11
|
* metrics.
|
|
12
12
|
*
|
|
13
|
-
* Note that the `kind` passed here is a declaration, not something checked
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the declaration has to be right.
|
|
13
|
+
* Note that the `kind` passed here is a declaration, not something checked at
|
|
14
|
+
* query time. Joins are single-match (`leftAnyJoin`, ClickHouse
|
|
15
|
+
* `LEFT ANY JOIN`), so a mis-declared to-one relationship cannot inflate an
|
|
16
|
+
* aggregate, but it does make the join pick an arbitrary one of the matching
|
|
17
|
+
* target rows. Builders without `leftAnyJoin` are refused rather than
|
|
18
|
+
* downgraded to a fan-out `leftJoin`. The in-memory backend refuses duplicate
|
|
19
|
+
* target keys outright, and `checkRelationships()` reports them against a live
|
|
20
|
+
* database, so a wrong declaration can be caught before it skews results.
|
|
22
21
|
*
|
|
23
22
|
* @example
|
|
24
23
|
* ```ts
|
|
@@ -29,7 +28,8 @@
|
|
|
29
28
|
* customerId: dimension.string({ column: "customer_id" }),
|
|
30
29
|
* },
|
|
31
30
|
* relationships: {
|
|
32
|
-
*
|
|
31
|
+
* // Join keys are source columns, not dimension names.
|
|
32
|
+
* customer: belongsTo(() => Customers, { from: "customer_id", to: "id" }),
|
|
33
33
|
* },
|
|
34
34
|
* });
|
|
35
35
|
*
|
package/dist/relationships.js
CHANGED
|
@@ -10,15 +10,14 @@
|
|
|
10
10
|
* is not addressable through a relationship, so there are no cross-dataset
|
|
11
11
|
* metrics.
|
|
12
12
|
*
|
|
13
|
-
* Note that the `kind` passed here is a declaration, not something checked
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the declaration has to be right.
|
|
13
|
+
* Note that the `kind` passed here is a declaration, not something checked at
|
|
14
|
+
* query time. Joins are single-match (`leftAnyJoin`, ClickHouse
|
|
15
|
+
* `LEFT ANY JOIN`), so a mis-declared to-one relationship cannot inflate an
|
|
16
|
+
* aggregate, but it does make the join pick an arbitrary one of the matching
|
|
17
|
+
* target rows. Builders without `leftAnyJoin` are refused rather than
|
|
18
|
+
* downgraded to a fan-out `leftJoin`. The in-memory backend refuses duplicate
|
|
19
|
+
* target keys outright, and `checkRelationships()` reports them against a live
|
|
20
|
+
* database, so a wrong declaration can be caught before it skews results.
|
|
22
21
|
*
|
|
23
22
|
* @example
|
|
24
23
|
* ```ts
|
|
@@ -29,7 +28,8 @@
|
|
|
29
28
|
* customerId: dimension.string({ column: "customer_id" }),
|
|
30
29
|
* },
|
|
31
30
|
* relationships: {
|
|
32
|
-
*
|
|
31
|
+
* // Join keys are source columns, not dimension names.
|
|
32
|
+
* customer: belongsTo(() => Customers, { from: "customer_id", to: "id" }),
|
|
33
33
|
* },
|
|
34
34
|
* });
|
|
35
35
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-planner.d.ts","sourceRoot":"","sources":["../src/semantic-planner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"semantic-planner.d.ts","sourceRoot":"","sources":["../src/semantic-planner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,oBAAoB,CAAC;AA6L5B,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,kBAAkB,EACtB,KAAK,GAAE,YAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAgBV;AA2FD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,KAAK,GAAE,WAAgB,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAYV"}
|
package/dist/semantic-planner.js
CHANGED
|
@@ -4,6 +4,7 @@ import { validateDatasetQuery } from './dataset-query.js';
|
|
|
4
4
|
import { isSupportedTimeGrain } from './constants.js';
|
|
5
5
|
import { getRuntimeTenantPredicate } from './utils/tenant-runtime.js';
|
|
6
6
|
import { isQualifiedField, resolveQualifiedField } from './utils/relationship-fields.js';
|
|
7
|
+
import { segmentFilters } from './utils/segments.js';
|
|
7
8
|
function resolveField(ds, field) {
|
|
8
9
|
const dimension = ds.dimensions[field];
|
|
9
10
|
if (dimension?.sql) {
|
|
@@ -135,7 +136,10 @@ function aggregatePlan(ds, query, aggregations, context) {
|
|
|
135
136
|
source: ds.source,
|
|
136
137
|
dimensions: dimensionsForQuery(ds, query.dimensions),
|
|
137
138
|
aggregations,
|
|
138
|
-
filters:
|
|
139
|
+
filters: [
|
|
140
|
+
...normalizeFilters(ds, query.filters),
|
|
141
|
+
...segmentFilters(ds, query.segments).map(filter => ({ ...filter, field: resolveField(ds, filter.field) })),
|
|
142
|
+
],
|
|
139
143
|
grain: grainForQuery(ds, query.by),
|
|
140
144
|
orderBy: query.orderBy,
|
|
141
145
|
limit: query.limit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,iFAAiF;IACjF,UAAU,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,gGAAgG;AAChG,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAC9C,CAAC;AAEF,eAAO,MAAM,oCAAoC;;;;;;;EAO/C,CAAC;AAEH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,iFAAiF;IACjF,UAAU,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,gGAAgG;AAChG,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAC9C,CAAC;AAEF,eAAO,MAAM,oCAAoC;;;;;;;EAO/C,CAAC;AAEH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AA8K9E,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE,0BAA+B,GACvC,UAAU,CASZ;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,yBAAyB,EAClC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,0BAA+B,EACxC,cAAc,CAAC,EAAE,2BAA2B,GAC3C,UAAU,CAcZ;AAsBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,UAAU,CAMZ;AAyBD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,EACnD,OAAO,GAAE,0BAA+B,GACvC,6BAA6B,CAgD/B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE,0BAA+B,EACxC,IAAI,SAAkB,GACrB,cAAc,CAEhB;AAED,kEAAkE;AAClE,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,yBAAyB,EAClC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,0BAA+B,EACxC,cAAc,CAAC,EAAE,2BAA2B,EAC5C,IAAI,SAAiB,GACpB,cAAc,CAKhB"}
|
|
@@ -138,6 +138,11 @@ function queryShape(catalog, metricName, options, relationshipOperators, metric,
|
|
|
138
138
|
measures: boundedArray(fieldEnum([...Object.keys(catalog.measures), ...Object.keys(catalog.derivedMeasures ?? {})]), lowerLimit(catalog.limits?.maxMeasures, limits.maxMeasures)),
|
|
139
139
|
}),
|
|
140
140
|
filters: boundedArray(filterSchema(catalog, filterFields, relationshipOperators), lowerLimit(catalog.limits?.maxFilters, limits.maxFilters)),
|
|
141
|
+
// Only datasets that declare segments accept the field, so every other
|
|
142
|
+
// schema, including hosted catalogs, is unchanged.
|
|
143
|
+
...(Object.keys(catalog.segments ?? {}).length > 0 ? {
|
|
144
|
+
segments: boundedArray(fieldEnum(uniqueSorted(Object.keys(catalog.segments ?? {})))),
|
|
145
|
+
} : {}),
|
|
141
146
|
orderBy: boundedArray(z.object({
|
|
142
147
|
field: fieldEnum(orderable),
|
|
143
148
|
direction: z.enum(['asc', 'desc']),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-portability.d.ts","sourceRoot":"","sources":["../src/sql-portability.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AAEH,MAAM,MAAM,uBAAuB,GAC/B,oBAAoB,GACpB,kCAAkC,GAClC,kCAAkC,GAClC,iCAAiC,GACjC,gCAAgC,GAChC,yBAAyB,GACzB,uBAAuB,CAAC;AAE5B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACjD;AAED,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,GAAG,qBAAqB,CAAC;AAEjF,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,oBAAoB,CAKtE,CAAC;
|
|
1
|
+
{"version":3,"file":"sql-portability.d.ts","sourceRoot":"","sources":["../src/sql-portability.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AAEH,MAAM,MAAM,uBAAuB,GAC/B,oBAAoB,GACpB,kCAAkC,GAClC,kCAAkC,GAClC,iCAAiC,GACjC,gCAAgC,GAChC,yBAAyB,GACzB,uBAAuB,CAAC;AAE5B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACjD;AAED,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,GAAG,qBAAqB,CAAC;AAEjF,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,oBAAoB,CAKtE,CAAC;AAi0BL;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAyBtB"}
|
package/dist/sql-portability.js
CHANGED
|
@@ -141,6 +141,20 @@ function tokenize(sql) {
|
|
|
141
141
|
if (current === '\\') {
|
|
142
142
|
fail('HQ_SQL_PORT_UNSUPPORTED_LITERAL', 'Backslash escapes are not portable; double the quote instead.', index, index + 1);
|
|
143
143
|
}
|
|
144
|
+
const code = current.charCodeAt(0);
|
|
145
|
+
if ((code <= 0x1f && code !== 0x09 && code !== 0x0a && code !== 0x0d)
|
|
146
|
+
|| (code >= 0x7f && code <= 0x9f)) {
|
|
147
|
+
fail('HQ_SQL_PORT_UNSUPPORTED_LITERAL', 'String literals cannot contain control characters.', index, index + 1);
|
|
148
|
+
}
|
|
149
|
+
if (code >= 0xd800 && code <= 0xdfff) {
|
|
150
|
+
const low = sql.charCodeAt(index + 1);
|
|
151
|
+
if (code > 0xdbff || !(low >= 0xdc00 && low <= 0xdfff)) {
|
|
152
|
+
fail('HQ_SQL_PORT_UNSUPPORTED_LITERAL', 'String literals must be well-formed Unicode.', index, index + 1);
|
|
153
|
+
}
|
|
154
|
+
value += current + sql[index + 1];
|
|
155
|
+
index += 2;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
144
158
|
value += current;
|
|
145
159
|
index += 1;
|
|
146
160
|
}
|
|
@@ -222,7 +236,9 @@ function tokenize(sql) {
|
|
|
222
236
|
}
|
|
223
237
|
continue;
|
|
224
238
|
}
|
|
225
|
-
|
|
239
|
+
// Report a whole code point: an astral character is two UTF-16 code units.
|
|
240
|
+
const unexpected = String.fromCodePoint(sql.codePointAt(index));
|
|
241
|
+
fail('HQ_SQL_PORT_UNSUPPORTED_SYNTAX', `Unexpected character "${unexpected}".`, start, start + unexpected.length);
|
|
226
242
|
}
|
|
227
243
|
return tokens;
|
|
228
244
|
}
|
|
@@ -260,12 +276,32 @@ const COMPARISON_OPERATORS = Object.freeze({
|
|
|
260
276
|
function node(expression) {
|
|
261
277
|
return Object.freeze(expression);
|
|
262
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* RFC 0003 caps the operands of one AND/OR node at the expression validator's
|
|
281
|
+
* collection limit.
|
|
282
|
+
*/
|
|
283
|
+
const MAX_LOGICAL_OPERANDS = 100;
|
|
284
|
+
function childExpressions(expression) {
|
|
285
|
+
switch (expression.kind) {
|
|
286
|
+
case 'binary':
|
|
287
|
+
case 'comparison':
|
|
288
|
+
return [expression.left, expression.right];
|
|
289
|
+
case 'logical':
|
|
290
|
+
return 'operands' in expression ? expression.operands : [expression.operand];
|
|
291
|
+
case 'call':
|
|
292
|
+
return expression.args;
|
|
293
|
+
default:
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
263
297
|
class Parser {
|
|
264
298
|
tokens;
|
|
265
299
|
limits;
|
|
266
300
|
position = 0;
|
|
267
301
|
nodes = 0;
|
|
268
302
|
dependencies = new Set();
|
|
303
|
+
/** Tree levels beneath each composite node, counting it; leaves are 1. */
|
|
304
|
+
levels = new WeakMap();
|
|
269
305
|
constructor(tokens, limits) {
|
|
270
306
|
this.tokens = tokens;
|
|
271
307
|
this.limits = limits;
|
|
@@ -303,38 +339,67 @@ class Parser {
|
|
|
303
339
|
fail('HQ_SQL_PORT_TOO_COMPLEX', 'The expression exceeds its depth limit.', token.start, token.end);
|
|
304
340
|
}
|
|
305
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Freeze a composite node, rejecting it once its tree has more levels than
|
|
344
|
+
* the depth limit (RFC 0003 counts the root as depth 1). Parse depth alone
|
|
345
|
+
* undercounts left-associative chains such as `a + b + c`, whose left spine
|
|
346
|
+
* grows with every operator.
|
|
347
|
+
*/
|
|
348
|
+
build(expression, token) {
|
|
349
|
+
let levels = 1;
|
|
350
|
+
for (const child of childExpressions(expression)) {
|
|
351
|
+
levels = Math.max(levels, (this.levels.get(child) ?? 1) + 1);
|
|
352
|
+
}
|
|
353
|
+
if (levels > this.limits.maxDepth) {
|
|
354
|
+
fail('HQ_SQL_PORT_TOO_COMPLEX', 'The expression exceeds its depth limit.', token.start, token.end);
|
|
355
|
+
}
|
|
356
|
+
const frozen = node(expression);
|
|
357
|
+
this.levels.set(frozen, levels);
|
|
358
|
+
return frozen;
|
|
359
|
+
}
|
|
360
|
+
/** Called after consuming AND/OR, before parsing the operand it introduces. */
|
|
361
|
+
rejectExtraOperand(operands) {
|
|
362
|
+
if (operands.length >= MAX_LOGICAL_OPERANDS) {
|
|
363
|
+
const token = this.tokens[this.position - 1];
|
|
364
|
+
fail('HQ_SQL_PORT_TOO_COMPLEX', 'The expression exceeds its operand limit.', token.start, token.end);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
306
367
|
parseOr(depth) {
|
|
307
368
|
const operands = [this.parseAnd(depth)];
|
|
308
369
|
while (this.peek()?.type === 'keyword' && this.peek().value === 'or') {
|
|
309
370
|
this.next();
|
|
371
|
+
this.rejectExtraOperand(operands);
|
|
310
372
|
operands.push(this.parseAnd(depth));
|
|
311
373
|
}
|
|
312
374
|
if (operands.length === 1)
|
|
313
375
|
return operands[0];
|
|
314
|
-
|
|
315
|
-
|
|
376
|
+
const token = this.tokens[this.position - 1];
|
|
377
|
+
this.enter(depth, token);
|
|
378
|
+
return this.build({ kind: 'logical', operator: 'or', operands: Object.freeze(operands) }, token);
|
|
316
379
|
}
|
|
317
380
|
parseAnd(depth) {
|
|
318
381
|
const operands = [this.parseNot(depth)];
|
|
319
382
|
while (this.peek()?.type === 'keyword' && this.peek().value === 'and') {
|
|
320
383
|
this.next();
|
|
384
|
+
this.rejectExtraOperand(operands);
|
|
321
385
|
operands.push(this.parseNot(depth));
|
|
322
386
|
}
|
|
323
387
|
if (operands.length === 1)
|
|
324
388
|
return operands[0];
|
|
325
|
-
|
|
326
|
-
|
|
389
|
+
const token = this.tokens[this.position - 1];
|
|
390
|
+
this.enter(depth, token);
|
|
391
|
+
return this.build({ kind: 'logical', operator: 'and', operands: Object.freeze(operands) }, token);
|
|
327
392
|
}
|
|
328
393
|
parseNot(depth) {
|
|
329
394
|
const token = this.peek();
|
|
330
395
|
if (token?.type === 'keyword' && token.value === 'not') {
|
|
331
396
|
this.next();
|
|
332
397
|
this.enter(depth + 1, token);
|
|
333
|
-
return
|
|
398
|
+
return this.build({
|
|
334
399
|
kind: 'logical',
|
|
335
400
|
operator: 'not',
|
|
336
401
|
operand: this.parseNot(depth + 1),
|
|
337
|
-
});
|
|
402
|
+
}, token);
|
|
338
403
|
}
|
|
339
404
|
return this.parseComparison(depth);
|
|
340
405
|
}
|
|
@@ -349,14 +414,17 @@ class Parser {
|
|
|
349
414
|
const right = this.parseAdditive(depth);
|
|
350
415
|
this.enter(depth + 1, token);
|
|
351
416
|
this.rejectChained();
|
|
352
|
-
return
|
|
417
|
+
return this.build({ kind: 'comparison', operator, left, right }, token);
|
|
353
418
|
}
|
|
354
419
|
if (token.type === 'keyword' && token.value === 'like') {
|
|
355
420
|
this.next();
|
|
356
421
|
const right = this.parseAdditive(depth);
|
|
357
422
|
this.enter(depth + 1, token);
|
|
358
423
|
this.rejectChained();
|
|
359
|
-
|
|
424
|
+
if (right.kind !== 'literal' || typeof right.value !== 'string') {
|
|
425
|
+
fail('HQ_SQL_PORT_UNSUPPORTED_SYNTAX', 'LIKE patterns must be string literals.', token.start, token.end);
|
|
426
|
+
}
|
|
427
|
+
return this.build({ kind: 'comparison', operator: 'like', left, right }, token);
|
|
360
428
|
}
|
|
361
429
|
if (token.type === 'keyword' && token.value === 'between') {
|
|
362
430
|
this.next();
|
|
@@ -369,12 +437,12 @@ class Parser {
|
|
|
369
437
|
const upper = this.parseAdditive(depth);
|
|
370
438
|
this.enter(depth + 1, token);
|
|
371
439
|
this.rejectChained();
|
|
372
|
-
return
|
|
440
|
+
return this.build({
|
|
373
441
|
kind: 'comparison',
|
|
374
442
|
operator: 'between',
|
|
375
443
|
left,
|
|
376
444
|
right: this.tupleLiteral(lower, upper, token),
|
|
377
|
-
});
|
|
445
|
+
}, token);
|
|
378
446
|
}
|
|
379
447
|
if (token.type === 'keyword' && (token.value === 'in' || token.value === 'not')) {
|
|
380
448
|
this.next();
|
|
@@ -449,7 +517,7 @@ class Parser {
|
|
|
449
517
|
}
|
|
450
518
|
this.enter(depth + 1, token);
|
|
451
519
|
this.rejectChained();
|
|
452
|
-
return
|
|
520
|
+
return this.build({
|
|
453
521
|
kind: 'comparison',
|
|
454
522
|
operator,
|
|
455
523
|
left,
|
|
@@ -463,7 +531,7 @@ class Parser {
|
|
|
463
531
|
},
|
|
464
532
|
},
|
|
465
533
|
},
|
|
466
|
-
});
|
|
534
|
+
}, token);
|
|
467
535
|
}
|
|
468
536
|
parseAdditive(depth) {
|
|
469
537
|
let left = this.parseMultiplicative(depth);
|
|
@@ -474,12 +542,12 @@ class Parser {
|
|
|
474
542
|
this.next();
|
|
475
543
|
const right = this.parseMultiplicative(depth + 1);
|
|
476
544
|
this.enter(depth + 1, token);
|
|
477
|
-
left =
|
|
545
|
+
left = this.build({
|
|
478
546
|
kind: 'binary',
|
|
479
547
|
operator: BINARY_OPERATORS[token.value],
|
|
480
548
|
left,
|
|
481
549
|
right,
|
|
482
|
-
});
|
|
550
|
+
}, token);
|
|
483
551
|
}
|
|
484
552
|
return left;
|
|
485
553
|
}
|
|
@@ -492,12 +560,12 @@ class Parser {
|
|
|
492
560
|
this.next();
|
|
493
561
|
const right = this.parseUnary(depth + 1);
|
|
494
562
|
this.enter(depth + 1, token);
|
|
495
|
-
left =
|
|
563
|
+
left = this.build({
|
|
496
564
|
kind: 'binary',
|
|
497
565
|
operator: BINARY_OPERATORS[token.value],
|
|
498
566
|
left,
|
|
499
567
|
right,
|
|
500
|
-
});
|
|
568
|
+
}, token);
|
|
501
569
|
}
|
|
502
570
|
return left;
|
|
503
571
|
}
|
|
@@ -514,6 +582,7 @@ class Parser {
|
|
|
514
582
|
fail('HQ_SQL_PORT_UNSUPPORTED_OPERATOR', 'Negation is only portable on numeric literals.', token.start, token.end);
|
|
515
583
|
}
|
|
516
584
|
this.next();
|
|
585
|
+
this.enter(depth, operand);
|
|
517
586
|
return this.numericLiteral(operand, true);
|
|
518
587
|
}
|
|
519
588
|
return this.parsePrimary(depth);
|
|
@@ -595,11 +664,11 @@ class Parser {
|
|
|
595
664
|
if (args.length < arity[0] || args.length > arity[1]) {
|
|
596
665
|
fail('HQ_SQL_PORT_SYNTAX', `Function "${canonical}" expects ${arity[0] === arity[1] ? arity[0] : `${arity[0]} to ${arity[1]}`} arguments.`, name.start, close.end);
|
|
597
666
|
}
|
|
598
|
-
return
|
|
667
|
+
return this.build({
|
|
599
668
|
kind: 'call',
|
|
600
669
|
function: canonical,
|
|
601
670
|
args: Object.freeze(args),
|
|
602
|
-
});
|
|
671
|
+
}, name);
|
|
603
672
|
}
|
|
604
673
|
numericLiteral(token, negated) {
|
|
605
674
|
const value = Number(token.value) * (negated ? -1 : 1);
|
package/dist/types.d.ts
CHANGED
|
@@ -59,7 +59,9 @@ export interface RelationshipDefinition<TTarget extends {
|
|
|
59
59
|
from: string;
|
|
60
60
|
to: string;
|
|
61
61
|
}
|
|
62
|
-
export type AggregationType = 'sum' | 'count' | 'countDistinct' | 'avg' | 'min' | 'max' | 'argMax' | 'argMin' | 'percentile' | 'stddev' | 'variance'
|
|
62
|
+
export type AggregationType = 'sum' | 'count' | 'countDistinct' | 'avg' | 'min' | 'max' | 'argMax' | 'argMin' | 'percentile' | 'stddev' | 'variance'
|
|
63
|
+
/** Estimated distinct count (ClickHouse `uniq`); results carry `approximate`. */
|
|
64
|
+
| 'approxCountDistinct';
|
|
63
65
|
export type MeasureAggregation = AggregationType;
|
|
64
66
|
export interface AggregationSpec {
|
|
65
67
|
__type: 'aggregation_spec';
|
|
@@ -176,7 +178,7 @@ export interface GrainedMetricRef<TDatasetName extends string = string, TMetricN
|
|
|
176
178
|
contract(): MetricContract;
|
|
177
179
|
}
|
|
178
180
|
export type MetricHandle<TDatasetName extends string = string, TMetricName extends string = string, TSpec extends AggregationSpec | DerivedMetricSpec<TDatasetName> = AggregationSpec | DerivedMetricSpec<TDatasetName>, TDataset extends DatasetInstance<any, any, any, TDatasetName> = DefaultMetricDataset<TDatasetName>> = MetricRef<TDatasetName, TMetricName, TSpec, TDataset> | GrainedMetricRef<TDatasetName, TMetricName, TSpec, TDataset>;
|
|
179
|
-
export type TimeGrain = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
181
|
+
export type TimeGrain = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
180
182
|
export interface MetricContract {
|
|
181
183
|
kind: 'metric' | 'derived_metric' | 'grained_metric';
|
|
182
184
|
name: string;
|
|
@@ -208,9 +210,18 @@ export interface MetricOrderBy<TField extends string = string> {
|
|
|
208
210
|
field: TField;
|
|
209
211
|
direction: 'asc' | 'desc';
|
|
210
212
|
}
|
|
213
|
+
/** A named row filter declared on a dataset; see `DatasetConfig.segments`. */
|
|
214
|
+
export interface SegmentDefinition {
|
|
215
|
+
/** Comparisons over the dataset's own dimensions, combined with AND. */
|
|
216
|
+
filters: MetricFilter[];
|
|
217
|
+
label?: string;
|
|
218
|
+
description?: string;
|
|
219
|
+
}
|
|
211
220
|
export interface MetricQuery {
|
|
212
221
|
dimensions?: string[];
|
|
213
222
|
filters?: MetricFilter[];
|
|
223
|
+
/** Segment names declared on the dataset, combined with `filters` by AND. */
|
|
224
|
+
segments?: string[];
|
|
214
225
|
orderBy?: MetricOrderBy[];
|
|
215
226
|
limit?: number;
|
|
216
227
|
offset?: number;
|
|
@@ -220,6 +231,8 @@ export interface DatasetQuery {
|
|
|
220
231
|
dimensions?: string[];
|
|
221
232
|
measures?: string[];
|
|
222
233
|
filters?: MetricFilter[];
|
|
234
|
+
/** Segment names declared on the dataset, combined with `filters` by AND. */
|
|
235
|
+
segments?: string[];
|
|
223
236
|
orderBy?: MetricOrderBy[];
|
|
224
237
|
limit?: number;
|
|
225
238
|
offset?: number;
|
|
@@ -336,7 +349,7 @@ export interface DerivedMetricConfig<TDatasetName extends string = string> exten
|
|
|
336
349
|
label?: string;
|
|
337
350
|
description?: string;
|
|
338
351
|
}
|
|
339
|
-
export interface DatasetConfig<TDimensions extends Record<string, DimensionDefinition> = Record<string, DimensionDefinition>, TMeasures extends Record<string, DatasetMeasureDefinition> = Record<string, MeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition> = Record<string, never>> extends SemanticMetadata {
|
|
352
|
+
export interface DatasetConfig<TDimensions extends Record<string, DimensionDefinition> = Record<string, DimensionDefinition>, TMeasures extends Record<string, DatasetMeasureDefinition> = Record<string, MeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition> = Record<string, never>, TSegments extends Record<string, SegmentDefinition> = Record<string, SegmentDefinition>> extends SemanticMetadata {
|
|
340
353
|
source: string;
|
|
341
354
|
description?: string;
|
|
342
355
|
freshness?: DatasetFreshness;
|
|
@@ -344,14 +357,26 @@ export interface DatasetConfig<TDimensions extends Record<string, DimensionDefin
|
|
|
344
357
|
defaults?: DatasetDefaults;
|
|
345
358
|
tenantKey?: string;
|
|
346
359
|
timeKey?: string;
|
|
360
|
+
/**
|
|
361
|
+
* Restricts the time grains this dataset supports. Defaults to every grain
|
|
362
|
+
* the planner can bucket on. Use it, for example, to refuse `minute` and
|
|
363
|
+
* `hour` when `timeKey` is a `Date` column without a time of day.
|
|
364
|
+
*/
|
|
365
|
+
timeGrains?: readonly TimeGrain[];
|
|
347
366
|
dimensions: TDimensions;
|
|
348
367
|
measures?: TMeasures & CheckedDatasetMeasures<TMeasures>;
|
|
349
368
|
filters?: SemanticFiltersDefinition;
|
|
350
369
|
relationships?: TRelationships;
|
|
351
370
|
limits?: DatasetLimits;
|
|
352
371
|
cache?: DatasetCachePolicy;
|
|
372
|
+
/**
|
|
373
|
+
* Named, author-defined row filters that a query selects by name (RFC 0015).
|
|
374
|
+
* Each is an AND of comparisons over this dataset's own dimensions, and it
|
|
375
|
+
* may use dimensions that are not exposed as filters.
|
|
376
|
+
*/
|
|
377
|
+
segments?: TSegments;
|
|
353
378
|
}
|
|
354
|
-
export interface DatasetInstance<TDimensions extends Record<string, DimensionDefinition> = Record<string, DimensionDefinition>, TMeasures extends Record<string, MeasureDefinition> = Record<string, MeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition> = Record<string, never>, TDatasetName extends string = string, TDerivedMeasures extends Record<string, DerivedMeasureDefinition> = Record<string, DerivedMeasureDefinition>> {
|
|
379
|
+
export interface DatasetInstance<TDimensions extends Record<string, DimensionDefinition> = Record<string, DimensionDefinition>, TMeasures extends Record<string, MeasureDefinition> = Record<string, MeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition> = Record<string, never>, TDatasetName extends string = string, TDerivedMeasures extends Record<string, DerivedMeasureDefinition> = Record<string, DerivedMeasureDefinition>, TSegments extends Record<string, SegmentDefinition> = Record<string, SegmentDefinition>> {
|
|
355
380
|
__type: 'dataset';
|
|
356
381
|
name: TDatasetName;
|
|
357
382
|
source: string;
|
|
@@ -368,6 +393,7 @@ export interface DatasetInstance<TDimensions extends Record<string, DimensionDef
|
|
|
368
393
|
defaults?: DatasetDefaults;
|
|
369
394
|
tenantKey?: string;
|
|
370
395
|
timeKey?: string;
|
|
396
|
+
timeGrains?: readonly TimeGrain[];
|
|
371
397
|
dimensions: TDimensions;
|
|
372
398
|
measures: TMeasures;
|
|
373
399
|
derivedMeasures: TDerivedMeasures;
|
|
@@ -375,8 +401,9 @@ export interface DatasetInstance<TDimensions extends Record<string, DimensionDef
|
|
|
375
401
|
relationships: TRelationships;
|
|
376
402
|
limits?: DatasetLimits;
|
|
377
403
|
cache?: DatasetCachePolicy;
|
|
378
|
-
|
|
379
|
-
metric<TName extends string>(metricName: TName, metricConfig:
|
|
404
|
+
segments: TSegments;
|
|
405
|
+
metric<TName extends string>(metricName: TName, metricConfig: BaseMetricConfig<TMeasures>): BaseMetricRef<TDatasetName, TName, DatasetInstance<TDimensions, TMeasures, TRelationships, TDatasetName, TDerivedMeasures, TSegments>>;
|
|
406
|
+
metric<TName extends string>(metricName: TName, metricConfig: DerivedMetricConfig<TDatasetName>): DerivedMetricRef<TDatasetName, TName, DatasetInstance<TDimensions, TMeasures, TRelationships, TDatasetName, TDerivedMeasures, TSegments>>;
|
|
380
407
|
}
|
|
381
408
|
export interface DatasetRegistryInstance {
|
|
382
409
|
register(ds: DatasetInstance): void;
|
|
@@ -413,6 +440,8 @@ export type DatasetQueryableDimensions<TDataset extends DatasetInstance<any, any
|
|
|
413
440
|
export type DatasetFieldNames<TDataset extends DatasetInstance<any, any, any, any>> = DatasetDimensionNames<TDataset>;
|
|
414
441
|
/** Measure names declared by a dataset. */
|
|
415
442
|
export type DatasetMeasureNames<TDataset extends DatasetInstance<any, any, any, any>> = KnownStringKeysOrFallback<TDataset['measures']> | KnownStringKeys<TDataset['derivedMeasures']>;
|
|
443
|
+
/** Segment names declared by a dataset. */
|
|
444
|
+
export type DatasetSegmentNames<TDataset extends DatasetInstance<any, any, any, any>> = keyof TDataset['segments'] & string;
|
|
416
445
|
/**
|
|
417
446
|
* Fields a result can be ordered by. This is the selection-independent superset
|
|
418
447
|
* (every dimension + measure, plus the synthetic `period` column for grained
|
|
@@ -424,6 +453,8 @@ export interface DatasetQueryFor<TDataset extends DatasetInstance<any, any, any,
|
|
|
424
453
|
dimensions?: readonly DatasetDimensionNames<TDataset>[];
|
|
425
454
|
measures?: readonly DatasetMeasureNames<TDataset>[];
|
|
426
455
|
filters?: readonly MetricFilter[];
|
|
456
|
+
/** Segment names declared on the dataset. */
|
|
457
|
+
segments?: readonly DatasetSegmentNames<TDataset>[];
|
|
427
458
|
orderBy?: readonly MetricOrderBy<DatasetOrderableNames<TDataset>>[];
|
|
428
459
|
limit?: number;
|
|
429
460
|
offset?: number;
|
|
@@ -463,6 +494,8 @@ export interface DatasetQueryResultFor<TDataset extends DatasetInstance<any, any
|
|
|
463
494
|
export interface MetricQueryFor<TDataset extends DatasetInstance<any, any, any, any>, TMetricName extends string> {
|
|
464
495
|
dimensions?: readonly DatasetDimensionNames<TDataset>[];
|
|
465
496
|
filters?: readonly MetricFilter[];
|
|
497
|
+
/** Segment names declared on the dataset. */
|
|
498
|
+
segments?: readonly DatasetSegmentNames<TDataset>[];
|
|
466
499
|
orderBy?: readonly MetricOrderBy<DatasetDimensionNames<TDataset> | TMetricName | 'period'>[];
|
|
467
500
|
limit?: number;
|
|
468
501
|
offset?: number;
|