envio 3.5.0-alpha.2 → 3.5.0-rc.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/package.json +6 -6
- package/src/Batch.res +8 -8
- package/src/Batch.res.mjs +4 -3
- package/src/BatchProcessing.res +24 -0
- package/src/BatchProcessing.res.mjs +12 -0
- package/src/ChainFetching.res +20 -19
- package/src/ChainFetching.res.mjs +14 -14
- package/src/ChainId.res +58 -0
- package/src/ChainId.res.mjs +49 -0
- package/src/ChainId.resi +27 -0
- package/src/ChainMap.res +12 -26
- package/src/ChainMap.res.mjs +5 -41
- package/src/ChainMap.resi +5 -20
- package/src/ChainMetadata.res +1 -1
- package/src/ChainMetadata.res.mjs +3 -2
- package/src/ChainState.res +45 -24
- package/src/ChainState.res.mjs +40 -16
- package/src/ChainState.resi +3 -1
- package/src/Config.res +43 -26
- package/src/Config.res.mjs +19 -16
- package/src/ContractRegisterContext.res +1 -1
- package/src/ContractRegisterContext.res.mjs +1 -1
- package/src/Core.res +1 -0
- package/src/CrossChainState.res +90 -22
- package/src/CrossChainState.res.mjs +55 -16
- package/src/CrossChainState.resi +5 -1
- package/src/Env.res +0 -3
- package/src/Env.res.mjs +0 -3
- package/src/EventConfigBuilder.res +1 -1
- package/src/EventProcessing.res +4 -4
- package/src/EventProcessing.res.mjs +5 -5
- package/src/FetchState.res +489 -153
- package/src/FetchState.res.mjs +399 -129
- package/src/FinalizeBackfill.res +61 -0
- package/src/FinalizeBackfill.res.mjs +44 -0
- package/src/HandlerRegister.res +8 -8
- package/src/HandlerRegister.res.mjs +5 -4
- package/src/HandlerRegister.resi +1 -1
- package/src/InMemoryStore.res +4 -0
- package/src/InMemoryStore.res.mjs +1 -1
- package/src/InMemoryTable.res +15 -15
- package/src/InMemoryTable.res.mjs +14 -14
- package/src/IndexerLoop.res +9 -2
- package/src/IndexerLoop.res.mjs +8 -1
- package/src/IndexerState.res +52 -23
- package/src/IndexerState.res.mjs +52 -14
- package/src/IndexerState.resi +11 -6
- package/src/Internal.res +19 -17
- package/src/Internal.res.mjs +18 -9
- package/src/LoadLayer.res +10 -2
- package/src/LoadLayer.res.mjs +3 -1
- package/src/LogSelection.res +2 -2
- package/src/Main.res +7 -11
- package/src/Main.res.mjs +4 -6
- package/src/Metrics.res +6 -6
- package/src/Metrics.res.mjs +4 -3
- package/src/Persistence.res +23 -4
- package/src/PgStorage.res +447 -100
- package/src/PgStorage.res.mjs +285 -99
- package/src/RawEvent.res +1 -2
- package/src/RawEvent.res.mjs +1 -1
- package/src/Rollback.res +18 -20
- package/src/Rollback.res.mjs +9 -9
- package/src/RollbackCommit.res +2 -2
- package/src/RollbackCommit.res.mjs +3 -3
- package/src/SafeCheckpointTracking.res +2 -2
- package/src/SimulateDeadInputTracker.res +17 -21
- package/src/SimulateDeadInputTracker.res.mjs +9 -8
- package/src/SimulateItems.res +8 -10
- package/src/SimulateItems.res.mjs +9 -10
- package/src/Sink.res +2 -2
- package/src/Sink.res.mjs +3 -2
- package/src/TestIndexer.res +14 -14
- package/src/TestIndexer.res.mjs +16 -10
- package/src/UserContext.res +2 -2
- package/src/UserContext.res.mjs +2 -1
- package/src/Utils.res +0 -4
- package/src/Utils.res.mjs +1 -6
- package/src/bindings/ClickHouse.res +20 -2
- package/src/bindings/ClickHouse.res.mjs +25 -14
- package/src/db/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/EntityHistory.res +1 -1
- package/src/db/IndexCatalog.res +248 -0
- package/src/db/IndexCatalog.res.mjs +222 -0
- package/src/db/IndexDefinition.res +131 -0
- package/src/db/IndexDefinition.res.mjs +138 -0
- package/src/db/IndexManager.res +145 -0
- package/src/db/IndexManager.res.mjs +130 -0
- package/src/db/InternalTable.res +54 -23
- package/src/db/InternalTable.res.mjs +38 -23
- package/src/db/Table.res +24 -14
- package/src/db/Table.res.mjs +21 -16
- package/src/sources/AddressSet.res +5 -4
- package/src/sources/AddressStore.res +25 -14
- package/src/sources/AddressStore.res.mjs +10 -12
- package/src/sources/Evm.res +2 -2
- package/src/sources/Evm.res.mjs +1 -1
- package/src/sources/EvmChain.res +3 -3
- package/src/sources/EvmChain.res.mjs +3 -3
- package/src/sources/EvmHyperSyncSource.res +7 -7
- package/src/sources/EvmHyperSyncSource.res.mjs +6 -6
- package/src/sources/Fuel.res +2 -2
- package/src/sources/Fuel.res.mjs +1 -1
- package/src/sources/FuelHyperSyncClient.res +3 -0
- package/src/sources/FuelHyperSyncClient.res.mjs +1 -0
- package/src/sources/FuelHyperSyncSource.res +5 -7
- package/src/sources/FuelHyperSyncSource.res.mjs +5 -5
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +5 -0
- package/src/sources/HyperSyncClient.res.mjs +1 -0
- package/src/sources/RpcSource.res +13 -14
- package/src/sources/RpcSource.res.mjs +13 -12
- package/src/sources/SimulateSource.res +42 -15
- package/src/sources/SimulateSource.res.mjs +27 -9
- package/src/sources/Source.res +1 -5
- package/src/sources/SourceManager.res +12 -12
- package/src/sources/SourceManager.res.mjs +11 -10
- package/src/sources/SourceManager.resi +2 -2
- package/src/sources/Svm.res +4 -5
- package/src/sources/Svm.res.mjs +5 -4
- package/src/sources/SvmHyperSyncClient.res +3 -0
- package/src/sources/SvmHyperSyncClient.res.mjs +1 -0
- package/src/sources/SvmHyperSyncSource.res +4 -4
- package/src/sources/SvmHyperSyncSource.res.mjs +3 -3
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +2 -1
- package/src/tui/components/CustomHooks.res +5 -4
- package/src/tui/components/CustomHooks.res.mjs +4 -2
package/src/PgStorage.res
CHANGED
|
@@ -20,60 +20,73 @@ let makeClient = () => {
|
|
|
20
20
|
)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
let formatSeconds = (timeRef: Performance.timeRef) =>
|
|
24
|
+
(Math.round(timeRef->Performance.secondsSince *. 100.) /. 100.)->Float.toString
|
|
25
|
+
|
|
26
|
+
// Every index build blocks writes to its table for as long as it runs, and on a
|
|
27
|
+
// large database that is not quick. Both build paths say so up front, so a
|
|
28
|
+
// stalled-looking indexer is explainable from the logs alone.
|
|
29
|
+
let slowOnLargeDatabaseNotice = "This can take a long time on a large database."
|
|
30
|
+
|
|
31
|
+
// Every index the entity schema promises: an `@index` field, a composite index,
|
|
32
|
+
// or the index backing a derived relationship. Deferred past the initial DDL and
|
|
33
|
+
// created in one transaction once backfill completes, so a resumed indexer that
|
|
34
|
+
// reports itself ready always has all of them.
|
|
35
|
+
//
|
|
36
|
+
// `entities` is the Postgres-backed set, and every `@derivedFrom` target within
|
|
37
|
+
// it resolves: config parsing rejects a Postgres entity deriving from one that
|
|
38
|
+
// isn't in Postgres (`validate_relationship_storage`).
|
|
39
|
+
let getSchemaIndexes = (~entities: array<Internal.entityConfig>): array<IndexDefinition.t> => {
|
|
40
|
+
let derivedSchema = Schema.make(entities->Array.map(e => e.table))
|
|
41
|
+
let all = []
|
|
42
|
+
|
|
43
|
+
entities->Array.forEach(({table}) => {
|
|
44
|
+
table
|
|
45
|
+
->Table.getSingleIndexes
|
|
46
|
+
->Array.forEach(column =>
|
|
47
|
+
all->Array.push(IndexDefinition.single(~tableName=table.tableName, ~column))->ignore
|
|
48
|
+
)
|
|
49
|
+
table
|
|
50
|
+
->Table.getCompositeIndexes
|
|
51
|
+
->Array.forEach(indexFields =>
|
|
52
|
+
all
|
|
53
|
+
->Array.push(IndexDefinition.fromIndexFields(~tableName=table.tableName, ~indexFields))
|
|
54
|
+
->ignore
|
|
55
|
+
)
|
|
56
|
+
})
|
|
30
57
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
entities->Array.forEach(({table}) =>
|
|
59
|
+
table
|
|
60
|
+
->Table.getDerivedFromFields
|
|
61
|
+
->Array.forEach(derivedFromField => {
|
|
62
|
+
let column =
|
|
63
|
+
derivedSchema->Schema.getDerivedFromPgFieldName(derivedFromField)->Utils.unwrapResultExn
|
|
64
|
+
all
|
|
65
|
+
->Array.push(IndexDefinition.single(~tableName=derivedFromField.derivedFromEntity, ~column))
|
|
66
|
+
->ignore
|
|
67
|
+
})
|
|
68
|
+
)
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
// An `@index` field and a derived relationship pointing at it describe the
|
|
71
|
+
// same index, so the list is deduped on identity rather than on name.
|
|
72
|
+
let seen = Utils.Set.make()
|
|
73
|
+
all->Array.filter(definition => {
|
|
74
|
+
let key = definition->IndexDefinition.key
|
|
75
|
+
if seen->Utils.Set.has(key) {
|
|
76
|
+
false
|
|
77
|
+
} else {
|
|
78
|
+
seen->Utils.Set.add(key)->ignore
|
|
79
|
+
true
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
42
83
|
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
~indexFields: array<Table.compositeIndexField>,
|
|
84
|
+
let makeCreateTableQuery = (
|
|
85
|
+
table: Table.table,
|
|
46
86
|
~pgSchema,
|
|
87
|
+
~isNumericArrayAsText,
|
|
88
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
47
89
|
) => {
|
|
48
|
-
let indexName =
|
|
49
|
-
tableName ++
|
|
50
|
-
"_" ++
|
|
51
|
-
indexFields
|
|
52
|
-
->Array.map(f => f.fieldName ++ directionToIndexName(f.direction))
|
|
53
|
-
->Array.joinUnsafe("_")
|
|
54
|
-
let index =
|
|
55
|
-
indexFields
|
|
56
|
-
->Array.map(f => `"${f.fieldName}"${directionToSql(f.direction)}`)
|
|
57
|
-
->Array.joinUnsafe(", ")
|
|
58
|
-
`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${pgSchema}"."${tableName}"(${index});`
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let makeCreateTableIndicesQuery = (table: Table.table, ~pgSchema) => {
|
|
62
|
-
let tableName = table.tableName
|
|
63
|
-
let createIndex = indexField =>
|
|
64
|
-
makeCreateIndexQuery(~tableName, ~indexFields=[indexField], ~pgSchema)
|
|
65
|
-
let createCompositeIndex = indexFields => {
|
|
66
|
-
makeCreateCompositeIndexQuery(~tableName, ~indexFields, ~pgSchema)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let singleIndices = table->Table.getSingleIndices
|
|
70
|
-
let compositeIndices = table->Table.getCompositeIndices
|
|
71
|
-
|
|
72
|
-
singleIndices->Array.map(createIndex)->Array.joinUnsafe("\n") ++
|
|
73
|
-
compositeIndices->Array.map(createCompositeIndex)->Array.joinUnsafe("\n")
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let makeCreateTableQuery = (table: Table.table, ~pgSchema, ~isNumericArrayAsText) => {
|
|
77
90
|
let fieldsMapped =
|
|
78
91
|
table
|
|
79
92
|
->Table.getFields
|
|
@@ -83,6 +96,7 @@ let makeCreateTableQuery = (table: Table.table, ~pgSchema, ~isNumericArrayAsText
|
|
|
83
96
|
|
|
84
97
|
{
|
|
85
98
|
`"${fieldName}" ${Table.getPgFieldType(
|
|
99
|
+
~chainIdMode,
|
|
86
100
|
~fieldType,
|
|
87
101
|
~pgSchema,
|
|
88
102
|
~isArray,
|
|
@@ -152,7 +166,7 @@ let getEntityHistory = (~entityConfig: Internal.entityConfig): EntityHistory.pgE
|
|
|
152
166
|
~entityName=entityTableName,
|
|
153
167
|
~entityIndex=entityConfig.index,
|
|
154
168
|
)
|
|
155
|
-
//ignore composite
|
|
169
|
+
//ignore composite indexes
|
|
156
170
|
let table = Table.mkTable(
|
|
157
171
|
historyTableName,
|
|
158
172
|
~fields=dataFields->Array.concat([checkpointIdField, actionField]),
|
|
@@ -183,6 +197,11 @@ let makeInitializeTransaction = (
|
|
|
183
197
|
~entities=[],
|
|
184
198
|
~enums=[],
|
|
185
199
|
~isEmptyPgSchema=false,
|
|
200
|
+
// Backfill writes are far cheaper without the schema's read indexes, so the
|
|
201
|
+
// initial DDL creates only tables, primary keys, views and chain rows; the
|
|
202
|
+
// rest is created by `finalizeBackfill` before the indexer reports ready.
|
|
203
|
+
~deferSchemaIndexes=false,
|
|
204
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
186
205
|
) => {
|
|
187
206
|
let generalTables = [
|
|
188
207
|
InternalTable.Chains.table,
|
|
@@ -192,13 +211,12 @@ let makeInitializeTransaction = (
|
|
|
192
211
|
]
|
|
193
212
|
|
|
194
213
|
let allTables = generalTables->Array.copy
|
|
195
|
-
let allEntityTables = []
|
|
196
214
|
entities->Array.forEach((entityConfig: Internal.entityConfig) => {
|
|
197
|
-
allEntityTables->Array.push(entityConfig.table)->ignore
|
|
198
215
|
allTables->Array.push(entityConfig.table)->ignore
|
|
199
216
|
allTables->Array.push(getEntityHistory(~entityConfig).table)->ignore
|
|
200
217
|
})
|
|
201
|
-
|
|
218
|
+
|
|
219
|
+
let schemaIndexes = getSchemaIndexes(~entities)
|
|
202
220
|
|
|
203
221
|
let query = ref(
|
|
204
222
|
(
|
|
@@ -229,34 +247,15 @@ GRANT ALL ON SCHEMA "${pgSchema}" TO public;`,
|
|
|
229
247
|
query :=
|
|
230
248
|
query.contents ++
|
|
231
249
|
"\n" ++
|
|
232
|
-
makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=isHasuraEnabled)
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
// Then batch all indices (better performance when tables exist)
|
|
236
|
-
allTables->Array.forEach((table: Table.table) => {
|
|
237
|
-
let indices = makeCreateTableIndicesQuery(table, ~pgSchema)
|
|
238
|
-
if indices !== "" {
|
|
239
|
-
query := query.contents ++ "\n" ++ indices
|
|
240
|
-
}
|
|
250
|
+
makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=isHasuraEnabled, ~chainIdMode)
|
|
241
251
|
})
|
|
242
252
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
->Array.forEach(derivedFromField => {
|
|
248
|
-
let indexField =
|
|
249
|
-
derivedSchema->Schema.getDerivedFromPgFieldName(derivedFromField)->Utils.unwrapResultExn
|
|
250
|
-
query :=
|
|
251
|
-
query.contents ++
|
|
252
|
-
"\n" ++
|
|
253
|
-
makeCreateIndexQuery(
|
|
254
|
-
~tableName=derivedFromField.derivedFromEntity,
|
|
255
|
-
~indexFields=[indexField],
|
|
256
|
-
~pgSchema,
|
|
257
|
-
)
|
|
253
|
+
// Then batch all indexes (better performance when tables exist)
|
|
254
|
+
if !deferSchemaIndexes {
|
|
255
|
+
schemaIndexes->Array.forEach(definition => {
|
|
256
|
+
query := query.contents ++ "\n" ++ definition->IndexDefinition.makeCreateQuery(~pgSchema)
|
|
258
257
|
})
|
|
259
|
-
}
|
|
258
|
+
}
|
|
260
259
|
|
|
261
260
|
// Create views for Hasura integration
|
|
262
261
|
query := query.contents ++ "\n" ++ InternalTable.Views.makeMetaViewQuery(~pgSchema)
|
|
@@ -366,9 +365,15 @@ let makeLoadAllQuery = (~pgSchema, ~tableName) => {
|
|
|
366
365
|
`SELECT * FROM "${pgSchema}"."${tableName}";`
|
|
367
366
|
}
|
|
368
367
|
|
|
369
|
-
let makeInsertUnnestSetQuery = (
|
|
368
|
+
let makeInsertUnnestSetQuery = (
|
|
369
|
+
~pgSchema,
|
|
370
|
+
~table: Table.table,
|
|
371
|
+
~itemSchema,
|
|
372
|
+
~isRawEvents,
|
|
373
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
374
|
+
) => {
|
|
370
375
|
let {quotedFieldNames, quotedNonPrimaryFieldNames, arrayFieldTypes} =
|
|
371
|
-
table->Table.toSqlParams(~schema=itemSchema, ~pgSchema)
|
|
376
|
+
table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
|
|
372
377
|
|
|
373
378
|
let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table)
|
|
374
379
|
|
|
@@ -396,9 +401,15 @@ SELECT * FROM unnest(${arrayFieldTypes
|
|
|
396
401
|
} ++ ";"
|
|
397
402
|
}
|
|
398
403
|
|
|
399
|
-
let makeInsertValuesSetQuery = (
|
|
404
|
+
let makeInsertValuesSetQuery = (
|
|
405
|
+
~pgSchema,
|
|
406
|
+
~table: Table.table,
|
|
407
|
+
~itemSchema,
|
|
408
|
+
~itemsCount,
|
|
409
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
410
|
+
) => {
|
|
400
411
|
let {quotedFieldNames, quotedNonPrimaryFieldNames} =
|
|
401
|
-
table->Table.toSqlParams(~schema=itemSchema, ~pgSchema)
|
|
412
|
+
table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
|
|
402
413
|
|
|
403
414
|
let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table)
|
|
404
415
|
let fieldsCount = quotedFieldNames->Array.length
|
|
@@ -441,8 +452,14 @@ VALUES${placeholders.contents}` ++
|
|
|
441
452
|
// Constants for chunking
|
|
442
453
|
let maxItemsPerQuery = 500
|
|
443
454
|
|
|
444
|
-
let makeTableBatchSetQuery = (
|
|
445
|
-
|
|
455
|
+
let makeTableBatchSetQuery = (
|
|
456
|
+
~pgSchema,
|
|
457
|
+
~table: Table.table,
|
|
458
|
+
~itemSchema: S.t<'item>,
|
|
459
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
460
|
+
) => {
|
|
461
|
+
let {dbSchema, hasArrayField} =
|
|
462
|
+
table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
|
|
446
463
|
|
|
447
464
|
// Should move this to a better place
|
|
448
465
|
// We need it for the isRawEvents check in makeTableBatchSet
|
|
@@ -466,7 +483,7 @@ let makeTableBatchSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema: S.t<'
|
|
|
466
483
|
|
|
467
484
|
if (isRawEvents || !hasArrayField) && !isHistoryUpdate {
|
|
468
485
|
{
|
|
469
|
-
"query": makeInsertUnnestSetQuery(~pgSchema, ~table, ~itemSchema, ~isRawEvents),
|
|
486
|
+
"query": makeInsertUnnestSetQuery(~pgSchema, ~table, ~itemSchema, ~isRawEvents, ~chainIdMode),
|
|
470
487
|
"convertOrThrow": S.compile(
|
|
471
488
|
S.unnest(dbSchema),
|
|
472
489
|
~input=Value,
|
|
@@ -483,6 +500,7 @@ let makeTableBatchSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema: S.t<'
|
|
|
483
500
|
~table,
|
|
484
501
|
~itemSchema,
|
|
485
502
|
~itemsCount=maxItemsPerQuery,
|
|
503
|
+
~chainIdMode,
|
|
486
504
|
),
|
|
487
505
|
"convertOrThrow": S.compile(
|
|
488
506
|
S.unnest(itemSchema)->S.preprocess(_ => {
|
|
@@ -571,7 +589,14 @@ let classifyWriteError = (~specificError: ref<option<exn>>, ~table: Table.table,
|
|
|
571
589
|
|
|
572
590
|
// WeakMap for caching table batch set queries
|
|
573
591
|
let setQueryCache = Utils.WeakMap.make()
|
|
574
|
-
let setOrThrow = async (
|
|
592
|
+
let setOrThrow = async (
|
|
593
|
+
sql,
|
|
594
|
+
~items,
|
|
595
|
+
~table: Table.table,
|
|
596
|
+
~itemSchema,
|
|
597
|
+
~pgSchema,
|
|
598
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
599
|
+
) => {
|
|
575
600
|
if items->Array.length === 0 {
|
|
576
601
|
()
|
|
577
602
|
} else {
|
|
@@ -583,6 +608,7 @@ let setOrThrow = async (sql, ~items, ~table: Table.table, ~itemSchema, ~pgSchema
|
|
|
583
608
|
~pgSchema,
|
|
584
609
|
~table,
|
|
585
610
|
~itemSchema=itemSchema->S.toUnknown,
|
|
611
|
+
~chainIdMode,
|
|
586
612
|
)
|
|
587
613
|
setQueryCache->Utils.WeakMap.set(table, newQuery)->ignore
|
|
588
614
|
newQuery
|
|
@@ -603,7 +629,13 @@ let setOrThrow = async (sql, ~items, ~table: Table.table, ~itemSchema, ~pgSchema
|
|
|
603
629
|
let response = isFullChunk
|
|
604
630
|
? sql->Postgres.preparedUnsafe(data["query"], params)
|
|
605
631
|
: sql->Postgres.unpreparedUnsafe(
|
|
606
|
-
makeInsertValuesSetQuery(
|
|
632
|
+
makeInsertValuesSetQuery(
|
|
633
|
+
~pgSchema,
|
|
634
|
+
~table,
|
|
635
|
+
~itemSchema,
|
|
636
|
+
~itemsCount=chunkSize,
|
|
637
|
+
~chainIdMode,
|
|
638
|
+
),
|
|
607
639
|
params,
|
|
608
640
|
)
|
|
609
641
|
responses->Array.push(response)->ignore
|
|
@@ -857,6 +889,7 @@ let rec writeBatch = async (
|
|
|
857
889
|
~escapeTables=?,
|
|
858
890
|
) => {
|
|
859
891
|
try {
|
|
892
|
+
let chainIdMode = config.chainIdMode
|
|
860
893
|
let shouldSaveHistory = config->Config.shouldSaveHistory(~isInReorgThreshold)
|
|
861
894
|
|
|
862
895
|
let specificError = ref(None)
|
|
@@ -871,7 +904,7 @@ let rec writeBatch = async (
|
|
|
871
904
|
| Internal.Event(_) =>
|
|
872
905
|
let coordinate = `${item
|
|
873
906
|
->Internal.getItemChainId
|
|
874
|
-
->
|
|
907
|
+
->ChainId.toString}-${item
|
|
875
908
|
->Internal.getItemBlockNumber
|
|
876
909
|
->Int.toString}-${item->Internal.getItemLogIndex->Int.toString}`
|
|
877
910
|
if seenLogCoordinates->Utils.Set.has(coordinate) {
|
|
@@ -901,6 +934,7 @@ let rec writeBatch = async (
|
|
|
901
934
|
~table=InternalTable.RawEvents.table,
|
|
902
935
|
~itemSchema=InternalTable.RawEvents.schema,
|
|
903
936
|
~pgSchema,
|
|
937
|
+
~chainIdMode,
|
|
904
938
|
)
|
|
905
939
|
}, ~items=rawEvents)
|
|
906
940
|
} catch {
|
|
@@ -1020,6 +1054,7 @@ let rec writeBatch = async (
|
|
|
1020
1054
|
~itemSchema=entityHistory.setChangeSchema,
|
|
1021
1055
|
~table=entityHistory.table,
|
|
1022
1056
|
~pgSchema,
|
|
1057
|
+
~chainIdMode,
|
|
1023
1058
|
),
|
|
1024
1059
|
)
|
|
1025
1060
|
->ignore
|
|
@@ -1036,6 +1071,7 @@ let rec writeBatch = async (
|
|
|
1036
1071
|
~table=entityConfig.table,
|
|
1037
1072
|
~itemSchema=entityConfig.schema,
|
|
1038
1073
|
~pgSchema,
|
|
1074
|
+
~chainIdMode,
|
|
1039
1075
|
),
|
|
1040
1076
|
)
|
|
1041
1077
|
}
|
|
@@ -1067,14 +1103,19 @@ let rec writeBatch = async (
|
|
|
1067
1103
|
| Some({targetCheckpointId: rollbackTargetCheckpointId}) =>
|
|
1068
1104
|
Some(
|
|
1069
1105
|
sql => {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1106
|
+
// Postgres owns history tables only for Postgres-backed entities;
|
|
1107
|
+
// ClickHouse-only entities have none to roll back.
|
|
1108
|
+
let promises =
|
|
1109
|
+
allEntities
|
|
1110
|
+
->Array.filter(entityConfig => entityConfig.storage.postgres)
|
|
1111
|
+
->Array.map(entityConfig => {
|
|
1112
|
+
sql->EntityHistory.rollback(
|
|
1113
|
+
~pgSchema,
|
|
1114
|
+
~entityName=entityConfig.name,
|
|
1115
|
+
~entityIndex=entityConfig.index,
|
|
1116
|
+
~rollbackTargetCheckpointId,
|
|
1117
|
+
)
|
|
1118
|
+
})
|
|
1078
1119
|
promises
|
|
1079
1120
|
->Array.push(
|
|
1080
1121
|
sql->InternalTable.Checkpoints.rollback(~pgSchema, ~rollbackTargetCheckpointId),
|
|
@@ -1131,6 +1172,7 @@ let rec writeBatch = async (
|
|
|
1131
1172
|
~checkpointBlockNumbers=batch.checkpointBlockNumbers,
|
|
1132
1173
|
~checkpointBlockHashes=batch.checkpointBlockHashes,
|
|
1133
1174
|
~checkpointEventsProcessed=batch.checkpointEventsProcessed,
|
|
1175
|
+
~chainIdMode,
|
|
1134
1176
|
)
|
|
1135
1177
|
)
|
|
1136
1178
|
}
|
|
@@ -1277,6 +1319,7 @@ let make = (
|
|
|
1277
1319
|
~pgDatabase,
|
|
1278
1320
|
~pgPassword,
|
|
1279
1321
|
~isHasuraEnabled,
|
|
1322
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
1280
1323
|
~sink: option<Sink.t>=?,
|
|
1281
1324
|
~onInitialize=?,
|
|
1282
1325
|
~onNewTables=?,
|
|
@@ -1293,6 +1336,44 @@ let make = (
|
|
|
1293
1336
|
"cache",
|
|
1294
1337
|
])
|
|
1295
1338
|
|
|
1339
|
+
// The metric label, the `storage` field on this backend's logs, and the
|
|
1340
|
+
// storage record's own name are all the same string.
|
|
1341
|
+
let storageName = "postgres"
|
|
1342
|
+
|
|
1343
|
+
let indexManager = IndexManager.make()
|
|
1344
|
+
|
|
1345
|
+
let loadCatalogRows = (sql, ~indexName=?) =>
|
|
1346
|
+
sql
|
|
1347
|
+
->Postgres.unsafe(IndexCatalog.makeQuery(~pgSchema, ~indexName?))
|
|
1348
|
+
->Promise.thenResolve(rows => rows->S.parseOrThrow(IndexCatalog.rowsSchema))
|
|
1349
|
+
|
|
1350
|
+
// The whole-schema snapshot, taken on a clean initialize and on every resume.
|
|
1351
|
+
// Individual indexes are re-read from the catalog as they are built, so this
|
|
1352
|
+
// is the only point where the full listing is needed.
|
|
1353
|
+
let refreshIndexCatalog = async () => {
|
|
1354
|
+
let rows = await sql->loadCatalogRows
|
|
1355
|
+
indexManager->IndexManager.reload(~rows)
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
let reloadIndexCatalog = async () => {
|
|
1359
|
+
let catalog = await refreshIndexCatalog()
|
|
1360
|
+
switch catalog->IndexCatalog.invalidNames {
|
|
1361
|
+
| [] => ()
|
|
1362
|
+
| invalidIndexNames =>
|
|
1363
|
+
Logging.warn({
|
|
1364
|
+
"storage": storageName,
|
|
1365
|
+
"msg": `Ignoring invalid PostgreSQL indexes in schema "${pgSchema}". They can't serve queries, so the indexer builds its own alongside them.`,
|
|
1366
|
+
"indexes": invalidIndexNames,
|
|
1367
|
+
})
|
|
1368
|
+
}
|
|
1369
|
+
Logging.info({
|
|
1370
|
+
"storage": storageName,
|
|
1371
|
+
"msg": `Found ${catalog
|
|
1372
|
+
->IndexCatalog.size
|
|
1373
|
+
->Int.toString} existing indexes in the indexer schema.`,
|
|
1374
|
+
})
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1296
1377
|
let isInitialized = async () => {
|
|
1297
1378
|
let envioTables = await sql->Postgres.unsafe(
|
|
1298
1379
|
`SELECT table_schema FROM information_schema.tables WHERE table_schema = '${pgSchema}' AND (table_name = '${// This is for indexer before envio@2.28
|
|
@@ -1484,6 +1565,8 @@ let make = (
|
|
|
1484
1565
|
~chainConfigs,
|
|
1485
1566
|
~isEmptyPgSchema=schemaTableNames->Utils.Array.isEmpty,
|
|
1486
1567
|
~isHasuraEnabled,
|
|
1568
|
+
~deferSchemaIndexes=true,
|
|
1569
|
+
~chainIdMode,
|
|
1487
1570
|
)
|
|
1488
1571
|
// Execute all queries within a single transaction for integrity.
|
|
1489
1572
|
// The envio_info row is written in the same transaction so a successful
|
|
@@ -1511,15 +1594,25 @@ let make = (
|
|
|
1511
1594
|
})
|
|
1512
1595
|
})
|
|
1513
1596
|
if ids->Array.length > 0 {
|
|
1597
|
+
let addrChainIdArrayType = Table.getPgFieldType(
|
|
1598
|
+
~fieldType=ChainId,
|
|
1599
|
+
~pgSchema,
|
|
1600
|
+
~isArray=true,
|
|
1601
|
+
~isNumericArrayAsText=false,
|
|
1602
|
+
~isNullable=false,
|
|
1603
|
+
~chainIdMode,
|
|
1604
|
+
)
|
|
1514
1605
|
await sql->Postgres.unpreparedUnsafe(
|
|
1515
1606
|
`INSERT INTO "${pgSchema}"."${Config.EnvioAddresses.table.tableName}" ("id", "chain_id", "registration_block", "registration_log_index", "contract_name")
|
|
1516
|
-
SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2
|
|
1607
|
+
SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChainIdArrayType},$3::text[]) AS t(id, chain_id, contract_name);`,
|
|
1517
1608
|
(ids, addrChainIds, addrContractNames)->(Utils.magic: _ => unknown),
|
|
1518
1609
|
)
|
|
1519
1610
|
}
|
|
1520
1611
|
|
|
1521
1612
|
let cache = await restoreEffectCache(~withUpload=true)
|
|
1522
1613
|
|
|
1614
|
+
await reloadIndexCatalog()
|
|
1615
|
+
|
|
1523
1616
|
// Integration with other tools like Hasura
|
|
1524
1617
|
switch onInitialize {
|
|
1525
1618
|
| Some(onInitialize) => await onInitialize()
|
|
@@ -1576,6 +1669,253 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3::
|
|
|
1576
1669
|
}
|
|
1577
1670
|
}
|
|
1578
1671
|
|
|
1672
|
+
// The physical columns a filter reads, deduped and in first-seen order.
|
|
1673
|
+
// Unknown field names are left to `loadOrThrow`, which reports them properly.
|
|
1674
|
+
let filterColumns = (~table: Table.table, ~filters: array<EntityFilter.t>) => {
|
|
1675
|
+
let queryFields = table->Table.queryFields
|
|
1676
|
+
let columns = []
|
|
1677
|
+
let seen = Utils.Set.make()
|
|
1678
|
+
let rec collect = (filter: EntityFilter.t) =>
|
|
1679
|
+
switch filter {
|
|
1680
|
+
| Eq({fieldName}) | Gt({fieldName}) | Lt({fieldName}) | In({fieldName}) =>
|
|
1681
|
+
switch queryFields->Utils.Dict.dangerouslyGetNonOption(fieldName) {
|
|
1682
|
+
| Some({pgDbFieldName}) =>
|
|
1683
|
+
if !(seen->Utils.Set.has(pgDbFieldName)) {
|
|
1684
|
+
seen->Utils.Set.add(pgDbFieldName)->ignore
|
|
1685
|
+
columns->Array.push(pgDbFieldName)->ignore
|
|
1686
|
+
}
|
|
1687
|
+
| None => ()
|
|
1688
|
+
}
|
|
1689
|
+
| And({filters}) => filters->Array.forEach(collect)
|
|
1690
|
+
}
|
|
1691
|
+
filters->Array.forEach(collect)
|
|
1692
|
+
columns
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// Runs a prepared build's DDL and reads the index back from pg_catalog before
|
|
1696
|
+
// it counts as existing. `sql` is the transaction's handle when there is one,
|
|
1697
|
+
// so a failed verification rolls the DDL back with it.
|
|
1698
|
+
let runAndVerify = async (sql, prepared: IndexManager.prepared) => {
|
|
1699
|
+
// Sequential rather than Promise.all: inside a transaction they share one
|
|
1700
|
+
// connection, and a rebuild's DROP has to land before its CREATE.
|
|
1701
|
+
for idx in 0 to prepared.queries->Array.length - 1 {
|
|
1702
|
+
let _ = await sql->Postgres.unsafe(prepared.queries->Array.getUnsafe(idx))
|
|
1703
|
+
}
|
|
1704
|
+
let rows = await sql->loadCatalogRows(~indexName=prepared.name)
|
|
1705
|
+
prepared->IndexManager.verifyOrThrow(~rows, ~pgSchema)
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// A build outside a transaction can commit its DDL and still fail — the
|
|
1709
|
+
// read-back is a second round trip. Re-reading the index puts the catalog
|
|
1710
|
+
// back in step, so the next attempt plans against what the database holds
|
|
1711
|
+
// instead of retrying a create that can only raise "already exists".
|
|
1712
|
+
//
|
|
1713
|
+
// If this read fails too, the next attempt does waste a create before landing
|
|
1714
|
+
// here again — bounded, and it recovers as soon as the database answers.
|
|
1715
|
+
let resyncIndex = async name =>
|
|
1716
|
+
switch await sql->loadCatalogRows(~indexName=name) {
|
|
1717
|
+
| rows => indexManager->IndexManager.resync(~name, ~rows)
|
|
1718
|
+
| exception exn =>
|
|
1719
|
+
Logging.debug({
|
|
1720
|
+
"storage": storageName,
|
|
1721
|
+
"msg": `Could not re-read the index "${name}" after a failed build. The next attempt reads it again.`,
|
|
1722
|
+
"err": exn->Utils.prettifyExn,
|
|
1723
|
+
})
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
let ensureQueryIndexes = async (~table: Table.table, ~filters: array<EntityFilter.t>) => {
|
|
1727
|
+
let columns = filterColumns(~table, ~filters)
|
|
1728
|
+
let _ = await columns
|
|
1729
|
+
->Array.map(column => {
|
|
1730
|
+
let definition = IndexDefinition.single(~tableName=table.tableName, ~column)
|
|
1731
|
+
indexManager
|
|
1732
|
+
->IndexManager.ensure(~definition, ~coverage=LeadingColumns, ~build=async () => {
|
|
1733
|
+
// Resolved before logging so a rebuild is reported as one, and an
|
|
1734
|
+
// unrelated index holding the name fails before any DDL runs.
|
|
1735
|
+
switch indexManager->IndexManager.prepare(
|
|
1736
|
+
~definition,
|
|
1737
|
+
~coverage=LeadingColumns,
|
|
1738
|
+
~pgSchema,
|
|
1739
|
+
) {
|
|
1740
|
+
| None => ()
|
|
1741
|
+
| Some(prepared) =>
|
|
1742
|
+
let verb = prepared.isRebuild ? "Rebuilding unusable index" : "Creating index"
|
|
1743
|
+
// Logged from inside the build so it reports the one request that
|
|
1744
|
+
// actually creates the index, not the ones waiting on it.
|
|
1745
|
+
Logging.info({
|
|
1746
|
+
"storage": storageName,
|
|
1747
|
+
"msg": `${verb} "${prepared.name}" to serve a getWhere query on "${table.tableName}". Writes to the table are paused until it completes. ${slowOnLargeDatabaseNotice}`,
|
|
1748
|
+
})
|
|
1749
|
+
let timeRef = Performance.now()
|
|
1750
|
+
let entry = await sql->runAndVerify(prepared)
|
|
1751
|
+
indexManager->IndexManager.record(entry)
|
|
1752
|
+
Logging.info({
|
|
1753
|
+
"storage": storageName,
|
|
1754
|
+
"msg": `Index "${prepared.name}" is ready after ${timeRef->formatSeconds}s. Resuming indexing.`,
|
|
1755
|
+
})
|
|
1756
|
+
}
|
|
1757
|
+
})
|
|
1758
|
+
// A failed build records nothing, so the next getWhere retries. Meanwhile
|
|
1759
|
+
// the query still runs — just without the index.
|
|
1760
|
+
->Promise.catch(async exn => {
|
|
1761
|
+
Logging.warn({
|
|
1762
|
+
"storage": storageName,
|
|
1763
|
+
"msg": `Failed to create an index on "${table.tableName}"("${column}") for a getWhere query. The query runs without it.`,
|
|
1764
|
+
"err": exn->Utils.prettifyExn,
|
|
1765
|
+
})
|
|
1766
|
+
await resyncIndex(definition->IndexDefinition.name)
|
|
1767
|
+
})
|
|
1768
|
+
})
|
|
1769
|
+
->Promise.all
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// Goes through `IndexManager.ensure`, unlike `finalizeBackfill` below: that
|
|
1773
|
+
// one runs with processing paused, while this runs on a resumed indexer that
|
|
1774
|
+
// is already ready, so handlers may be issuing getWhere queries alongside it
|
|
1775
|
+
// and the per-table queues are what keep the two from colliding. Nothing here
|
|
1776
|
+
// writes `ready_at` — the chains already carry theirs.
|
|
1777
|
+
let ensureSchemaIndexes = async (~entities: array<Internal.entityConfig>) => {
|
|
1778
|
+
let schemaIndexes = getSchemaIndexes(
|
|
1779
|
+
~entities=entities->Array.filter((e: Internal.entityConfig) => e.storage.postgres),
|
|
1780
|
+
)
|
|
1781
|
+
|
|
1782
|
+
let _ = await schemaIndexes
|
|
1783
|
+
->Array.map(definition =>
|
|
1784
|
+
indexManager
|
|
1785
|
+
->IndexManager.ensure(~definition, ~coverage=Exact, ~build=async () => {
|
|
1786
|
+
switch indexManager->IndexManager.prepare(~definition, ~coverage=Exact, ~pgSchema) {
|
|
1787
|
+
| None => ()
|
|
1788
|
+
| Some(prepared) =>
|
|
1789
|
+
let verb = prepared.isRebuild ? "Rebuilding unusable index" : "Creating missing index"
|
|
1790
|
+
Logging.info({
|
|
1791
|
+
"storage": storageName,
|
|
1792
|
+
"msg": `${verb} "${prepared.name}" the schema promises but the database no longer has. Writes to the table are paused until it completes. ${slowOnLargeDatabaseNotice}`,
|
|
1793
|
+
})
|
|
1794
|
+
let timeRef = Performance.now()
|
|
1795
|
+
let entry = await sql->runAndVerify(prepared)
|
|
1796
|
+
indexManager->IndexManager.record(entry)
|
|
1797
|
+
Logging.info({
|
|
1798
|
+
"storage": storageName,
|
|
1799
|
+
"msg": `Index "${prepared.name}" is ready after ${timeRef->formatSeconds}s.`,
|
|
1800
|
+
})
|
|
1801
|
+
}
|
|
1802
|
+
})
|
|
1803
|
+
->Promise.catch(async exn => {
|
|
1804
|
+
Logging.warn({
|
|
1805
|
+
"storage": storageName,
|
|
1806
|
+
"msg": `Failed to restore the schema index "${definition->IndexDefinition.name}". Queries relying on it run unindexed until the next restart.`,
|
|
1807
|
+
"err": exn->Utils.prettifyExn,
|
|
1808
|
+
})
|
|
1809
|
+
await resyncIndex(definition->IndexDefinition.name)
|
|
1810
|
+
})
|
|
1811
|
+
)
|
|
1812
|
+
->Promise.all
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Unlike `ensureQueryIndexes`, this doesn't go through `IndexManager.ensure`.
|
|
1816
|
+
// It's safe because the caller guarantees exclusivity — `FinalizeBackfill.run`
|
|
1817
|
+
// is reached from the processing loop with processing already paused, so no
|
|
1818
|
+
// handler can be running a getWhere.
|
|
1819
|
+
//
|
|
1820
|
+
// Each index is built on its own rather than in one transaction with
|
|
1821
|
+
// `ready_at`: a build that dies half way through a large schema would
|
|
1822
|
+
// otherwise roll back every index before it and make the retry start over.
|
|
1823
|
+
let finalizeBackfill = async (
|
|
1824
|
+
~entities: array<Internal.entityConfig>,
|
|
1825
|
+
~chainIds: array<ChainId.t>,
|
|
1826
|
+
~readyAt: Date.t,
|
|
1827
|
+
) => {
|
|
1828
|
+
let schemaIndexes = getSchemaIndexes(
|
|
1829
|
+
~entities=entities->Array.filter((e: Internal.entityConfig) => e.storage.postgres),
|
|
1830
|
+
)
|
|
1831
|
+
|
|
1832
|
+
// Resolved up front so a name held by an unrelated index fails before any
|
|
1833
|
+
// DDL runs, rather than part way through the set.
|
|
1834
|
+
//
|
|
1835
|
+
// `Exact`, so the set built here is decided by the schema alone: matching a
|
|
1836
|
+
// declared index against a composite that happens to lead with the same
|
|
1837
|
+
// column would make a fresh database and an upgraded one end up holding
|
|
1838
|
+
// different tables.
|
|
1839
|
+
let missing =
|
|
1840
|
+
schemaIndexes->Array.filterMap(definition =>
|
|
1841
|
+
indexManager->IndexManager.prepare(~definition, ~coverage=Exact, ~pgSchema)
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
switch missing->Array.filter((prepared: IndexManager.prepared) => prepared.isRebuild) {
|
|
1845
|
+
| [] => ()
|
|
1846
|
+
| rebuilt =>
|
|
1847
|
+
Logging.warn({
|
|
1848
|
+
"storage": storageName,
|
|
1849
|
+
"msg": `PostgreSQL reports ${rebuilt
|
|
1850
|
+
->Array.length
|
|
1851
|
+
->Int.toString} of the indexer's own indexes as invalid, so they can't serve queries. Rebuilding them.`,
|
|
1852
|
+
"indexes": rebuilt->Array.map((prepared: IndexManager.prepared) => prepared.name),
|
|
1853
|
+
})
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
switch missing {
|
|
1857
|
+
| [] =>
|
|
1858
|
+
Logging.info({
|
|
1859
|
+
"storage": storageName,
|
|
1860
|
+
"msg": `All ${schemaIndexes
|
|
1861
|
+
->Array.length
|
|
1862
|
+
->Int.toString} schema indexes are already in place. Marking the indexer ready.`,
|
|
1863
|
+
})
|
|
1864
|
+
| _ =>
|
|
1865
|
+
Logging.info({
|
|
1866
|
+
"storage": storageName,
|
|
1867
|
+
"msg": `Creating the ${missing
|
|
1868
|
+
->Array.length
|
|
1869
|
+
->Int.toString} remaining schema indexes before the indexer reports ready. Writes are paused until they are committed. ${slowOnLargeDatabaseNotice}`,
|
|
1870
|
+
"indexes": missing->Array.map((prepared: IndexManager.prepared) => prepared.name),
|
|
1871
|
+
})
|
|
1872
|
+
}
|
|
1873
|
+
let timeRef = Performance.now()
|
|
1874
|
+
|
|
1875
|
+
// Sequential, one committed index at a time: whatever is built before a
|
|
1876
|
+
// failure stays built and recorded, so the retry owes only the rest.
|
|
1877
|
+
for idx in 0 to missing->Array.length - 1 {
|
|
1878
|
+
let prepared = missing->Array.getUnsafe(idx)
|
|
1879
|
+
switch await sql->runAndVerify(prepared) {
|
|
1880
|
+
| entry => indexManager->IndexManager.record(entry)
|
|
1881
|
+
| exception exn =>
|
|
1882
|
+
// The DDL is outside a transaction, so a create that commits and then
|
|
1883
|
+
// fails its read-back leaves the index in place and unrecorded.
|
|
1884
|
+
// Re-reading it means the retry plans against the database rather than
|
|
1885
|
+
// replaying a create that can only raise "already exists".
|
|
1886
|
+
await resyncIndex(prepared.name)
|
|
1887
|
+
throw(exn)
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
// Reached only once every definition is verified against pg_catalog, so a
|
|
1892
|
+
// crash either leaves `ready_at` null and the retry finds the indexes
|
|
1893
|
+
// already built, or commits readiness the schema backs.
|
|
1894
|
+
//
|
|
1895
|
+
// One transaction for the whole set: readiness is an indexer-wide fact, and
|
|
1896
|
+
// a crash part way through would otherwise leave some chains stamped and
|
|
1897
|
+
// some not, reporting the indexer as half ready.
|
|
1898
|
+
let setReadyAtQuery = InternalTable.Chains.makeSetReadyAtQuery(~pgSchema)
|
|
1899
|
+
let _ = await sql->Postgres.beginSql(async sql => {
|
|
1900
|
+
for idx in 0 to chainIds->Array.length - 1 {
|
|
1901
|
+
let _ = await sql->Postgres.preparedUnsafe(
|
|
1902
|
+
setReadyAtQuery,
|
|
1903
|
+
[
|
|
1904
|
+
readyAt->(Utils.magic: Date.t => unknown),
|
|
1905
|
+
chainIds->Array.getUnsafe(idx)->(Utils.magic: ChainId.t => unknown),
|
|
1906
|
+
]->(Utils.magic: array<unknown> => unknown),
|
|
1907
|
+
)
|
|
1908
|
+
}
|
|
1909
|
+
})
|
|
1910
|
+
|
|
1911
|
+
Logging.info({
|
|
1912
|
+
"storage": storageName,
|
|
1913
|
+
"msg": `Committed ${missing
|
|
1914
|
+
->Array.length
|
|
1915
|
+
->Int.toString} schema indexes and the ready timestamp in ${timeRef->formatSeconds}s.`,
|
|
1916
|
+
})
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1579
1919
|
let setOrThrow = (
|
|
1580
1920
|
type item,
|
|
1581
1921
|
~items: array<item>,
|
|
@@ -1720,12 +2060,14 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3::
|
|
|
1720
2060
|
InternalTable.EnvioInfo.read(sql, ~pgSchema),
|
|
1721
2061
|
))
|
|
1722
2062
|
|
|
2063
|
+
await reloadIndexCatalog()
|
|
2064
|
+
|
|
1723
2065
|
let checkpointId = (checkpointIdResult->Array.getUnsafe(0))["id"]->BigInt.fromStringOrThrow
|
|
1724
2066
|
|
|
1725
2067
|
// Convert string checkpoint IDs from DB to bigint
|
|
1726
2068
|
let reorgCheckpoints = Array.map(reorgCheckpoints, (raw): Internal.reorgCheckpoint => {
|
|
1727
2069
|
checkpointId: raw["id"]->BigInt.fromStringOrThrow,
|
|
1728
|
-
chainId: raw["chain_id"],
|
|
2070
|
+
chainId: raw["chain_id"]->ChainId.normalizeOrThrow,
|
|
1729
2071
|
blockNumber: raw["block_number"],
|
|
1730
2072
|
blockHash: raw["block_hash"],
|
|
1731
2073
|
})
|
|
@@ -1869,17 +2211,20 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3::
|
|
|
1869
2211
|
~sinkPromise,
|
|
1870
2212
|
~chainMetaData,
|
|
1871
2213
|
)
|
|
1872
|
-
onWrite(~storage=
|
|
2214
|
+
onWrite(~storage=storageName, ~timeSeconds=primaryTimerRef->Performance.secondsSince)
|
|
1873
2215
|
}
|
|
1874
2216
|
|
|
1875
2217
|
let close = () => sql->Postgres.endSql
|
|
1876
2218
|
|
|
1877
2219
|
{
|
|
1878
|
-
name:
|
|
2220
|
+
name: storageName,
|
|
1879
2221
|
isInitialized,
|
|
1880
2222
|
initialize,
|
|
1881
2223
|
resumeInitialState,
|
|
1882
2224
|
loadOrThrow,
|
|
2225
|
+
ensureQueryIndexes,
|
|
2226
|
+
ensureSchemaIndexes,
|
|
2227
|
+
finalizeBackfill,
|
|
1883
2228
|
dumpEffectCache,
|
|
1884
2229
|
reset,
|
|
1885
2230
|
setChainMeta,
|
|
@@ -1907,6 +2252,7 @@ let makeStorageFromEnv = (
|
|
|
1907
2252
|
~pgPort=Env.Db.port,
|
|
1908
2253
|
~pgDatabase=Env.Db.database,
|
|
1909
2254
|
~pgPassword=Env.Db.password,
|
|
2255
|
+
~chainIdMode=config.chainIdMode,
|
|
1910
2256
|
~sink=?{
|
|
1911
2257
|
// Internally ClickHouse storage is implemented as a sync of the
|
|
1912
2258
|
// Postgres storage. Required env vars are validated here only when
|
|
@@ -1939,6 +2285,7 @@ let makeStorageFromEnv = (
|
|
|
1939
2285
|
~database=database->Option.getUnsafe,
|
|
1940
2286
|
~username=username->Option.getUnsafe,
|
|
1941
2287
|
~password=password->Option.getUnsafe,
|
|
2288
|
+
~chainIdMode=config.chainIdMode,
|
|
1942
2289
|
),
|
|
1943
2290
|
)
|
|
1944
2291
|
} else {
|