envio 3.7.0 → 3.9.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/README.md +2 -2
- package/index.d.ts +399 -156
- package/package.json +6 -6
- package/src/AddressRows.res +121 -0
- package/src/AddressRows.res.mjs +143 -0
- package/src/Batch.res +2 -0
- package/src/Batch.res.mjs +2 -1
- package/src/ChainState.res +82 -68
- package/src/ChainState.res.mjs +81 -65
- package/src/ChainState.resi +14 -9
- package/src/Config.res +41 -91
- package/src/Config.res.mjs +34 -68
- package/src/ContractMapping.res +66 -0
- package/src/ContractMapping.res.mjs +73 -0
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +4 -0
- package/src/Envio.res +54 -76
- package/src/EventConfigBuilder.res +157 -30
- package/src/EventConfigBuilder.res.mjs +118 -24
- package/src/FetchState.res +59 -67
- package/src/FetchState.res.mjs +34 -44
- package/src/HandlerRegister.res +20 -11
- package/src/HandlerRegister.res.mjs +22 -5
- package/src/Hasura.res +30 -22
- package/src/Hasura.res.mjs +11 -5
- package/src/InMemoryStore.res +18 -36
- package/src/InMemoryStore.res.mjs +12 -26
- package/src/IndexerState.res +21 -1
- package/src/IndexerState.res.mjs +9 -3
- package/src/IndexerState.resi +1 -0
- package/src/Internal.res +33 -13
- package/src/Internal.res.mjs +12 -16
- package/src/Main.res +27 -20
- package/src/Main.res.mjs +19 -14
- package/src/MemoryStorage.res +78 -73
- package/src/MemoryStorage.res.mjs +66 -81
- package/src/Metrics.res +15 -1
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +34 -18
- package/src/Persistence.res.mjs +8 -5
- package/src/PgStorage.res +189 -66
- package/src/PgStorage.res.mjs +93 -59
- package/src/Rollback.res +14 -10
- package/src/Rollback.res.mjs +4 -2
- package/src/SimulateItems.res +254 -9
- package/src/SimulateItems.res.mjs +214 -47
- package/src/TestIndexer.res +119 -121
- package/src/TestIndexer.res.mjs +95 -100
- package/src/Utils.res +7 -0
- package/src/Utils.res.mjs +9 -2
- package/src/Writing.res +2 -0
- package/src/Writing.res.mjs +2 -1
- package/src/bindings/ClickHouse.res +39 -6
- package/src/bindings/ClickHouse.res.mjs +29 -4
- package/src/bindings/NodeJs.res +9 -0
- package/src/bindings/NodeJs.res.mjs +8 -1
- package/src/bindings/Postgres.res +9 -0
- package/src/bindings/Postgres.res.mjs +3 -0
- package/src/db/EntityHistory.res +12 -18
- package/src/db/EntityHistory.res.mjs +3 -14
- package/src/db/InternalTable.res +182 -65
- package/src/db/InternalTable.res.mjs +183 -73
- package/src/db/Table.res +24 -0
- package/src/db/Table.res.mjs +20 -1
- package/src/sources/AddressSet.res +0 -22
- package/src/sources/AddressStore.res +48 -88
- package/src/sources/AddressStore.res.mjs +11 -22
- package/src/sources/EvmHyperSyncSource.res +3 -2
- package/src/sources/SimulateSource.res +8 -4
- package/src/sources/SimulateSource.res.mjs +7 -3
- package/src/sources/Svm.res +34 -7
- package/src/sources/Svm.res.mjs +32 -4
- package/src/sources/SvmHyperSyncClient.res +24 -30
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +88 -36
- package/src/sources/SvmHyperSyncSource.res.mjs +71 -39
- package/src/sources/TransactionStore.res +38 -0
- package/src/sources/TransactionStore.res.mjs +5 -0
- package/svm.schema.json +37 -82
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -9,13 +9,14 @@ import * as Logging from "./Logging.res.mjs";
|
|
|
9
9
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
10
10
|
import * as EntityId from "./EntityId.res.mjs";
|
|
11
11
|
import * as Internal from "./Internal.res.mjs";
|
|
12
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
12
13
|
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
14
|
+
import * as AddressRows from "./AddressRows.res.mjs";
|
|
13
15
|
import * as IndexerLoop from "./IndexerLoop.res.mjs";
|
|
14
16
|
import * as Persistence from "./Persistence.res.mjs";
|
|
15
17
|
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
16
18
|
import * as EntityFilter from "./db/EntityFilter.res.mjs";
|
|
17
19
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
18
|
-
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
19
20
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
20
21
|
import * as HandlerLoader from "./HandlerLoader.res.mjs";
|
|
21
22
|
import * as InternalTable from "./db/InternalTable.res.mjs";
|
|
@@ -24,37 +25,17 @@ import * as SimulateItems from "./SimulateItems.res.mjs";
|
|
|
24
25
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
25
26
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
26
27
|
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
28
|
+
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
27
29
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
28
30
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
29
31
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
30
32
|
|
|
31
|
-
function
|
|
32
|
-
return
|
|
33
|
-
address: Config.EnvioAddresses.getAddress(dc),
|
|
34
|
-
contractName: dc.contract_name,
|
|
35
|
-
registrationBlock: dc.registration_block
|
|
36
|
-
};
|
|
33
|
+
function addressRowsByChain(state) {
|
|
34
|
+
return AddressRows.Table.groupByChain(state.addresses);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
let dcDict = state.entities[Config.EnvioAddresses.name];
|
|
42
|
-
if (dcDict !== undefined) {
|
|
43
|
-
Object.values(dcDict).forEach(entity => {
|
|
44
|
-
let chainIdStr = ChainId.toString(entity.chain_id);
|
|
45
|
-
let arr = byChain[chainIdStr];
|
|
46
|
-
let contracts;
|
|
47
|
-
if (arr !== undefined) {
|
|
48
|
-
contracts = arr;
|
|
49
|
-
} else {
|
|
50
|
-
let arr$1 = [];
|
|
51
|
-
byChain[chainIdStr] = arr$1;
|
|
52
|
-
contracts = arr$1;
|
|
53
|
-
}
|
|
54
|
-
contracts.push(toIndexingAddress(entity));
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return byChain;
|
|
37
|
+
function renderRows(rows, config) {
|
|
38
|
+
return AddressRows.render(rows, config.ecosystem.name, !config.lowercaseAddresses);
|
|
58
39
|
}
|
|
59
40
|
|
|
60
41
|
function rowKey(scope, entityId) {
|
|
@@ -88,8 +69,18 @@ function handleLoad(state, tableName, filter) {
|
|
|
88
69
|
}
|
|
89
70
|
}
|
|
90
71
|
|
|
91
|
-
function handleWriteBatch(state, updatedEntities, checkpointIds, checkpointChainIds, checkpointBlockNumbers, checkpointEventsProcessed) {
|
|
72
|
+
function handleWriteBatch(state, config, updatedEntities, registeredAddresses, checkpointIds, checkpointChainIds, checkpointBlockNumbers, checkpointEventsProcessed) {
|
|
92
73
|
let changesByCheckpoint = {};
|
|
74
|
+
let addressesByCheckpoint = {};
|
|
75
|
+
let rendered = renderRows(registeredAddresses.map(param => param.row), config);
|
|
76
|
+
registeredAddresses.forEach((param, idx) => {
|
|
77
|
+
let row = param.row;
|
|
78
|
+
AddressRows.Table.insert(state.addresses, row);
|
|
79
|
+
Utils.Dict.push(addressesByCheckpoint, param.checkpointId.toString(), {
|
|
80
|
+
address: rendered[idx],
|
|
81
|
+
contract: ContractMapping.nameOfOrThrow(state.contractMapping, row.contractId)
|
|
82
|
+
});
|
|
83
|
+
});
|
|
93
84
|
updatedEntities.forEach(param => {
|
|
94
85
|
let scope = param.scope;
|
|
95
86
|
let entityConfig = param.entityConfig;
|
|
@@ -151,40 +142,33 @@ function handleWriteBatch(state, updatedEntities, checkpointIds, checkpointChain
|
|
|
151
142
|
change["chainId"] = checkpointChainIds[i];
|
|
152
143
|
change["eventsProcessed"] = checkpointEventsProcessed[i];
|
|
153
144
|
let checkpointKey = checkpointId.toString();
|
|
145
|
+
let rendered$1 = addressesByCheckpoint[checkpointKey];
|
|
146
|
+
if (rendered$1 !== undefined) {
|
|
147
|
+
let addressesObj = {};
|
|
148
|
+
addressesObj["sets"] = rendered$1;
|
|
149
|
+
change["addresses"] = addressesObj;
|
|
150
|
+
}
|
|
154
151
|
let entityChanges = changesByCheckpoint[checkpointKey];
|
|
155
152
|
if (entityChanges !== undefined) {
|
|
156
153
|
Object.entries(entityChanges).forEach(param => {
|
|
157
154
|
let match = param[1];
|
|
158
155
|
let deleted = match.deleted;
|
|
159
156
|
let sets = match.sets;
|
|
160
|
-
let
|
|
161
|
-
if (entityName === Config.EnvioAddresses.name) {
|
|
162
|
-
let entityObj = {};
|
|
163
|
-
if (sets.length !== 0) {
|
|
164
|
-
let simplifiedSets = sets.map(entity => ({
|
|
165
|
-
address: Config.EnvioAddresses.getAddress(entity),
|
|
166
|
-
contract: entity.contract_name
|
|
167
|
-
}));
|
|
168
|
-
entityObj["sets"] = simplifiedSets;
|
|
169
|
-
}
|
|
170
|
-
change["addresses"] = entityObj;
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
let entityObj$1 = {};
|
|
157
|
+
let entityObj = {};
|
|
174
158
|
if (sets.length !== 0) {
|
|
175
|
-
entityObj
|
|
159
|
+
entityObj["sets"] = sets;
|
|
176
160
|
}
|
|
177
161
|
if (deleted.length !== 0) {
|
|
178
|
-
entityObj
|
|
162
|
+
entityObj["deleted"] = deleted;
|
|
179
163
|
}
|
|
180
|
-
change[Utils.$$String.capitalize(
|
|
164
|
+
change[Utils.$$String.capitalize(param[0])] = entityObj;
|
|
181
165
|
});
|
|
182
166
|
}
|
|
183
167
|
state.processChanges.push(change);
|
|
184
168
|
}
|
|
185
169
|
}
|
|
186
170
|
|
|
187
|
-
function makeInitialState(config, processConfigChains,
|
|
171
|
+
function makeInitialState(config, processConfigChains, addressRowsByChain, contractMapping) {
|
|
188
172
|
let chainKeys = Object.keys(processConfigChains);
|
|
189
173
|
let chains = chainKeys.map(chainIdStr => {
|
|
190
174
|
let chain = ChainId.normalizeOrThrow(chainIdStr);
|
|
@@ -192,7 +176,7 @@ function makeInitialState(config, processConfigChains, indexingAddressesByChain)
|
|
|
192
176
|
Stdlib_JsError.throwWithMessage(`Chain ` + chainIdStr + ` is not configured in config.yaml`);
|
|
193
177
|
}
|
|
194
178
|
let processChainConfig = processConfigChains[chainIdStr];
|
|
195
|
-
let
|
|
179
|
+
let addressRows = Stdlib_Option.getOr(addressRowsByChain[chainIdStr], AddressRows.emptySeedRows());
|
|
196
180
|
return {
|
|
197
181
|
id: chain,
|
|
198
182
|
startBlock: processChainConfig.startBlock,
|
|
@@ -202,17 +186,18 @@ function makeInitialState(config, processConfigChains, indexingAddressesByChain)
|
|
|
202
186
|
numEventsProcessed: 0,
|
|
203
187
|
firstEventBlockNumber: undefined,
|
|
204
188
|
timestampCaughtUpToHeadOrEndblock: undefined,
|
|
205
|
-
|
|
189
|
+
addressRows: addressRows,
|
|
206
190
|
sourceBlockNumber: Stdlib_Option.getOr(processChainConfig.endBlock, 0)
|
|
207
191
|
};
|
|
208
192
|
});
|
|
209
193
|
return {
|
|
210
194
|
cleanRun: true,
|
|
195
|
+
contractMapping: contractMapping,
|
|
196
|
+
envioInfo: {},
|
|
211
197
|
cache: {},
|
|
212
198
|
chains: chains,
|
|
213
199
|
checkpointId: InternalTable.Checkpoints.initialCheckpointId,
|
|
214
|
-
reorgCheckpoints: []
|
|
215
|
-
envioInfo: Config.stripSensitiveData(Config.getPublicConfigJson())
|
|
200
|
+
reorgCheckpoints: []
|
|
216
201
|
};
|
|
217
202
|
}
|
|
218
203
|
|
|
@@ -230,22 +215,44 @@ function getSimulateEndBlock(simulateItems, config, startBlock) {
|
|
|
230
215
|
let maxBlock = {
|
|
231
216
|
contents: startBlock
|
|
232
217
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
218
|
+
let match = config.ecosystem.name;
|
|
219
|
+
let blockNumberKey;
|
|
220
|
+
switch (match) {
|
|
221
|
+
case "evm" :
|
|
222
|
+
case "fuel" :
|
|
223
|
+
blockNumberKey = config.ecosystem.blockNumberName;
|
|
224
|
+
break;
|
|
225
|
+
case "svm" :
|
|
226
|
+
blockNumberKey = "slot";
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
let bump = n => {
|
|
245
230
|
if (n !== undefined && n > maxBlock.contents) {
|
|
246
231
|
maxBlock.contents = n;
|
|
247
232
|
return;
|
|
248
233
|
}
|
|
234
|
+
};
|
|
235
|
+
let getInt = (d, key) => Stdlib_Option.flatMap(d[key], v => {
|
|
236
|
+
if (v == null) {
|
|
237
|
+
return;
|
|
238
|
+
} else {
|
|
239
|
+
return Primitive_option.some(v);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
simulateItems.forEach(rawJson => {
|
|
243
|
+
let match = config.ecosystem.name;
|
|
244
|
+
switch (match) {
|
|
245
|
+
case "evm" :
|
|
246
|
+
case "fuel" :
|
|
247
|
+
break;
|
|
248
|
+
case "svm" :
|
|
249
|
+
bump(getInt(rawJson, "slot"));
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
let blockJson = rawJson.block;
|
|
253
|
+
if (!(blockJson == null)) {
|
|
254
|
+
return bump(getInt(blockJson, blockNumberKey));
|
|
255
|
+
}
|
|
249
256
|
});
|
|
250
257
|
return maxBlock.contents;
|
|
251
258
|
}
|
|
@@ -377,7 +384,7 @@ function makeInMemoryStorage(state) {
|
|
|
377
384
|
return {
|
|
378
385
|
name: "test-inmemory",
|
|
379
386
|
isInitialized: async () => true,
|
|
380
|
-
initialize: async (param, param$1, param$2, param$3) => Stdlib_JsError.throwWithMessage("TestIndexer: initialize should not be called; the initial state is derived from config."),
|
|
387
|
+
initialize: async (param, param$1, param$2, param$3, param$4) => Stdlib_JsError.throwWithMessage("TestIndexer: initialize should not be called; the initial state is derived from config."),
|
|
381
388
|
resumeInitialState: async () => Stdlib_JsError.throwWithMessage("TestIndexer: resumeInitialState should not be called; the initial state is derived from config."),
|
|
382
389
|
loadOrThrow: async (filter, table) => handleLoad(state, table.tableName, filter),
|
|
383
390
|
ensureQueryIndexes: async (param, param$1) => {},
|
|
@@ -391,7 +398,7 @@ function makeInMemoryStorage(state) {
|
|
|
391
398
|
getRollbackTargetCheckpoint: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
392
399
|
getRollbackProgressDiff: async param => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
393
400
|
getRollbackData: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
394
|
-
writeBatch: async (batch, param, param$1, param$2, param$3,
|
|
401
|
+
writeBatch: async (batch, param, param$1, config, param$2, param$3, updatedEntities, registeredAddresses, param$4, param$5) => handleWriteBatch(state, config, updatedEntities, registeredAddresses, batch.checkpointIds, batch.checkpointChainIds, batch.checkpointBlockNumbers, batch.checkpointEventsProcessed),
|
|
395
402
|
close: async () => {}
|
|
396
403
|
};
|
|
397
404
|
}
|
|
@@ -424,36 +431,25 @@ function getRegistrations(config) {
|
|
|
424
431
|
|
|
425
432
|
function createTestIndexer() {
|
|
426
433
|
let config = Config.load();
|
|
427
|
-
let allEntities = config.
|
|
434
|
+
let allEntities = config.userEntities;
|
|
428
435
|
let entities = {};
|
|
429
436
|
let entityConfigs = {};
|
|
430
437
|
allEntities.forEach(entityConfig => {
|
|
431
438
|
entities[entityConfig.name] = {};
|
|
432
439
|
entityConfigs[entityConfig.name] = entityConfig;
|
|
433
440
|
});
|
|
434
|
-
let
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let entity_chain_id = chainConfig.id;
|
|
440
|
-
let entity_contract_name = contract.name;
|
|
441
|
-
let entity = {
|
|
442
|
-
id: entity_id,
|
|
443
|
-
chain_id: entity_chain_id,
|
|
444
|
-
registration_block: -1,
|
|
445
|
-
registration_log_index: -1,
|
|
446
|
-
contract_name: entity_contract_name
|
|
447
|
-
};
|
|
448
|
-
envioAddressesDict[entity_id] = entity;
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
});
|
|
441
|
+
let chainConfigs = ChainMap.values(config.chainMap);
|
|
442
|
+
let contractMapping = config.contractMapping;
|
|
443
|
+
let ecosystem = config.ecosystem.name;
|
|
444
|
+
let addresses = AddressRows.Table.make();
|
|
445
|
+
ChainState.seedConfigAddresses(addresses, chainConfigs, ecosystem, contractMapping);
|
|
452
446
|
let state = {
|
|
453
447
|
processInProgress: false,
|
|
454
448
|
progressBlockByChain: {},
|
|
455
449
|
entities: entities,
|
|
456
450
|
entityConfigs: entityConfigs,
|
|
451
|
+
addresses: addresses,
|
|
452
|
+
contractMapping: contractMapping,
|
|
457
453
|
processChanges: []
|
|
458
454
|
};
|
|
459
455
|
let storage = makeInMemoryStorage(state);
|
|
@@ -465,16 +461,13 @@ function createTestIndexer() {
|
|
|
465
461
|
}
|
|
466
462
|
let entityOpsDict = {};
|
|
467
463
|
allEntities.forEach(entityConfig => {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
};
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
464
|
+
entityOpsDict[entityConfig.name] = {
|
|
465
|
+
get: makeEntityGet(state, entityConfig),
|
|
466
|
+
getAll: makeEntityGetAll(state, entityConfig),
|
|
467
|
+
getWhere: makeEntityGetWhere(state, entityConfig),
|
|
468
|
+
getOrThrow: makeEntityGetOrThrow(state, entityConfig),
|
|
469
|
+
set: makeEntitySet(state, entityConfig)
|
|
470
|
+
};
|
|
478
471
|
});
|
|
479
472
|
let chainIds = [];
|
|
480
473
|
let chains = Object.create(null);
|
|
@@ -512,11 +505,14 @@ function createTestIndexer() {
|
|
|
512
505
|
if (state.processInProgress) {
|
|
513
506
|
Stdlib_JsError.throwWithMessage(`Cannot access ` + contract.name + `.addresses while indexer.process() is running. ` + "Wait for process() to complete before reading contract addresses.");
|
|
514
507
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
508
|
+
let contractId = ContractMapping.idOfOrThrow(state.contractMapping, contract.name, undefined);
|
|
509
|
+
return renderRows(AddressRows.Table.rows(state.addresses).filter(row => {
|
|
510
|
+
if (row.chainId === chainConfig.id) {
|
|
511
|
+
return row.contractId === contractId;
|
|
512
|
+
} else {
|
|
513
|
+
return false;
|
|
518
514
|
}
|
|
519
|
-
});
|
|
515
|
+
}), config);
|
|
520
516
|
}
|
|
521
517
|
});
|
|
522
518
|
Object.defineProperty(chainObj, contract.name, {
|
|
@@ -586,8 +582,7 @@ function createTestIndexer() {
|
|
|
586
582
|
let chainIdStr = param[0];
|
|
587
583
|
let chains = {};
|
|
588
584
|
chains[chainIdStr] = processChainConfig;
|
|
589
|
-
let
|
|
590
|
-
let initialState = makeInitialState(config, chains, indexingAddressesByChain);
|
|
585
|
+
let initialState = makeInitialState(config, chains, AddressRows.Table.groupByChain(state.addresses), state.contractMapping);
|
|
591
586
|
let exitAfterFirstEventBlock = Stdlib_Option.isNone(processChainConfig.endBlock);
|
|
592
587
|
let resolvedChainDict = {};
|
|
593
588
|
resolvedChainDict["startBlock"] = processChainConfig.startBlock;
|
|
@@ -674,8 +669,8 @@ function createTestIndexer() {
|
|
|
674
669
|
}
|
|
675
670
|
|
|
676
671
|
export {
|
|
677
|
-
|
|
678
|
-
|
|
672
|
+
addressRowsByChain,
|
|
673
|
+
renderRows,
|
|
679
674
|
rowKey,
|
|
680
675
|
readChainId,
|
|
681
676
|
handleLoad,
|
package/src/Utils.res
CHANGED
|
@@ -199,6 +199,13 @@ module Dict = {
|
|
|
199
199
|
}
|
|
200
200
|
`)
|
|
201
201
|
|
|
202
|
+
let clearInPlace: dict<'a> => unit = %raw(`(dict) => {
|
|
203
|
+
for (const key in dict) {
|
|
204
|
+
delete dict[key];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
`)
|
|
208
|
+
|
|
202
209
|
let unsafeDeleteUndefinedFieldsInPlace: 'a => unit = %raw(`(dict) => {
|
|
203
210
|
for (var key in dict) {
|
|
204
211
|
if (dict[key] === undefined) {
|
package/src/Utils.res.mjs
CHANGED
|
@@ -149,6 +149,12 @@ let deleteInPlace = ((dict, key) => {
|
|
|
149
149
|
delete dict[key];
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
+
let clearInPlace = ((dict) => {
|
|
153
|
+
for (const key in dict) {
|
|
154
|
+
delete dict[key];
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
152
158
|
let unsafeDeleteUndefinedFieldsInPlace = ((dict) => {
|
|
153
159
|
for (var key in dict) {
|
|
154
160
|
if (dict[key] === undefined) {
|
|
@@ -175,6 +181,7 @@ let Dict = {
|
|
|
175
181
|
size: size,
|
|
176
182
|
isEmpty: isEmpty,
|
|
177
183
|
deleteInPlace: deleteInPlace,
|
|
184
|
+
clearInPlace: clearInPlace,
|
|
178
185
|
unsafeDeleteUndefinedFieldsInPlace: unsafeDeleteUndefinedFieldsInPlace,
|
|
179
186
|
updateImmutable: updateImmutable,
|
|
180
187
|
shallowCopy: shallowCopy
|
|
@@ -249,7 +256,7 @@ function mergeSorted(f, xs, ys) {
|
|
|
249
256
|
return result;
|
|
250
257
|
}
|
|
251
258
|
|
|
252
|
-
let clearInPlace = ((arr) => {
|
|
259
|
+
let clearInPlace$1 = ((arr) => {
|
|
253
260
|
arr.length = 0
|
|
254
261
|
});
|
|
255
262
|
|
|
@@ -387,7 +394,7 @@ let $$Array$1 = {
|
|
|
387
394
|
unzip: Stdlib_Array.unzip,
|
|
388
395
|
immutableEmpty: immutableEmpty,
|
|
389
396
|
mergeSorted: mergeSorted,
|
|
390
|
-
clearInPlace: clearInPlace,
|
|
397
|
+
clearInPlace: clearInPlace$1,
|
|
391
398
|
setIndexImmutable: setIndexImmutable,
|
|
392
399
|
transposeResults: transposeResults,
|
|
393
400
|
includes: includes,
|
package/src/Writing.res
CHANGED
|
@@ -120,6 +120,7 @@ let runOneWrite = async (state: IndexerState.t) => {
|
|
|
120
120
|
}
|
|
121
121
|
})
|
|
122
122
|
let updatedEffectsCache = snapshotEffects(state, ~cache)
|
|
123
|
+
let registeredAddresses = batch.registeredAddresses
|
|
123
124
|
|
|
124
125
|
let writtenEntityNames = Utils.Set.make()
|
|
125
126
|
updatedEntities->Array.forEach(({entityConfig}) =>
|
|
@@ -145,6 +146,7 @@ let runOneWrite = async (state: IndexerState.t) => {
|
|
|
145
146
|
~config,
|
|
146
147
|
~allEntities=persistence.allEntities,
|
|
147
148
|
~updatedEntities,
|
|
149
|
+
~registeredAddresses,
|
|
148
150
|
~updatedEffectsCache,
|
|
149
151
|
~chainMetaData,
|
|
150
152
|
~onWrite=(~storage, ~timeSeconds) =>
|
package/src/Writing.res.mjs
CHANGED
|
@@ -118,13 +118,14 @@ async function runOneWrite(state) {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
let updatedEffectsCache = snapshotEffects(state, cache);
|
|
121
|
+
let registeredAddresses = batch.registeredAddresses;
|
|
121
122
|
let writtenEntityNames = new Set();
|
|
122
123
|
updatedEntities.forEach(param => {
|
|
123
124
|
writtenEntityNames.add(param.entityConfig.name);
|
|
124
125
|
});
|
|
125
126
|
let pruneTargets = PruneStaleHistory.select(state, writtenEntityNames, Stdlib_Option.isSome(rollback));
|
|
126
127
|
await Promise.all([
|
|
127
|
-
persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
|
|
128
|
+
persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
|
|
128
129
|
PruneStaleHistory.runConcurrent(state, pruneTargets)
|
|
129
130
|
]);
|
|
130
131
|
IndexerState.markCommitted(state, upToCheckpointId);
|
|
@@ -60,6 +60,12 @@ let getClickHouseFieldType = (
|
|
|
60
60
|
}
|
|
61
61
|
| Uint32 => "UInt32"
|
|
62
62
|
| UInt52 => "UInt64"
|
|
63
|
+
// Internal-only column types, never on an entity the sink mirrors.
|
|
64
|
+
| SmallInt
|
|
65
|
+
| Bytea =>
|
|
66
|
+
JsError.throwWithMessage(
|
|
67
|
+
"ClickHouse doesn't support the internal SmallInt and Bytea column types",
|
|
68
|
+
)
|
|
63
69
|
| UInt64 => "UInt64"
|
|
64
70
|
| Serial => "Int32"
|
|
65
71
|
| BigSerial => "Int64"
|
|
@@ -357,6 +363,17 @@ let setUpdatesOrThrow = async (
|
|
|
357
363
|
// The '{cluster}' macro resolves to each node's configured cluster name.
|
|
358
364
|
let onClusterClause = (~onCluster: bool) => onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
359
365
|
|
|
366
|
+
// ReplicatedMergeTree drops an insert whose block hash is already in Keeper, and
|
|
367
|
+
// mutations don't clear those hashes. Crash recovery trims the history tail past
|
|
368
|
+
// the committed Postgres checkpoint with ALTER ... DELETE and then replays it, so
|
|
369
|
+
// an identical replayed block would be discarded while still reporting success —
|
|
370
|
+
// a permanent gap that nothing surfaces. Trim-then-replay is what makes recovery
|
|
371
|
+
// correct here, and the duplicates dedup would have caught are already collapsed
|
|
372
|
+
// by the entity view's `LIMIT 1 BY`. Plain MergeTree goes by
|
|
373
|
+
// non_replicated_deduplication_window (0 by default), so it needs no clause.
|
|
374
|
+
let replicatedTableSettingsClause = (~replicated: bool) =>
|
|
375
|
+
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
376
|
+
|
|
360
377
|
// Strip both engine arguments `(...)` and a trailing `SETTINGS ...` clause to
|
|
361
378
|
// get the bare engine name, e.g. `Replicated('/p','{shard}','{replica}') SETTINGS x=1`
|
|
362
379
|
// and `Replicated SETTINGS x=1` both yield `Replicated`.
|
|
@@ -395,9 +412,9 @@ let makeCreateHistoryTableQuery = (
|
|
|
395
412
|
}
|
|
396
413
|
})
|
|
397
414
|
|
|
398
|
-
let (partitionBy, orderBy, ttl) = switch entityConfig.storage.clickhouseOptions {
|
|
399
|
-
| Some(options) => (options.partitionBy, options.orderBy, options.ttl)
|
|
400
|
-
| None => (None, None, None)
|
|
415
|
+
let (partitionBy, orderBy, ttl, skippingIndexes) = switch entityConfig.storage.clickhouseOptions {
|
|
416
|
+
| Some(options) => (options.partitionBy, options.orderBy, options.ttl, options.skippingIndexes)
|
|
417
|
+
| None => (None, None, None, None)
|
|
401
418
|
}
|
|
402
419
|
|
|
403
420
|
// Schema field name -> ClickHouse column name, so @storage(clickhouse: {...})
|
|
@@ -461,6 +478,22 @@ let makeCreateHistoryTableQuery = (
|
|
|
461
478
|
| None => ""
|
|
462
479
|
}
|
|
463
480
|
|
|
481
|
+
// Data skipping indexes live inside the column list, after the last column.
|
|
482
|
+
// GRANULARITY is omitted when unset, leaving ClickHouse's default of 1.
|
|
483
|
+
let skippingIndexDefinitions = switch skippingIndexes {
|
|
484
|
+
| Some(skippingIndexes) =>
|
|
485
|
+
skippingIndexes
|
|
486
|
+
->Array.map(index => {
|
|
487
|
+
let granularityClause = switch index.granularity {
|
|
488
|
+
| Some(granularity) => ` GRANULARITY ${granularity->Int.toString}`
|
|
489
|
+
| None => ""
|
|
490
|
+
}
|
|
491
|
+
`,\n INDEX \`${index.name}\` ${index.expr->resolveExpressionColumns} TYPE ${index.type_}${granularityClause}`
|
|
492
|
+
})
|
|
493
|
+
->Array.joinUnsafe("")
|
|
494
|
+
| None => ""
|
|
495
|
+
}
|
|
496
|
+
|
|
464
497
|
`CREATE TABLE IF NOT EXISTS ${database}.\`${EntityHistory.historyTableName(
|
|
465
498
|
~entityName=entityConfig.name,
|
|
466
499
|
~entityIndex=entityConfig.index,
|
|
@@ -475,10 +508,10 @@ let makeCreateHistoryTableQuery = (
|
|
|
475
508
|
~fieldType=Enum({config: EntityHistory.RowAction.config->Table.fromGenericEnumConfig}),
|
|
476
509
|
~isNullable=false,
|
|
477
510
|
~isArray=false,
|
|
478
|
-
)}
|
|
511
|
+
)}${skippingIndexDefinitions}
|
|
479
512
|
)
|
|
480
513
|
ENGINE = ${tableEngine}${partitionByClause}
|
|
481
|
-
ORDER BY (${orderByColumns})${ttlClause}`
|
|
514
|
+
ORDER BY (${orderByColumns})${ttlClause}${replicatedTableSettingsClause(~replicated)}`
|
|
482
515
|
}
|
|
483
516
|
|
|
484
517
|
// Generate CREATE TABLE query for checkpoints
|
|
@@ -522,7 +555,7 @@ let makeCreateCheckpointsTableQuery = (
|
|
|
522
555
|
)}
|
|
523
556
|
)
|
|
524
557
|
ENGINE = ${tableEngine}
|
|
525
|
-
ORDER BY (${idField})`
|
|
558
|
+
ORDER BY (${idField})${replicatedTableSettingsClause(~replicated)}`
|
|
526
559
|
}
|
|
527
560
|
|
|
528
561
|
// Generate CREATE VIEW query for entity current state
|
|
@@ -26,6 +26,10 @@ function getClickHouseFieldType(fieldType, isNullable, isArray, chainIdModeOpt)
|
|
|
26
26
|
case "Uint32" :
|
|
27
27
|
baseType = "UInt32";
|
|
28
28
|
break;
|
|
29
|
+
case "SmallInt" :
|
|
30
|
+
case "Bytea" :
|
|
31
|
+
baseType = Stdlib_JsError.throwWithMessage("ClickHouse doesn't support the internal SmallInt and Bytea column types");
|
|
32
|
+
break;
|
|
29
33
|
case "UInt52" :
|
|
30
34
|
case "UInt64" :
|
|
31
35
|
baseType = "UInt64";
|
|
@@ -273,6 +277,14 @@ function onClusterClause(onCluster) {
|
|
|
273
277
|
}
|
|
274
278
|
}
|
|
275
279
|
|
|
280
|
+
function replicatedTableSettingsClause(replicated) {
|
|
281
|
+
if (replicated) {
|
|
282
|
+
return "\nSETTINGS replicated_deduplication_window = 0";
|
|
283
|
+
} else {
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
276
288
|
function databaseEngineName(engineSpec) {
|
|
277
289
|
return engineSpec.trim().split("(")[0].split(" ")[0].trim();
|
|
278
290
|
}
|
|
@@ -295,12 +307,15 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onCl
|
|
|
295
307
|
let match = options !== undefined ? [
|
|
296
308
|
options.partitionBy,
|
|
297
309
|
options.orderBy,
|
|
298
|
-
options.ttl
|
|
310
|
+
options.ttl,
|
|
311
|
+
options.skippingIndexes
|
|
299
312
|
] : [
|
|
313
|
+
undefined,
|
|
300
314
|
undefined,
|
|
301
315
|
undefined,
|
|
302
316
|
undefined
|
|
303
317
|
];
|
|
318
|
+
let skippingIndexes = match[3];
|
|
304
319
|
let ttl = match[2];
|
|
305
320
|
let orderBy = match[1];
|
|
306
321
|
let partitionBy = match[0];
|
|
@@ -336,6 +351,11 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onCl
|
|
|
336
351
|
});
|
|
337
352
|
let partitionByClause = partitionBy !== undefined ? `\nPARTITION BY ` + resolveExpressionColumns(partitionBy) : "";
|
|
338
353
|
let ttlClause = ttl !== undefined ? `\nTTL ` + resolveExpressionColumns(ttl) : "";
|
|
354
|
+
let skippingIndexDefinitions = skippingIndexes !== undefined ? skippingIndexes.map(index => {
|
|
355
|
+
let granularity = index.granularity;
|
|
356
|
+
let granularityClause = granularity !== undefined ? ` GRANULARITY ` + granularity.toString() : "";
|
|
357
|
+
return `,\n INDEX \`` + index.name + `\` ` + resolveExpressionColumns(index.expr) + ` TYPE ` + index.type + granularityClause;
|
|
358
|
+
}).join("") : "";
|
|
339
359
|
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\`` + (
|
|
340
360
|
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
341
361
|
) + ` (
|
|
@@ -344,10 +364,12 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onCl
|
|
|
344
364
|
\`` + EntityHistory.changeFieldName + `\` ` + getClickHouseFieldType({
|
|
345
365
|
type: "Enum",
|
|
346
366
|
config: EntityHistory.RowAction.config
|
|
347
|
-
}, false, false, undefined) + `
|
|
367
|
+
}, false, false, undefined) + skippingIndexDefinitions + `
|
|
348
368
|
)
|
|
349
369
|
ENGINE = ` + tableEngine + partitionByClause + `
|
|
350
|
-
ORDER BY (` + orderByColumns + `)` + ttlClause
|
|
370
|
+
ORDER BY (` + orderByColumns + `)` + ttlClause + (
|
|
371
|
+
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
372
|
+
);
|
|
351
373
|
}
|
|
352
374
|
|
|
353
375
|
function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt, chainIdModeOpt) {
|
|
@@ -365,7 +387,9 @@ function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt,
|
|
|
365
387
|
\`` + "events_processed" + `\` ` + getClickHouseFieldType("UInt64", false, false, undefined) + `
|
|
366
388
|
)
|
|
367
389
|
ENGINE = ` + tableEngine + `
|
|
368
|
-
ORDER BY (` + "id" + `)
|
|
390
|
+
ORDER BY (` + "id" + `)` + (
|
|
391
|
+
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
392
|
+
);
|
|
369
393
|
}
|
|
370
394
|
|
|
371
395
|
function makeCreateViewQuery(entityConfig, database, onClusterOpt) {
|
|
@@ -505,6 +529,7 @@ export {
|
|
|
505
529
|
setCheckpointsOrThrow,
|
|
506
530
|
setUpdatesOrThrow,
|
|
507
531
|
onClusterClause,
|
|
532
|
+
replicatedTableSettingsClause,
|
|
508
533
|
databaseEngineName,
|
|
509
534
|
makeCreateHistoryTableQuery,
|
|
510
535
|
makeCreateCheckpointsTableQuery,
|
package/src/bindings/NodeJs.res
CHANGED
|
@@ -66,6 +66,15 @@ module Process = {
|
|
|
66
66
|
external getActiveResourcesInfo: unit => array<string> = "getActiveResourcesInfo"
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
module Buffer = {
|
|
70
|
+
type t
|
|
71
|
+
@val @scope("Buffer") external concat: array<t> => t = "concat"
|
|
72
|
+
@val @scope("Buffer") external alloc: int => t = "alloc"
|
|
73
|
+
@get external length: t => int = "length"
|
|
74
|
+
@send external toBase64: (t, @as("base64") _) => string = "toString"
|
|
75
|
+
let empty = alloc(0)
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
module V8 = {
|
|
70
79
|
type heapSpaceStatistics = {
|
|
71
80
|
@as("space_name") spaceName: string,
|
|
@@ -18,6 +18,12 @@ let Util$1 = {
|
|
|
18
18
|
|
|
19
19
|
let Process = {};
|
|
20
20
|
|
|
21
|
+
let empty = globalThis.Buffer.alloc(0);
|
|
22
|
+
|
|
23
|
+
let Buffer = {
|
|
24
|
+
empty: empty
|
|
25
|
+
};
|
|
26
|
+
|
|
21
27
|
let V8 = {};
|
|
22
28
|
|
|
23
29
|
let PerfHooks = {};
|
|
@@ -49,6 +55,7 @@ let Fs = {
|
|
|
49
55
|
export {
|
|
50
56
|
Util$1 as Util,
|
|
51
57
|
Process,
|
|
58
|
+
Buffer,
|
|
52
59
|
V8,
|
|
53
60
|
PerfHooks,
|
|
54
61
|
ChildProcess,
|
|
@@ -59,4 +66,4 @@ export {
|
|
|
59
66
|
WorkerThreads,
|
|
60
67
|
Fs,
|
|
61
68
|
}
|
|
62
|
-
/*
|
|
69
|
+
/* empty Not a pure module */
|
|
@@ -99,6 +99,13 @@ external makeSql: (~config: poolConfig) => sql = "default"
|
|
|
99
99
|
// @send @variadic
|
|
100
100
|
// external sql: array<string> => (sql, array<string>) => int = "sql"
|
|
101
101
|
|
|
102
|
+
// postgres.js infers a Buffer array's type from its first element, so a bytea
|
|
103
|
+
// column's array parameter comes out typed `bytea` and the server refuses the
|
|
104
|
+
// cast. Naming the array type explicitly is what makes it bind. 1001 is
|
|
105
|
+
// Postgres' `bytea[]` OID.
|
|
106
|
+
let byteaArrayOid = 1001
|
|
107
|
+
@send external typed: (sql, 'a, int) => unknown = "typed"
|
|
108
|
+
|
|
102
109
|
@send external unsafe: (sql, string) => promise<'a> = "unsafe"
|
|
103
110
|
@send external unpreparedUnsafe: (sql, string, unknown) => promise<'a> = "unsafe"
|
|
104
111
|
@send
|
|
@@ -107,8 +114,10 @@ external preparedUnsafe: (sql, string, unknown, @as(json`{prepare: true}`) _) =>
|
|
|
107
114
|
|
|
108
115
|
@unboxed
|
|
109
116
|
type columnType =
|
|
117
|
+
| @as("SMALLINT") SmallInt
|
|
110
118
|
| @as("INTEGER") Integer
|
|
111
119
|
| @as("BIGINT") BigInt
|
|
120
|
+
| @as("BYTEA") Bytea
|
|
112
121
|
| @as("BOOLEAN") Boolean
|
|
113
122
|
| @as("NUMERIC") Numeric
|
|
114
123
|
| @as("DOUBLE PRECISION") DoublePrecision
|