@webda/ql 4.0.0-beta.5 → 4.0.0-beta.6

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 (55) hide show
  1. package/README.md +68 -27
  2. package/lib/WebdaQLLexer.d.ts +44 -30
  3. package/lib/WebdaQLLexer.d.ts.map +1 -1
  4. package/lib/WebdaQLLexer.js +257 -152
  5. package/lib/WebdaQLLexer.js.map +1 -1
  6. package/lib/WebdaQLParserLexer.d.ts +44 -30
  7. package/lib/WebdaQLParserLexer.d.ts.map +1 -1
  8. package/lib/WebdaQLParserLexer.js +257 -152
  9. package/lib/WebdaQLParserLexer.js.map +1 -1
  10. package/lib/WebdaQLParserListener.d.ts +203 -30
  11. package/lib/WebdaQLParserListener.d.ts.map +1 -1
  12. package/lib/WebdaQLParserParser.d.ts +242 -44
  13. package/lib/WebdaQLParserParser.d.ts.map +1 -1
  14. package/lib/WebdaQLParserParser.js +2054 -715
  15. package/lib/WebdaQLParserParser.js.map +1 -1
  16. package/lib/WebdaQLParserVisitor.d.ts +126 -17
  17. package/lib/WebdaQLParserVisitor.d.ts.map +1 -1
  18. package/lib/aggregation-query.d.ts +95 -0
  19. package/lib/aggregation-query.d.ts.map +1 -0
  20. package/lib/aggregation-query.js +104 -0
  21. package/lib/aggregation-query.js.map +1 -0
  22. package/lib/aggregation-select.spec.d.ts +2 -0
  23. package/lib/aggregation-select.spec.d.ts.map +1 -0
  24. package/lib/aggregation-select.spec.js +164 -0
  25. package/lib/aggregation-select.spec.js.map +1 -0
  26. package/lib/aggregation.d.ts +97 -0
  27. package/lib/aggregation.d.ts.map +1 -0
  28. package/lib/aggregation.js +241 -0
  29. package/lib/aggregation.js.map +1 -0
  30. package/lib/aggregation.spec.d.ts +2 -0
  31. package/lib/aggregation.spec.d.ts.map +1 -0
  32. package/lib/aggregation.spec.js +360 -0
  33. package/lib/aggregation.spec.js.map +1 -0
  34. package/lib/bind.d.ts.map +1 -1
  35. package/lib/bind.js +29 -0
  36. package/lib/bind.js.map +1 -1
  37. package/lib/index.d.ts +1 -0
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js +1 -0
  40. package/lib/index.js.map +1 -1
  41. package/lib/query.d.ts +162 -8
  42. package/lib/query.d.ts.map +1 -1
  43. package/lib/query.js +489 -45
  44. package/lib/query.js.map +1 -1
  45. package/lib/query.spec.js +6 -4
  46. package/lib/query.spec.js.map +1 -1
  47. package/lib/statements.spec.d.ts +2 -0
  48. package/lib/statements.spec.d.ts.map +1 -0
  49. package/lib/statements.spec.js +422 -0
  50. package/lib/statements.spec.js.map +1 -0
  51. package/lib/webdaql-string.d.ts +9 -0
  52. package/lib/webdaql-string.d.ts.map +1 -1
  53. package/lib/webdaql-string.js +9 -8
  54. package/lib/webdaql-string.js.map +1 -1
  55. package/package.json +2 -2
@@ -0,0 +1,95 @@
1
+ import type { Expression } from "./query.js";
2
+ import { WebdaQLError } from "./webdaql-string.js";
3
+ /**
4
+ * Aggregation functions supported on every backend
5
+ */
6
+ export type AggregateFunction = "COUNT" | "COUNT_DISTINCT" | "SUM" | "AVG" | "MIN" | "MAX";
7
+ /**
8
+ * One computed column of an aggregation
9
+ */
10
+ export interface Metric {
11
+ fn: AggregateFunction;
12
+ /**
13
+ * Dotted path of the aggregated attribute; omitted only for COUNT, meaning COUNT(*)
14
+ */
15
+ field?: string;
16
+ }
17
+ /**
18
+ * Ordering of aggregated rows
19
+ */
20
+ export interface AggregationOrder {
21
+ /**
22
+ * A groupBy path or a metric alias
23
+ */
24
+ key: string;
25
+ direction: "ASC" | "DESC";
26
+ }
27
+ /**
28
+ * Canonical aggregation AST consumed by every store
29
+ */
30
+ export interface AggregationQuery {
31
+ /**
32
+ * Filter applied before grouping; `new AndExpression([])` matches everything
33
+ */
34
+ filter: Expression;
35
+ /**
36
+ * Dotted paths; an empty array produces a single global row
37
+ */
38
+ groupBy: string[];
39
+ /**
40
+ * alias → metric
41
+ */
42
+ metrics: Record<string, Metric>;
43
+ orderBy?: AggregationOrder[];
44
+ limit?: number;
45
+ }
46
+ /**
47
+ * Result of an aggregation
48
+ */
49
+ export interface AggregationResult<Row = Record<string, unknown>> {
50
+ rows: Row[];
51
+ /**
52
+ * false when the in-memory fallback ran, fully or partially
53
+ */
54
+ native: boolean;
55
+ }
56
+ /**
57
+ * Error codes raised by aggregations
58
+ */
59
+ export type AggregationErrorCode = "AGGREGATION_NOT_NATIVE" | "AGGREGATION_TOO_MANY_GROUPS";
60
+ /**
61
+ * Error raised when an aggregation is refused at runtime
62
+ */
63
+ export declare class AggregationError extends WebdaQLError {
64
+ readonly code: AggregationErrorCode;
65
+ /**
66
+ * @param code - machine readable code
67
+ * @param message - human readable message
68
+ */
69
+ constructor(code: AggregationErrorCode, message: string);
70
+ }
71
+ /**
72
+ * Valid attribute path: dotted identifiers, the only form translators interpolate
73
+ */
74
+ export declare const AGGREGATION_PATH: RegExp;
75
+ /**
76
+ * Valid metric alias
77
+ */
78
+ export declare const AGGREGATION_ALIAS: RegExp;
79
+ /**
80
+ * Check a metric alias
81
+ * @param alias - the alias
82
+ * @throws WebdaQLError when invalid or reserved
83
+ */
84
+ export declare function checkAlias(alias: string): void;
85
+ /**
86
+ * Validate an aggregation query
87
+ *
88
+ * Paths and aliases that pass are safe to interpolate in native queries. Path segments cannot be
89
+ * all digits (array indexes are not portable). A missing `groupBy` is normalized to `[]`.
90
+ * @param query - the query to validate
91
+ * @returns the same query
92
+ * @throws WebdaQLError when the query is invalid
93
+ */
94
+ export declare function validateAggregation(query: AggregationQuery): AggregationQuery;
95
+ //# sourceMappingURL=aggregation-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-query.d.ts","sourceRoot":"","sources":["../src/aggregation-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,gBAAgB,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE3F;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,iBAAiB,CAAC;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB;;OAEG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC9D,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,GAAG,6BAA6B,CAAC;AAE5F;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;aAM9B,IAAI,EAAE,oBAAoB;IAL5C;;;OAGG;IACH,YACkB,IAAI,EAAE,oBAAoB,EAC1C,OAAO,EAAE,MAAM,EAIhB;CACF;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAsC,CAAC;AACpE;;GAEG;AACH,eAAO,MAAM,iBAAiB,QAA4B,CAAC;AAwB3D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAwC7E"}
@@ -0,0 +1,104 @@
1
+ import { WebdaQLError } from "./webdaql-string.js";
2
+ /**
3
+ * Error raised when an aggregation is refused at runtime
4
+ */
5
+ export class AggregationError extends WebdaQLError {
6
+ code;
7
+ /**
8
+ * @param code - machine readable code
9
+ * @param message - human readable message
10
+ */
11
+ constructor(code, message) {
12
+ super(message);
13
+ this.code = code;
14
+ this.name = "AggregationError";
15
+ }
16
+ }
17
+ /**
18
+ * Valid attribute path: dotted identifiers, the only form translators interpolate
19
+ */
20
+ export const AGGREGATION_PATH = /^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*$/;
21
+ /**
22
+ * Valid metric alias
23
+ */
24
+ export const AGGREGATION_ALIAS = /^[a-zA-Z][a-zA-Z0-9_]*$/;
25
+ const FORBIDDEN_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
26
+ /**
27
+ * All-digit segment: MongoDB `$a.0` does not index arrays, so such paths would differ across backends
28
+ */
29
+ const DIGITS = /^[0-9]+$/;
30
+ const FUNCTIONS = new Set(["COUNT", "COUNT_DISTINCT", "SUM", "AVG", "MIN", "MAX"]);
31
+ /**
32
+ * Check an attribute path
33
+ * @param path - the path
34
+ * @param what - description for the error
35
+ */
36
+ function checkPath(path, what) {
37
+ if (typeof path !== "string" ||
38
+ !AGGREGATION_PATH.test(path) ||
39
+ path.split(".").some(segment => FORBIDDEN_SEGMENTS.has(segment) || DIGITS.test(segment))) {
40
+ throw new WebdaQLError(`Invalid ${what} '${path}'`);
41
+ }
42
+ }
43
+ /**
44
+ * Check a metric alias
45
+ * @param alias - the alias
46
+ * @throws WebdaQLError when invalid or reserved
47
+ */
48
+ export function checkAlias(alias) {
49
+ if (!AGGREGATION_ALIAS.test(alias) || FORBIDDEN_SEGMENTS.has(alias)) {
50
+ throw new WebdaQLError(`Invalid metric alias '${alias}'`);
51
+ }
52
+ }
53
+ /**
54
+ * Validate an aggregation query
55
+ *
56
+ * Paths and aliases that pass are safe to interpolate in native queries. Path segments cannot be
57
+ * all digits (array indexes are not portable). A missing `groupBy` is normalized to `[]`.
58
+ * @param query - the query to validate
59
+ * @returns the same query
60
+ * @throws WebdaQLError when the query is invalid
61
+ */
62
+ export function validateAggregation(query) {
63
+ query.groupBy ??= [];
64
+ const groupBy = query.groupBy;
65
+ groupBy.forEach(path => checkPath(path, "group by path"));
66
+ if (new Set(groupBy).size !== groupBy.length) {
67
+ throw new WebdaQLError("Duplicate group by path");
68
+ }
69
+ const aliases = Object.keys(query.metrics ?? {});
70
+ if (!aliases.length) {
71
+ throw new WebdaQLError("An aggregation needs at least one metric");
72
+ }
73
+ for (const alias of aliases) {
74
+ checkAlias(alias);
75
+ if (groupBy.includes(alias)) {
76
+ throw new WebdaQLError(`Metric alias '${alias}' collides with a group by path`);
77
+ }
78
+ const metric = query.metrics[alias];
79
+ if (!FUNCTIONS.has(metric?.fn)) {
80
+ throw new WebdaQLError(`Unknown aggregate function '${metric?.fn}'`);
81
+ }
82
+ if (metric.field === undefined) {
83
+ if (metric.fn !== "COUNT") {
84
+ throw new WebdaQLError(`${metric.fn} needs a field`);
85
+ }
86
+ }
87
+ else {
88
+ checkPath(metric.field, "metric field");
89
+ }
90
+ }
91
+ for (const order of query.orderBy ?? []) {
92
+ if (!groupBy.includes(order.key) && !aliases.includes(order.key)) {
93
+ throw new WebdaQLError(`ORDER BY '${order.key}' is neither a group by path nor a metric alias`);
94
+ }
95
+ if (order.direction !== "ASC" && order.direction !== "DESC") {
96
+ throw new WebdaQLError(`Invalid ORDER BY direction '${order.direction}'`);
97
+ }
98
+ }
99
+ if (query.limit !== undefined && !(Number.isInteger(query.limit) && query.limit > 0)) {
100
+ throw new WebdaQLError(`Invalid LIMIT '${query.limit}'`);
101
+ }
102
+ return query;
103
+ }
104
+ //# sourceMappingURL=aggregation-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-query.js","sourceRoot":"","sources":["../src/aggregation-query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAiEnD;;GAEG;AACH,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IAM9B,IAAI;IALtB;;;OAGG;IACH,YACkB,IAA0B,EAC1C,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;oBAHC,IAAI;QAIpB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,mCAAmC,CAAC;AACpE;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC;AAE3D,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;AAC9E;;GAEG;AACH,MAAM,MAAM,GAAG,UAAU,CAAC;AAC1B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAE3F;;;;GAIG;AACH,SAAS,SAAS,CAAC,IAAY,EAAE,IAAY;IAC3C,IACE,OAAO,IAAI,KAAK,QAAQ;QACxB,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EACxF,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,WAAW,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,YAAY,CAAC,yBAAyB,KAAK,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAuB;IACzD,KAAK,CAAC,OAAO,KAAK,EAAE,CAAC;IACrB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC;IAC1D,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QAC7C,MAAM,IAAI,YAAY,CAAC,yBAAyB,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,IAAI,YAAY,CAAC,0CAA0C,CAAC,CAAC;IACrE,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,UAAU,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,YAAY,CAAC,iBAAiB,KAAK,iCAAiC,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,YAAY,CAAC,+BAA+B,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,MAAM,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC1B,MAAM,IAAI,YAAY,CAAC,GAAG,MAAM,CAAC,EAAE,gBAAgB,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACjE,MAAM,IAAI,YAAY,CAAC,aAAa,KAAK,CAAC,GAAG,iDAAiD,CAAC,CAAC;QAClG,CAAC;QACD,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC5D,MAAM,IAAI,YAAY,CAAC,+BAA+B,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACrF,MAAM,IAAI,YAAY,CAAC,kBAAkB,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=aggregation-select.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-select.spec.d.ts","sourceRoot":"","sources":["../src/aggregation-select.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,164 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { suite, test } from "@webda/test";
36
+ import * as assert from "assert";
37
+ import { toAggregationQuery } from "./aggregation.js";
38
+ import { assertFilterQuery, normalizeQuery, parse } from "./query.js";
39
+ /**
40
+ * Comparable form of an aggregation query
41
+ * @param query - the query
42
+ * @returns a plain object
43
+ */
44
+ function plain(query) {
45
+ return { ...query, filter: query.filter.toString() };
46
+ }
47
+ let AggregationSelectTest = (() => {
48
+ let _classDecorators = [suite];
49
+ let _classDescriptor;
50
+ let _classExtraInitializers = [];
51
+ let _classThis;
52
+ let _instanceExtraInitializers = [];
53
+ let _roundTrip_decorators;
54
+ let _globalAggregation_decorators;
55
+ let _plainSelectUnchanged_decorators;
56
+ let _rejects_decorators;
57
+ let _reservedAliases_decorators;
58
+ let _allowedFields_decorators;
59
+ let _keywords_decorators;
60
+ var AggregationSelectTest = class {
61
+ static { _classThis = this; }
62
+ static {
63
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
64
+ _roundTrip_decorators = [test];
65
+ _globalAggregation_decorators = [test];
66
+ _plainSelectUnchanged_decorators = [test];
67
+ _rejects_decorators = [test];
68
+ _reservedAliases_decorators = [test];
69
+ _allowedFields_decorators = [test];
70
+ _keywords_decorators = [test];
71
+ __esDecorate(this, null, _roundTrip_decorators, { kind: "method", name: "roundTrip", static: false, private: false, access: { has: obj => "roundTrip" in obj, get: obj => obj.roundTrip }, metadata: _metadata }, null, _instanceExtraInitializers);
72
+ __esDecorate(this, null, _globalAggregation_decorators, { kind: "method", name: "globalAggregation", static: false, private: false, access: { has: obj => "globalAggregation" in obj, get: obj => obj.globalAggregation }, metadata: _metadata }, null, _instanceExtraInitializers);
73
+ __esDecorate(this, null, _plainSelectUnchanged_decorators, { kind: "method", name: "plainSelectUnchanged", static: false, private: false, access: { has: obj => "plainSelectUnchanged" in obj, get: obj => obj.plainSelectUnchanged }, metadata: _metadata }, null, _instanceExtraInitializers);
74
+ __esDecorate(this, null, _rejects_decorators, { kind: "method", name: "rejects", static: false, private: false, access: { has: obj => "rejects" in obj, get: obj => obj.rejects }, metadata: _metadata }, null, _instanceExtraInitializers);
75
+ __esDecorate(this, null, _reservedAliases_decorators, { kind: "method", name: "reservedAliases", static: false, private: false, access: { has: obj => "reservedAliases" in obj, get: obj => obj.reservedAliases }, metadata: _metadata }, null, _instanceExtraInitializers);
76
+ __esDecorate(this, null, _allowedFields_decorators, { kind: "method", name: "allowedFields", static: false, private: false, access: { has: obj => "allowedFields" in obj, get: obj => obj.allowedFields }, metadata: _metadata }, null, _instanceExtraInitializers);
77
+ __esDecorate(this, null, _keywords_decorators, { kind: "method", name: "keywords", static: false, private: false, access: { has: obj => "keywords" in obj, get: obj => obj.keywords }, metadata: _metadata }, null, _instanceExtraInitializers);
78
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
79
+ AggregationSelectTest = _classThis = _classDescriptor.value;
80
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
81
+ __runInitializers(_classThis, _classExtraInitializers);
82
+ }
83
+ roundTrip() {
84
+ const query = parse("SELECT status, owner.uuid, COUNT(*) AS n, COUNT(tag) AS withTag, COUNT(DISTINCT tag) AS tags, " +
85
+ "SUM(points) AS total, AVG(points) AS mean, MIN(createdAt) AS first, MAX(points) AS top " +
86
+ "WHERE completed = FALSE AND owner.uuid = 'u1' GROUP BY status, owner.uuid ORDER BY n DESC, status LIMIT 10");
87
+ assert.strictEqual(query.type, "SELECT");
88
+ assert.deepStrictEqual(query.fields, ["status", "owner.uuid"]);
89
+ assert.deepStrictEqual(plain(query.aggregation), plain(toAggregationQuery({
90
+ filter: "completed = FALSE AND owner.uuid = 'u1'",
91
+ groupBy: ["status", "owner.uuid"],
92
+ metrics: {
93
+ n: { count: "*" },
94
+ withTag: { count: "tag" },
95
+ tags: { countDistinct: "tag" },
96
+ total: { sum: "points" },
97
+ mean: { avg: "points" },
98
+ first: { min: "createdAt" },
99
+ top: { max: "points" }
100
+ },
101
+ orderBy: [
102
+ { key: "n", direction: "DESC" },
103
+ { key: "status", direction: "ASC" }
104
+ ],
105
+ limit: 10
106
+ })));
107
+ assert.deepStrictEqual(plain(parse(query.toString()).aggregation), plain(query.aggregation));
108
+ }
109
+ globalAggregation() {
110
+ const query = parse("SELECT COUNT(*) AS n");
111
+ assert.deepStrictEqual(query.fields, []);
112
+ assert.deepStrictEqual(plain(query.aggregation), {
113
+ filter: "",
114
+ groupBy: [],
115
+ metrics: { n: { fn: "COUNT" } },
116
+ orderBy: undefined,
117
+ limit: undefined
118
+ });
119
+ // Repository query() / iterate() refuse it like any SELECT field list
120
+ assert.throws(() => assertFilterQuery(query));
121
+ assert.deepStrictEqual(plain(parse(query.toString()).aggregation), plain(query.aggregation));
122
+ assert.throws(() => normalizeQuery(query), /Aggregation/);
123
+ }
124
+ plainSelectUnchanged() {
125
+ const query = parse("SELECT a, b WHERE a = 1");
126
+ assert.deepStrictEqual(query.fields, ["a", "b"]);
127
+ assert.strictEqual(query.aggregation, undefined);
128
+ }
129
+ rejects() {
130
+ for (const bad of [
131
+ "SELECT status, COUNT(*) AS n",
132
+ "SELECT COUNT(*) AS n GROUP BY status",
133
+ "SELECT status, owner GROUP BY status",
134
+ "SELECT COUNT(*)",
135
+ "SELECT SUM(*) AS s",
136
+ "SELECT COUNT(*) AS n, COUNT(a) AS n",
137
+ "SELECT COUNT(*) AS n OFFSET 'x'",
138
+ "SELECT COUNT(*) AS n ORDER BY nope",
139
+ "SELECT status GROUP BY status"
140
+ ]) {
141
+ assert.throws(() => parse(bad), undefined, bad);
142
+ }
143
+ }
144
+ reservedAliases() {
145
+ for (const bad of ["SELECT COUNT(*) AS n, SUM(x) AS __proto__", "SELECT SUM(x) AS __proto__"]) {
146
+ assert.throws(() => parse(bad), /Invalid metric alias/, bad);
147
+ }
148
+ }
149
+ allowedFields() {
150
+ assert.throws(() => parse("SELECT SUM(secret) AS s", ["points"]), /secret/);
151
+ assert.ok(parse("SELECT SUM(points) AS s", ["points"]).aggregation);
152
+ }
153
+ keywords() {
154
+ assert.strictEqual(parse("count = 1 AND min = 2").filter.toString(), "count = 1 AND min = 2", "lowercase stays an attribute");
155
+ assert.throws(() => parse("COUNT = 1"), undefined, "uppercase COUNT is now a keyword");
156
+ assert.throws(() => parse("a = 1 GROUP BY a"), undefined, "GROUP BY only in a SELECT");
157
+ }
158
+ constructor() {
159
+ __runInitializers(this, _instanceExtraInitializers);
160
+ }
161
+ };
162
+ return AggregationSelectTest = _classThis;
163
+ })();
164
+ //# sourceMappingURL=aggregation-select.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-select.spec.js","sourceRoot":"","sources":["../src/aggregation-select.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { type QueryParameters } from "./bind.js";
2
+ import { type AggregationOrder, type AggregationQuery } from "./aggregation-query.js";
3
+ export * from "./aggregation-query.js";
4
+ /**
5
+ * Compare two values the way every backend orders aggregated rows
6
+ *
7
+ * null / undefined first, then numbers, strings (code unit order), booleans, then anything
8
+ * else by its JSON representation.
9
+ * @param a - first value
10
+ * @param b - second value
11
+ * @returns negative, zero or positive
12
+ */
13
+ export declare function compareValues(a: unknown, b: unknown): number;
14
+ /**
15
+ * Running computation of one metric
16
+ */
17
+ interface Accumulator {
18
+ add(item: any): void;
19
+ result(): unknown;
20
+ }
21
+ /**
22
+ * Streaming reference implementation of an aggregation
23
+ *
24
+ * Items must already match the query filter. Memory is O(groups), plus one Set per group per
25
+ * COUNT_DISTINCT. It is the fallback engine of repositories and the oracle of the
26
+ * conformance tests.
27
+ */
28
+ export declare class Aggregator {
29
+ protected query: AggregationQuery;
30
+ protected maxGroups: number;
31
+ protected groups: Map<string, {
32
+ keys: unknown[];
33
+ accumulators: [string, Accumulator][];
34
+ }>;
35
+ /**
36
+ * @param query - the aggregation; validated here
37
+ * @param maxGroups - throw AGGREGATION_TOO_MANY_GROUPS beyond this number of groups
38
+ */
39
+ constructor(query: AggregationQuery, maxGroups?: number);
40
+ /**
41
+ * Create an empty group
42
+ * @param keys - group key values
43
+ * @returns the group
44
+ */
45
+ protected createGroup(keys: unknown[]): {
46
+ keys: unknown[];
47
+ accumulators: [string, Accumulator][];
48
+ };
49
+ /**
50
+ * Add a matching item
51
+ * @param item - the item
52
+ */
53
+ add(item: any): void;
54
+ /**
55
+ * Compute the rows, ordered and limited
56
+ * @returns flat rows keyed by group path and metric alias
57
+ */
58
+ rows(): Record<string, unknown>[];
59
+ }
60
+ /**
61
+ * Ergonomic metric definition used by the object API
62
+ */
63
+ export type MetricInput = {
64
+ count: string;
65
+ } | {
66
+ countDistinct: string;
67
+ } | {
68
+ sum: string;
69
+ } | {
70
+ avg: string;
71
+ } | {
72
+ min: string;
73
+ } | {
74
+ max: string;
75
+ };
76
+ /**
77
+ * Untyped object form of an aggregation, as accepted by repositories
78
+ */
79
+ export interface AggregationInput {
80
+ /**
81
+ * WebdaQL filter, without ORDER BY / LIMIT / OFFSET
82
+ */
83
+ filter?: string;
84
+ groupBy?: readonly string[];
85
+ metrics: Record<string, MetricInput>;
86
+ orderBy?: readonly AggregationOrder[];
87
+ limit?: number;
88
+ }
89
+ /**
90
+ * Convert the object form of an aggregation to the canonical AST
91
+ * @param input - the object form
92
+ * @param params - values for the `?` / `:name` placeholders of the filter
93
+ * @returns the validated AST
94
+ * @throws WebdaQLError when the input is invalid
95
+ */
96
+ export declare function toAggregationQuery(input: AggregationInput, params?: QueryParameters): AggregationQuery;
97
+ //# sourceMappingURL=aggregation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation.d.ts","sourceRoot":"","sources":["../src/aggregation.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAItB,MAAM,wBAAwB,CAAC;AAGhC,cAAc,wBAAwB,CAAC;AAIvC;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,MAAM,CAc5D;AAED;;GAEG;AACH,UAAU,WAAW;IACnB,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;IACrB,MAAM,IAAI,OAAO,CAAC;CACnB;AAkFD;;;;;;GAMG;AACH,qBAAa,UAAU;IAQnB,SAAS,CAAC,KAAK,EAAE,gBAAgB;IACjC,SAAS,CAAC,SAAS,EAAE,MAAM;IAR7B,SAAS,CAAC,MAAM;cAA2B,OAAO,EAAE;sBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE;OAAM;IAEjG;;;OAGG;IACH,YACY,KAAK,EAAE,gBAAgB,EACvB,SAAS,GAAE,MAAiC,EAGvD;IAED;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE;;;MAOpC;IAED;;;OAGG;IACH,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAcnB;IAED;;;OAGG;IACH,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAyBhC;CACF;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GACrB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAExH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,gBAAgB,CA8BtG"}