envio 3.15.0 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -34
- package/evm.schema.json +11 -2
- package/fuel.schema.json +10 -1
- package/index.d.ts +6 -0
- package/licenses/README.md +1 -1
- package/package.json +8 -8
- package/src/Batch.res.mjs +5 -5
- package/src/BatchProcessing.res +1 -0
- package/src/BatchProcessing.res.mjs +1 -0
- package/src/ChainState.res +48 -7
- package/src/ChainState.res.mjs +27 -11
- package/src/Clock.res +57 -0
- package/src/Clock.res.mjs +72 -0
- package/src/Config.res +10 -3
- package/src/Config.res.mjs +4 -2
- package/src/Core.res +4 -40
- package/src/EffectState.res +10 -2
- package/src/EffectState.res.mjs +8 -4
- package/src/EffectState.resi +3 -1
- package/src/EntityId.res +2 -7
- package/src/EntityId.res.mjs +1 -8
- package/src/EntityTables.res +4 -1
- package/src/EntityTables.res.mjs +1 -1
- package/src/FetchState.res +157 -69
- package/src/FetchState.res.mjs +144 -49
- package/src/FinalizeBackfill.res +2 -4
- package/src/FinalizeBackfill.res.mjs +3 -4
- package/src/InMemoryTable.res +17 -16
- package/src/InMemoryTable.res.mjs +14 -15
- package/src/IndexerState.res +28 -4
- package/src/IndexerState.res.mjs +19 -6
- package/src/IndexerState.resi +4 -0
- package/src/Internal.res +0 -49
- package/src/Internal.res.mjs +1 -80
- package/src/LoadLayer.res +14 -8
- package/src/LoadLayer.res.mjs +8 -6
- package/src/Main.res.mjs +1 -1
- package/src/Persistence.res +3 -23
- package/src/Persistence.res.mjs +3 -3
- package/src/Rollback.res +0 -1
- package/src/Rollback.res.mjs +1 -1
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +1 -1
- package/src/StagedRows.res +106 -0
- package/src/StagedRows.res.mjs +102 -0
- package/src/Staging.res +32 -14
- package/src/Staging.res.mjs +17 -20
- package/src/Storage.res +277 -948
- package/src/Storage.res.mjs +215 -707
- package/src/Supervisor.res +75 -56
- package/src/Supervisor.res.mjs +55 -26
- package/src/TestIndexer.res +22 -27
- package/src/TestIndexer.res.mjs +20 -17
- package/src/Throttler.res +15 -8
- package/src/Throttler.res.mjs +9 -6
- package/src/Throttler.resi +1 -1
- package/src/Utils.res +0 -6
- package/src/Utils.res.mjs +0 -5
- package/src/Writing.res +0 -1
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +0 -322
- package/src/bindings/ClickHouse.res.mjs +0 -338
- package/src/bindings/StorageClient.res +107 -174
- package/src/bindings/StorageClient.res.mjs +19 -8
- package/src/bindings/Vitest.res +0 -26
- package/src/bindings/Vitest.res.mjs +0 -3
- package/src/db/Table.res +24 -150
- package/src/db/Table.res.mjs +38 -272
- package/src/sources/ChainSources.res +2 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +2 -2
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/EvmRpcClient.res +13 -3
- package/src/sources/EvmRpcClient.res.mjs +4 -3
- package/src/sources/EvmRpcWs.res +4 -3
- package/src/sources/EvmRpcWs.res.mjs +2 -2
- package/src/sources/HeightFeed.res +31 -10
- package/src/sources/HeightFeed.res.mjs +10 -6
- package/src/sources/HeightStream.res +32 -17
- package/src/sources/HeightStream.res.mjs +10 -10
- package/src/sources/HyperSyncSSE.res +4 -6
- package/src/sources/HyperSyncSSE.res.mjs +2 -2
- package/src/sources/RpcSource.res +10 -1
- package/src/sources/RpcSource.res.mjs +4 -3
- package/src/sources/Source.res +3 -0
- package/src/sources/SourceManager.res +84 -77
- package/src/sources/SourceManager.res.mjs +17 -20
- package/src/sources/SourceManager.resi +3 -4
- package/src/sources/StartBlockResolver.res +1 -0
- package/src/sources/StartBlockResolver.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +2 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/svm.schema.json +11 -2
- package/src/PgWriting.res +0 -36
- package/src/PgWriting.res.mjs +0 -34
- package/src/db/IndexDefinition.res +0 -47
- package/src/db/IndexDefinition.res.mjs +0 -67
package/src/Utils.res
CHANGED
|
@@ -10,12 +10,6 @@ external toMethod: 'a => 'a = "%unsafe_to_method"
|
|
|
10
10
|
|
|
11
11
|
@val external importPath: string => promise<unknown> = "import"
|
|
12
12
|
|
|
13
|
-
// Half the delay fixed, half spread across it. Every indexer pointed at one
|
|
14
|
-
// provider loses its stream, or gives up on a quiet chain, in the same instant
|
|
15
|
-
// that provider blinks, and putting them all back on the same schedule is how a
|
|
16
|
-
// blip becomes a stampede.
|
|
17
|
-
let jitter = delay => delay / 2 + (Math.random() *. (delay / 2)->Int.toFloat)->Float.toInt
|
|
18
|
-
|
|
19
13
|
// Clearing a pending timer and forgetting it are one action. A ref left holding
|
|
20
14
|
// a spent id reads as a timer still pending, and the next clear looks like it
|
|
21
15
|
// did something.
|
package/src/Utils.res.mjs
CHANGED
|
@@ -13,10 +13,6 @@ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js
|
|
|
13
13
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
14
14
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
15
15
|
|
|
16
|
-
function jitter(delay) {
|
|
17
|
-
return (delay / 2 | 0) + (Math.random() * (delay / 2 | 0) | 0) | 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
16
|
function clearTimeoutRef(timeoutId) {
|
|
21
17
|
let id = timeoutId.contents;
|
|
22
18
|
if (id !== undefined) {
|
|
@@ -813,7 +809,6 @@ function replaceLoneSurrogateEscapes(json) {
|
|
|
813
809
|
let maxBackoffExponent = 20;
|
|
814
810
|
|
|
815
811
|
export {
|
|
816
|
-
jitter,
|
|
817
812
|
clearTimeoutRef,
|
|
818
813
|
maxBackoffExponent,
|
|
819
814
|
expBackoff,
|
package/src/Writing.res
CHANGED
package/src/Writing.res.mjs
CHANGED
|
@@ -121,7 +121,7 @@ async function runOneWrite(state) {
|
|
|
121
121
|
});
|
|
122
122
|
let pruneTargets = PruneStaleHistory.select(state, writtenEntityNames, Stdlib_Option.isSome(rollback));
|
|
123
123
|
await Promise.all([
|
|
124
|
-
persistence.storage.writeBatch(batch, rollback, config,
|
|
124
|
+
persistence.storage.writeBatch(batch, rollback, config, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
|
|
125
125
|
PruneStaleHistory.runConcurrent(state, pruneTargets)
|
|
126
126
|
]);
|
|
127
127
|
IndexerState.markCommitted(state, writtenFrontier);
|
|
@@ -1,325 +1,3 @@
|
|
|
1
|
-
let fieldTypeName = (fieldType: Table.fieldType) =>
|
|
2
|
-
switch fieldType {
|
|
3
|
-
| String => "String"
|
|
4
|
-
| Boolean => "Boolean"
|
|
5
|
-
| Uint32 => "Uint32"
|
|
6
|
-
| UInt52 => "UInt52"
|
|
7
|
-
| SmallInt =>
|
|
8
|
-
JsError.throwWithMessage("ClickHouse doesn't support the internal SmallInt column type")
|
|
9
|
-
| Bytea => "Bytea"
|
|
10
|
-
| UInt64 => "UInt64"
|
|
11
|
-
| Int32 => "Int32"
|
|
12
|
-
| ChainId => "ChainId"
|
|
13
|
-
| Number => "Number"
|
|
14
|
-
| Serial => "Serial"
|
|
15
|
-
| BigSerial => "BigSerial"
|
|
16
|
-
| Json => "Json"
|
|
17
|
-
| Date => "Date"
|
|
18
|
-
| BigInt(_) => "BigInt"
|
|
19
|
-
| BigDecimal(_) => "BigDecimal"
|
|
20
|
-
| Enum(_) => "Enum"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let column = (
|
|
24
|
-
~name,
|
|
25
|
-
~rowName=?,
|
|
26
|
-
~fieldName=?,
|
|
27
|
-
~fieldType: Table.fieldType,
|
|
28
|
-
~isNullable=false,
|
|
29
|
-
~isArray=false,
|
|
30
|
-
): StorageClient.clickHouseColumn => {
|
|
31
|
-
name,
|
|
32
|
-
?rowName,
|
|
33
|
-
?fieldName,
|
|
34
|
-
fieldType: fieldType->fieldTypeName,
|
|
35
|
-
isNullable,
|
|
36
|
-
isArray,
|
|
37
|
-
precision: ?switch fieldType {
|
|
38
|
-
| BigInt({?precision}) => precision
|
|
39
|
-
| BigDecimal({?config}) => config->Option.map(((precision, _)) => precision)
|
|
40
|
-
| _ => None
|
|
41
|
-
},
|
|
42
|
-
scale: ?switch fieldType {
|
|
43
|
-
| BigDecimal({?config}) => config->Option.map(((_, scale)) => scale)
|
|
44
|
-
| _ => None
|
|
45
|
-
},
|
|
46
|
-
enumVariants: ?switch fieldType {
|
|
47
|
-
| Enum({config}) =>
|
|
48
|
-
Some(config.variants->Array.map(variant => variant->(Utils.magic: Table.enum => string)))
|
|
49
|
-
| _ => None
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let fieldColumn = (field: Table.field) =>
|
|
54
|
-
column(
|
|
55
|
-
~name=field->Table.getClickHouseDbFieldName,
|
|
56
|
-
~rowName=field->Table.getPgDbFieldName,
|
|
57
|
-
~fieldName=field.fieldName,
|
|
58
|
-
~fieldType=field.fieldType,
|
|
59
|
-
~isNullable=field.isNullable,
|
|
60
|
-
~isArray=field.isArray,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
let entityTable = (entityConfig: Internal.entityConfig): StorageClient.clickHouseTable => {
|
|
64
|
-
let options = entityConfig.storage.clickhouseOptions
|
|
65
|
-
{
|
|
66
|
-
name: entityConfig.name,
|
|
67
|
-
historyTable: EntityHistory.historyTableName(
|
|
68
|
-
~entityName=entityConfig.name,
|
|
69
|
-
~entityIndex=entityConfig.index,
|
|
70
|
-
),
|
|
71
|
-
columns: entityConfig.table.fields->Array.filterMap(field =>
|
|
72
|
-
switch field {
|
|
73
|
-
| Table.Field(f) => Some(fieldColumn(f))
|
|
74
|
-
| DerivedFrom(_) => None
|
|
75
|
-
}
|
|
76
|
-
),
|
|
77
|
-
chainIdColumn: ?(
|
|
78
|
-
entityConfig.table->Table.getChainIdField->Option.map(Table.getClickHouseDbFieldName)
|
|
79
|
-
),
|
|
80
|
-
partitionBy: ?(options->Option.flatMap(options => options.partitionBy)),
|
|
81
|
-
orderBy: ?(options->Option.flatMap(options => options.orderBy)),
|
|
82
|
-
ttl: ?(options->Option.flatMap(options => options.ttl)),
|
|
83
|
-
skippingIndexes: ?(
|
|
84
|
-
options->Option.flatMap(options =>
|
|
85
|
-
options.skippingIndexes->Option.map(indexes =>
|
|
86
|
-
indexes->Array.map(
|
|
87
|
-
(index): StorageClient.skippingIndex => {
|
|
88
|
-
name: index.name,
|
|
89
|
-
expr: index.expr,
|
|
90
|
-
indexType: index.type_,
|
|
91
|
-
granularity: ?index.granularity,
|
|
92
|
-
},
|
|
93
|
-
)
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
),
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Visits every node on the way into JSON text. A bigint would make
|
|
101
|
-
// `JSON.stringify` throw and fail the whole batch, so it travels as its digits.
|
|
102
|
-
%%private(
|
|
103
|
-
let jsonSafe = (~column) =>
|
|
104
|
-
(_, value: JSON.t) =>
|
|
105
|
-
switch value->typeof {
|
|
106
|
-
| #bigint =>
|
|
107
|
-
value->(Utils.magic: JSON.t => unknown)->Staging.stringOf->(Utils.magic: string => JSON.t)
|
|
108
|
-
// A Uint8Array inside a list column travels as its byte values, which the
|
|
109
|
-
// addon reads back into raw bytes.
|
|
110
|
-
| #object =>
|
|
111
|
-
switch value->(Utils.magic: JSON.t => unknown)->Utils.Bytes.asUint8Array {
|
|
112
|
-
| Some(bytes) =>
|
|
113
|
-
bytes
|
|
114
|
-
->(Utils.magic: Uint8Array.t => Array.arrayLike<int>)
|
|
115
|
-
->Array.fromArrayLike
|
|
116
|
-
->(Utils.magic: array<int> => JSON.t)
|
|
117
|
-
| None => value
|
|
118
|
-
}
|
|
119
|
-
| #number =>
|
|
120
|
-
let _ = value->(Utils.magic: JSON.t => float)->Staging.finiteOrThrow(~column)
|
|
121
|
-
value
|
|
122
|
-
| _ => value
|
|
123
|
-
}
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
type cell = Change.t<Internal.entity> => unknown
|
|
127
|
-
|
|
128
|
-
type historyCells = {
|
|
129
|
-
setCells: array<cell>,
|
|
130
|
-
deleteCells: array<cell>,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
type table = {
|
|
134
|
-
handle: int,
|
|
135
|
-
name: string,
|
|
136
|
-
columns: array<Staging.column>,
|
|
137
|
-
// An entity's history cells, by the scope whose chain id they stamp.
|
|
138
|
-
historyCells: dict<historyCells>,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let makeTable = (~handle, ~name, {names, kinds, nullable}: StorageClient.clickHouseRegistered) => {
|
|
142
|
-
handle,
|
|
143
|
-
name,
|
|
144
|
-
columns: names->Array.mapWithIndex((name, index): Staging.column => {
|
|
145
|
-
name,
|
|
146
|
-
kind: kinds->Array.getUnsafe(index)->Staging.kindOfOrdinal,
|
|
147
|
-
isNullable: nullable->Array.getUnsafe(index),
|
|
148
|
-
replacer: Replacer(jsonSafe(~column=name)),
|
|
149
|
-
}),
|
|
150
|
-
historyCells: Dict.make(),
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// A Json column holds the document's text. Staging writes a string as is, so a
|
|
154
|
-
// document that is a string has to become its own JSON text here, or it would
|
|
155
|
-
// read back as something JSON can't parse. JSON `null` and an absent value are
|
|
156
|
-
// both stored as `null`: the column is never Nullable.
|
|
157
|
-
%%private(
|
|
158
|
-
let documentText = (value: unknown) =>
|
|
159
|
-
switch value->(Utils.magic: unknown => Nullable.t<JSON.t>) {
|
|
160
|
-
| Value(_) => value->Sql.canonicalDocument->(Utils.magic: string => unknown)
|
|
161
|
-
| Null | Undefined => "null"->(Utils.magic: string => unknown)
|
|
162
|
-
}
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
%%private(
|
|
166
|
-
let compileSerializer = schema =>
|
|
167
|
-
S.compile(schema, ~input=Value, ~output=Json, ~typeValidation=false, ~mode=Sync)->(
|
|
168
|
-
Utils.magic: (unknown => JSON.t) => unknown => unknown
|
|
169
|
-
)
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
%%private(
|
|
173
|
-
let documentsText = (f: Table.field): (unknown => unknown) =>
|
|
174
|
-
if f.isArray {
|
|
175
|
-
value =>
|
|
176
|
-
switch value->(Utils.magic: unknown => Nullable.t<array<unknown>>) {
|
|
177
|
-
| Value(documents) =>
|
|
178
|
-
documents->Array.map(documentText)->(Utils.magic: array<unknown> => unknown)
|
|
179
|
-
| Null | Undefined => value
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
documentText
|
|
183
|
-
}
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
%%private(
|
|
187
|
-
let serializerOf = (f: Table.field): (unknown => unknown) => {
|
|
188
|
-
let shaped = baseSchema =>
|
|
189
|
-
if f.isNullable {
|
|
190
|
-
Utils.Schema.nullTolerant(baseSchema)->S.toUnknown
|
|
191
|
-
} else if f.isArray {
|
|
192
|
-
S.array(baseSchema)->S.toUnknown
|
|
193
|
-
} else {
|
|
194
|
-
baseSchema
|
|
195
|
-
}
|
|
196
|
-
switch f.fieldType {
|
|
197
|
-
| Json => documentsText(f)
|
|
198
|
-
| Date => compileSerializer(shaped(Utils.Schema.clickHouseDate->S.toUnknown))
|
|
199
|
-
| Bytea => compileSerializer(shaped(S.json(~validate=false)->S.toUnknown))
|
|
200
|
-
| ChainId => compileSerializer(ChainId.schema->S.toUnknown)
|
|
201
|
-
| _ => compileSerializer(f.fieldSchema)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
@get external changeEntity: Change.t<Internal.entity> => dict<unknown> = "entity"
|
|
207
|
-
|
|
208
|
-
%%private(let absent: unknown = %raw(`undefined`))
|
|
209
|
-
|
|
210
|
-
%%private(
|
|
211
|
-
let makeHistoryCells = (
|
|
212
|
-
table,
|
|
213
|
-
~entityConfig: Internal.entityConfig,
|
|
214
|
-
~scope: Internal.chainScope,
|
|
215
|
-
): historyCells => {
|
|
216
|
-
let sourceTable = entityConfig.table
|
|
217
|
-
let chainIdTag = switch (
|
|
218
|
-
sourceTable->Table.getChainIdField,
|
|
219
|
-
scope->Internal.chainScopeChainId,
|
|
220
|
-
) {
|
|
221
|
-
| (Some(field), Some(chainId)) =>
|
|
222
|
-
Some((field->Table.getClickHouseDbFieldName, chainId->(Utils.magic: ChainId.t => unknown)))
|
|
223
|
-
| _ => None
|
|
224
|
-
}
|
|
225
|
-
let fieldsByColumn = Dict.make()
|
|
226
|
-
sourceTable.fields->Array.forEach(field =>
|
|
227
|
-
switch field {
|
|
228
|
-
| Table.Field(f) => fieldsByColumn->Dict.set(f->Table.getClickHouseDbFieldName, f)
|
|
229
|
-
| DerivedFrom(_) => ()
|
|
230
|
-
}
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
let setCells = []
|
|
234
|
-
let deleteCells = []
|
|
235
|
-
let registeredNames = table.columns->Array.map(column => column.name)->Utils.Set.fromArray
|
|
236
|
-
table.columns->Array.forEach(({name}) => {
|
|
237
|
-
let (set, delete) = if name === EntityHistory.checkpointIdFieldName {
|
|
238
|
-
let cell = change => change->Change.getCheckpointId->(Utils.magic: bigint => unknown)
|
|
239
|
-
(cell, cell)
|
|
240
|
-
} else if name === EntityHistory.changeFieldName {
|
|
241
|
-
(
|
|
242
|
-
_ => (EntityHistory.RowAction.SET :> string)->(Utils.magic: string => unknown),
|
|
243
|
-
_ => (EntityHistory.RowAction.DELETE :> string)->(Utils.magic: string => unknown),
|
|
244
|
-
)
|
|
245
|
-
} else {
|
|
246
|
-
switch (chainIdTag, fieldsByColumn->Utils.Dict.dangerouslyGetNonOption(name)) {
|
|
247
|
-
| (Some((column, chainId)), _) if column === name => (_ => chainId, _ => chainId)
|
|
248
|
-
| (_, Some(f)) =>
|
|
249
|
-
let serialize = serializerOf(f)
|
|
250
|
-
let apiName = f->Table.getApiFieldName
|
|
251
|
-
if apiName === Table.idFieldName {
|
|
252
|
-
let cell = change =>
|
|
253
|
-
serialize(change->Change.getEntityId->(Utils.magic: EntityId.t => unknown))
|
|
254
|
-
(cell, cell)
|
|
255
|
-
} else {
|
|
256
|
-
(change => serialize(change->changeEntity->Dict.getUnsafe(apiName)), _ => absent)
|
|
257
|
-
}
|
|
258
|
-
| (_, None) =>
|
|
259
|
-
JsError.throwWithMessage(
|
|
260
|
-
`ClickHouse table "${table.name}" registered a column "${name}" that entity "${entityConfig.name}" has no value for`,
|
|
261
|
-
)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
setCells->Array.push(set)
|
|
265
|
-
deleteCells->Array.push(delete)
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
fieldsByColumn->Dict.forEachWithKey((f, column) =>
|
|
269
|
-
if !(registeredNames->Utils.Set.has(column)) {
|
|
270
|
-
JsError.throwWithMessage(
|
|
271
|
-
`ClickHouse table "${table.name}" registered no column for field "${f.fieldName}" of entity "${entityConfig.name}"`,
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
{setCells, deleteCells}
|
|
277
|
-
}
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
// Every change as a row of the entity's history: a deletion keeps only the
|
|
281
|
-
// columns that say which entity it removed.
|
|
282
|
-
let stageChangesOrThrow = (
|
|
283
|
-
arena,
|
|
284
|
-
table,
|
|
285
|
-
~entityConfig: Internal.entityConfig,
|
|
286
|
-
~scope: Internal.chainScope,
|
|
287
|
-
~changes: array<Change.t<Internal.entity>>,
|
|
288
|
-
) => {
|
|
289
|
-
let stage =
|
|
290
|
-
arena->Staging.begin(~table=table.handle, ~rows=changes->Array.length, ~columns=table.columns)
|
|
291
|
-
try {
|
|
292
|
-
let cacheKey = scope->Internal.chainScopeToString
|
|
293
|
-
let cells = switch table.historyCells->Utils.Dict.dangerouslyGetNonOption(cacheKey) {
|
|
294
|
-
| Some(cached) => cached
|
|
295
|
-
| None =>
|
|
296
|
-
let cells = table->makeHistoryCells(~entityConfig, ~scope)
|
|
297
|
-
table.historyCells->Dict.set(cacheKey, cells)
|
|
298
|
-
cells
|
|
299
|
-
}
|
|
300
|
-
let columns = stage->Staging.columnCount
|
|
301
|
-
changes->Array.forEachWithIndex((change, row) => {
|
|
302
|
-
let (cells, write) = switch change {
|
|
303
|
-
| Change.Set(_) => (cells.setCells, Staging.writeValue)
|
|
304
|
-
| Delete(_) => (cells.deleteCells, Staging.writeDeletedValue)
|
|
305
|
-
}
|
|
306
|
-
for column in 0 to columns - 1 {
|
|
307
|
-
stage->write(~column, ~row, (cells->Array.getUnsafe(column))(change))
|
|
308
|
-
}
|
|
309
|
-
})
|
|
310
|
-
stage->Staging.commit
|
|
311
|
-
} catch {
|
|
312
|
-
| exn =>
|
|
313
|
-
stage->Staging.abort
|
|
314
|
-
throw(
|
|
315
|
-
Persistence.StorageError({
|
|
316
|
-
message: `Failed to convert items for ClickHouse table "${table.name}"`,
|
|
317
|
-
reason: exn->Utils.prettifyExn,
|
|
318
|
-
}),
|
|
319
|
-
)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
1
|
// Every connection setting has to be there: an unset one would otherwise reach
|
|
324
2
|
// the server as an empty value and fail there, for a reason naming neither.
|
|
325
3
|
let optionsFromEnvOrThrow = (): StorageClient.clickHouseOptions => {
|