envio 3.11.0 → 3.12.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/index.d.ts +3 -0
- package/package.json +6 -6
- package/src/Batch.res +111 -78
- package/src/Batch.res.mjs +53 -46
- package/src/BatchProcessing.res +1 -11
- package/src/BatchProcessing.res.mjs +1 -4
- package/src/ChainState.res +72 -19
- package/src/ChainState.res.mjs +50 -12
- package/src/ChainState.resi +5 -0
- package/src/Config.res +58 -37
- package/src/Config.res.mjs +63 -53
- package/src/CrossChainState.res +49 -52
- package/src/CrossChainState.res.mjs +25 -23
- package/src/CrossChainState.resi +6 -13
- package/src/EffectState.res +13 -4
- package/src/EffectState.resi +7 -1
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +6 -6
- package/src/FetchState.res +14 -0
- package/src/FetchState.res.mjs +14 -0
- package/src/FinalizeBackfill.res +16 -12
- package/src/FinalizeBackfill.res.mjs +3 -3
- package/src/HistoryPolicy.res +46 -0
- package/src/HistoryPolicy.res.mjs +50 -0
- package/src/InMemoryStore.res +30 -32
- package/src/InMemoryStore.res.mjs +33 -28
- package/src/IndexerState.res +66 -45
- package/src/IndexerState.res.mjs +57 -29
- package/src/IndexerState.resi +8 -13
- package/src/Internal.res +4 -2
- package/src/LoadLayer.res +11 -4
- package/src/LoadLayer.res.mjs +8 -5
- package/src/Main.res +25 -24
- package/src/Main.res.mjs +24 -25
- package/src/Metrics.res +49 -1
- package/src/Metrics.res.mjs +26 -3
- package/src/Persistence.res +55 -16
- package/src/Persistence.res.mjs +18 -3
- package/src/PgStorage.res +194 -118
- package/src/PgStorage.res.mjs +132 -72
- package/src/PruneStaleHistory.res +9 -15
- package/src/PruneStaleHistory.res.mjs +11 -20
- package/src/Rollback.res +9 -14
- package/src/Rollback.res.mjs +5 -3
- package/src/Sink.res +7 -5
- package/src/Sink.res.mjs +4 -4
- package/src/Staging.res +298 -0
- package/src/Staging.res.mjs +257 -0
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +7 -6
- package/src/UserContext.res +7 -4
- package/src/UserContext.res.mjs +5 -2
- package/src/Utils.res +9 -0
- package/src/Utils.res.mjs +9 -0
- package/src/Writing.res +34 -20
- package/src/Writing.res.mjs +15 -20
- package/src/bindings/ClickHouse.res +41 -34
- package/src/bindings/ClickHouse.res.mjs +36 -30
- package/src/bindings/ClickHouseSink.res +45 -186
- package/src/bindings/ClickHouseSink.res.mjs +21 -174
- package/src/db/CheckpointSequence.res +109 -0
- package/src/db/CheckpointSequence.res.mjs +99 -0
- package/src/db/EntityHistory.res +6 -5
- package/src/db/EntityHistory.res.mjs +5 -5
- package/src/db/Frontier.res +86 -0
- package/src/db/Frontier.res.mjs +126 -0
- package/src/db/InternalTable.res +145 -27
- package/src/db/InternalTable.res.mjs +115 -29
- package/src/db/RollbackFloors.res +34 -41
- package/src/db/RollbackFloors.res.mjs +27 -71
- package/src/sources/BlockStore.res +3 -0
- package/src/sources/EvmHyperSyncSource.res +3 -1
- package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
- package/src/sources/HyperSync.res +2 -0
- package/src/sources/HyperSync.res.mjs +1 -0
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +3 -0
- package/src/sources/RequestStat.res +8 -1
- package/src/sources/Source.res +4 -3
- package/src/sources/SourceManager.res +30 -10
- package/src/sources/SourceManager.res.mjs +24 -7
- package/src/sources/SourceManager.resi +2 -0
- package/src/tui/Tui.res +56 -143
- package/src/tui/Tui.res.mjs +70 -166
- package/src/tui/components/TuiData.res +61 -16
- package/src/tui/components/TuiData.res.mjs +53 -15
- package/svm.schema.json +0 -7
- package/src/db/CheckpointBounds.res +0 -53
- package/src/db/CheckpointBounds.res.mjs +0 -44
package/src/TestIndexer.res
CHANGED
|
@@ -266,6 +266,7 @@ let makeInitialState = (
|
|
|
266
266
|
sourceBlockNumber: processChainConfig.endBlock->Option.getOr(0),
|
|
267
267
|
maxReorgDepth: 0, // No reorg support in test indexer
|
|
268
268
|
progressBlockNumber: -1,
|
|
269
|
+
progressBlockTime: None,
|
|
269
270
|
numEventsProcessed: 0.,
|
|
270
271
|
firstEventBlockNumber: None,
|
|
271
272
|
timestampCaughtUpToHeadOrEndblock: None,
|
|
@@ -279,7 +280,7 @@ let makeInitialState = (
|
|
|
279
280
|
envioInfo: Some(JSON.Encode.object(Dict.make())),
|
|
280
281
|
cache: Dict.make(),
|
|
281
282
|
chains,
|
|
282
|
-
|
|
283
|
+
checkpointFrontier: Frontier.empty(),
|
|
283
284
|
reorgCheckpoints: [],
|
|
284
285
|
}
|
|
285
286
|
}
|
|
@@ -590,7 +591,7 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
590
591
|
JsError.throwWithMessage(
|
|
591
592
|
"TestIndexer: initialize should not be called; the initial state is derived from config.",
|
|
592
593
|
),
|
|
593
|
-
resumeInitialState: async (~entities as _, ~throwIfIncompatible as _) =>
|
|
594
|
+
resumeInitialState: async (~entities as _, ~chainIds as _, ~throwIfIncompatible as _) =>
|
|
594
595
|
JsError.throwWithMessage(
|
|
595
596
|
"TestIndexer: resumeInitialState should not be called; the initial state is derived from config.",
|
|
596
597
|
),
|
|
@@ -599,13 +600,12 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
599
600
|
->handleLoad(~tableName=table.tableName, ~filter)
|
|
600
601
|
->(Utils.magic: array<Internal.entity> => array<unknown>),
|
|
601
602
|
// The in-memory storage has no indexes to build, and it's always ready.
|
|
602
|
-
ensureQueryIndexes: async (~
|
|
603
|
-
ensureSchemaIndexes: async (~entities as _) => (),
|
|
603
|
+
ensureQueryIndexes: async (~entityConfig as _, ~scope as _, ~filters as _) => (),
|
|
604
|
+
ensureSchemaIndexes: async (~entities as _, ~chainIds as _) => (),
|
|
604
605
|
finalizeBackfill: async (~entities as _, ~chainIds as _, ~readyAt as _) => (),
|
|
605
606
|
writeBatch: async (
|
|
606
607
|
~batch,
|
|
607
608
|
~rollback as _,
|
|
608
|
-
~isInReorgThreshold as _,
|
|
609
609
|
~config,
|
|
610
610
|
~allEntities as _,
|
|
611
611
|
~updatedEffectsCache as _,
|
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import * as ChainId from "./ChainId.res.mjs";
|
|
|
8
8
|
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
|
+
import * as Frontier from "./db/Frontier.res.mjs";
|
|
11
12
|
import * as Internal from "./Internal.res.mjs";
|
|
12
13
|
import * as ChainState from "./ChainState.res.mjs";
|
|
13
14
|
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
@@ -19,7 +20,6 @@ import * as EntityFilter from "./db/EntityFilter.res.mjs";
|
|
|
19
20
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
20
21
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
21
22
|
import * as HandlerLoader from "./HandlerLoader.res.mjs";
|
|
22
|
-
import * as InternalTable from "./db/InternalTable.res.mjs";
|
|
23
23
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
24
24
|
import * as SimulateItems from "./SimulateItems.res.mjs";
|
|
25
25
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
@@ -183,6 +183,7 @@ function makeInitialState(config, processConfigChains, addressRowsByChain, contr
|
|
|
183
183
|
endBlock: processChainConfig.endBlock,
|
|
184
184
|
maxReorgDepth: 0,
|
|
185
185
|
progressBlockNumber: -1,
|
|
186
|
+
progressBlockTime: undefined,
|
|
186
187
|
numEventsProcessed: 0,
|
|
187
188
|
firstEventBlockNumber: undefined,
|
|
188
189
|
timestampCaughtUpToHeadOrEndblock: undefined,
|
|
@@ -196,7 +197,7 @@ function makeInitialState(config, processConfigChains, addressRowsByChain, contr
|
|
|
196
197
|
envioInfo: {},
|
|
197
198
|
cache: {},
|
|
198
199
|
chains: chains,
|
|
199
|
-
|
|
200
|
+
checkpointFrontier: Frontier.empty(),
|
|
200
201
|
reorgCheckpoints: []
|
|
201
202
|
};
|
|
202
203
|
}
|
|
@@ -386,10 +387,10 @@ function makeInMemoryStorage(state) {
|
|
|
386
387
|
name: "test-inmemory",
|
|
387
388
|
isInitialized: async () => true,
|
|
388
389
|
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."),
|
|
389
|
-
resumeInitialState: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: resumeInitialState should not be called; the initial state is derived from config."),
|
|
390
|
+
resumeInitialState: async (param, param$1, param$2) => Stdlib_JsError.throwWithMessage("TestIndexer: resumeInitialState should not be called; the initial state is derived from config."),
|
|
390
391
|
loadOrThrow: async (filter, table) => handleLoad(state, table.tableName, filter),
|
|
391
|
-
ensureQueryIndexes: async (param, param$1) => {},
|
|
392
|
-
ensureSchemaIndexes: async param => {},
|
|
392
|
+
ensureQueryIndexes: async (param, param$1, param$2) => {},
|
|
393
|
+
ensureSchemaIndexes: async (param, param$1) => {},
|
|
393
394
|
finalizeBackfill: async (param, param$1, param$2) => {},
|
|
394
395
|
dumpEffectCache: async () => {},
|
|
395
396
|
reset: async () => {},
|
|
@@ -399,7 +400,7 @@ function makeInMemoryStorage(state) {
|
|
|
399
400
|
getRollbackTargetCheckpoint: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
400
401
|
getRollbackProgressDiff: async param => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
401
402
|
getRollbackData: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
402
|
-
writeBatch: async (batch, param,
|
|
403
|
+
writeBatch: async (batch, param, config, param$1, param$2, updatedEntities, registeredAddresses, param$3, param$4) => handleWriteBatch(state, config, updatedEntities, registeredAddresses, batch.checkpointIds, batch.checkpointChainIds, batch.checkpointBlockNumbers, batch.checkpointEventsProcessed),
|
|
403
404
|
close: async () => {}
|
|
404
405
|
};
|
|
405
406
|
}
|
package/src/UserContext.res
CHANGED
|
@@ -95,6 +95,7 @@ let initEffect = (params: contextParams) => {
|
|
|
95
95
|
input,
|
|
96
96
|
context: effectContext,
|
|
97
97
|
cacheKey: input->S.reverseConvertOrThrow(effect.input)->Utils.Hash.makeOrThrow,
|
|
98
|
+
chainId: handlerChainId,
|
|
98
99
|
checkpointId: params.checkpointId,
|
|
99
100
|
}
|
|
100
101
|
LoadLayer.loadEffect(
|
|
@@ -171,14 +172,16 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
171
172
|
let prop = prop->(Utils.magic: unknown => string)
|
|
172
173
|
|
|
173
174
|
let isClickHouseOnly = !params.entityConfig.storage.postgres
|
|
175
|
+
let scope = params->entityScope
|
|
176
|
+
let committedCheckpointId = params.indexerState->IndexerState.committedCheckpointIdFor(~scope)
|
|
174
177
|
|
|
175
178
|
let set = params.isPreload
|
|
176
179
|
? noopSet
|
|
177
180
|
: (entity: Internal.entity) => {
|
|
178
181
|
params.indexerState
|
|
179
|
-
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope
|
|
182
|
+
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope)
|
|
180
183
|
->InMemoryTable.Entity.set(
|
|
181
|
-
~committedCheckpointId
|
|
184
|
+
~committedCheckpointId,
|
|
182
185
|
Set({
|
|
183
186
|
entityId: entity.id->EntityId.unsafeOfString,
|
|
184
187
|
checkpointId: params.checkpointId,
|
|
@@ -288,9 +291,9 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
288
291
|
} else {
|
|
289
292
|
entityId => {
|
|
290
293
|
params.indexerState
|
|
291
|
-
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope
|
|
294
|
+
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope)
|
|
292
295
|
->InMemoryTable.Entity.set(
|
|
293
|
-
~committedCheckpointId
|
|
296
|
+
~committedCheckpointId,
|
|
294
297
|
Delete({
|
|
295
298
|
entityId,
|
|
296
299
|
checkpointId: params.checkpointId,
|
package/src/UserContext.res.mjs
CHANGED
|
@@ -64,6 +64,7 @@ function initEffect(params) {
|
|
|
64
64
|
input: input,
|
|
65
65
|
context: effectContext,
|
|
66
66
|
cacheKey: effectArgs_cacheKey,
|
|
67
|
+
chainId: handlerChainId,
|
|
67
68
|
checkpointId: effectArgs_checkpointId
|
|
68
69
|
};
|
|
69
70
|
return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, scope, params.indexerState, params.isPreload, params.item, params.config.ecosystem);
|
|
@@ -99,7 +100,9 @@ function throwClickHouseReadOnly(entityConfig, op) {
|
|
|
99
100
|
|
|
100
101
|
let entityTraps_get = (params, prop) => {
|
|
101
102
|
let isClickHouseOnly = !params.entityConfig.storage.postgres;
|
|
102
|
-
let
|
|
103
|
+
let scope = entityScope(params);
|
|
104
|
+
let committedCheckpointId = IndexerState.committedCheckpointIdFor(params.indexerState, scope);
|
|
105
|
+
let set = params.isPreload ? noopSet : entity => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, scope), committedCheckpointId, {
|
|
103
106
|
type: "SET",
|
|
104
107
|
entityId: entity.id,
|
|
105
108
|
entity: entity,
|
|
@@ -110,7 +113,7 @@ let entityTraps_get = (params, prop) => {
|
|
|
110
113
|
if (params.isPreload) {
|
|
111
114
|
return noopDeleteUnsafe;
|
|
112
115
|
} else {
|
|
113
|
-
return entityId => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig,
|
|
116
|
+
return entityId => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, scope), committedCheckpointId, {
|
|
114
117
|
type: "DELETE",
|
|
115
118
|
entityId: entityId,
|
|
116
119
|
checkpointId: params.checkpointId
|
package/src/Utils.res
CHANGED
|
@@ -166,6 +166,15 @@ module Dict = {
|
|
|
166
166
|
|
|
167
167
|
let merge: (dict<'a>, dict<'a>) => dict<'a> = %raw(`(dictA, dictB) => ({...dictA, ...dictB})`)
|
|
168
168
|
|
|
169
|
+
// Keeps every key either dict has; `pick` decides a key both have.
|
|
170
|
+
let mergeWith: (dict<'a>, dict<'a>, ('a, 'a) => 'a) => dict<'a> = %raw(`(a, b, pick) => {
|
|
171
|
+
var merged = {...a}, i;
|
|
172
|
+
for (i in b) {
|
|
173
|
+
merged[i] = i in merged ? pick(merged[i], b[i]) : b[i];
|
|
174
|
+
}
|
|
175
|
+
return merged;
|
|
176
|
+
}`)
|
|
177
|
+
|
|
169
178
|
@val
|
|
170
179
|
external mergeInPlace: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign"
|
|
171
180
|
|
package/src/Utils.res.mjs
CHANGED
|
@@ -114,6 +114,14 @@ function pushMany(dict, key, values) {
|
|
|
114
114
|
|
|
115
115
|
let merge = ((dictA, dictB) => ({...dictA, ...dictB}));
|
|
116
116
|
|
|
117
|
+
let mergeWith = ((a, b, pick) => {
|
|
118
|
+
var merged = {...a}, i;
|
|
119
|
+
for (i in b) {
|
|
120
|
+
merged[i] = i in merged ? pick(merged[i], b[i]) : b[i];
|
|
121
|
+
}
|
|
122
|
+
return merged;
|
|
123
|
+
});
|
|
124
|
+
|
|
117
125
|
let mapValues = ((dict, f) => {
|
|
118
126
|
var target = {}, i;
|
|
119
127
|
for (i in dict) {
|
|
@@ -197,6 +205,7 @@ let Dict = {
|
|
|
197
205
|
push: push,
|
|
198
206
|
pushMany: pushMany,
|
|
199
207
|
merge: merge,
|
|
208
|
+
mergeWith: mergeWith,
|
|
200
209
|
mapValues: mapValues,
|
|
201
210
|
filterMapValues: filterMapValues,
|
|
202
211
|
mapValuesToArray: mapValuesToArray,
|
package/src/Writing.res
CHANGED
|
@@ -42,12 +42,10 @@ let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updated
|
|
|
42
42
|
switch idsToStore {
|
|
43
43
|
| [] => ()
|
|
44
44
|
| ids =>
|
|
45
|
-
let items = ids->Array.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
)
|
|
45
|
+
let items = ids->Array.map((id): Internal.effectCacheItem => {
|
|
46
|
+
id,
|
|
47
|
+
output: (dict->Dict.getUnsafe(id)).output,
|
|
48
|
+
})
|
|
51
49
|
let effectName = effect.name
|
|
52
50
|
let tableName = table.tableName
|
|
53
51
|
let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(tableName) {
|
|
@@ -101,22 +99,34 @@ let runOneWrite = async (state: IndexerState.t) => {
|
|
|
101
99
|
| None => ()
|
|
102
100
|
}
|
|
103
101
|
| _ =>
|
|
104
|
-
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
105
102
|
let batch = state->IndexerState.drainBatchRun
|
|
106
|
-
// The run's last checkpoint; entity changes above it stay queued for the next write.
|
|
107
|
-
let upToCheckpointId = switch batch.checkpointIds->Utils.Array.last {
|
|
108
|
-
| Some(checkpointId) => checkpointId
|
|
109
|
-
| None => committedCheckpointId
|
|
110
|
-
}
|
|
111
103
|
|
|
112
104
|
let rollback = state->IndexerState.takeRollback
|
|
113
105
|
|
|
106
|
+
// Entity changes above it stay queued for the next write.
|
|
107
|
+
let writtenFrontier = Persistence.writtenFrontier(~batch, ~rollback)
|
|
108
|
+
|
|
114
109
|
let updatedEntities = []
|
|
115
110
|
state->IndexerState.eachEntityTable((~entityConfig, ~scope, ~table) => {
|
|
116
111
|
let changes =
|
|
117
|
-
table->InMemoryTable.Entity.snapshotChanges(
|
|
112
|
+
table->InMemoryTable.Entity.snapshotChanges(
|
|
113
|
+
~committedCheckpointId=state->IndexerState.committedCheckpointIdFor(~scope),
|
|
114
|
+
~upToCheckpointId=config.checkpointSequence->CheckpointSequence.forScope(
|
|
115
|
+
writtenFrontier,
|
|
116
|
+
~scope,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
118
119
|
if changes->Utils.Array.notEmpty {
|
|
119
|
-
updatedEntities->Array.push(
|
|
120
|
+
updatedEntities->Array.push(
|
|
121
|
+
(
|
|
122
|
+
{
|
|
123
|
+
entityConfig,
|
|
124
|
+
scope,
|
|
125
|
+
changes,
|
|
126
|
+
shouldSaveHistory: batch.history->HistoryPolicy.forScope(~scope),
|
|
127
|
+
}: Persistence.updatedEntity
|
|
128
|
+
),
|
|
129
|
+
)
|
|
120
130
|
}
|
|
121
131
|
})
|
|
122
132
|
let updatedEffectsCache = snapshotEffects(state, ~cache)
|
|
@@ -142,7 +152,6 @@ let runOneWrite = async (state: IndexerState.t) => {
|
|
|
142
152
|
persistence.storage.writeBatch(
|
|
143
153
|
~batch,
|
|
144
154
|
~rollback,
|
|
145
|
-
~isInReorgThreshold=batch.isInReorgThreshold,
|
|
146
155
|
~config,
|
|
147
156
|
~allEntities=persistence.allEntities,
|
|
148
157
|
~updatedEntities,
|
|
@@ -155,7 +164,7 @@ let runOneWrite = async (state: IndexerState.t) => {
|
|
|
155
164
|
PruneStaleHistory.runConcurrent(state, ~targets=pruneTargets),
|
|
156
165
|
))
|
|
157
166
|
|
|
158
|
-
state->IndexerState.markCommitted(~
|
|
167
|
+
state->IndexerState.markCommitted(~writtenFrontier)
|
|
159
168
|
|
|
160
169
|
switch rollback {
|
|
161
170
|
| Some({progressedChains}) if RollbackCommit.callbacks->Utils.Array.notEmpty =>
|
|
@@ -218,14 +227,19 @@ let commitBatch = (state: IndexerState.t, ~batch: Batch.t) => {
|
|
|
218
227
|
// Drops committed entity and effect entries across all tables. With
|
|
219
228
|
// keepLoadedFromDb, entries seeded from a db read are spared.
|
|
220
229
|
let dropCommitted = (state: IndexerState.t, ~keepLoadedFromDb) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
state->IndexerState.eachEntityTable((~entityConfig as _, ~scope, ~table) =>
|
|
231
|
+
table->InMemoryTable.Entity.dropCommittedChanges(
|
|
232
|
+
~committedCheckpointId=state->IndexerState.committedCheckpointIdFor(~scope),
|
|
233
|
+
~keepLoadedFromDb,
|
|
234
|
+
)
|
|
224
235
|
)
|
|
225
236
|
state
|
|
226
237
|
->IndexerState.effectState
|
|
227
238
|
->EffectState.forEach(inMemTable =>
|
|
228
|
-
inMemTable->InMemoryStore.dropCommittedEffects(
|
|
239
|
+
inMemTable->InMemoryStore.dropCommittedEffects(
|
|
240
|
+
~committedFrontier=state->IndexerState.committedFrontier,
|
|
241
|
+
~keepLoadedFromDb,
|
|
242
|
+
)
|
|
229
243
|
)
|
|
230
244
|
}
|
|
231
245
|
|
package/src/Writing.res.mjs
CHANGED
|
@@ -5,14 +5,16 @@ import * as Utils from "./Utils.res.mjs";
|
|
|
5
5
|
import * as Throttler from "./Throttler.res.mjs";
|
|
6
6
|
import * as EffectState from "./EffectState.res.mjs";
|
|
7
7
|
import * as Performance from "./bindings/Performance.res.mjs";
|
|
8
|
+
import * as Persistence from "./Persistence.res.mjs";
|
|
8
9
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
9
|
-
import * as
|
|
10
|
+
import * as HistoryPolicy from "./HistoryPolicy.res.mjs";
|
|
10
11
|
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
11
12
|
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
12
13
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
13
14
|
import * as RollbackCommit from "./RollbackCommit.res.mjs";
|
|
14
15
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
15
16
|
import * as PruneStaleHistory from "./PruneStaleHistory.res.mjs";
|
|
17
|
+
import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
|
|
16
18
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
17
19
|
|
|
18
20
|
function getChangesCount(state) {
|
|
@@ -44,15 +46,10 @@ function snapshotEffects(state, cache) {
|
|
|
44
46
|
let table = inMemTable.table;
|
|
45
47
|
let effect = inMemTable.effect;
|
|
46
48
|
if (idsToStore.length !== 0) {
|
|
47
|
-
let items =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
id: id,
|
|
52
|
-
output: match.entity
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
});
|
|
49
|
+
let items = idsToStore.map(id => ({
|
|
50
|
+
id: id,
|
|
51
|
+
output: dict[id].output
|
|
52
|
+
}));
|
|
56
53
|
let effectName = effect.name;
|
|
57
54
|
let tableName = table.tableName;
|
|
58
55
|
let c = cache[tableName];
|
|
@@ -100,19 +97,18 @@ async function runOneWrite(state) {
|
|
|
100
97
|
return;
|
|
101
98
|
}
|
|
102
99
|
}
|
|
103
|
-
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
104
100
|
let batch = IndexerState.drainBatchRun(state);
|
|
105
|
-
let checkpointId = Utils.$$Array.last(batch.checkpointIds);
|
|
106
|
-
let upToCheckpointId = checkpointId !== undefined ? checkpointId : committedCheckpointId;
|
|
107
101
|
let rollback = IndexerState.takeRollback(state);
|
|
102
|
+
let writtenFrontier = Persistence.writtenFrontier(batch, rollback);
|
|
108
103
|
let updatedEntities = [];
|
|
109
104
|
IndexerState.eachEntityTable(state, (entityConfig, scope, table) => {
|
|
110
|
-
let changes = InMemoryTable.Entity.snapshotChanges(table,
|
|
105
|
+
let changes = InMemoryTable.Entity.snapshotChanges(table, IndexerState.committedCheckpointIdFor(state, scope), CheckpointSequence.forScope(config.checkpointSequence, writtenFrontier, scope));
|
|
111
106
|
if (Utils.$$Array.notEmpty(changes)) {
|
|
112
107
|
updatedEntities.push({
|
|
113
108
|
entityConfig: entityConfig,
|
|
114
109
|
scope: scope,
|
|
115
|
-
changes: changes
|
|
110
|
+
changes: changes,
|
|
111
|
+
shouldSaveHistory: HistoryPolicy.forScope(batch.history, scope)
|
|
116
112
|
});
|
|
117
113
|
return;
|
|
118
114
|
}
|
|
@@ -125,10 +121,10 @@ async function runOneWrite(state) {
|
|
|
125
121
|
});
|
|
126
122
|
let pruneTargets = PruneStaleHistory.select(state, writtenEntityNames, Stdlib_Option.isSome(rollback));
|
|
127
123
|
await Promise.all([
|
|
128
|
-
persistence.storage.writeBatch(batch, rollback,
|
|
124
|
+
persistence.storage.writeBatch(batch, rollback, config, persistence.allEntities, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
|
|
129
125
|
PruneStaleHistory.runConcurrent(state, pruneTargets)
|
|
130
126
|
]);
|
|
131
|
-
IndexerState.markCommitted(state,
|
|
127
|
+
IndexerState.markCommitted(state, writtenFrontier);
|
|
132
128
|
if (rollback !== undefined && Utils.$$Array.notEmpty(RollbackCommit.callbacks)) {
|
|
133
129
|
await RollbackCommit.fire(rollback.progressedChains);
|
|
134
130
|
}
|
|
@@ -179,9 +175,8 @@ function commitBatch(state, batch) {
|
|
|
179
175
|
}
|
|
180
176
|
|
|
181
177
|
function dropCommitted(state, keepLoadedFromDb) {
|
|
182
|
-
|
|
183
|
-
IndexerState.
|
|
184
|
-
EffectState.forEach(IndexerState.effectState(state), inMemTable => InMemoryStore.dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb));
|
|
178
|
+
IndexerState.eachEntityTable(state, (param, scope, table) => InMemoryTable.Entity.dropCommittedChanges(table, IndexerState.committedCheckpointIdFor(state, scope), keepLoadedFromDb));
|
|
179
|
+
EffectState.forEach(IndexerState.effectState(state), inMemTable => InMemoryStore.dropCommittedEffects(inMemTable, IndexerState.committedFrontier(state), keepLoadedFromDb));
|
|
185
180
|
}
|
|
186
181
|
|
|
187
182
|
async function awaitCapacityLoop(state) {
|
|
@@ -90,6 +90,7 @@ let makeColumnSpec = (
|
|
|
90
90
|
type checkpointColumn = {
|
|
91
91
|
spec: ClickHouseSink.columnSpec,
|
|
92
92
|
valuesOf: Batch.t => array<unknown>,
|
|
93
|
+
diffValuesOf: array<InternalTable.Checkpoints.diffCheckpoint> => array<unknown>,
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
// Registration and staging both read this one list, so the column a batch's
|
|
@@ -98,12 +99,14 @@ let checkpointColumns = InternalTable.Checkpoints.columns->Array.filterMap(({
|
|
|
98
99
|
field,
|
|
99
100
|
clickHouseFieldType,
|
|
100
101
|
valuesOf,
|
|
102
|
+
diffValuesOf,
|
|
101
103
|
}) =>
|
|
102
104
|
switch field {
|
|
103
105
|
| Table.Field({fieldName, isNullable}) =>
|
|
104
106
|
Some({
|
|
105
107
|
spec: makeColumnSpec(~name=fieldName, ~fieldType=clickHouseFieldType, ~isNullable),
|
|
106
108
|
valuesOf,
|
|
109
|
+
diffValuesOf,
|
|
107
110
|
})
|
|
108
111
|
| DerivedFrom(_) => None
|
|
109
112
|
}
|
|
@@ -259,25 +262,18 @@ let makeRegistry = () => {
|
|
|
259
262
|
)
|
|
260
263
|
|
|
261
264
|
%%private(
|
|
262
|
-
let
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
~changes: array<Change.t<Internal.entity>>,
|
|
266
|
-
) => {
|
|
267
|
-
let rows = changes->Array.length
|
|
268
|
-
let builders = table.columns->Array.map(ClickHouseSink.makeBuilder(_, ~rows))
|
|
269
|
-
let columns = builders->Array.length
|
|
270
|
-
for row in 0 to rows - 1 {
|
|
265
|
+
let fillStage = (stage, ~converters, ~changes: array<Change.t<Internal.entity>>) => {
|
|
266
|
+
let columns = stage->Staging.columnCount
|
|
267
|
+
for row in 0 to changes->Array.length - 1 {
|
|
271
268
|
let change = changes->Array.getUnsafe(row)
|
|
272
269
|
let (cells, write) = switch change {
|
|
273
|
-
| Change.Set(_) => (converters.setCells,
|
|
274
|
-
| Delete(_) => (converters.deleteCells,
|
|
270
|
+
| Change.Set(_) => (converters.setCells, Staging.writeValue)
|
|
271
|
+
| Delete(_) => (converters.deleteCells, Staging.writeDeletedValue)
|
|
275
272
|
}
|
|
276
273
|
for column in 0 to columns - 1 {
|
|
277
|
-
|
|
274
|
+
stage->write(~column, ~row, (cells->Array.getUnsafe(column))(change))
|
|
278
275
|
}
|
|
279
276
|
}
|
|
280
|
-
builders
|
|
281
277
|
}
|
|
282
278
|
)
|
|
283
279
|
|
|
@@ -306,31 +302,33 @@ let checkpointsTable = (sink, ~registry) =>
|
|
|
306
302
|
table
|
|
307
303
|
}
|
|
308
304
|
|
|
309
|
-
let
|
|
310
|
-
sink
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
let stageCheckpointsOrThrow = (sink, ~registry, ~batch: Batch.t) => {
|
|
317
|
-
let rows = batch.checkpointIds->Array.length
|
|
305
|
+
let stageCheckpointsOrThrow = (
|
|
306
|
+
sink,
|
|
307
|
+
~registry,
|
|
308
|
+
~batch: Batch.t,
|
|
309
|
+
~diffCheckpoints: array<InternalTable.Checkpoints.diffCheckpoint>,
|
|
310
|
+
) => {
|
|
311
|
+
let rows = batch.checkpointIds->Array.length + diffCheckpoints->Array.length
|
|
318
312
|
if rows === 0 {
|
|
319
313
|
Null.null
|
|
320
314
|
} else {
|
|
321
315
|
let table = sink->checkpointsTable(~registry)
|
|
316
|
+
let stage =
|
|
317
|
+
sink->ClickHouseSink.arena->Staging.begin(~table=table.handle, ~rows, ~columns=table.columns)
|
|
322
318
|
try {
|
|
323
319
|
// The table was registered from `checkpointColumns`, in this order.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
let columnValues =
|
|
320
|
+
table.columns->Array.forEachWithIndex((_, column) => {
|
|
321
|
+
let source = checkpointColumns->Array.getUnsafe(column)
|
|
322
|
+
let columnValues =
|
|
323
|
+
source.valuesOf(batch)->Array.concat(source.diffValuesOf(diffCheckpoints))
|
|
327
324
|
for row in 0 to rows - 1 {
|
|
328
|
-
|
|
325
|
+
stage->Staging.writeValue(~column, ~row, columnValues->Array.getUnsafe(row))
|
|
329
326
|
}
|
|
330
327
|
})
|
|
331
|
-
Null.make(
|
|
328
|
+
Null.make(stage->Staging.commit)
|
|
332
329
|
} catch {
|
|
333
330
|
| exn =>
|
|
331
|
+
stage->Staging.abort
|
|
334
332
|
throw(
|
|
335
333
|
Persistence.StorageError({
|
|
336
334
|
message: `Failed to convert checkpoints for ClickHouse table "${table.name}"`,
|
|
@@ -355,6 +353,8 @@ let stageUpdatesOrThrow = (
|
|
|
355
353
|
let table = sink->entityTable(~registry, ~entityConfig)
|
|
356
354
|
let tableName = table.name
|
|
357
355
|
let cacheKey = `${entityConfig.name}|${scope->Internal.chainScopeToString}`
|
|
356
|
+
let stage =
|
|
357
|
+
sink->ClickHouseSink.arena->Staging.begin(~table=table.handle, ~rows, ~columns=table.columns)
|
|
358
358
|
try {
|
|
359
359
|
let converters = switch registry.converters->Utils.Dict.dangerouslyGetNonOption(cacheKey) {
|
|
360
360
|
| Some(cached) => cached
|
|
@@ -363,10 +363,11 @@ let stageUpdatesOrThrow = (
|
|
|
363
363
|
registry.converters->Dict.set(cacheKey, cached)
|
|
364
364
|
cached
|
|
365
365
|
}
|
|
366
|
-
|
|
367
|
-
Some(
|
|
366
|
+
stage->fillStage(~converters, ~changes)
|
|
367
|
+
Some(stage->Staging.commit)
|
|
368
368
|
} catch {
|
|
369
369
|
| exn =>
|
|
370
|
+
stage->Staging.abort
|
|
370
371
|
throw(
|
|
371
372
|
Persistence.StorageError({
|
|
372
373
|
message: `Failed to convert items for ClickHouse table "${tableName}"`,
|
|
@@ -415,20 +416,26 @@ let initialize = async (sink, ~entities: array<Internal.entityConfig>) => {
|
|
|
415
416
|
|
|
416
417
|
let resume = async (
|
|
417
418
|
sink,
|
|
418
|
-
~
|
|
419
|
+
~sequence: CheckpointSequence.t,
|
|
420
|
+
~frontier: Frontier.t,
|
|
419
421
|
~chains: array<Persistence.initialChainState>,
|
|
420
422
|
~entities: array<Internal.entityConfig>,
|
|
421
423
|
) => {
|
|
422
424
|
let chainProgress = chains->Array.map(chain => {
|
|
423
425
|
ClickHouseSink.chainId: chain.id->ChainId.toString,
|
|
424
426
|
progressBlockNumber: chain.progressBlockNumber,
|
|
427
|
+
committedCheckpointId: frontier->Frontier.get(chain.id)->BigInt.toString,
|
|
425
428
|
})
|
|
426
429
|
try await sink->ClickHouseSink.resume({
|
|
427
|
-
|
|
430
|
+
perChain: switch sequence {
|
|
431
|
+
| PerChain => true
|
|
432
|
+
| SharedAcrossChains => false
|
|
433
|
+
},
|
|
428
434
|
chainProgress,
|
|
429
|
-
historyTables: entities->Array.map(entityConfig =>
|
|
430
|
-
|
|
431
|
-
|
|
435
|
+
historyTables: entities->Array.map(entityConfig => {
|
|
436
|
+
let spec = entitySpec(~entityConfig)
|
|
437
|
+
{ClickHouseSink.name: spec.historyTable, chainIdColumn: ?spec.chainIdColumn}
|
|
438
|
+
}),
|
|
432
439
|
}) catch {
|
|
433
440
|
| exn => {
|
|
434
441
|
Logging.errorWithExn(exn, "Failed to resume ClickHouse storage")
|