envio 3.9.0 → 3.10.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/evm.schema.json +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +35 -3
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +52 -44
- package/src/Config.res.mjs +51 -17
- package/src/Core.res +4 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/svm.schema.json +1 -75
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
|
@@ -7,533 +7,483 @@ import * as ChainId from "../ChainId.res.mjs";
|
|
|
7
7
|
import * as Logging from "../Logging.res.mjs";
|
|
8
8
|
import * as Internal from "../Internal.res.mjs";
|
|
9
9
|
import * as Persistence from "../Persistence.res.mjs";
|
|
10
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
10
11
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
11
12
|
import * as EntityHistory from "../db/EntityHistory.res.mjs";
|
|
12
13
|
import * as InternalTable from "../db/InternalTable.res.mjs";
|
|
14
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
15
|
+
import * as ClickHouseSink from "./ClickHouseSink.res.mjs";
|
|
13
16
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
14
17
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
15
18
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
16
19
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
17
20
|
|
|
18
|
-
function
|
|
19
|
-
let
|
|
20
|
-
|
|
21
|
+
function clickHouseFieldSchema(f) {
|
|
22
|
+
let shaped = baseSchema => {
|
|
23
|
+
if (f.isNullable) {
|
|
24
|
+
return Utils.Schema.nullTolerant(baseSchema);
|
|
25
|
+
} else if (f.isArray) {
|
|
26
|
+
return S$RescriptSchema.array(baseSchema);
|
|
27
|
+
} else {
|
|
28
|
+
return baseSchema;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
let match = f.fieldType;
|
|
32
|
+
if (typeof match === "object") {
|
|
33
|
+
return f.fieldSchema;
|
|
34
|
+
}
|
|
35
|
+
switch (match) {
|
|
36
|
+
case "Bytea" :
|
|
37
|
+
return shaped(S$RescriptSchema.json(false));
|
|
38
|
+
case "ChainId" :
|
|
39
|
+
return ChainId.schema;
|
|
40
|
+
case "Json" :
|
|
41
|
+
if (f.isArray) {
|
|
42
|
+
return f.fieldSchema;
|
|
43
|
+
} else {
|
|
44
|
+
return Utils.Schema.clickHouseJson;
|
|
45
|
+
}
|
|
46
|
+
case "Date" :
|
|
47
|
+
return shaped(Utils.Schema.clickHouseDate);
|
|
48
|
+
default:
|
|
49
|
+
return f.fieldSchema;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeClickHouseEntitySchema(table) {
|
|
54
|
+
return S$RescriptSchema.object(s => {
|
|
55
|
+
let dict = {};
|
|
56
|
+
table.fields.forEach(field => {
|
|
57
|
+
if (field.TAG !== "Field") {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let f = field._0;
|
|
61
|
+
dict[Table.getApiFieldName(f)] = s.f(Table.getClickHouseDbFieldName(f), clickHouseFieldSchema(f));
|
|
62
|
+
});
|
|
63
|
+
return dict;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let logger = Logging.createChild({
|
|
68
|
+
context: "ClickHouse"
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function makeSink(host, username, password, database, chainIdMode) {
|
|
72
|
+
return ClickHouseSink.make(host, username, password, database, chainIdMode, msg => Logging.childWarn(logger, {
|
|
73
|
+
msg: msg
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeColumnSpec(name, fieldNameOpt, fieldType, isNullableOpt, isArrayOpt) {
|
|
78
|
+
let fieldName = fieldNameOpt !== undefined ? fieldNameOpt : name;
|
|
79
|
+
let isNullable = isNullableOpt !== undefined ? isNullableOpt : false;
|
|
80
|
+
let isArray = isArrayOpt !== undefined ? isArrayOpt : false;
|
|
81
|
+
let tmp;
|
|
21
82
|
if (typeof fieldType !== "object") {
|
|
22
83
|
switch (fieldType) {
|
|
84
|
+
case "String" :
|
|
85
|
+
tmp = "String";
|
|
86
|
+
break;
|
|
23
87
|
case "Boolean" :
|
|
24
|
-
|
|
88
|
+
tmp = "Boolean";
|
|
25
89
|
break;
|
|
26
90
|
case "Uint32" :
|
|
27
|
-
|
|
91
|
+
tmp = "Uint32";
|
|
92
|
+
break;
|
|
93
|
+
case "UInt52" :
|
|
94
|
+
tmp = "UInt52";
|
|
28
95
|
break;
|
|
29
96
|
case "SmallInt" :
|
|
97
|
+
tmp = Stdlib_JsError.throwWithMessage("ClickHouse doesn't support the internal SmallInt column type");
|
|
98
|
+
break;
|
|
30
99
|
case "Bytea" :
|
|
31
|
-
|
|
100
|
+
tmp = "Bytea";
|
|
32
101
|
break;
|
|
33
|
-
case "UInt52" :
|
|
34
102
|
case "UInt64" :
|
|
35
|
-
|
|
103
|
+
tmp = "UInt64";
|
|
104
|
+
break;
|
|
105
|
+
case "Int32" :
|
|
106
|
+
tmp = "Int32";
|
|
36
107
|
break;
|
|
37
108
|
case "ChainId" :
|
|
38
|
-
|
|
109
|
+
tmp = "ChainId";
|
|
39
110
|
break;
|
|
40
111
|
case "Number" :
|
|
41
|
-
|
|
112
|
+
tmp = "Number";
|
|
42
113
|
break;
|
|
43
|
-
case "Int32" :
|
|
44
114
|
case "Serial" :
|
|
45
|
-
|
|
115
|
+
tmp = "Serial";
|
|
46
116
|
break;
|
|
47
117
|
case "BigSerial" :
|
|
48
|
-
|
|
118
|
+
tmp = "BigSerial";
|
|
49
119
|
break;
|
|
50
|
-
case "String" :
|
|
51
120
|
case "Json" :
|
|
52
|
-
|
|
121
|
+
tmp = "Json";
|
|
53
122
|
break;
|
|
54
123
|
case "Date" :
|
|
55
|
-
|
|
124
|
+
tmp = "Date";
|
|
56
125
|
break;
|
|
57
126
|
}
|
|
58
127
|
} else {
|
|
59
128
|
switch (fieldType.type) {
|
|
60
129
|
case "BigInt" :
|
|
61
|
-
|
|
62
|
-
baseType = precision !== undefined && precision <= 38 ? `Decimal(` + precision.toString() + `,0)` : "String";
|
|
130
|
+
tmp = "BigInt";
|
|
63
131
|
break;
|
|
64
132
|
case "BigDecimal" :
|
|
65
|
-
|
|
66
|
-
if (config !== undefined) {
|
|
67
|
-
let scale = config[1];
|
|
68
|
-
let precision$1 = config[0];
|
|
69
|
-
baseType = precision$1 > 38 || scale > precision$1 ? "String" : `Decimal(` + precision$1.toString() + `,` + scale.toString() + `)`;
|
|
70
|
-
} else {
|
|
71
|
-
baseType = "String";
|
|
72
|
-
}
|
|
133
|
+
tmp = "BigDecimal";
|
|
73
134
|
break;
|
|
74
135
|
case "Enum" :
|
|
75
|
-
|
|
76
|
-
let variantsLength = config$1.variants.length;
|
|
77
|
-
let enumType = variantsLength <= 127 ? "Enum8" : "Enum16";
|
|
78
|
-
let enumValues = config$1.variants.map(variant => `'` + variant + `'`).join(", ");
|
|
79
|
-
baseType = enumType + `(` + enumValues + `)`;
|
|
136
|
+
tmp = "Enum";
|
|
80
137
|
break;
|
|
81
138
|
}
|
|
82
139
|
}
|
|
83
|
-
let
|
|
84
|
-
if (
|
|
85
|
-
|
|
140
|
+
let tmp$1;
|
|
141
|
+
if (typeof fieldType !== "object") {
|
|
142
|
+
tmp$1 = undefined;
|
|
86
143
|
} else {
|
|
87
|
-
|
|
144
|
+
switch (fieldType.type) {
|
|
145
|
+
case "BigInt" :
|
|
146
|
+
tmp$1 = fieldType.precision;
|
|
147
|
+
break;
|
|
148
|
+
case "BigDecimal" :
|
|
149
|
+
tmp$1 = Stdlib_Option.map(fieldType.config, param => param[0]);
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
tmp$1 = undefined;
|
|
153
|
+
}
|
|
88
154
|
}
|
|
155
|
+
let tmp$2;
|
|
156
|
+
tmp$2 = typeof fieldType !== "object" || fieldType.type !== "BigDecimal" ? undefined : Stdlib_Option.map(fieldType.config, param => param[1]);
|
|
157
|
+
let tmp$3;
|
|
158
|
+
tmp$3 = typeof fieldType !== "object" || fieldType.type !== "Enum" ? undefined : fieldType.config.variants.map(variant => variant);
|
|
159
|
+
return {
|
|
160
|
+
name: name,
|
|
161
|
+
fieldName: fieldName,
|
|
162
|
+
fieldType: tmp,
|
|
163
|
+
isNullable: isNullable,
|
|
164
|
+
isArray: isArray,
|
|
165
|
+
precision: tmp$1,
|
|
166
|
+
scale: tmp$2,
|
|
167
|
+
enumVariants: tmp$3
|
|
168
|
+
};
|
|
89
169
|
}
|
|
90
170
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
171
|
+
let checkpointColumns = Stdlib_Array.filterMap(InternalTable.Checkpoints.columns, param => {
|
|
172
|
+
let field = param.field;
|
|
173
|
+
if (field.TAG !== "Field") {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
let match = field._0;
|
|
177
|
+
return {
|
|
178
|
+
spec: makeColumnSpec(match.fieldName, undefined, param.clickHouseFieldType, match.isNullable, undefined),
|
|
179
|
+
valuesOf: param.valuesOf
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
let checkpointColumnSpecs = checkpointColumns.map(param => param.spec);
|
|
184
|
+
|
|
185
|
+
function entitySpec(entityConfig) {
|
|
186
|
+
let options = entityConfig.storage.clickhouseOptions;
|
|
187
|
+
return {
|
|
188
|
+
name: entityConfig.name,
|
|
189
|
+
historyTable: EntityHistory.historyTableName(entityConfig.name, entityConfig.index),
|
|
190
|
+
columns: Stdlib_Array.filterMap(entityConfig.table.fields, field => {
|
|
95
191
|
if (field.TAG !== "Field") {
|
|
96
192
|
return;
|
|
97
193
|
}
|
|
98
194
|
let f = field._0;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
195
|
+
return makeColumnSpec(Table.getClickHouseDbFieldName(f), f.fieldName, f.fieldType, f.isNullable, f.isArray);
|
|
196
|
+
}),
|
|
197
|
+
chainIdColumn: Stdlib_Option.map(Table.getChainIdField(entityConfig.table), Table.getClickHouseDbFieldName),
|
|
198
|
+
partitionBy: Stdlib_Option.flatMap(options, options => options.partitionBy),
|
|
199
|
+
orderBy: Stdlib_Option.flatMap(options, options => options.orderBy),
|
|
200
|
+
ttl: Stdlib_Option.flatMap(options, options => options.ttl),
|
|
201
|
+
skippingIndexes: Stdlib_Option.flatMap(options, options => Stdlib_Option.map(options.skippingIndexes, indexes => indexes.map(index => ({
|
|
202
|
+
name: index.name,
|
|
203
|
+
expr: index.expr,
|
|
204
|
+
indexType: index.type,
|
|
205
|
+
granularity: index.granularity
|
|
206
|
+
}))))
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function makeRegistry() {
|
|
211
|
+
return {
|
|
212
|
+
entities: {},
|
|
213
|
+
checkpoints: undefined,
|
|
214
|
+
converters: {}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let absent = undefined;
|
|
219
|
+
|
|
220
|
+
function compileSerializer(schema) {
|
|
221
|
+
return S$RescriptSchema.compile(schema, "Output", "Json", "Sync", false);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function makeConverters(entityConfig, scope, table) {
|
|
225
|
+
let columns = table.columns;
|
|
226
|
+
let tableName = table.name;
|
|
227
|
+
let sourceTable = entityConfig.table;
|
|
228
|
+
let match = Table.getChainIdField(sourceTable);
|
|
229
|
+
let match$1 = Internal.chainScopeChainId(scope);
|
|
230
|
+
let chainIdTag = match !== undefined && match$1 !== undefined ? [
|
|
231
|
+
Table.getClickHouseDbFieldName(match),
|
|
232
|
+
Primitive_option.valFromOption(match$1)
|
|
233
|
+
] : undefined;
|
|
234
|
+
let fieldsByColumn = {};
|
|
235
|
+
sourceTable.fields.forEach(field => {
|
|
236
|
+
if (field.TAG !== "Field") {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
let f = field._0;
|
|
240
|
+
fieldsByColumn[Table.getClickHouseDbFieldName(f)] = f;
|
|
241
|
+
});
|
|
242
|
+
let setCells = [];
|
|
243
|
+
let deleteCells = [];
|
|
244
|
+
let registeredNames = new Set(columns.map(column => column.name));
|
|
245
|
+
columns.forEach(param => {
|
|
246
|
+
let name = param.name;
|
|
247
|
+
let match;
|
|
248
|
+
if (name === EntityHistory.checkpointIdFieldName) {
|
|
249
|
+
let cell = change => change.checkpointId;
|
|
250
|
+
match = [
|
|
251
|
+
cell,
|
|
252
|
+
cell
|
|
253
|
+
];
|
|
254
|
+
} else if (name === EntityHistory.changeFieldName) {
|
|
255
|
+
match = [
|
|
256
|
+
param => "SET",
|
|
257
|
+
param => "DELETE"
|
|
258
|
+
];
|
|
259
|
+
} else {
|
|
260
|
+
let match$1 = fieldsByColumn[name];
|
|
261
|
+
let exit = 0;
|
|
262
|
+
if (chainIdTag !== undefined && chainIdTag[0] === name) {
|
|
263
|
+
let chainId = chainIdTag[1];
|
|
264
|
+
match = [
|
|
265
|
+
param => chainId,
|
|
266
|
+
param => chainId
|
|
267
|
+
];
|
|
124
268
|
} else {
|
|
125
|
-
|
|
269
|
+
exit = 1;
|
|
126
270
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
271
|
+
if (exit === 1) {
|
|
272
|
+
if (match$1 !== undefined) {
|
|
273
|
+
let serialize = compileSerializer(clickHouseFieldSchema(match$1));
|
|
274
|
+
let apiName = Table.getApiFieldName(match$1);
|
|
275
|
+
if (apiName === Table.idFieldName) {
|
|
276
|
+
let cell$1 = change => serialize(change.entityId);
|
|
277
|
+
match = [
|
|
278
|
+
cell$1,
|
|
279
|
+
cell$1
|
|
280
|
+
];
|
|
281
|
+
} else {
|
|
282
|
+
match = [
|
|
283
|
+
change => serialize(change.entity[apiName]),
|
|
284
|
+
param => absent
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
match = Stdlib_JsError.throwWithMessage(`ClickHouse table "` + tableName + `" registered a column "` + name + `" that entity "` + entityConfig.name + `" has no value for`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
setCells.push(match[0]);
|
|
293
|
+
deleteCells.push(match[1]);
|
|
130
294
|
});
|
|
295
|
+
Stdlib_Dict.forEachWithKey(fieldsByColumn, (f, column) => {
|
|
296
|
+
if (!registeredNames.has(column)) {
|
|
297
|
+
return Stdlib_JsError.throwWithMessage(`ClickHouse table "` + tableName + `" registered no column for field "` + f.fieldName + `" of entity "` + entityConfig.name + `"`);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
setCells: setCells,
|
|
302
|
+
deleteCells: deleteCells
|
|
303
|
+
};
|
|
131
304
|
}
|
|
132
305
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
msg: "ClickHouse insert failed, splitting batch in half and retrying",
|
|
152
|
-
table: table,
|
|
153
|
-
batchSize: values.length,
|
|
154
|
-
retriesLeft: retries,
|
|
155
|
-
err: Utils.prettifyExn(exn)
|
|
156
|
-
});
|
|
157
|
-
await Utils.delay(delayMs);
|
|
158
|
-
let mid = (values.length >> 1);
|
|
159
|
-
let first = values.slice(0, mid);
|
|
160
|
-
let second = values.slice(mid);
|
|
161
|
-
await insertWithRetry(client, table, first, format, retries - 1 | 0);
|
|
162
|
-
return await insertWithRetry(client, table, second, format, retries - 1 | 0);
|
|
163
|
-
}
|
|
164
|
-
Logging.childWarn(logger, {
|
|
165
|
-
msg: "ClickHouse insert failed, retrying after delay",
|
|
166
|
-
table: table,
|
|
167
|
-
retriesLeft: retries,
|
|
168
|
-
err: Utils.prettifyExn(exn)
|
|
169
|
-
});
|
|
170
|
-
await Utils.delay(delayMs);
|
|
171
|
-
return await insertWithRetry(client, table, values, format, retries - 1 | 0);
|
|
306
|
+
function fillBuilders(table, converters, changes) {
|
|
307
|
+
let rows = changes.length;
|
|
308
|
+
let builders = table.columns.map(__x => ClickHouseSink.makeBuilder(__x, rows));
|
|
309
|
+
let columns = builders.length;
|
|
310
|
+
for (let row = 0; row < rows; ++row) {
|
|
311
|
+
let change = changes[row];
|
|
312
|
+
let match;
|
|
313
|
+
match = change.type === "SET" ? [
|
|
314
|
+
converters.setCells,
|
|
315
|
+
ClickHouseSink.writeValue
|
|
316
|
+
] : [
|
|
317
|
+
converters.deleteCells,
|
|
318
|
+
ClickHouseSink.writeDeletedValue
|
|
319
|
+
];
|
|
320
|
+
let write = match[1];
|
|
321
|
+
let cells = match[0];
|
|
322
|
+
for (let column = 0; column < columns; ++column) {
|
|
323
|
+
write(builders[column], row, cells[column](change));
|
|
172
324
|
}
|
|
173
|
-
throw exn;
|
|
174
325
|
}
|
|
326
|
+
return builders;
|
|
175
327
|
}
|
|
176
328
|
|
|
177
|
-
|
|
178
|
-
let
|
|
179
|
-
if (
|
|
180
|
-
return;
|
|
329
|
+
function entityTable(sink, registry, entityConfig) {
|
|
330
|
+
let table = registry.entities[entityConfig.name];
|
|
331
|
+
if (table !== undefined) {
|
|
332
|
+
return table;
|
|
181
333
|
}
|
|
182
|
-
let
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
334
|
+
let spec = entitySpec(entityConfig);
|
|
335
|
+
let table$1 = ClickHouseSink.makeTable(spec.historyTable, sink.registerEntityTable(spec));
|
|
336
|
+
registry.entities[entityConfig.name] = table$1;
|
|
337
|
+
return table$1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function checkpointsTable(sink, registry) {
|
|
341
|
+
let table = registry.checkpoints;
|
|
342
|
+
if (table !== undefined) {
|
|
343
|
+
return table;
|
|
191
344
|
}
|
|
345
|
+
let table$1 = ClickHouseSink.makeTable(InternalTable.Checkpoints.table.tableName, sink.registerCheckpointsTable(checkpointColumnSpecs));
|
|
346
|
+
registry.checkpoints = table$1;
|
|
347
|
+
return table$1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function stageBuilders(sink, table, builders, rows) {
|
|
351
|
+
return sink.stage(table.handle, rows, builders.map(ClickHouseSink.builderPayload));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function stageCheckpointsOrThrow(sink, registry, batch) {
|
|
355
|
+
let rows = batch.checkpointIds.length;
|
|
356
|
+
if (rows === 0) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
let table = checkpointsTable(sink, registry);
|
|
192
360
|
try {
|
|
193
|
-
|
|
361
|
+
let builders = table.columns.map(__x => ClickHouseSink.makeBuilder(__x, rows));
|
|
362
|
+
builders.forEach((builder, index) => {
|
|
363
|
+
let columnValues = checkpointColumns[index].valuesOf(batch);
|
|
364
|
+
for (let row = 0; row < rows; ++row) {
|
|
365
|
+
ClickHouseSink.writeValue(builder, row, columnValues[row]);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
return stageBuilders(sink, table, builders, rows);
|
|
194
369
|
} catch (raw_exn) {
|
|
195
370
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
196
371
|
throw {
|
|
197
372
|
RE_EXN_ID: Persistence.StorageError,
|
|
198
|
-
message: `Failed to
|
|
373
|
+
message: `Failed to convert checkpoints for ClickHouse table "` + table.name + `"`,
|
|
199
374
|
reason: Utils.prettifyExn(exn),
|
|
200
375
|
Error: new Error()
|
|
201
376
|
};
|
|
202
377
|
}
|
|
203
378
|
}
|
|
204
379
|
|
|
205
|
-
|
|
206
|
-
|
|
380
|
+
function stageUpdatesOrThrow(sink, registry, changes, entityConfig, scope) {
|
|
381
|
+
let rows = changes.length;
|
|
382
|
+
if (rows === 0) {
|
|
207
383
|
return;
|
|
208
384
|
}
|
|
209
|
-
let
|
|
210
|
-
let
|
|
385
|
+
let table = entityTable(sink, registry, entityConfig);
|
|
386
|
+
let tableName = table.name;
|
|
211
387
|
let cacheKey = entityConfig.name + `|` + Internal.chainScopeToString(scope);
|
|
212
|
-
let cached = cache[cacheKey];
|
|
213
|
-
let match;
|
|
214
|
-
if (cached !== undefined) {
|
|
215
|
-
match = cached;
|
|
216
|
-
} else {
|
|
217
|
-
let cached_tableName = database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\``;
|
|
218
|
-
let cached_convertOrThrow = S$RescriptSchema.compile(S$RescriptSchema.array(S$RescriptSchema.union([
|
|
219
|
-
EntityHistory.makeSetUpdateSchema(Table.getIdSchema(entityConfig.table), makeClickHouseEntitySchema(entityConfig.table)),
|
|
220
|
-
S$RescriptSchema.object(s => {
|
|
221
|
-
s.tag(EntityHistory.changeFieldName, "DELETE");
|
|
222
|
-
if (chainIdField !== undefined && scopeChainId !== undefined) {
|
|
223
|
-
s.tag(Table.getClickHouseDbFieldName(chainIdField), Primitive_option.valFromOption(scopeChainId));
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
type: "DELETE",
|
|
227
|
-
entityId: s.f(Table.idFieldName, Table.getIdSchema(entityConfig.table)),
|
|
228
|
-
checkpointId: s.f(EntityHistory.checkpointIdFieldName, EntityHistory.unsafeCheckpointIdSchema)
|
|
229
|
-
};
|
|
230
|
-
})
|
|
231
|
-
])), "Output", "Json", "Sync", false);
|
|
232
|
-
let cached$1 = {
|
|
233
|
-
tableName: cached_tableName,
|
|
234
|
-
convertOrThrow: cached_convertOrThrow
|
|
235
|
-
};
|
|
236
|
-
cache[cacheKey] = cached$1;
|
|
237
|
-
match = cached$1;
|
|
238
|
-
}
|
|
239
|
-
let tableName = match.tableName;
|
|
240
|
-
let changes$1;
|
|
241
|
-
if (chainIdField !== undefined && scopeChainId !== undefined) {
|
|
242
|
-
let chainId = Primitive_option.valFromOption(scopeChainId);
|
|
243
|
-
changes$1 = changes.map(change => {
|
|
244
|
-
if (change.type === "SET") {
|
|
245
|
-
return {
|
|
246
|
-
type: "SET",
|
|
247
|
-
entityId: change.entityId,
|
|
248
|
-
entity: Internal.stampChainId(change.entity, chainIdField.fieldName, chainId),
|
|
249
|
-
checkpointId: change.checkpointId
|
|
250
|
-
};
|
|
251
|
-
} else {
|
|
252
|
-
return change;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
} else {
|
|
256
|
-
changes$1 = changes;
|
|
257
|
-
}
|
|
258
388
|
try {
|
|
259
|
-
let
|
|
260
|
-
|
|
389
|
+
let cached = registry.converters[cacheKey];
|
|
390
|
+
let converters;
|
|
391
|
+
if (cached !== undefined) {
|
|
392
|
+
converters = cached;
|
|
393
|
+
} else {
|
|
394
|
+
let cached$1 = makeConverters(entityConfig, scope, table);
|
|
395
|
+
registry.converters[cacheKey] = cached$1;
|
|
396
|
+
converters = cached$1;
|
|
397
|
+
}
|
|
398
|
+
let builders = fillBuilders(table, converters, changes);
|
|
399
|
+
return stageBuilders(sink, table, builders, rows);
|
|
261
400
|
} catch (raw_exn) {
|
|
262
401
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
263
402
|
throw {
|
|
264
403
|
RE_EXN_ID: Persistence.StorageError,
|
|
265
|
-
message: `Failed to
|
|
404
|
+
message: `Failed to convert items for ClickHouse table "` + tableName + `"`,
|
|
266
405
|
reason: Utils.prettifyExn(exn),
|
|
267
406
|
Error: new Error()
|
|
268
407
|
};
|
|
269
408
|
}
|
|
270
409
|
}
|
|
271
410
|
|
|
272
|
-
function
|
|
273
|
-
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
} else {
|
|
284
|
-
return "";
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function databaseEngineName(engineSpec) {
|
|
289
|
-
return engineSpec.trim().split("(")[0].split(" ")[0].trim();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onClusterOpt, chainIdModeOpt) {
|
|
293
|
-
let replicated = replicatedOpt !== undefined ? replicatedOpt : false;
|
|
294
|
-
let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
|
|
295
|
-
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
296
|
-
let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()";
|
|
297
|
-
let fieldDefinitions = Stdlib_Array.filterMap(entityConfig.table.fields, field => {
|
|
298
|
-
if (field.TAG !== "Field") {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
let field$1 = field._0;
|
|
302
|
-
let fieldName = Table.getClickHouseDbFieldName(field$1);
|
|
303
|
-
let clickHouseType = getClickHouseFieldType(field$1.fieldType, field$1.isNullable, field$1.isArray, chainIdMode);
|
|
304
|
-
return `\`` + fieldName + `\` ` + clickHouseType;
|
|
305
|
-
});
|
|
306
|
-
let options = entityConfig.storage.clickhouseOptions;
|
|
307
|
-
let match = options !== undefined ? [
|
|
308
|
-
options.partitionBy,
|
|
309
|
-
options.orderBy,
|
|
310
|
-
options.ttl,
|
|
311
|
-
options.skippingIndexes
|
|
312
|
-
] : [
|
|
313
|
-
undefined,
|
|
314
|
-
undefined,
|
|
315
|
-
undefined,
|
|
316
|
-
undefined
|
|
317
|
-
];
|
|
318
|
-
let skippingIndexes = match[3];
|
|
319
|
-
let ttl = match[2];
|
|
320
|
-
let orderBy = match[1];
|
|
321
|
-
let partitionBy = match[0];
|
|
322
|
-
let columnByFieldName = {};
|
|
323
|
-
entityConfig.table.fields.forEach(field => {
|
|
324
|
-
if (field.TAG !== "Field") {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
let f = field._0;
|
|
328
|
-
columnByFieldName[f.fieldName] = Table.getClickHouseDbFieldName(f);
|
|
329
|
-
});
|
|
330
|
-
let orderByColumns;
|
|
331
|
-
if (orderBy !== undefined) {
|
|
332
|
-
let userColumns = orderBy.map(fieldName => {
|
|
333
|
-
let column = columnByFieldName[fieldName];
|
|
334
|
-
if (column !== undefined) {
|
|
335
|
-
return `\`` + column + `\``;
|
|
336
|
-
} else {
|
|
337
|
-
return Stdlib_JsError.throwWithMessage(`ClickHouse orderBy field "` + fieldName + `" is not defined on entity "` + entityConfig.name + `"`);
|
|
338
|
-
}
|
|
339
|
-
}).join(", ");
|
|
340
|
-
orderByColumns = userColumns + `, ` + EntityHistory.checkpointIdFieldName;
|
|
341
|
-
} else {
|
|
342
|
-
orderByColumns = Table.idFieldName + `, ` + EntityHistory.checkpointIdFieldName;
|
|
411
|
+
async function writeStagedOrThrow(sink, entities, checkpoints) {
|
|
412
|
+
try {
|
|
413
|
+
return await sink.writeBatch(entities, checkpoints);
|
|
414
|
+
} catch (raw_exn) {
|
|
415
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
416
|
+
throw {
|
|
417
|
+
RE_EXN_ID: Persistence.StorageError,
|
|
418
|
+
message: "Failed to write a batch to ClickHouse",
|
|
419
|
+
reason: Utils.prettifyExn(exn),
|
|
420
|
+
Error: new Error()
|
|
421
|
+
};
|
|
343
422
|
}
|
|
344
|
-
let resolveExpressionColumns = expression => expression.replace(/'[^']*'|`[^`]*`|[A-Za-z_][A-Za-z0-9_]*/g, (match, param, param$1) => {
|
|
345
|
-
let column = columnByFieldName[match];
|
|
346
|
-
if (column !== undefined) {
|
|
347
|
-
return `\`` + column + `\``;
|
|
348
|
-
} else {
|
|
349
|
-
return match;
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
let partitionByClause = partitionBy !== undefined ? `\nPARTITION BY ` + resolveExpressionColumns(partitionBy) : "";
|
|
353
|
-
let ttlClause = ttl !== undefined ? `\nTTL ` + resolveExpressionColumns(ttl) : "";
|
|
354
|
-
let skippingIndexDefinitions = skippingIndexes !== undefined ? skippingIndexes.map(index => {
|
|
355
|
-
let granularity = index.granularity;
|
|
356
|
-
let granularityClause = granularity !== undefined ? ` GRANULARITY ` + granularity.toString() : "";
|
|
357
|
-
return `,\n INDEX \`` + index.name + `\` ` + resolveExpressionColumns(index.expr) + ` TYPE ` + index.type + granularityClause;
|
|
358
|
-
}).join("") : "";
|
|
359
|
-
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\`` + (
|
|
360
|
-
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
361
|
-
) + ` (
|
|
362
|
-
` + fieldDefinitions.join(",\n ") + `,
|
|
363
|
-
\`` + EntityHistory.checkpointIdFieldName + `\` ` + getClickHouseFieldType("UInt64", false, false, undefined) + `,
|
|
364
|
-
\`` + EntityHistory.changeFieldName + `\` ` + getClickHouseFieldType({
|
|
365
|
-
type: "Enum",
|
|
366
|
-
config: EntityHistory.RowAction.config
|
|
367
|
-
}, false, false, undefined) + skippingIndexDefinitions + `
|
|
368
|
-
)
|
|
369
|
-
ENGINE = ` + tableEngine + partitionByClause + `
|
|
370
|
-
ORDER BY (` + orderByColumns + `)` + ttlClause + (
|
|
371
|
-
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt, chainIdModeOpt) {
|
|
376
|
-
let replicated = replicatedOpt !== undefined ? replicatedOpt : false;
|
|
377
|
-
let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
|
|
378
|
-
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
379
|
-
let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()";
|
|
380
|
-
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + InternalTable.Checkpoints.table.tableName + `\`` + (
|
|
381
|
-
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
382
|
-
) + ` (
|
|
383
|
-
\`` + "id" + `\` ` + getClickHouseFieldType("UInt64", false, false, undefined) + `,
|
|
384
|
-
\`` + "chain_id" + `\` ` + getClickHouseFieldType("ChainId", false, false, chainIdMode) + `,
|
|
385
|
-
\`` + "block_number" + `\` ` + getClickHouseFieldType("Int32", false, false, undefined) + `,
|
|
386
|
-
\`` + "block_hash" + `\` ` + getClickHouseFieldType("String", true, false, undefined) + `,
|
|
387
|
-
\`` + "events_processed" + `\` ` + getClickHouseFieldType("UInt64", false, false, undefined) + `
|
|
388
|
-
)
|
|
389
|
-
ENGINE = ` + tableEngine + `
|
|
390
|
-
ORDER BY (` + "id" + `)` + (
|
|
391
|
-
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
392
|
-
);
|
|
393
423
|
}
|
|
394
424
|
|
|
395
|
-
function
|
|
396
|
-
let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
|
|
397
|
-
let historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
398
|
-
let field = Table.getChainIdField(entityConfig.table);
|
|
399
|
-
let dedupKey = (
|
|
400
|
-
field !== undefined ? [
|
|
401
|
-
Table.idFieldName,
|
|
402
|
-
Table.getClickHouseDbFieldName(field)
|
|
403
|
-
] : [Table.idFieldName]
|
|
404
|
-
).map(name => `\`` + name + `\``).join(", ");
|
|
405
|
-
let checkpointsTableName = InternalTable.Checkpoints.table.tableName;
|
|
406
|
-
let entityFields = Stdlib_Array.filterMap(entityConfig.table.fields, field => {
|
|
407
|
-
if (field.TAG !== "Field") {
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
let fieldName = Table.getClickHouseDbFieldName(field._0);
|
|
411
|
-
return `\`` + fieldName + `\``;
|
|
412
|
-
}).join(", ");
|
|
413
|
-
return `CREATE VIEW IF NOT EXISTS ` + database + `.\`` + entityConfig.name + `\`` + (
|
|
414
|
-
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
415
|
-
) + ` AS
|
|
416
|
-
SELECT ` + entityFields + `
|
|
417
|
-
FROM (
|
|
418
|
-
SELECT ` + entityFields + `, \`` + EntityHistory.changeFieldName + `\`
|
|
419
|
-
FROM ` + database + `.\`` + historyTableName + `\`
|
|
420
|
-
WHERE \`` + EntityHistory.checkpointIdFieldName + `\` <= (SELECT max(` + "id" + `) FROM ` + database + `.\`` + checkpointsTableName + `\`)
|
|
421
|
-
ORDER BY \`` + EntityHistory.checkpointIdFieldName + `\` DESC
|
|
422
|
-
LIMIT 1 BY ` + dedupKey + `
|
|
423
|
-
)
|
|
424
|
-
WHERE \`` + EntityHistory.changeFieldName + `\` = '` + "SET" + `'`;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async function initialize(client, database, entities, param, chainIdModeOpt) {
|
|
428
|
-
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
425
|
+
async function initialize(sink, entities) {
|
|
429
426
|
try {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (hasReplicatedDatabaseEngine && !envReplicated) {
|
|
436
|
-
Logging.info("ENVIO_CLICKHOUSE_DATABASE_ENGINE is Replicated; enabling replicated mode so tables use the ReplicatedMergeTree engine.");
|
|
437
|
-
}
|
|
438
|
-
let databaseOnClusterClause = replicated ? ` ON CLUSTER '{cluster}'` : "";
|
|
439
|
-
let ddlOnCluster = replicated && !hasReplicatedDatabaseEngine;
|
|
440
|
-
if (databaseEngine !== undefined) {
|
|
441
|
-
let expectedEngineName = databaseEngineName(databaseEngine);
|
|
442
|
-
let existingResult = await client.query({
|
|
443
|
-
query: `SELECT engine FROM system.databases WHERE name = '` + database + `'`
|
|
444
|
-
});
|
|
445
|
-
let rows = (await existingResult.json()).data;
|
|
446
|
-
let row = rows[0];
|
|
447
|
-
if (row !== undefined) {
|
|
448
|
-
let row$1 = Primitive_option.valFromOption(row);
|
|
449
|
-
if (row$1.engine !== expectedEngineName) {
|
|
450
|
-
Stdlib_JsError.throwWithMessage(`ClickHouse database "` + database + `" exists with engine "` + row$1.engine + `" but ENVIO_CLICKHOUSE_DATABASE_ENGINE specifies "` + expectedEngineName + `". Drop the database manually to change its engine.`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (hasReplicatedDatabaseEngine) {
|
|
455
|
-
await client.command({
|
|
456
|
-
query: `DROP DATABASE IF EXISTS ` + database + ` ON CLUSTER '{cluster}' SYNC`
|
|
457
|
-
});
|
|
458
|
-
} else {
|
|
459
|
-
await client.command({
|
|
460
|
-
query: `TRUNCATE DATABASE IF EXISTS ` + database + (
|
|
461
|
-
ddlOnCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
462
|
-
)
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
await client.command({
|
|
466
|
-
query: `CREATE DATABASE IF NOT EXISTS ` + database + databaseOnClusterClause + databaseEngineClause
|
|
467
|
-
});
|
|
468
|
-
await Promise.all(entities.map(entityConfig => client.command({
|
|
469
|
-
query: makeCreateHistoryTableQuery(entityConfig, database, replicated, ddlOnCluster, chainIdMode)
|
|
470
|
-
})));
|
|
471
|
-
await client.command({
|
|
472
|
-
query: makeCreateCheckpointsTableQuery(database, replicated, ddlOnCluster, chainIdMode)
|
|
427
|
+
await sink.initialize({
|
|
428
|
+
entities: entities.map(entitySpec),
|
|
429
|
+
checkpointColumns: checkpointColumnSpecs,
|
|
430
|
+
replicated: Env.ClickHouse.replicated(),
|
|
431
|
+
databaseEngine: Env.ClickHouse.databaseEngine()
|
|
473
432
|
});
|
|
474
|
-
if (hasReplicatedDatabaseEngine) {
|
|
475
|
-
await client.command({
|
|
476
|
-
query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ` + database
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
await Promise.all(entities.map(entityConfig => client.command({
|
|
480
|
-
query: makeCreateViewQuery(entityConfig, database, ddlOnCluster)
|
|
481
|
-
})));
|
|
482
433
|
return Logging.trace("ClickHouse storage initialization completed successfully");
|
|
483
434
|
} catch (raw_exn) {
|
|
484
435
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
485
436
|
Logging.errorWithExn(exn, "Failed to initialize ClickHouse storage");
|
|
486
|
-
|
|
437
|
+
throw {
|
|
438
|
+
RE_EXN_ID: Persistence.StorageError,
|
|
439
|
+
message: "Failed to initialize ClickHouse storage",
|
|
440
|
+
reason: Utils.prettifyExn(exn),
|
|
441
|
+
Error: new Error()
|
|
442
|
+
};
|
|
487
443
|
}
|
|
488
444
|
}
|
|
489
445
|
|
|
490
|
-
async function resume(
|
|
446
|
+
async function resume(sink, checkpointId, chains, entities) {
|
|
447
|
+
let chainProgress = chains.map(chain => ({
|
|
448
|
+
chainId: ChainId.toString(chain.id),
|
|
449
|
+
progressBlockNumber: chain.progressBlockNumber
|
|
450
|
+
}));
|
|
491
451
|
try {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
Logging.errorWithExn(exn, `ClickHouse storage database "` + database + `" not found. Please run 'envio start -r' to reinitialize the indexer (it'll also drop Postgres database).`);
|
|
499
|
-
Stdlib_JsError.throwWithMessage("ClickHouse resume failed");
|
|
500
|
-
}
|
|
501
|
-
let tablesResult = await client.query({
|
|
502
|
-
query: `SHOW TABLES FROM ` + database + ` LIKE '` + EntityHistory.historyTablePrefix + `%'`
|
|
503
|
-
});
|
|
504
|
-
let tables = (await tablesResult.json()).data;
|
|
505
|
-
await Promise.all(tables.map(table => {
|
|
506
|
-
let tableName = table.name;
|
|
507
|
-
return client.command({
|
|
508
|
-
query: `ALTER TABLE ` + database + `.\`` + tableName + `\` DELETE WHERE \`` + EntityHistory.checkpointIdFieldName + `\` > ` + checkpointId.toString()
|
|
509
|
-
});
|
|
510
|
-
}));
|
|
511
|
-
return await client.command({
|
|
512
|
-
query: `DELETE FROM ` + database + `.\`` + InternalTable.Checkpoints.table.tableName + `\` WHERE \`` + Table.idFieldName + `\` > ` + checkpointId.toString()
|
|
452
|
+
return await sink.resume({
|
|
453
|
+
checkpointId: checkpointId.toString(),
|
|
454
|
+
chainProgress: chainProgress,
|
|
455
|
+
historyTables: entities.map(entityConfig => entitySpec(entityConfig).historyTable),
|
|
456
|
+
replicated: Env.ClickHouse.replicated(),
|
|
457
|
+
databaseEngine: Env.ClickHouse.databaseEngine()
|
|
513
458
|
});
|
|
514
|
-
} catch (raw_exn
|
|
515
|
-
let exn
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
459
|
+
} catch (raw_exn) {
|
|
460
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
461
|
+
Logging.errorWithExn(exn, "Failed to resume ClickHouse storage");
|
|
462
|
+
throw {
|
|
463
|
+
RE_EXN_ID: Persistence.StorageError,
|
|
464
|
+
message: "Failed to resume ClickHouse storage",
|
|
465
|
+
reason: Utils.prettifyExn(exn),
|
|
466
|
+
Error: new Error()
|
|
467
|
+
};
|
|
521
468
|
}
|
|
522
469
|
}
|
|
523
470
|
|
|
524
471
|
export {
|
|
525
|
-
|
|
472
|
+
clickHouseFieldSchema,
|
|
526
473
|
makeClickHouseEntitySchema,
|
|
527
474
|
logger,
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
475
|
+
makeSink,
|
|
476
|
+
makeColumnSpec,
|
|
477
|
+
checkpointColumns,
|
|
478
|
+
checkpointColumnSpecs,
|
|
479
|
+
entitySpec,
|
|
480
|
+
makeRegistry,
|
|
481
|
+
entityTable,
|
|
482
|
+
checkpointsTable,
|
|
483
|
+
stageBuilders,
|
|
484
|
+
stageCheckpointsOrThrow,
|
|
485
|
+
stageUpdatesOrThrow,
|
|
486
|
+
writeStagedOrThrow,
|
|
537
487
|
initialize,
|
|
538
488
|
resume,
|
|
539
489
|
}
|