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.
Files changed (89) hide show
  1. package/index.d.ts +3 -0
  2. package/package.json +6 -6
  3. package/src/Batch.res +111 -78
  4. package/src/Batch.res.mjs +53 -46
  5. package/src/BatchProcessing.res +1 -11
  6. package/src/BatchProcessing.res.mjs +1 -4
  7. package/src/ChainState.res +72 -19
  8. package/src/ChainState.res.mjs +50 -12
  9. package/src/ChainState.resi +5 -0
  10. package/src/Config.res +58 -37
  11. package/src/Config.res.mjs +63 -53
  12. package/src/CrossChainState.res +49 -52
  13. package/src/CrossChainState.res.mjs +25 -23
  14. package/src/CrossChainState.resi +6 -13
  15. package/src/EffectState.res +13 -4
  16. package/src/EffectState.resi +7 -1
  17. package/src/EventProcessing.res +6 -6
  18. package/src/EventProcessing.res.mjs +6 -6
  19. package/src/FetchState.res +14 -0
  20. package/src/FetchState.res.mjs +14 -0
  21. package/src/FinalizeBackfill.res +16 -12
  22. package/src/FinalizeBackfill.res.mjs +3 -3
  23. package/src/HistoryPolicy.res +46 -0
  24. package/src/HistoryPolicy.res.mjs +50 -0
  25. package/src/InMemoryStore.res +30 -32
  26. package/src/InMemoryStore.res.mjs +33 -28
  27. package/src/IndexerState.res +66 -45
  28. package/src/IndexerState.res.mjs +57 -29
  29. package/src/IndexerState.resi +8 -13
  30. package/src/Internal.res +4 -2
  31. package/src/LoadLayer.res +11 -4
  32. package/src/LoadLayer.res.mjs +8 -5
  33. package/src/Main.res +25 -24
  34. package/src/Main.res.mjs +24 -25
  35. package/src/Metrics.res +49 -1
  36. package/src/Metrics.res.mjs +26 -3
  37. package/src/Persistence.res +55 -16
  38. package/src/Persistence.res.mjs +18 -3
  39. package/src/PgStorage.res +194 -118
  40. package/src/PgStorage.res.mjs +132 -72
  41. package/src/PruneStaleHistory.res +9 -15
  42. package/src/PruneStaleHistory.res.mjs +11 -20
  43. package/src/Rollback.res +9 -14
  44. package/src/Rollback.res.mjs +5 -3
  45. package/src/Sink.res +7 -5
  46. package/src/Sink.res.mjs +4 -4
  47. package/src/Staging.res +298 -0
  48. package/src/Staging.res.mjs +257 -0
  49. package/src/TestIndexer.res +5 -5
  50. package/src/TestIndexer.res.mjs +7 -6
  51. package/src/UserContext.res +7 -4
  52. package/src/UserContext.res.mjs +5 -2
  53. package/src/Utils.res +9 -0
  54. package/src/Utils.res.mjs +9 -0
  55. package/src/Writing.res +34 -20
  56. package/src/Writing.res.mjs +15 -20
  57. package/src/bindings/ClickHouse.res +41 -34
  58. package/src/bindings/ClickHouse.res.mjs +36 -30
  59. package/src/bindings/ClickHouseSink.res +45 -186
  60. package/src/bindings/ClickHouseSink.res.mjs +21 -174
  61. package/src/db/CheckpointSequence.res +109 -0
  62. package/src/db/CheckpointSequence.res.mjs +99 -0
  63. package/src/db/EntityHistory.res +6 -5
  64. package/src/db/EntityHistory.res.mjs +5 -5
  65. package/src/db/Frontier.res +86 -0
  66. package/src/db/Frontier.res.mjs +126 -0
  67. package/src/db/InternalTable.res +145 -27
  68. package/src/db/InternalTable.res.mjs +115 -29
  69. package/src/db/RollbackFloors.res +34 -41
  70. package/src/db/RollbackFloors.res.mjs +27 -71
  71. package/src/sources/BlockStore.res +3 -0
  72. package/src/sources/EvmHyperSyncSource.res +3 -1
  73. package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
  74. package/src/sources/HyperSync.res +2 -0
  75. package/src/sources/HyperSync.res.mjs +1 -0
  76. package/src/sources/HyperSync.resi +1 -0
  77. package/src/sources/HyperSyncClient.res +3 -0
  78. package/src/sources/RequestStat.res +8 -1
  79. package/src/sources/Source.res +4 -3
  80. package/src/sources/SourceManager.res +30 -10
  81. package/src/sources/SourceManager.res.mjs +24 -7
  82. package/src/sources/SourceManager.resi +2 -0
  83. package/src/tui/Tui.res +56 -143
  84. package/src/tui/Tui.res.mjs +70 -166
  85. package/src/tui/components/TuiData.res +61 -16
  86. package/src/tui/components/TuiData.res.mjs +53 -15
  87. package/svm.schema.json +0 -7
  88. package/src/db/CheckpointBounds.res +0 -53
  89. package/src/db/CheckpointBounds.res.mjs +0 -44
@@ -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
- checkpointId: InternalTable.Checkpoints.initialCheckpointId,
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 (~table as _, ~filters as _) => (),
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 _,
@@ -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
- checkpointId: InternalTable.Checkpoints.initialCheckpointId,
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, 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),
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
  }
@@ -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=params->entityScope)
182
+ ->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope)
180
183
  ->InMemoryTable.Entity.set(
181
- ~committedCheckpointId=params.indexerState->IndexerState.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=params->entityScope)
294
+ ->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope)
292
295
  ->InMemoryTable.Entity.set(
293
- ~committedCheckpointId=params.indexerState->IndexerState.committedCheckpointId,
296
+ ~committedCheckpointId,
294
297
  Delete({
295
298
  entityId,
296
299
  checkpointId: params.checkpointId,
@@ -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 set = params.isPreload ? noopSet : entity => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params)), IndexerState.committedCheckpointId(params.indexerState), {
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, entityScope(params)), IndexerState.committedCheckpointId(params.indexerState), {
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.filterMap((id): option<Internal.effectCacheItem> =>
46
- switch dict->Dict.getUnsafe(id) {
47
- | Set({entity: output}) => Some({id, output})
48
- | Delete(_) => None
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(~committedCheckpointId, ~upToCheckpointId)
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(({entityConfig, scope, changes}: Persistence.updatedEntity))
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(~upToCheckpointId)
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
- let committedCheckpointId = state->IndexerState.committedCheckpointId
222
- state->IndexerState.eachEntityTable((~entityConfig as _, ~scope as _, ~table) =>
223
- table->InMemoryTable.Entity.dropCommittedChanges(~committedCheckpointId, ~keepLoadedFromDb)
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(~committedCheckpointId, ~keepLoadedFromDb)
239
+ inMemTable->InMemoryStore.dropCommittedEffects(
240
+ ~committedFrontier=state->IndexerState.committedFrontier,
241
+ ~keepLoadedFromDb,
242
+ )
229
243
  )
230
244
  }
231
245
 
@@ -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 Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
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 = Stdlib_Array.filterMap(idsToStore, id => {
48
- let match = dict[id];
49
- if (match.type === "SET") {
50
- return {
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, committedCheckpointId, upToCheckpointId);
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, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
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, upToCheckpointId);
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
- let committedCheckpointId = IndexerState.committedCheckpointId(state);
183
- IndexerState.eachEntityTable(state, (param, param$1, table) => InMemoryTable.Entity.dropCommittedChanges(table, committedCheckpointId, keepLoadedFromDb));
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 fillBuilders = (
263
- ~table: ClickHouseSink.table,
264
- ~converters,
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, ClickHouseSink.writeValue)
274
- | Delete(_) => (converters.deleteCells, ClickHouseSink.writeDeletedValue)
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
- builders->Array.getUnsafe(column)->write(~row, (cells->Array.getUnsafe(column))(change))
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 stageBuilders = (sink, ~table: ClickHouseSink.table, ~builders, ~rows) =>
310
- sink->ClickHouseSink.stage(
311
- ~table=table.handle,
312
- ~rows,
313
- ~columns=builders->Array.map(ClickHouseSink.builderPayload),
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
- let builders = table.columns->Array.map(ClickHouseSink.makeBuilder(_, ~rows))
325
- builders->Array.forEachWithIndex((builder, index) => {
326
- let columnValues = (checkpointColumns->Array.getUnsafe(index)).valuesOf(batch)
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
- builder->ClickHouseSink.writeValue(~row, columnValues->Array.getUnsafe(row))
325
+ stage->Staging.writeValue(~column, ~row, columnValues->Array.getUnsafe(row))
329
326
  }
330
327
  })
331
- Null.make(sink->stageBuilders(~table, ~builders, ~rows))
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
- let builders = fillBuilders(~table, ~converters, ~changes)
367
- Some(sink->stageBuilders(~table, ~builders, ~rows))
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
- ~checkpointId: Internal.checkpointId,
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
- checkpointId: checkpointId->BigInt.toString,
430
+ perChain: switch sequence {
431
+ | PerChain => true
432
+ | SharedAcrossChains => false
433
+ },
428
434
  chainProgress,
429
- historyTables: entities->Array.map(entityConfig => entitySpec(~entityConfig).historyTable),
430
- replicated: Env.ClickHouse.replicated(),
431
- databaseEngine: ?Env.ClickHouse.databaseEngine(),
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")