measurr 0.1.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/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/analytics.d.ts +133 -0
- package/dist/compile.d.ts +84 -0
- package/dist/dataset.d.ts +303 -0
- package/dist/dialects/postgres.d.ts +16 -0
- package/dist/dialects/postgres.js +51 -0
- package/dist/expr.d.ts +210 -0
- package/dist/guidance.d.ts +5 -0
- package/dist/index-e6k30sfp.js +218 -0
- package/dist/index-m0czpsyv.js +1292 -0
- package/dist/index-nrjdssft.js +200 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +78 -0
- package/dist/period.d.ts +44 -0
- package/dist/plan.d.ts +86 -0
- package/dist/query.d.ts +619 -0
- package/dist/testing.d.ts +38 -0
- package/dist/testing.js +191 -0
- package/package.json +84 -0
- package/src/analytics.ts +663 -0
- package/src/compile.ts +332 -0
- package/src/dataset.ts +783 -0
- package/src/dialects/postgres.ts +74 -0
- package/src/expr.ts +539 -0
- package/src/guidance.ts +10 -0
- package/src/index.ts +74 -0
- package/src/period.ts +212 -0
- package/src/plan.ts +588 -0
- package/src/query.ts +399 -0
- package/src/testing.ts +283 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
rootOf,
|
|
3
|
+
walk
|
|
4
|
+
} from "./index-e6k30sfp.js";
|
|
5
|
+
|
|
6
|
+
// src/compile.ts
|
|
7
|
+
class CompileError2 extends Error {
|
|
8
|
+
name = "CompileError";
|
|
9
|
+
}
|
|
10
|
+
function decodeNumber2(value) {
|
|
11
|
+
if (value === null || value === undefined)
|
|
12
|
+
return null;
|
|
13
|
+
if (typeof value === "number")
|
|
14
|
+
return Number.isNaN(value) ? null : value;
|
|
15
|
+
if (typeof value === "bigint")
|
|
16
|
+
return Number(value);
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
const parsed = value.trim() === "" ? Number.NaN : Number(value);
|
|
19
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === "object" && "toNumber" in value && typeof value.toNumber === "function") {
|
|
22
|
+
return decodeNumber2(value.toNumber());
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function renderStatement2(statement, renderer) {
|
|
27
|
+
return new RenderContext(renderer, statement.now).render(statement);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class RenderContext {
|
|
31
|
+
renderer;
|
|
32
|
+
now;
|
|
33
|
+
aliases = new Map;
|
|
34
|
+
usedAliases = new Set;
|
|
35
|
+
values = [];
|
|
36
|
+
constructor(renderer, now) {
|
|
37
|
+
this.renderer = renderer;
|
|
38
|
+
this.now = now;
|
|
39
|
+
}
|
|
40
|
+
render(statement) {
|
|
41
|
+
const r = this.renderer;
|
|
42
|
+
const nodes = [
|
|
43
|
+
...statement.select.map((item) => item.node),
|
|
44
|
+
...statement.where
|
|
45
|
+
];
|
|
46
|
+
const joins = collectJoins(nodes, statement.from, statement.virtualJoins);
|
|
47
|
+
const select = statement.select.map((item) => `${this.expr(item.node)} AS ${r.quote(item.alias)}`);
|
|
48
|
+
const lines = [
|
|
49
|
+
`SELECT ${select.join(", ")}`,
|
|
50
|
+
`FROM ${this.tableSource(statement.from, joins)}`
|
|
51
|
+
];
|
|
52
|
+
if (statement.where.length > 0) {
|
|
53
|
+
lines.push(`WHERE ${statement.where.map((node) => this.expr(node)).join(" AND ")}`);
|
|
54
|
+
}
|
|
55
|
+
if (statement.groupBy.length > 0) {
|
|
56
|
+
const refs = statement.groupBy.map((index) => {
|
|
57
|
+
if (!statement.select[index])
|
|
58
|
+
throw new CompileError2(`GROUP BY refers to missing select item ${index}`);
|
|
59
|
+
return String(index + 1);
|
|
60
|
+
});
|
|
61
|
+
lines.push(`GROUP BY ${refs.join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
if (statement.orderBy.length > 0) {
|
|
64
|
+
const refs = statement.orderBy.map(({ index, direction }) => {
|
|
65
|
+
const item = statement.select[index];
|
|
66
|
+
if (!item)
|
|
67
|
+
throw new CompileError2(`ORDER BY refers to missing select item ${index}`);
|
|
68
|
+
return `${r.quote(item.alias)} ${direction === "asc" ? "ASC" : "DESC"}`;
|
|
69
|
+
});
|
|
70
|
+
lines.push(`ORDER BY ${refs.join(", ")}`);
|
|
71
|
+
}
|
|
72
|
+
if (statement.limit !== null)
|
|
73
|
+
lines.push(`LIMIT ${Math.trunc(statement.limit)}`);
|
|
74
|
+
return { text: lines.join(`
|
|
75
|
+
`), values: this.values };
|
|
76
|
+
}
|
|
77
|
+
alias(ref) {
|
|
78
|
+
const existing = this.aliases.get(ref);
|
|
79
|
+
if (existing)
|
|
80
|
+
return existing;
|
|
81
|
+
const base = ref.alias ?? ref.name;
|
|
82
|
+
let alias = base;
|
|
83
|
+
for (let n = 2;this.usedAliases.has(alias); n++)
|
|
84
|
+
alias = `${base}_${n}`;
|
|
85
|
+
this.aliases.set(ref, alias);
|
|
86
|
+
this.usedAliases.add(alias);
|
|
87
|
+
return alias;
|
|
88
|
+
}
|
|
89
|
+
tableSource(root, joins) {
|
|
90
|
+
const q = this.renderer.quote;
|
|
91
|
+
const parts = [`${q(root.name)} AS ${q(this.alias(root))}`];
|
|
92
|
+
for (const join of joins) {
|
|
93
|
+
const keyword = join.type === "left" ? "LEFT JOIN" : "INNER JOIN";
|
|
94
|
+
const source = `${q(join.table.name)} AS ${q(this.alias(join.table))}`;
|
|
95
|
+
parts.push(`${keyword} ${source} ON ${this.expr(join.on)}`);
|
|
96
|
+
}
|
|
97
|
+
return parts.join(" ");
|
|
98
|
+
}
|
|
99
|
+
existsSubquery(table, where) {
|
|
100
|
+
const from = this.tableSource(table, collectJoins([where], table, []));
|
|
101
|
+
return `SELECT 1 FROM ${from} WHERE ${this.expr(where)}`;
|
|
102
|
+
}
|
|
103
|
+
param(value) {
|
|
104
|
+
const index = this.values.length + 1;
|
|
105
|
+
const { placeholder, value: bound } = this.renderer.param(value, index);
|
|
106
|
+
this.values.push(bound);
|
|
107
|
+
return placeholder;
|
|
108
|
+
}
|
|
109
|
+
expr(node) {
|
|
110
|
+
const r = this.renderer;
|
|
111
|
+
switch (node.kind) {
|
|
112
|
+
case "column":
|
|
113
|
+
return `${r.quote(this.alias(node.table))}.${r.quote(node.column)}`;
|
|
114
|
+
case "param":
|
|
115
|
+
return this.param(node.value);
|
|
116
|
+
case "minutesAgo":
|
|
117
|
+
return this.param(new Date(this.now.getTime() - node.minutes * 60000));
|
|
118
|
+
case "compare":
|
|
119
|
+
return `(${this.expr(node.left)} ${node.op} ${this.expr(node.right)})`;
|
|
120
|
+
case "logical": {
|
|
121
|
+
if (node.items.length === 0)
|
|
122
|
+
return node.op === "and" ? "(1 = 1)" : "(1 = 0)";
|
|
123
|
+
const joiner = node.op === "and" ? " AND " : " OR ";
|
|
124
|
+
return `(${node.items.map((item) => this.expr(item)).join(joiner)})`;
|
|
125
|
+
}
|
|
126
|
+
case "not":
|
|
127
|
+
return `(NOT ${this.expr(node.item)})`;
|
|
128
|
+
case "isNull":
|
|
129
|
+
return `(${this.expr(node.item)} IS ${node.negated ? "NOT " : ""}NULL)`;
|
|
130
|
+
case "in": {
|
|
131
|
+
if (node.values.length === 0)
|
|
132
|
+
return node.negated ? "(1 = 1)" : "(1 = 0)";
|
|
133
|
+
const values = node.values.map((value) => this.expr(value)).join(", ");
|
|
134
|
+
return `(${this.expr(node.item)} ${node.negated ? "NOT IN" : "IN"} (${values}))`;
|
|
135
|
+
}
|
|
136
|
+
case "case": {
|
|
137
|
+
const branches = node.branches.map((branch) => `WHEN ${this.expr(branch.when)} THEN ${this.expr(branch.result)}`).join(" ");
|
|
138
|
+
const otherwise = node.otherwise ? ` ELSE ${this.expr(node.otherwise)}` : "";
|
|
139
|
+
return `(CASE ${branches}${otherwise} END)`;
|
|
140
|
+
}
|
|
141
|
+
case "exists": {
|
|
142
|
+
const body = this.existsSubquery(node.table, node.where);
|
|
143
|
+
return `(${node.negated ? "NOT " : ""}EXISTS (${body}))`;
|
|
144
|
+
}
|
|
145
|
+
case "coalesce":
|
|
146
|
+
return `coalesce(${node.items.map((item) => this.expr(item)).join(", ")})`;
|
|
147
|
+
case "arithmetic": {
|
|
148
|
+
const left = this.expr(node.left);
|
|
149
|
+
const right = this.expr(node.right);
|
|
150
|
+
if (node.op === "/")
|
|
151
|
+
return r.divide(left, right);
|
|
152
|
+
return `(${left} ${node.op} ${right})`;
|
|
153
|
+
}
|
|
154
|
+
case "lower":
|
|
155
|
+
return `lower(${this.expr(node.item)})`;
|
|
156
|
+
case "toText":
|
|
157
|
+
return r.toText(this.expr(node.item));
|
|
158
|
+
case "aggregate":
|
|
159
|
+
return r.aggregate(node.fn, node.arg ? this.expr(node.arg) : null, node.where ? this.expr(node.where) : null);
|
|
160
|
+
case "timeBucket":
|
|
161
|
+
return r.timeBucket(node.unit, this.expr(node.item), node.timezone);
|
|
162
|
+
case "timePart":
|
|
163
|
+
return r.timePart(node.part, this.expr(node.item), node.timezone);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function collectJoins(nodes, root, virtualJoins) {
|
|
168
|
+
const virtualByRoot = new Map(virtualJoins.map((join) => [join.table, join]));
|
|
169
|
+
const ordered = [];
|
|
170
|
+
const added = new Set([root]);
|
|
171
|
+
const visit = (node) => walk(node, (inner) => {
|
|
172
|
+
if (inner.kind !== "column")
|
|
173
|
+
return;
|
|
174
|
+
const columnRoot = rootOf(inner.table);
|
|
175
|
+
if (columnRoot === root || virtualByRoot.has(columnRoot))
|
|
176
|
+
ensure(inner.table);
|
|
177
|
+
});
|
|
178
|
+
const ensure = (ref) => {
|
|
179
|
+
if (added.has(ref))
|
|
180
|
+
return;
|
|
181
|
+
if (ref.join) {
|
|
182
|
+
ensure(ref.join.parent);
|
|
183
|
+
added.add(ref);
|
|
184
|
+
visit(ref.join.on);
|
|
185
|
+
ordered.push({ table: ref, type: ref.join.type, on: ref.join.on });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const virtual = virtualByRoot.get(ref);
|
|
189
|
+
if (!virtual)
|
|
190
|
+
return;
|
|
191
|
+
added.add(ref);
|
|
192
|
+
visit(virtual.on);
|
|
193
|
+
ordered.push(virtual);
|
|
194
|
+
};
|
|
195
|
+
for (const node of nodes)
|
|
196
|
+
visit(node);
|
|
197
|
+
return ordered;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { CompileError2, decodeNumber2, renderStatement2 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { type Analytics, AnalyticsError, type AnalyticsErrorCode, type AnalyticsErrorDetails, type AnalyticsErrorOf, type AnalyticsOptions, type AnalyticsQuery, type AnalyticsQueryEvent, type AnalyticsResult, type AnalyticsSource, createAnalytics, type DatasetCatalogEntry, isAnalyticsError } from './analytics.js';
|
|
2
|
+
export { CompileError, type CompiledStatement, type Dialect, type JoinClause, type SelectItem, type SelectStatement, type SqlRenderer, decodeNumber, renderStatement } from './compile.js';
|
|
3
|
+
export { type AnyDataset, type AnyDimension, type AnyMeasure, type Dataset, type DimensionValue, defineDataset, dimension, type MeasureValue, measure, type Ratio } from './dataset.js';
|
|
4
|
+
export { and, caseWhen, coalesce, type ColumnKey, eq, Expr, type ExprNode, exists, gt, gte, inList, isNotNull, isNull, literal, lt, lte, minutesAgo, ne, not, or, type SqlType, Table, type TableRef, table, toText } from './expr.js';
|
|
5
|
+
export { analyticsToolGuidance } from './guidance.js';
|
|
6
|
+
export type { PeriodInput, PeriodPreset } from './period.js';
|
|
7
|
+
export { AnalyticsResultSchema, type DatasetFilter, type DatasetQuery, type QueryResult, type ResultOf } from './query.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalyticsResultSchema2,
|
|
3
|
+
AnalyticsError2,
|
|
4
|
+
isAnalyticsError2,
|
|
5
|
+
createAnalytics2,
|
|
6
|
+
measure2,
|
|
7
|
+
dimension2,
|
|
8
|
+
defineDataset2
|
|
9
|
+
} from "./index-m0czpsyv.js";
|
|
10
|
+
import {
|
|
11
|
+
CompileError2,
|
|
12
|
+
decodeNumber2,
|
|
13
|
+
renderStatement2
|
|
14
|
+
} from "./index-nrjdssft.js";
|
|
15
|
+
import {
|
|
16
|
+
Expr2,
|
|
17
|
+
Table2,
|
|
18
|
+
table2,
|
|
19
|
+
literal2,
|
|
20
|
+
eq2,
|
|
21
|
+
ne2,
|
|
22
|
+
gt2,
|
|
23
|
+
gte2,
|
|
24
|
+
lt2,
|
|
25
|
+
lte2,
|
|
26
|
+
and2,
|
|
27
|
+
or2,
|
|
28
|
+
not2,
|
|
29
|
+
isNull2,
|
|
30
|
+
isNotNull2,
|
|
31
|
+
caseWhen2,
|
|
32
|
+
exists2,
|
|
33
|
+
coalesce2,
|
|
34
|
+
inList2,
|
|
35
|
+
toText2,
|
|
36
|
+
minutesAgo2
|
|
37
|
+
} from "./index-e6k30sfp.js";
|
|
38
|
+
// src/guidance.ts
|
|
39
|
+
var analyticsToolGuidance = `- Answer every "how many", "how much", "per week" or "split by" question with the analytics tool. Never count by hand from lists or pages.
|
|
40
|
+
- Pick the dataset whose description matches the question, the fewest measures that answer it and at most two groupings.
|
|
41
|
+
- Say which period and filters the numbers cover, using the dates in the result ("1-30 September").
|
|
42
|
+
- Compare with the previous period (compareToPrevious) when the change is the point, such as "is it going up".
|
|
43
|
+
- When the result has notes (overlapping rows, such as a record counted once per tag), say so in a few words.
|
|
44
|
+
- Ask one short question only when the period is genuinely ambiguous ("this season") and someone can answer; otherwise take the natural reading and state it.`;
|
|
45
|
+
export {
|
|
46
|
+
AnalyticsError2 as AnalyticsError,
|
|
47
|
+
AnalyticsResultSchema2 as AnalyticsResultSchema,
|
|
48
|
+
CompileError2 as CompileError,
|
|
49
|
+
Expr2 as Expr,
|
|
50
|
+
Table2 as Table,
|
|
51
|
+
analyticsToolGuidance,
|
|
52
|
+
and2 as and,
|
|
53
|
+
caseWhen2 as caseWhen,
|
|
54
|
+
coalesce2 as coalesce,
|
|
55
|
+
createAnalytics2 as createAnalytics,
|
|
56
|
+
decodeNumber2 as decodeNumber,
|
|
57
|
+
defineDataset2 as defineDataset,
|
|
58
|
+
dimension2 as dimension,
|
|
59
|
+
eq2 as eq,
|
|
60
|
+
exists2 as exists,
|
|
61
|
+
gt2 as gt,
|
|
62
|
+
gte2 as gte,
|
|
63
|
+
inList2 as inList,
|
|
64
|
+
isAnalyticsError2 as isAnalyticsError,
|
|
65
|
+
isNotNull2 as isNotNull,
|
|
66
|
+
isNull2 as isNull,
|
|
67
|
+
literal2 as literal,
|
|
68
|
+
lt2 as lt,
|
|
69
|
+
lte2 as lte,
|
|
70
|
+
measure2 as measure,
|
|
71
|
+
minutesAgo2 as minutesAgo,
|
|
72
|
+
ne2 as ne,
|
|
73
|
+
not2 as not,
|
|
74
|
+
or2 as or,
|
|
75
|
+
renderStatement2 as renderStatement,
|
|
76
|
+
table2 as table,
|
|
77
|
+
toText2 as toText
|
|
78
|
+
};
|
package/dist/period.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periods are whole local days in the tenant's timezone. A period `{ from, to }` covers
|
|
3
|
+
* `from` 00:00 up to (not including) the day after `to` 00:00, converted to instants, so the
|
|
4
|
+
* database compares plain timestamps and can use its time index.
|
|
5
|
+
*/
|
|
6
|
+
export type PeriodPreset = 'today' | 'yesterday' | 'thisWeek' | 'lastWeek' | 'thisMonth' | 'lastMonth' | 'thisYear';
|
|
7
|
+
export type PeriodInput = {
|
|
8
|
+
last: {
|
|
9
|
+
days: number;
|
|
10
|
+
};
|
|
11
|
+
} | {
|
|
12
|
+
from: string;
|
|
13
|
+
to: string;
|
|
14
|
+
} | {
|
|
15
|
+
preset: PeriodPreset;
|
|
16
|
+
};
|
|
17
|
+
export type ResolvedPeriod = {
|
|
18
|
+
/** First local day, `YYYY-MM-DD`. */
|
|
19
|
+
from: string;
|
|
20
|
+
/** Last local day, inclusive. */
|
|
21
|
+
to: string;
|
|
22
|
+
timezone: string;
|
|
23
|
+
/** Inclusive start instant. */
|
|
24
|
+
start: Date;
|
|
25
|
+
/** Exclusive end instant. */
|
|
26
|
+
end: Date;
|
|
27
|
+
days: number;
|
|
28
|
+
};
|
|
29
|
+
export declare const MAX_PERIOD_DAYS = 731;
|
|
30
|
+
export declare const DEFAULT_PERIOD: PeriodInput;
|
|
31
|
+
/** True for an IANA name the runtime knows. Names are also inlined in SQL, so be strict. */
|
|
32
|
+
export declare function isValidTimezone(timezone: string): boolean;
|
|
33
|
+
/** The local calendar date of `instant` in `timezone`, as `YYYY-MM-DD`. */
|
|
34
|
+
export declare function localDate(instant: Date, timezone: string): string;
|
|
35
|
+
/** The instant local midnight starts on `date` in `timezone` (handles DST changes). */
|
|
36
|
+
export declare function startOfLocalDay(date: string, timezone: string): Date;
|
|
37
|
+
export declare function addDays(date: string, days: number): string;
|
|
38
|
+
export declare class PeriodError extends Error {
|
|
39
|
+
readonly name = "PeriodError";
|
|
40
|
+
}
|
|
41
|
+
/** Resolves a period input to local days and instants in `timezone`. */
|
|
42
|
+
export declare function resolvePeriod(input: PeriodInput, timezone: string, now: Date): ResolvedPeriod;
|
|
43
|
+
/** The period of equal length that ends the day before `period` starts. */
|
|
44
|
+
export declare function previousPeriod(period: ResolvedPeriod): ResolvedPeriod;
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Dialect, SelectStatement } from './compile.js';
|
|
2
|
+
import type { AnyDataset } from './dataset.js';
|
|
3
|
+
import { type PeriodInput, type ResolvedPeriod } from './period.js';
|
|
4
|
+
/** A parsed query with every default filled in and duplicates removed. */
|
|
5
|
+
export type NormalizedQuery = {
|
|
6
|
+
dataset: string;
|
|
7
|
+
measures: string[];
|
|
8
|
+
groupBy: string[];
|
|
9
|
+
filters: NormalizedFilter[];
|
|
10
|
+
period: PeriodInput;
|
|
11
|
+
time: string;
|
|
12
|
+
compareToPrevious: boolean;
|
|
13
|
+
sort: {
|
|
14
|
+
by: string;
|
|
15
|
+
direction: 'asc' | 'desc';
|
|
16
|
+
} | null;
|
|
17
|
+
limit: number;
|
|
18
|
+
};
|
|
19
|
+
export type NormalizedFilter = {
|
|
20
|
+
dimension: string;
|
|
21
|
+
op: 'in' | 'notIn';
|
|
22
|
+
values: (string | number)[];
|
|
23
|
+
} | {
|
|
24
|
+
dimension: string;
|
|
25
|
+
op: 'between';
|
|
26
|
+
from: string | number;
|
|
27
|
+
to: string | number;
|
|
28
|
+
} | {
|
|
29
|
+
dimension: string;
|
|
30
|
+
op: 'isEmpty' | 'isNotEmpty';
|
|
31
|
+
};
|
|
32
|
+
/** The parsed shape the schema produces (all optional fields possibly absent). */
|
|
33
|
+
export type ParsedQuery = {
|
|
34
|
+
dataset: string;
|
|
35
|
+
measures: readonly string[];
|
|
36
|
+
groupBy?: readonly string[] | undefined;
|
|
37
|
+
filters?: readonly NormalizedFilter[] | undefined;
|
|
38
|
+
period?: PeriodInput | undefined;
|
|
39
|
+
time?: string | undefined;
|
|
40
|
+
compareToPrevious?: boolean | undefined;
|
|
41
|
+
sort?: {
|
|
42
|
+
by: string;
|
|
43
|
+
direction?: 'asc' | 'desc' | undefined;
|
|
44
|
+
} | undefined;
|
|
45
|
+
limit?: number | undefined;
|
|
46
|
+
};
|
|
47
|
+
export declare function normalizeQuery(dataset: AnyDataset, parsed: ParsedQuery): NormalizedQuery;
|
|
48
|
+
/** What the planner needs besides the query: values from the hooks and resolved labels. */
|
|
49
|
+
export type PlanContext = {
|
|
50
|
+
tenant: string | number;
|
|
51
|
+
timezone: string;
|
|
52
|
+
period: ResolvedPeriod;
|
|
53
|
+
/** The moment the query runs. */
|
|
54
|
+
now: Date;
|
|
55
|
+
/** Keys for filters on open dimensions, after label resolution, by dimension. */
|
|
56
|
+
resolvedValues: ReadonlyMap<string, readonly (string | number)[]>;
|
|
57
|
+
};
|
|
58
|
+
/** Output column aliases. Keys come from definitions, never from input. */
|
|
59
|
+
export declare const columnAlias: {
|
|
60
|
+
measure: (key: string) => string;
|
|
61
|
+
groupKey: (key: string) => string;
|
|
62
|
+
groupLabel: (key: string) => string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The statement for a query's rows, or for its totals when `grouped` is false. Totals never
|
|
66
|
+
* group, so a many-to-many grouping cannot inflate them.
|
|
67
|
+
*/
|
|
68
|
+
export declare function planQuery(dataset: AnyDataset, query: NormalizedQuery, context: PlanContext, grouped: boolean): SelectStatement;
|
|
69
|
+
/**
|
|
70
|
+
* Looks up an open dimension's keys and names. With `values`, only entries whose key or name
|
|
71
|
+
* (case-insensitive) matches; without, the first entries by name, to list valid values.
|
|
72
|
+
*/
|
|
73
|
+
export declare function planLookup(dataset: AnyDataset, dimensionKey: string, tenant: string | number, values: readonly string[] | null, now: Date): SelectStatement;
|
|
74
|
+
export type ShapedValue = {
|
|
75
|
+
key: string | number | null;
|
|
76
|
+
label: string;
|
|
77
|
+
};
|
|
78
|
+
export type ShapedRow = Record<string, ShapedValue | number | null>;
|
|
79
|
+
/** Decodes measures in one raw row; counts are never null. */
|
|
80
|
+
export declare function shapeMeasures(dataset: AnyDataset, measures: readonly string[], raw: Record<string, unknown>, dialect: Dialect): Record<string, number | null>;
|
|
81
|
+
export declare function shapeRows(dataset: AnyDataset, query: NormalizedQuery, rawRows: readonly Record<string, unknown>[], dialect: Dialect): {
|
|
82
|
+
rows: ShapedRow[];
|
|
83
|
+
truncated: boolean;
|
|
84
|
+
};
|
|
85
|
+
/** Notes a reader needs to read the numbers right. */
|
|
86
|
+
export declare function resultNotes(dataset: AnyDataset, query: NormalizedQuery): string[];
|