@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.
Files changed (106) hide show
  1. package/dist/agent-catalog.d.ts +9 -0
  2. package/dist/agent-catalog.d.ts.map +1 -1
  3. package/dist/aggregations.d.ts +2 -0
  4. package/dist/aggregations.d.ts.map +1 -1
  5. package/dist/aggregations.js +4 -0
  6. package/dist/api.type-test.js +40 -1
  7. package/dist/cache/query-signature.d.ts.map +1 -1
  8. package/dist/cache/query-signature.js +17 -0
  9. package/dist/catalog.d.ts +11 -0
  10. package/dist/catalog.d.ts.map +1 -1
  11. package/dist/catalog.js +12 -2
  12. package/dist/cloud-publishing.d.ts +23 -0
  13. package/dist/cloud-publishing.d.ts.map +1 -0
  14. package/dist/cloud-publishing.js +57 -0
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +2 -0
  17. package/dist/contract.d.ts +5 -1
  18. package/dist/contract.d.ts.map +1 -1
  19. package/dist/contract.js +7 -0
  20. package/dist/dataset-query.d.ts.map +1 -1
  21. package/dist/dataset-query.js +6 -1
  22. package/dist/dataset.d.ts +2 -2
  23. package/dist/dataset.d.ts.map +1 -1
  24. package/dist/dataset.js +3 -0
  25. package/dist/executor.d.ts.map +1 -1
  26. package/dist/executor.js +15 -4
  27. package/dist/in-memory-backend.d.ts.map +1 -1
  28. package/dist/in-memory-backend.js +34 -7
  29. package/dist/index.d.ts +10 -6
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -2
  32. package/dist/measure.d.ts +5 -0
  33. package/dist/measure.d.ts.map +1 -1
  34. package/dist/measure.js +5 -0
  35. package/dist/portable-execution-errors.d.ts +4 -0
  36. package/dist/portable-execution-errors.d.ts.map +1 -1
  37. package/dist/portable-execution-errors.js +4 -1
  38. package/dist/portable-executor.d.ts.map +1 -1
  39. package/dist/portable-executor.js +6 -3
  40. package/dist/protocol-adapter.d.ts.map +1 -1
  41. package/dist/protocol-adapter.js +15 -7
  42. package/dist/protocol-rehydrate.d.ts +5 -1
  43. package/dist/protocol-rehydrate.d.ts.map +1 -1
  44. package/dist/protocol-rehydrate.js +20 -10
  45. package/dist/query-builder-protocol.d.ts +7 -3
  46. package/dist/query-builder-protocol.d.ts.map +1 -1
  47. package/dist/query-planner.d.ts.map +1 -1
  48. package/dist/query-planner.js +5 -0
  49. package/dist/relationship-check.d.ts +47 -0
  50. package/dist/relationship-check.d.ts.map +1 -0
  51. package/dist/relationship-check.js +59 -0
  52. package/dist/relationships.d.ts +10 -10
  53. package/dist/relationships.js +10 -10
  54. package/dist/semantic-planner.d.ts.map +1 -1
  55. package/dist/semantic-planner.js +5 -1
  56. package/dist/semantic-query-schema.d.ts.map +1 -1
  57. package/dist/semantic-query-schema.js +5 -0
  58. package/dist/sql-portability.d.ts.map +1 -1
  59. package/dist/sql-portability.js +88 -19
  60. package/dist/types.d.ts +39 -6
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/utils/agent-catalog-projection.d.ts +6 -0
  63. package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
  64. package/dist/utils/agent-catalog-projection.js +4 -0
  65. package/dist/utils/approximate-measures.d.ts +10 -0
  66. package/dist/utils/approximate-measures.d.ts.map +1 -0
  67. package/dist/utils/approximate-measures.js +15 -0
  68. package/dist/utils/collect-cloud-datasets.d.ts +3 -0
  69. package/dist/utils/collect-cloud-datasets.d.ts.map +1 -0
  70. package/dist/utils/collect-cloud-datasets.js +12 -0
  71. package/dist/utils/dataset-contract.d.ts.map +1 -1
  72. package/dist/utils/dataset-contract.js +2 -2
  73. package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
  74. package/dist/utils/dataset-metric-ref.js +5 -0
  75. package/dist/utils/dataset-query-validation.d.ts.map +1 -1
  76. package/dist/utils/dataset-query-validation.js +6 -3
  77. package/dist/utils/dataset-time-grains.d.ts +15 -0
  78. package/dist/utils/dataset-time-grains.d.ts.map +1 -0
  79. package/dist/utils/dataset-time-grains.js +17 -0
  80. package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
  81. package/dist/utils/filtered-aggregation-sql.js +1 -0
  82. package/dist/utils/portable-aggregations.d.ts +14 -0
  83. package/dist/utils/portable-aggregations.d.ts.map +1 -0
  84. package/dist/utils/portable-aggregations.js +15 -0
  85. package/dist/utils/portable-grains.d.ts +22 -0
  86. package/dist/utils/portable-grains.d.ts.map +1 -0
  87. package/dist/utils/portable-grains.js +35 -0
  88. package/dist/utils/portable-semantic-query.d.ts.map +1 -1
  89. package/dist/utils/portable-semantic-query.js +2 -1
  90. package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
  91. package/dist/utils/protocol-metric-expressions.js +2 -1
  92. package/dist/utils/relationship-builder-plan.d.ts +6 -3
  93. package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
  94. package/dist/utils/relationship-builder-plan.js +13 -5
  95. package/dist/utils/relationship-key-check.d.ts +43 -0
  96. package/dist/utils/relationship-key-check.d.ts.map +1 -0
  97. package/dist/utils/relationship-key-check.js +72 -0
  98. package/dist/utils/segments.d.ts +31 -0
  99. package/dist/utils/segments.d.ts.map +1 -0
  100. package/dist/utils/segments.js +91 -0
  101. package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
  102. package/dist/utils/semantic-metadata-validation.js +22 -1
  103. package/dist/utils/unsupported-contract-reasons.d.ts +32 -0
  104. package/dist/utils/unsupported-contract-reasons.d.ts.map +1 -0
  105. package/dist/utils/unsupported-contract-reasons.js +30 -0
  106. 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
+ }
@@ -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
- * against the data — there is no uniqueness concept in the model, so nothing
15
- * verifies that a `belongsTo` target column really is unique. What a
16
- * mis-declaration costs depends on the builder. One that implements the
17
- * optional `leftAnyJoin` (ClickHouse `LEFT ANY JOIN`) takes at most one target
18
- * row per base row, so the aggregate cannot inflate — it just silently picks an
19
- * arbitrary one of the matches. A builder without it falls back to `leftJoin`,
20
- * where duplicate target keys fan out and do inflate the aggregate. Either way
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
- * customer: belongsTo(() => Customers, { from: "customerId", to: "id" }),
31
+ * // Join keys are source columns, not dimension names.
32
+ * customer: belongsTo(() => Customers, { from: "customer_id", to: "id" }),
33
33
  * },
34
34
  * });
35
35
  *
@@ -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
- * against the data — there is no uniqueness concept in the model, so nothing
15
- * verifies that a `belongsTo` target column really is unique. What a
16
- * mis-declaration costs depends on the builder. One that implements the
17
- * optional `leftAnyJoin` (ClickHouse `LEFT ANY JOIN`) takes at most one target
18
- * row per base row, so the aggregate cannot inflate — it just silently picks an
19
- * arbitrary one of the matches. A builder without it falls back to `leftJoin`,
20
- * where duplicate target keys fan out and do inflate the aggregate. Either way
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
- * customer: belongsTo(() => Customers, { from: "customerId", to: "id" }),
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;AAyL5B,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"}
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"}
@@ -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: normalizeFilters(ds, query.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;AAyK9E,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"}
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;AAouBL;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAyBtB"}
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"}
@@ -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
- fail('HQ_SQL_PORT_UNSUPPORTED_SYNTAX', `Unexpected character "${char}".`, start, start + 1);
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
- this.enter(depth, this.tokens[this.position - 1]);
315
- return node({ kind: 'logical', operator: 'or', operands: Object.freeze(operands) });
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
- this.enter(depth, this.tokens[this.position - 1]);
326
- return node({ kind: 'logical', operator: 'and', operands: Object.freeze(operands) });
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 node({
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 node({ kind: 'comparison', operator, left, right });
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
- return node({ kind: 'comparison', operator: 'like', left, right });
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 node({
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 node({
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 = node({
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 = node({
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 node({
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
- metric<TName extends string>(metricName: TName, metricConfig: BaseMetricConfig<TMeasures>): BaseMetricRef<TDatasetName, TName, DatasetInstance<TDimensions, TMeasures, TRelationships, TDatasetName, TDerivedMeasures>>;
379
- metric<TName extends string>(metricName: TName, metricConfig: DerivedMetricConfig<TDatasetName>): DerivedMetricRef<TDatasetName, TName, DatasetInstance<TDimensions, TMeasures, TRelationships, TDatasetName, TDerivedMeasures>>;
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;