envio 3.15.0-subgraph → 3.16.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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +3 -3
  3. package/fuel.schema.json +2 -2
  4. package/index.d.ts +3 -3
  5. package/licenses/README.md +1 -1
  6. package/package.json +9 -10
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +0 -7
  11. package/src/Bin.res.mjs +0 -8
  12. package/src/ChainFetching.res +17 -456
  13. package/src/ChainFetching.res.mjs +33 -404
  14. package/src/ChainState.res +48 -83
  15. package/src/ChainState.res.mjs +83 -115
  16. package/src/ChainState.resi +0 -26
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +7 -16
  20. package/src/Config.res.mjs +4 -11
  21. package/src/ContractRegisterContext.res +6 -3
  22. package/src/ContractRegisterContext.res.mjs +2 -4
  23. package/src/Core.res +5 -136
  24. package/src/Core.res.mjs +1 -90
  25. package/src/CrossChainState.res +2 -8
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +4 -7
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectState.res +10 -2
  30. package/src/EffectState.res.mjs +8 -4
  31. package/src/EffectState.resi +3 -1
  32. package/src/EntityId.res +2 -7
  33. package/src/EntityId.res.mjs +1 -8
  34. package/src/EntityTables.res +4 -1
  35. package/src/EntityTables.res.mjs +1 -1
  36. package/src/Env.res +0 -16
  37. package/src/Env.res.mjs +0 -11
  38. package/src/EventProcessing.res +1 -38
  39. package/src/EventProcessing.res.mjs +1 -28
  40. package/src/FetchState.res +160 -319
  41. package/src/FetchState.res.mjs +182 -292
  42. package/src/FinalizeBackfill.res +2 -4
  43. package/src/FinalizeBackfill.res.mjs +3 -4
  44. package/src/HandlerLoader.res +15 -39
  45. package/src/HandlerLoader.res.mjs +8 -27
  46. package/src/HandlerRegister.res +0 -4
  47. package/src/HandlerRegister.res.mjs +2 -5
  48. package/src/HandlerRegister.resi +0 -2
  49. package/src/InMemoryTable.res +17 -16
  50. package/src/InMemoryTable.res.mjs +14 -15
  51. package/src/IndexerState.res +28 -4
  52. package/src/IndexerState.res.mjs +19 -6
  53. package/src/IndexerState.resi +4 -0
  54. package/src/Internal.res +0 -110
  55. package/src/Internal.res.mjs +1 -115
  56. package/src/LoadLayer.res +21 -47
  57. package/src/LoadLayer.res.mjs +47 -72
  58. package/src/LoadLayer.resi +0 -26
  59. package/src/Main.res +1 -14
  60. package/src/Main.res.mjs +3 -4
  61. package/src/Persistence.res +3 -23
  62. package/src/Persistence.res.mjs +3 -3
  63. package/src/Rollback.res +0 -1
  64. package/src/Rollback.res.mjs +1 -1
  65. package/src/RunScope.res +0 -3
  66. package/src/SafeCheckpointTracking.res +4 -4
  67. package/src/SafeCheckpointTracking.res.mjs +1 -1
  68. package/src/SimulateItems.res +26 -42
  69. package/src/SimulateItems.res.mjs +16 -40
  70. package/src/StagedRows.res +106 -0
  71. package/src/StagedRows.res.mjs +102 -0
  72. package/src/Staging.res +32 -14
  73. package/src/Staging.res.mjs +17 -20
  74. package/src/Storage.res +277 -998
  75. package/src/Storage.res.mjs +216 -731
  76. package/src/Supervisor.res +75 -56
  77. package/src/Supervisor.res.mjs +55 -26
  78. package/src/TestIndexer.res +22 -27
  79. package/src/TestIndexer.res.mjs +20 -17
  80. package/src/Throttler.res +15 -8
  81. package/src/Throttler.res.mjs +9 -6
  82. package/src/Throttler.resi +1 -1
  83. package/src/UserContext.res +46 -410
  84. package/src/UserContext.res.mjs +30 -318
  85. package/src/Utils.res +0 -23
  86. package/src/Utils.res.mjs +1 -17
  87. package/src/Writing.res +0 -1
  88. package/src/Writing.res.mjs +1 -1
  89. package/src/bindings/ClickHouse.res +0 -322
  90. package/src/bindings/ClickHouse.res.mjs +0 -338
  91. package/src/bindings/StorageClient.res +107 -175
  92. package/src/bindings/StorageClient.res.mjs +19 -8
  93. package/src/bindings/Vitest.res +0 -26
  94. package/src/bindings/Vitest.res.mjs +0 -3
  95. package/src/db/Table.res +24 -150
  96. package/src/db/Table.res.mjs +38 -272
  97. package/src/sources/AddressSet.res +0 -2
  98. package/src/sources/AddressStore.res +0 -3
  99. package/src/sources/ChainSources.res +2 -0
  100. package/src/sources/ChainSources.res.mjs +2 -2
  101. package/src/sources/EvmChain.res +4 -7
  102. package/src/sources/EvmChain.res.mjs +5 -4
  103. package/src/sources/EvmHyperSyncSource.res +2 -2
  104. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  105. package/src/sources/EvmRpcClient.res +13 -3
  106. package/src/sources/EvmRpcClient.res.mjs +4 -3
  107. package/src/sources/EvmRpcWs.res +4 -3
  108. package/src/sources/EvmRpcWs.res.mjs +2 -2
  109. package/src/sources/HeightFeed.res +31 -10
  110. package/src/sources/HeightFeed.res.mjs +10 -6
  111. package/src/sources/HeightStream.res +32 -17
  112. package/src/sources/HeightStream.res.mjs +10 -10
  113. package/src/sources/HyperSync.res +1 -2
  114. package/src/sources/HyperSync.res.mjs +1 -2
  115. package/src/sources/HyperSyncSSE.res +4 -6
  116. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  117. package/src/sources/RpcSource.res +10 -1
  118. package/src/sources/RpcSource.res.mjs +4 -3
  119. package/src/sources/Source.res +3 -0
  120. package/src/sources/SourceManager.res +85 -104
  121. package/src/sources/SourceManager.res.mjs +18 -37
  122. package/src/sources/SourceManager.resi +3 -5
  123. package/src/sources/StartBlockResolver.res +1 -0
  124. package/src/sources/StartBlockResolver.res.mjs +4 -3
  125. package/src/sources/SvmHyperSyncSource.res +2 -2
  126. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  127. package/svm.schema.json +3 -3
  128. package/src/EffectCacheLookup.res +0 -98
  129. package/src/EffectCacheLookup.res.mjs +0 -116
  130. package/src/PgWriting.res +0 -36
  131. package/src/PgWriting.res.mjs +0 -34
  132. package/src/PreloadLookahead.res +0 -184
  133. package/src/PreloadLookahead.res.mjs +0 -197
  134. package/src/SubgraphLog.res +0 -19
  135. package/src/SubgraphLog.res.mjs +0 -26
  136. package/src/SyncWrites.res +0 -101
  137. package/src/SyncWrites.res.mjs +0 -98
  138. package/src/db/IndexDefinition.res +0 -47
  139. package/src/db/IndexDefinition.res.mjs +0 -60
  140. package/src/sources/CallSource.res +0 -274
  141. package/src/sources/CallSource.res.mjs +0 -196
  142. package/src/subgraph/abi-types.ts +0 -84
  143. package/src/subgraph/aggregations.ts +0 -745
  144. package/src/subgraph/as-package.ts +0 -143
  145. package/src/subgraph/assemblyscript-regex.ts +0 -67
  146. package/src/subgraph/assemblyscript.ts +0 -731
  147. package/src/subgraph/blocks.ts +0 -398
  148. package/src/subgraph/calls-source.ts +0 -702
  149. package/src/subgraph/calls.ts +0 -258
  150. package/src/subgraph/conformance.ts +0 -106
  151. package/src/subgraph/errors.ts +0 -85
  152. package/src/subgraph/graft.ts +0 -73
  153. package/src/subgraph/graph-cli-worker.mjs +0 -35
  154. package/src/subgraph/graph-cli.ts +0 -900
  155. package/src/subgraph/graph-node-decimal.ts +0 -199
  156. package/src/subgraph/graph-ts-enums.ts +0 -28
  157. package/src/subgraph/graph-ts-types/VERSION +0 -2
  158. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  159. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  160. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  161. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  162. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  163. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  164. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  165. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  166. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  167. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  168. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  169. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  170. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  171. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  172. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  173. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  174. package/src/subgraph/graph-ts.ts +0 -4280
  175. package/src/subgraph/hosts.ts +0 -554
  176. package/src/subgraph/log.ts +0 -28
  177. package/src/subgraph/receipts.ts +0 -184
  178. package/src/subgraph/register-slots.ts +0 -93
  179. package/src/subgraph/rollup.ts +0 -109
  180. package/src/subgraph/rpc.ts +0 -337
  181. package/src/subgraph/runtime.ts +0 -2060
  182. package/src/subgraph/scope.ts +0 -177
  183. package/src/subgraph/stats.ts +0 -42
  184. package/src/subgraph/template-instances.ts +0 -66
  185. package/src/subgraph/toolchain.ts +0 -327
  186. package/src/subgraph/transient.ts +0 -176
@@ -1,745 +0,0 @@
1
- /**
2
- * Timeseries and aggregations, as graph-node does them (docs/aggregations.md,
3
- * store/postgres/src/relational/rollup.rs).
4
- *
5
- * A timeseries entity is written like any entity, except that the runtime sets
6
- * its `id` (Int8, `block << 32 | sequence`, increasing in insertion order) and
7
- * its `timestamp` (the block's time). An aggregation is rolled up from those
8
- * rows into one entity per interval, `<Aggregation>_<hour|day>`, when a block
9
- * crosses the interval's boundary: the first bucket between the previous block
10
- * time and this one is aggregated and written, exactly as `Layout::rollup`.
11
- *
12
- * Everything here is a function of the stored rows plus two kinds of entities
13
- * (`EnvioSubgraphRollup`: the time of the last block seen, and the latest
14
- * cumulative values per group). Both are ordinary entities, so reorg rollback
15
- * restores them with the rest, and a restart picks up where the data left off.
16
- */
17
- import BigNumber from "bignumber.js";
18
-
19
- // ---------------------------------------------------------------------------
20
- // Schema shapes (from the Rust translation)
21
- // ---------------------------------------------------------------------------
22
-
23
- export type Aggregate = {
24
- field: string;
25
- /** The aggregation table's column: `<field>_id` for an entity reference. */
26
- column?: string;
27
- func: "sum" | "count" | "min" | "max" | "first" | "last";
28
- arg?: string;
29
- cumulative: boolean;
30
- /** `first`/`last` also take a String, Bytes or entity reference (graph-node v0.42). */
31
- kind: "Int" | "Int8" | "BigInt" | "BigDecimal" | "String" | "Bytes" | "Reference";
32
- };
33
-
34
- const isPicked = (kind: Aggregate["kind"]) => kind === "String" || kind === "Bytes" || kind === "Reference";
35
-
36
- /** A picked (non-numeric) value as the cumulative state keeps it: text. */
37
- function pickedText(value: unknown): string {
38
- return value instanceof Uint8Array ? `0x${Buffer.from(value).toString("hex")}` : String(value);
39
- }
40
-
41
- export type AggregationDef = {
42
- name: string;
43
- source: string;
44
- intervals: ("hour" | "day")[];
45
- dimensions: { field: string; column: string }[];
46
- aggregates: Aggregate[];
47
- };
48
-
49
- export const INTERVAL_MICROS: Record<string, bigint> = {
50
- hour: 3_600_000_000n,
51
- day: 86_400_000_000n,
52
- };
53
-
54
- export const ROLLUP_STATE_ENTITY = "EnvioSubgraphRollup";
55
-
56
- /** The entity an aggregation's interval is stored as. */
57
- export function aggregationEntity(name: string, interval: string): string {
58
- return `${name}_${interval}`;
59
- }
60
-
61
- /** Start of the bucket (microseconds since the epoch) a time falls in. */
62
- export function bucketIndex(micros: bigint, interval: string): bigint {
63
- const size = INTERVAL_MICROS[interval];
64
- // Block times are after the epoch; flooring division for completeness.
65
- const q = micros / size;
66
- return micros % size < 0n ? q - 1n : q;
67
- }
68
-
69
- /**
70
- * Time ranges of the buckets that overlap `from..to` and end before `to`,
71
- * oldest first (`AggregationInterval::buckets`). The rollup only ever needs
72
- * the first.
73
- */
74
- export function bucketsBetween(
75
- from: bigint,
76
- to: bigint,
77
- interval: string,
78
- ): { start: bigint; end: bigint }[] {
79
- const size = INTERVAL_MICROS[interval];
80
- const first = bucketIndex(from, interval);
81
- const last = bucketIndex(to, interval);
82
- const buckets = [];
83
- for (let n = first; n < last; n = n + 1n) buckets.push({ start: n * size, end: (n + 1n) * size });
84
- return buckets;
85
- }
86
-
87
- // ---------------------------------------------------------------------------
88
- // Numbers
89
- // ---------------------------------------------------------------------------
90
-
91
- const Big = BigNumber.clone({ DECIMAL_PLACES: 40, ROUNDING_MODE: BigNumber.ROUND_HALF_UP });
92
-
93
- /** A numeric scalar with Postgres' notion of whether it is an integer type. */
94
- type Num = { v: BigNumber; int: boolean };
95
- type Val = Num | boolean | string | null;
96
-
97
- function toNum(value: unknown, int = false): Num | null {
98
- if (value === null || value === undefined) return null;
99
- if (typeof value === "bigint") return { v: new Big(value.toString()), int };
100
- if (typeof value === "number") return { v: new Big(value), int: int || Number.isInteger(value) };
101
- if (typeof value === "string") {
102
- const parsed = new Big(value);
103
- return parsed.isNaN() ? null : { v: parsed, int: false };
104
- }
105
- if (BigNumber.isBigNumber(value)) return { v: new Big(value as BigNumber), int: false };
106
- if (value instanceof Date) return { v: new Big(value.getTime()).times(1000), int: false };
107
- return null;
108
- }
109
-
110
- // ---------------------------------------------------------------------------
111
- // Expressions: the SQL subset graph-node lets an `arg` use
112
- // ---------------------------------------------------------------------------
113
-
114
- type Token =
115
- | { t: "num"; v: string }
116
- | { t: "str"; v: string }
117
- | { t: "id"; v: string }
118
- | { t: "op"; v: string }
119
- | { t: "end" };
120
-
121
- const KEYWORDS = new Set([
122
- "and",
123
- "or",
124
- "not",
125
- "is",
126
- "null",
127
- "true",
128
- "false",
129
- "distinct",
130
- "from",
131
- "case",
132
- "when",
133
- "then",
134
- "else",
135
- "end",
136
- ]);
137
-
138
- function tokenize(source: string): Token[] {
139
- const tokens: Token[] = [];
140
- let i = 0;
141
- while (i < source.length) {
142
- const c = source[i];
143
- if (/\s/.test(c)) {
144
- i++;
145
- } else if (/[0-9]/.test(c) || (c === "." && /[0-9]/.test(source[i + 1] ?? ""))) {
146
- const m = /^(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(source.slice(i))!;
147
- tokens.push({ t: "num", v: m[0] });
148
- i += m[0].length;
149
- } else if (/[A-Za-z_]/.test(c)) {
150
- const m = /^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(i))!;
151
- tokens.push({ t: "id", v: m[0] });
152
- i += m[0].length;
153
- } else if (c === '"') {
154
- const end = source.indexOf('"', i + 1);
155
- if (end < 0) throw new Error(`unterminated quoted identifier in "${source}"`);
156
- tokens.push({ t: "id", v: source.slice(i + 1, end) });
157
- i = end + 1;
158
- } else if (c === "'") {
159
- let value = "";
160
- i++;
161
- while (i < source.length && !(source[i] === "'" && source[i + 1] !== "'")) {
162
- value += source[i] === "'" ? "'" : source[i];
163
- i += source[i] === "'" ? 2 : 1;
164
- }
165
- i++;
166
- tokens.push({ t: "str", v: value });
167
- } else {
168
- const three = source.slice(i, i + 3);
169
- const two = source.slice(i, i + 2);
170
- if (three === "<->") {
171
- tokens.push({ t: "op", v: three });
172
- i += 3;
173
- } else if (["<=", ">=", "<>", "!="].includes(two)) {
174
- tokens.push({ t: "op", v: two === "<>" ? "!=" : two });
175
- i += 2;
176
- } else if ("+-*/%^=<>(),".includes(c)) {
177
- tokens.push({ t: "op", v: c });
178
- i++;
179
- } else {
180
- throw new Error(`unexpected "${c}" in the aggregate expression "${source}"`);
181
- }
182
- }
183
- }
184
- tokens.push({ t: "end" });
185
- return tokens;
186
- }
187
-
188
- type Row = Record<string, unknown>;
189
- type Expr = (row: Row) => Val;
190
-
191
- /** Compiles an `@aggregate(arg:)` expression to a function of a source row. */
192
- export function compileExpression(
193
- source: string,
194
- column: (field: string) => string,
195
- fieldIsInteger: (field: string) => boolean,
196
- ): Expr {
197
- const tokens = tokenize(source);
198
- let pos = 0;
199
- const peek = () => tokens[pos];
200
- const next = () => tokens[pos++];
201
- const isOp = (v: string) => peek().t === "op" && (peek() as { v: string }).v === v;
202
- const isKw = (v: string) => peek().t === "id" && (peek() as { v: string }).v.toLowerCase() === v;
203
- const fail = (what: string): never => {
204
- throw new Error(`cannot read the aggregate expression "${source}": ${what}`);
205
- };
206
- const expectOp = (v: string) => {
207
- if (!isOp(v)) fail(`expected "${v}"`);
208
- pos++;
209
- };
210
- const expectKw = (v: string) => {
211
- if (!isKw(v)) fail(`expected ${v}`);
212
- pos++;
213
- };
214
-
215
- const num = (value: Val): Num | null => {
216
- if (value === null) return null;
217
- if (typeof value === "object") return value;
218
- if (typeof value === "boolean") return { v: new Big(value ? 1 : 0), int: true };
219
- return toNum(value);
220
- };
221
- const truthy = (value: Val): boolean | null => (value === null ? null : Boolean(typeof value === "object" ? !value.v.isZero() : value));
222
- const binary = (op: string, a: Val, b: Val): Val => {
223
- const x = num(a);
224
- const y = num(b);
225
- if (x === null || y === null) return null;
226
- const int = x.int && y.int;
227
- switch (op) {
228
- case "+":
229
- return { v: x.v.plus(y.v), int };
230
- case "-":
231
- return { v: x.v.minus(y.v), int };
232
- case "*":
233
- return { v: x.v.times(y.v), int };
234
- case "/":
235
- if (y.v.isZero()) throw new Error("division by zero in an aggregate expression");
236
- return int
237
- ? { v: x.v.div(y.v).integerValue(BigNumber.ROUND_DOWN), int }
238
- : { v: x.v.div(y.v), int };
239
- case "%":
240
- if (y.v.isZero()) throw new Error("division by zero in an aggregate expression");
241
- return { v: x.v.mod(y.v), int };
242
- case "^":
243
- return { v: x.v.pow(y.v.toNumber()), int: false };
244
- default:
245
- return fail(`operator ${op}`);
246
- }
247
- };
248
- const compare = (op: string, a: Val, b: Val): Val => {
249
- if (a === null || b === null) return null;
250
- const x = typeof a === "object" || typeof b === "object" ? num(a) : null;
251
- const y = x ? num(b) : null;
252
- let c: number;
253
- if (x && y) c = x.v.comparedTo(y.v) ?? 0;
254
- else c = String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
255
- switch (op) {
256
- case "=":
257
- return c === 0;
258
- case "!=":
259
- return c !== 0;
260
- case "<":
261
- return c < 0;
262
- case "<=":
263
- return c <= 0;
264
- case ">":
265
- return c > 0;
266
- default:
267
- return c >= 0;
268
- }
269
- };
270
-
271
- // Precedence, lowest first: or, and, not, comparison / is, + -, * / %, ^, unary -.
272
- const parseOr = (): Expr => {
273
- let left = parseAnd();
274
- while (isKw("or")) {
275
- pos++;
276
- const l = left;
277
- const r = parseAnd();
278
- left = (row) => {
279
- const a = truthy(l(row));
280
- const b = truthy(r(row));
281
- if (a === true || b === true) return true;
282
- return a === null || b === null ? null : false;
283
- };
284
- }
285
- return left;
286
- };
287
- const parseAnd = (): Expr => {
288
- let left = parseNot();
289
- while (isKw("and")) {
290
- pos++;
291
- const l = left;
292
- const r = parseNot();
293
- left = (row) => {
294
- const a = truthy(l(row));
295
- const b = truthy(r(row));
296
- if (a === false || b === false) return false;
297
- return a === null || b === null ? null : true;
298
- };
299
- }
300
- return left;
301
- };
302
- const parseNot = (): Expr => {
303
- if (isKw("not")) {
304
- pos++;
305
- const inner = parseNot();
306
- return (row) => {
307
- const v = truthy(inner(row));
308
- return v === null ? null : !v;
309
- };
310
- }
311
- return parseComparison();
312
- };
313
- const parseComparison = (): Expr => {
314
- let left = parseAdditive();
315
- for (;;) {
316
- const t = peek();
317
- if (t.t === "op" && ["=", "!=", "<", "<=", ">", ">="].includes(t.v)) {
318
- pos++;
319
- const op = t.v;
320
- const l = left;
321
- const r = parseAdditive();
322
- left = (row) => compare(op, l(row), r(row));
323
- } else if (t.t === "op" && t.v === "<->") {
324
- pos++;
325
- const l = left;
326
- const r = parseAdditive();
327
- left = (row) => {
328
- const x = num(l(row));
329
- const y = num(r(row));
330
- return x && y ? { v: x.v.minus(y.v).abs(), int: x.int && y.int } : null;
331
- };
332
- } else if (isKw("is")) {
333
- pos++;
334
- let negate = false;
335
- if (isKw("not")) {
336
- pos++;
337
- negate = true;
338
- }
339
- const l = left;
340
- if (isKw("distinct")) {
341
- pos++;
342
- expectKw("from");
343
- const r = parseAdditive();
344
- left = (row) => {
345
- const a = l(row);
346
- const b = r(row);
347
- const same = a === null || b === null ? a === b : compare("=", a, b) === true;
348
- return negate ? same : !same;
349
- };
350
- } else if (isKw("null")) {
351
- pos++;
352
- left = (row) => (l(row) === null) !== negate;
353
- } else if (isKw("true") || isKw("false")) {
354
- const want = isKw("true");
355
- pos++;
356
- left = (row) => (truthy(l(row)) === want) !== negate;
357
- } else {
358
- fail("expected null, true, false or distinct from after is");
359
- }
360
- } else {
361
- return left;
362
- }
363
- }
364
- };
365
- const parseAdditive = (): Expr => {
366
- let left = parseMultiplicative();
367
- while (isOp("+") || isOp("-")) {
368
- const op = (next() as { v: string }).v;
369
- const l = left;
370
- const r = parseMultiplicative();
371
- left = (row) => binary(op, l(row), r(row));
372
- }
373
- return left;
374
- };
375
- const parseMultiplicative = (): Expr => {
376
- let left = parsePower();
377
- while (isOp("*") || isOp("/") || isOp("%")) {
378
- const op = (next() as { v: string }).v;
379
- const l = left;
380
- const r = parsePower();
381
- left = (row) => binary(op, l(row), r(row));
382
- }
383
- return left;
384
- };
385
- const parsePower = (): Expr => {
386
- const base = parseUnary();
387
- if (isOp("^")) {
388
- pos++;
389
- const exponent = parsePower();
390
- return (row) => binary("^", base(row), exponent(row));
391
- }
392
- return base;
393
- };
394
- const parseUnary = (): Expr => {
395
- if (isOp("-")) {
396
- pos++;
397
- const inner = parseUnary();
398
- return (row) => {
399
- const x = num(inner(row));
400
- return x ? { v: x.v.negated(), int: x.int } : null;
401
- };
402
- }
403
- if (isOp("+")) {
404
- pos++;
405
- return parseUnary();
406
- }
407
- return parsePrimary();
408
- };
409
- const args = (): Expr[] => {
410
- expectOp("(");
411
- const list: Expr[] = [];
412
- if (!isOp(")")) {
413
- list.push(parseOr());
414
- while (isOp(",")) {
415
- pos++;
416
- list.push(parseOr());
417
- }
418
- }
419
- expectOp(")");
420
- return list;
421
- };
422
- const parsePrimary = (): Expr => {
423
- const t = next();
424
- if (t.t === "num") {
425
- const literal: Num = { v: new Big(t.v), int: /^\d+$/.test(t.v) };
426
- return () => literal;
427
- }
428
- if (t.t === "str") return () => t.v;
429
- if (t.t === "op" && t.v === "(") {
430
- const inner = parseOr();
431
- expectOp(")");
432
- return inner;
433
- }
434
- if (t.t !== "id") return fail("unexpected end");
435
- const word = t.v.toLowerCase();
436
- if (word === "null") return () => null;
437
- if (word === "true") return () => true;
438
- if (word === "false") return () => false;
439
- if (word === "case") {
440
- const subject = isKw("when") ? null : parseOr();
441
- const arms: [Expr, Expr][] = [];
442
- while (isKw("when")) {
443
- pos++;
444
- const cond = parseOr();
445
- expectKw("then");
446
- arms.push([cond, parseOr()]);
447
- }
448
- let otherwise: Expr = () => null;
449
- if (isKw("else")) {
450
- pos++;
451
- otherwise = parseOr();
452
- }
453
- expectKw("end");
454
- return (row) => {
455
- const s = subject ? subject(row) : true;
456
- for (const [cond, result] of arms) {
457
- const c = cond(row);
458
- if (subject ? compare("=", s, c) === true : truthy(c) === true) return result(row);
459
- }
460
- return otherwise(row);
461
- };
462
- }
463
- if (isOp("(") && !KEYWORDS.has(word)) {
464
- const list = args();
465
- const arg = (row: Row, n: number) => num(list[n](row));
466
- const unary = (f: (x: BigNumber) => BigNumber, keepInt = false): Expr => (row) => {
467
- const x = arg(row, 0);
468
- return x ? { v: f(x.v), int: keepInt && x.int } : null;
469
- };
470
- switch (word) {
471
- case "abs":
472
- return unary((x) => x.abs(), true);
473
- case "ceil":
474
- case "ceiling":
475
- return unary((x) => x.integerValue(BigNumber.ROUND_CEIL), false);
476
- case "floor":
477
- return unary((x) => x.integerValue(BigNumber.ROUND_FLOOR), false);
478
- case "sign":
479
- return unary((x) => new Big(x.isZero() ? 0 : x.isNegative() ? -1 : 1), true);
480
- case "div":
481
- return (row) => {
482
- const x = arg(row, 0);
483
- const y = arg(row, 1);
484
- return x && y ? { v: x.v.div(y.v).integerValue(BigNumber.ROUND_DOWN), int: false } : null;
485
- };
486
- case "mod":
487
- return (row) => binary("%", list[0](row), list[1](row));
488
- case "power":
489
- return (row) => binary("^", list[0](row), list[1](row));
490
- case "gcd":
491
- case "lcm":
492
- return (row) => {
493
- const x = arg(row, 0);
494
- const y = arg(row, 1);
495
- if (!x || !y) return null;
496
- let a = x.v.abs();
497
- let b = y.v.abs();
498
- while (!b.isZero()) [a, b] = [b, a.mod(b)];
499
- return word === "gcd"
500
- ? { v: a, int: true }
501
- : { v: a.isZero() ? a : x.v.times(y.v).abs().div(a), int: true };
502
- };
503
- case "coalesce":
504
- return (row) => {
505
- for (const item of list) {
506
- const v = item(row);
507
- if (v !== null) return v;
508
- }
509
- return null;
510
- };
511
- case "nullif":
512
- return (row) => {
513
- const a = list[0](row);
514
- return compare("=", a, list[1](row)) === true ? null : a;
515
- };
516
- case "greatest":
517
- case "least":
518
- return (row) => {
519
- let best: Num | null = null;
520
- for (const item of list) {
521
- const v = num(item(row));
522
- if (v === null) continue;
523
- if (best === null || (word === "greatest" ? v.v.gt(best.v) : v.v.lt(best.v))) best = v;
524
- }
525
- return best;
526
- };
527
- default:
528
- return fail(`the function ${t.v}`);
529
- }
530
- }
531
- // A source field.
532
- const field = t.v;
533
- const col = column(field);
534
- const isInt = fieldIsInteger(field);
535
- return (row) => {
536
- const raw = row[col];
537
- if (typeof raw === "boolean") return raw;
538
- if (typeof raw === "string" && !/^-?\d/.test(raw)) return raw;
539
- return toNum(raw, isInt);
540
- };
541
- };
542
-
543
- const expr = parseOr();
544
- if (peek().t !== "end") fail(`unexpected input after the expression`);
545
- return expr;
546
- }
547
-
548
- // ---------------------------------------------------------------------------
549
- // Rolling a bucket up
550
- // ---------------------------------------------------------------------------
551
-
552
- export type SourceFields = {
553
- /** The declared scalar of each source field (`Int`, `BigDecimal`, ...). */
554
- kinds: Record<string, string>;
555
- /** Relation fields, whose column is `<field>_id`. */
556
- refs: Set<string>;
557
- };
558
-
559
- export function columnOf(fields: SourceFields, field: string): string {
560
- return fields.refs.has(field) ? `${field}_id` : field;
561
- }
562
-
563
- /** Compiled `arg`s of one aggregation, reusable across buckets. */
564
- export type CompiledAggregation = {
565
- def: AggregationDef;
566
- args: (Expr | null)[];
567
- };
568
-
569
- export function compileAggregation(def: AggregationDef, fields: SourceFields): CompiledAggregation {
570
- const args = def.aggregates.map((aggregate): Expr | null => {
571
- if (aggregate.arg === undefined) return null;
572
- // A String, Bytes or reference `first`/`last` names a source field of its
573
- // type (graph-node's `check_ident`): its value is taken as stored.
574
- if (isPicked(aggregate.kind)) {
575
- const column = columnOf(fields, aggregate.arg.trim().replace(/^"(.*)"$/, "$1"));
576
- return (row) => (row[column] ?? null) as Val;
577
- }
578
- return compileExpression(
579
- aggregate.arg,
580
- (field) => columnOf(fields, field),
581
- (field) => fields.kinds[field] === "Int" || fields.kinds[field] === "Int8",
582
- );
583
- });
584
- return { def, args };
585
- }
586
-
587
- /** The cumulative state kept for a group: the last row's values for each cumulative aggregate. */
588
- export type PrevState = Record<string, string | null>;
589
-
590
- export type BucketRow = {
591
- id: bigint;
592
- dimensions: Record<string, unknown>;
593
- values: Record<string, unknown>;
594
- /** The cumulative values to store as the group's new `prev`. */
595
- next: PrevState;
596
- dimKey: string;
597
- };
598
-
599
- function dimKeyOf(value: unknown): unknown {
600
- if (value instanceof Uint8Array) return `0x${Buffer.from(value).toString("hex")}`;
601
- if (typeof value === "bigint") return value.toString();
602
- if (value instanceof Date) return value.getTime();
603
- return value ?? null;
604
- }
605
-
606
- function idOf(row: Row): bigint {
607
- return BigInt(String(row.id));
608
- }
609
-
610
- /**
611
- * Aggregates the rows of one bucket (`select_bucket`), grouped by the
612
- * dimensions, and folds in each group's previous cumulative values
613
- * (`select_combined`). `prev` answers the lookup of a group's last stored
614
- * cumulative state by its key.
615
- */
616
- export function rollupBucket(
617
- compiled: CompiledAggregation,
618
- rows: Row[],
619
- prev: (dimKey: string) => PrevState | null,
620
- ): BucketRow[] {
621
- const { def, args } = compiled;
622
- const groups = new Map<string, Row[]>();
623
- for (const row of rows) {
624
- const key = JSON.stringify(def.dimensions.map((d) => dimKeyOf(row[d.column])));
625
- const group = groups.get(key);
626
- if (group) group.push(row);
627
- else groups.set(key, [row]);
628
- }
629
-
630
- const out: BucketRow[] = [];
631
- for (const [dimKey, group] of groups) {
632
- // first/last order by id, which graph-node sets in insertion order.
633
- group.sort((a, b) => {
634
- const x = idOf(a);
635
- const y = idOf(b);
636
- return x < y ? -1 : x > y ? 1 : 0;
637
- });
638
- const previous = def.aggregates.some((a) => a.cumulative) ? prev(dimKey) : null;
639
- const values: Record<string, unknown> = {};
640
- const next: PrevState = {};
641
- def.aggregates.forEach((aggregate, index) => {
642
- const arg = args[index];
643
- const evaluated: Val[] = aggregate.func === "count" ? [] : group.map((row) => arg!(row));
644
- if (isPicked(aggregate.kind)) {
645
- // arg_min/arg_max by id over the non-null values; a cumulative one
646
- // keeps the earlier bucket's for `first`, takes this one's for `last`.
647
- const present = evaluated.filter((v) => v !== null && v !== undefined) as unknown[];
648
- const bucket = present.length === 0 ? null : aggregate.func === "first" ? present[0] : present[present.length - 1];
649
- let value: unknown = bucket;
650
- if (aggregate.cumulative) {
651
- const before = previous?.[aggregate.field];
652
- const p = before === undefined || before === null ? null : before;
653
- if (p !== null && (aggregate.func === "first" || bucket === null)) {
654
- value = aggregate.kind === "Bytes" ? new Uint8Array(Buffer.from(p.slice(2), "hex")) : p;
655
- }
656
- next[aggregate.field] = value === null ? null : pickedText(value);
657
- }
658
- values[aggregate.field] = value;
659
- return;
660
- }
661
- const nums = evaluated.map((v) => (v === null ? null : num_(v))).filter((n): n is Num => n !== null);
662
- let bucket: Num | null;
663
- switch (aggregate.func) {
664
- case "count":
665
- bucket = { v: new Big(group.length), int: true };
666
- break;
667
- case "sum":
668
- bucket = nums.length === 0 ? null : { v: BigNumber.sum.apply(null, nums.map((n) => n.v) as any), int: nums.every((n) => n.int) };
669
- break;
670
- case "min":
671
- bucket = nums.length === 0 ? null : nums.reduce((a, b) => (b.v.lt(a.v) ? b : a));
672
- break;
673
- case "max":
674
- bucket = nums.length === 0 ? null : nums.reduce((a, b) => (b.v.gt(a.v) ? b : a));
675
- break;
676
- case "first":
677
- bucket = nums[0] ?? null;
678
- break;
679
- default:
680
- bucket = nums.length === 0 ? null : nums[nums.length - 1];
681
- }
682
- let value = bucket;
683
- if (aggregate.cumulative) {
684
- const before = previous?.[aggregate.field];
685
- const p: Num | null = before === undefined || before === null ? null : { v: new Big(before), int: false };
686
- if (p !== null) {
687
- switch (aggregate.func) {
688
- case "count":
689
- case "sum":
690
- value = bucket === null ? p : { v: p.v.plus(bucket.v), int: p.int && bucket.int };
691
- break;
692
- case "min":
693
- value = bucket === null || p.v.lte(bucket.v) ? p : bucket;
694
- break;
695
- case "max":
696
- value = bucket === null || p.v.gte(bucket.v) ? p : bucket;
697
- break;
698
- case "first":
699
- value = p;
700
- break;
701
- default:
702
- value = bucket ?? p;
703
- }
704
- }
705
- next[aggregate.field] = value === null ? null : value.v.toFixed();
706
- }
707
- values[aggregate.field] = value;
708
- });
709
- const dimensions: Record<string, unknown> = {};
710
- for (const d of def.dimensions) dimensions[d.column] = group[0][d.column] ?? null;
711
- out.push({
712
- id: group.reduce((max, row) => (idOf(row) > max ? idOf(row) : max), idOf(group[0])),
713
- dimensions,
714
- values,
715
- next,
716
- dimKey,
717
- });
718
- }
719
- return out;
720
- }
721
-
722
- function num_(v: Val): Num | null {
723
- if (v === null) return null;
724
- if (typeof v === "object") return v;
725
- if (typeof v === "boolean") return { v: new Big(v ? 1 : 0), int: true };
726
- return toNum(v);
727
- }
728
-
729
- /** A rolled-up value as the column holds it. */
730
- export function storedValue(value: unknown, kind: Aggregate["kind"]): unknown {
731
- if (value === null || value === undefined) return null;
732
- if (isPicked(kind)) return value;
733
- const num = value as Num;
734
- switch (kind) {
735
- case "Int": {
736
- const n = num.v.integerValue(BigNumber.ROUND_HALF_UP).toNumber();
737
- return n;
738
- }
739
- case "Int8":
740
- case "BigInt":
741
- return BigInt(num.v.integerValue(BigNumber.ROUND_HALF_UP).toFixed(0));
742
- default:
743
- return new BigNumber(num.v);
744
- }
745
- }