envio 2.32.1 → 3.0.0-alpha-main-clickhouse-sink
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -0
- package/package.json +6 -5
- package/src/Batch.res +4 -4
- package/src/Change.res +9 -0
- package/src/Change.res.js +2 -0
- package/src/Config.res +5 -5
- package/src/Config.res.js +3 -1
- package/src/Envio.gen.ts +3 -3
- package/src/Envio.res +14 -3
- package/src/EventRegister.res +3 -11
- package/src/EventRegister.res.js +4 -8
- package/src/EventRegister.resi +1 -1
- package/src/InMemoryStore.gen.ts +6 -0
- package/src/InMemoryStore.res +149 -0
- package/src/InMemoryStore.res.js +161 -0
- package/src/InMemoryTable.res +50 -35
- package/src/InMemoryTable.res.js +52 -84
- package/src/Internal.gen.ts +0 -2
- package/src/Internal.res +20 -38
- package/src/Internal.res.js +2 -16
- package/src/LoadManager.res +23 -16
- package/src/LoadManager.res.js +17 -15
- package/src/Persistence.res +190 -38
- package/src/Persistence.res.js +92 -39
- package/src/PgStorage.res +700 -14
- package/src/PgStorage.res.js +431 -19
- package/src/Platform.res +141 -0
- package/src/Platform.res.js +170 -0
- package/src/Prometheus.res +41 -0
- package/src/Prometheus.res.js +45 -0
- package/src/SafeCheckpointTracking.res +5 -4
- package/src/Sink.res +47 -0
- package/src/Sink.res.js +36 -0
- package/src/Utils.res +2 -0
- package/src/Utils.res.js +3 -0
- package/src/bindings/ClickHouse.res +387 -0
- package/src/bindings/ClickHouse.res.js +274 -0
- package/src/bindings/Postgres.res +15 -0
- package/src/bindings/Promise.res +3 -0
- package/src/db/EntityHistory.res +33 -156
- package/src/db/EntityHistory.res.js +40 -115
- package/src/db/InternalTable.res +56 -55
- package/src/db/InternalTable.res.js +49 -52
- package/src/db/Table.res +86 -22
- package/src/db/Table.res.js +77 -10
package/src/PgStorage.res.js
CHANGED
|
@@ -6,6 +6,8 @@ var Path = require("path");
|
|
|
6
6
|
var $$Array = require("rescript/lib/js/array.js");
|
|
7
7
|
var Table = require("./db/Table.res.js");
|
|
8
8
|
var Utils = require("./Utils.res.js");
|
|
9
|
+
var Config = require("./Config.res.js");
|
|
10
|
+
var Hrtime = require("./bindings/Hrtime.res.js");
|
|
9
11
|
var Js_exn = require("rescript/lib/js/js_exn.js");
|
|
10
12
|
var Schema = require("./db/Schema.res.js");
|
|
11
13
|
var Js_dict = require("rescript/lib/js/js_dict.js");
|
|
@@ -13,6 +15,7 @@ var Logging = require("./Logging.res.js");
|
|
|
13
15
|
var $$Promise = require("./bindings/Promise.res.js");
|
|
14
16
|
var Internal = require("./Internal.res.js");
|
|
15
17
|
var Belt_Array = require("rescript/lib/js/belt_Array.js");
|
|
18
|
+
var Prometheus = require("./Prometheus.res.js");
|
|
16
19
|
var Caml_option = require("rescript/lib/js/caml_option.js");
|
|
17
20
|
var Persistence = require("./Persistence.res.js");
|
|
18
21
|
var EntityHistory = require("./db/EntityHistory.res.js");
|
|
@@ -48,20 +51,11 @@ function makeCreateTableIndicesQuery(table, pgSchema) {
|
|
|
48
51
|
function makeCreateTableQuery(table, pgSchema, isNumericArrayAsText) {
|
|
49
52
|
var fieldsMapped = Belt_Array.map(Table.getFields(table), (function (field) {
|
|
50
53
|
var defaultValue = field.defaultValue;
|
|
51
|
-
var
|
|
52
|
-
var fieldType = field.fieldType;
|
|
54
|
+
var isNullable = field.isNullable;
|
|
53
55
|
var fieldName = Table.getDbFieldName(field);
|
|
54
|
-
|
|
55
|
-
tmp = fieldType === "TIMESTAMP" || fieldType === "TIMESTAMP WITH TIME ZONE" || fieldType === "JSONB" || fieldType === "SERIAL" || fieldType === "TEXT" || fieldType === "DOUBLE PRECISION" || fieldType === "NUMERIC" || fieldType === "BOOLEAN" || fieldType === "BIGINT" || fieldType === "INTEGER" || fieldType === "TIMESTAMP WITH TIME ZONE NULL" ? (
|
|
56
|
-
fieldType === "NUMERIC" && isArray && isNumericArrayAsText ? "TEXT" : fieldType
|
|
57
|
-
) : (
|
|
58
|
-
fieldType.startsWith("NUMERIC(") ? fieldType : "\"" + pgSchema + "\"." + fieldType
|
|
59
|
-
);
|
|
60
|
-
return "\"" + fieldName + "\" " + tmp + (
|
|
61
|
-
isArray ? "[]" : ""
|
|
62
|
-
) + (
|
|
56
|
+
return "\"" + fieldName + "\" " + Table.getPgFieldType(field.fieldType, pgSchema, field.isArray, isNumericArrayAsText, isNullable) + (
|
|
63
57
|
defaultValue !== undefined ? " DEFAULT " + defaultValue : (
|
|
64
|
-
|
|
58
|
+
isNullable ? "" : " NOT NULL"
|
|
65
59
|
)
|
|
66
60
|
);
|
|
67
61
|
})).join(", ");
|
|
@@ -74,6 +68,68 @@ function makeCreateTableQuery(table, pgSchema, isNumericArrayAsText) {
|
|
|
74
68
|
) + ");";
|
|
75
69
|
}
|
|
76
70
|
|
|
71
|
+
function getEntityHistory(entityConfig) {
|
|
72
|
+
var cache = entityConfig.pgEntityHistoryCache;
|
|
73
|
+
if (cache !== undefined) {
|
|
74
|
+
return cache;
|
|
75
|
+
}
|
|
76
|
+
var dataFields = Belt_Array.keepMap(entityConfig.table.fields, (function (field) {
|
|
77
|
+
if (field.TAG !== "Field") {
|
|
78
|
+
return ;
|
|
79
|
+
}
|
|
80
|
+
var field$1 = field._0;
|
|
81
|
+
var match = field$1.fieldName;
|
|
82
|
+
if (match === "id") {
|
|
83
|
+
return {
|
|
84
|
+
TAG: "Field",
|
|
85
|
+
_0: {
|
|
86
|
+
fieldName: "id",
|
|
87
|
+
fieldType: field$1.fieldType,
|
|
88
|
+
fieldSchema: field$1.fieldSchema,
|
|
89
|
+
isArray: field$1.isArray,
|
|
90
|
+
isNullable: field$1.isNullable,
|
|
91
|
+
isPrimaryKey: true,
|
|
92
|
+
isIndex: field$1.isIndex,
|
|
93
|
+
linkedEntity: field$1.linkedEntity,
|
|
94
|
+
defaultValue: field$1.defaultValue
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
} else {
|
|
98
|
+
return {
|
|
99
|
+
TAG: "Field",
|
|
100
|
+
_0: {
|
|
101
|
+
fieldName: field$1.fieldName,
|
|
102
|
+
fieldType: field$1.fieldType,
|
|
103
|
+
fieldSchema: field$1.fieldSchema,
|
|
104
|
+
isArray: field$1.isArray,
|
|
105
|
+
isNullable: true,
|
|
106
|
+
isPrimaryKey: field$1.isPrimaryKey,
|
|
107
|
+
isIndex: false,
|
|
108
|
+
linkedEntity: field$1.linkedEntity,
|
|
109
|
+
defaultValue: field$1.defaultValue
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
var actionField = Table.mkField(EntityHistory.changeFieldName, EntityHistory.changeFieldType, S$RescriptSchema.never, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
115
|
+
var checkpointIdField = Table.mkField(EntityHistory.checkpointIdFieldName, EntityHistory.checkpointIdFieldType, EntityHistory.unsafeCheckpointIdSchema, undefined, undefined, undefined, true, undefined, undefined);
|
|
116
|
+
var entityTableName = entityConfig.table.tableName;
|
|
117
|
+
var historyTableName = EntityHistory.historyTableName(entityTableName, entityConfig.index);
|
|
118
|
+
var table = Table.mkTable(historyTableName, undefined, Belt_Array.concat(dataFields, [
|
|
119
|
+
checkpointIdField,
|
|
120
|
+
actionField
|
|
121
|
+
]));
|
|
122
|
+
var setChangeSchema = EntityHistory.makeSetUpdateSchema(entityConfig.schema);
|
|
123
|
+
var cache_setChangeSchemaRows = S$RescriptSchema.array(setChangeSchema);
|
|
124
|
+
var cache$1 = {
|
|
125
|
+
table: table,
|
|
126
|
+
setChangeSchema: setChangeSchema,
|
|
127
|
+
setChangeSchemaRows: cache_setChangeSchemaRows
|
|
128
|
+
};
|
|
129
|
+
entityConfig.pgEntityHistoryCache = cache$1;
|
|
130
|
+
return cache$1;
|
|
131
|
+
}
|
|
132
|
+
|
|
77
133
|
function makeInitializeTransaction(pgSchema, pgUser, isHasuraEnabled, chainConfigsOpt, entitiesOpt, enumsOpt, isEmptyPgSchemaOpt) {
|
|
78
134
|
var chainConfigs = chainConfigsOpt !== undefined ? chainConfigsOpt : [];
|
|
79
135
|
var entities = entitiesOpt !== undefined ? entitiesOpt : [];
|
|
@@ -87,10 +143,10 @@ function makeInitializeTransaction(pgSchema, pgUser, isHasuraEnabled, chainConfi
|
|
|
87
143
|
];
|
|
88
144
|
var allTables = $$Array.copy(generalTables);
|
|
89
145
|
var allEntityTables = [];
|
|
90
|
-
entities.forEach(function (
|
|
91
|
-
allEntityTables.push(
|
|
92
|
-
allTables.push(
|
|
93
|
-
allTables.push(
|
|
146
|
+
entities.forEach(function (entityConfig) {
|
|
147
|
+
allEntityTables.push(entityConfig.table);
|
|
148
|
+
allTables.push(entityConfig.table);
|
|
149
|
+
allTables.push(getEntityHistory(entityConfig).table);
|
|
94
150
|
});
|
|
95
151
|
var derivedSchema = Schema.make(allEntityTables);
|
|
96
152
|
var query = {
|
|
@@ -145,6 +201,14 @@ function makeLoadByIdsQuery(pgSchema, tableName) {
|
|
|
145
201
|
return "SELECT * FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE id = ANY($1::text[]);";
|
|
146
202
|
}
|
|
147
203
|
|
|
204
|
+
function makeDeleteByIdQuery(pgSchema, tableName) {
|
|
205
|
+
return "DELETE FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE id = $1;";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function makeDeleteByIdsQuery(pgSchema, tableName) {
|
|
209
|
+
return "DELETE FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE id = ANY($1::text[]);";
|
|
210
|
+
}
|
|
211
|
+
|
|
148
212
|
function makeLoadAllQuery(pgSchema, tableName) {
|
|
149
213
|
return "SELECT * FROM \"" + pgSchema + "\".\"" + tableName + "\";";
|
|
150
214
|
}
|
|
@@ -360,7 +424,267 @@ async function getConnectedPsqlExec(pgUser, pgHost, pgDatabase, pgPort) {
|
|
|
360
424
|
return result;
|
|
361
425
|
}
|
|
362
426
|
|
|
363
|
-
function
|
|
427
|
+
async function deleteByIdsOrThrow(sql, pgSchema, ids, table) {
|
|
428
|
+
try {
|
|
429
|
+
await (
|
|
430
|
+
ids.length !== 1 ? sql.unsafe(makeDeleteByIdsQuery(pgSchema, table.tableName), [ids], {prepare: true}) : sql.unsafe(makeDeleteByIdQuery(pgSchema, table.tableName), ids, {prepare: true})
|
|
431
|
+
);
|
|
432
|
+
return ;
|
|
433
|
+
}
|
|
434
|
+
catch (raw_exn){
|
|
435
|
+
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
436
|
+
throw {
|
|
437
|
+
RE_EXN_ID: Persistence.StorageError,
|
|
438
|
+
message: "Failed deleting \"" + table.tableName + "\" from storage by ids",
|
|
439
|
+
reason: exn,
|
|
440
|
+
Error: new Error()
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function makeInsertDeleteUpdatesQuery(entityConfig, pgSchema) {
|
|
446
|
+
var historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
447
|
+
var allHistoryFieldNames = Belt_Array.keepMap(entityConfig.table.fields, (function (fieldOrDerived) {
|
|
448
|
+
if (fieldOrDerived.TAG === "Field") {
|
|
449
|
+
return Table.getDbFieldName(fieldOrDerived._0);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
}));
|
|
453
|
+
allHistoryFieldNames.push(EntityHistory.checkpointIdFieldName);
|
|
454
|
+
allHistoryFieldNames.push(EntityHistory.changeFieldName);
|
|
455
|
+
var allHistoryFieldNamesStr = Belt_Array.map(allHistoryFieldNames, (function (name) {
|
|
456
|
+
return "\"" + name + "\"";
|
|
457
|
+
})).join(", ");
|
|
458
|
+
var selectParts = Belt_Array.map(allHistoryFieldNames, (function (fieldName) {
|
|
459
|
+
if (fieldName === Table.idFieldName) {
|
|
460
|
+
return "u." + Table.idFieldName;
|
|
461
|
+
} else if (fieldName === EntityHistory.checkpointIdFieldName) {
|
|
462
|
+
return "u." + EntityHistory.checkpointIdFieldName;
|
|
463
|
+
} else if (fieldName === EntityHistory.changeFieldName) {
|
|
464
|
+
return "'" + "DELETE" + "'";
|
|
465
|
+
} else {
|
|
466
|
+
return "NULL";
|
|
467
|
+
}
|
|
468
|
+
}));
|
|
469
|
+
var selectPartsStr = selectParts.join(", ");
|
|
470
|
+
var checkpointIdPgType = Table.getPgFieldType(EntityHistory.checkpointIdFieldType, pgSchema, false, false, false);
|
|
471
|
+
return "INSERT INTO \"" + pgSchema + "\".\"" + historyTableName + "\" (" + allHistoryFieldNamesStr + ")\nSELECT " + selectPartsStr + "\nFROM UNNEST($1::text[], $2::" + checkpointIdPgType + "[]) AS u(" + Table.idFieldName + ", " + EntityHistory.checkpointIdFieldName + ")";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function executeSet(sql, items, dbFunction) {
|
|
475
|
+
if (items.length !== 0) {
|
|
476
|
+
return dbFunction(sql, items);
|
|
477
|
+
} else {
|
|
478
|
+
return Promise.resolve();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function writeBatch(sql, batch, rawEvents, pgSchema, rollbackTargetCheckpointId, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, escapeTables) {
|
|
483
|
+
try {
|
|
484
|
+
var shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold);
|
|
485
|
+
var specificError = {
|
|
486
|
+
contents: undefined
|
|
487
|
+
};
|
|
488
|
+
var setRawEvents = function (__x) {
|
|
489
|
+
return executeSet(__x, rawEvents, (function (sql, items) {
|
|
490
|
+
return setOrThrow(sql, items, InternalTable.RawEvents.table, InternalTable.RawEvents.schema, pgSchema);
|
|
491
|
+
}));
|
|
492
|
+
};
|
|
493
|
+
var setEntities = Belt_Array.map(updatedEntities, (function (param) {
|
|
494
|
+
var updates = param.updates;
|
|
495
|
+
var entityConfig = param.entityConfig;
|
|
496
|
+
var entitiesToSet = [];
|
|
497
|
+
var idsToDelete = [];
|
|
498
|
+
updates.forEach(function (row) {
|
|
499
|
+
var match = row.latestChange;
|
|
500
|
+
if (match.type === "SET") {
|
|
501
|
+
entitiesToSet.push(match.entity);
|
|
502
|
+
return ;
|
|
503
|
+
}
|
|
504
|
+
idsToDelete.push(match.entityId);
|
|
505
|
+
});
|
|
506
|
+
var shouldRemoveInvalidUtf8 = escapeTables !== undefined && Caml_option.valFromOption(escapeTables).has(entityConfig.table) ? true : false;
|
|
507
|
+
return async function (sql) {
|
|
508
|
+
try {
|
|
509
|
+
var promises = [];
|
|
510
|
+
if (shouldSaveHistory) {
|
|
511
|
+
var backfillHistoryIds = new Set();
|
|
512
|
+
var batchSetUpdates = [];
|
|
513
|
+
var batchDeleteCheckpointIds = [];
|
|
514
|
+
var batchDeleteEntityIds = [];
|
|
515
|
+
updates.forEach(function (update) {
|
|
516
|
+
var containsRollbackDiffChange = update.containsRollbackDiffChange;
|
|
517
|
+
update.history.forEach(function (change) {
|
|
518
|
+
if (!containsRollbackDiffChange) {
|
|
519
|
+
backfillHistoryIds.add(change.entityId);
|
|
520
|
+
}
|
|
521
|
+
if (change.type === "SET") {
|
|
522
|
+
batchSetUpdates.push(change);
|
|
523
|
+
return ;
|
|
524
|
+
}
|
|
525
|
+
batchDeleteEntityIds.push(change.entityId);
|
|
526
|
+
batchDeleteCheckpointIds.push(change.checkpointId);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
if (backfillHistoryIds.size !== 0) {
|
|
530
|
+
await EntityHistory.backfillHistory(sql, pgSchema, entityConfig.name, entityConfig.index, Array.from(backfillHistoryIds));
|
|
531
|
+
}
|
|
532
|
+
if (Utils.$$Array.notEmpty(batchDeleteCheckpointIds)) {
|
|
533
|
+
promises.push(sql.unsafe(makeInsertDeleteUpdatesQuery(entityConfig, pgSchema), [
|
|
534
|
+
batchDeleteEntityIds,
|
|
535
|
+
batchDeleteCheckpointIds
|
|
536
|
+
], {prepare: true}));
|
|
537
|
+
}
|
|
538
|
+
if (Utils.$$Array.notEmpty(batchSetUpdates)) {
|
|
539
|
+
if (shouldRemoveInvalidUtf8) {
|
|
540
|
+
var entities = batchSetUpdates.map(function (batchSetUpdate) {
|
|
541
|
+
if (batchSetUpdate.type === "SET") {
|
|
542
|
+
return batchSetUpdate.entity;
|
|
543
|
+
} else {
|
|
544
|
+
return Js_exn.raiseError("Expected Set action");
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
removeInvalidUtf8InPlace(entities);
|
|
548
|
+
}
|
|
549
|
+
var entityHistory = getEntityHistory(entityConfig);
|
|
550
|
+
promises.push(setOrThrow(sql, batchSetUpdates, entityHistory.table, entityHistory.setChangeSchema, pgSchema));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
}
|
|
554
|
+
if (Utils.$$Array.notEmpty(entitiesToSet)) {
|
|
555
|
+
if (shouldRemoveInvalidUtf8) {
|
|
556
|
+
removeInvalidUtf8InPlace(entitiesToSet);
|
|
557
|
+
}
|
|
558
|
+
promises.push(setOrThrow(sql, entitiesToSet, entityConfig.table, entityConfig.schema, pgSchema));
|
|
559
|
+
}
|
|
560
|
+
if (Utils.$$Array.notEmpty(idsToDelete)) {
|
|
561
|
+
promises.push(deleteByIdsOrThrow(sql, pgSchema, idsToDelete, entityConfig.table));
|
|
562
|
+
}
|
|
563
|
+
await Promise.all(promises);
|
|
564
|
+
return ;
|
|
565
|
+
}
|
|
566
|
+
catch (raw_exn){
|
|
567
|
+
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
568
|
+
var normalizedExn = Caml_js_exceptions.internalToOCamlException(exn.RE_EXN_ID === "JsError" || exn.RE_EXN_ID !== Persistence.StorageError ? exn : exn.reason);
|
|
569
|
+
if (normalizedExn.RE_EXN_ID === "JsError") {
|
|
570
|
+
var val;
|
|
571
|
+
try {
|
|
572
|
+
val = S$RescriptSchema.parseOrThrow(normalizedExn._1, pgErrorMessageSchema);
|
|
573
|
+
}
|
|
574
|
+
catch (exn$1){
|
|
575
|
+
return ;
|
|
576
|
+
}
|
|
577
|
+
switch (val) {
|
|
578
|
+
case "current transaction is aborted, commands ignored until end of transaction block" :
|
|
579
|
+
return ;
|
|
580
|
+
case "invalid byte sequence for encoding \"UTF8\": 0x00" :
|
|
581
|
+
specificError.contents = {
|
|
582
|
+
RE_EXN_ID: PgEncodingError,
|
|
583
|
+
table: entityConfig.table
|
|
584
|
+
};
|
|
585
|
+
return ;
|
|
586
|
+
default:
|
|
587
|
+
specificError.contents = Utils.prettifyExn(exn);
|
|
588
|
+
return ;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
if (normalizedExn.RE_EXN_ID !== S$RescriptSchema.Raised) {
|
|
592
|
+
return ;
|
|
593
|
+
}
|
|
594
|
+
throw normalizedExn;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}));
|
|
599
|
+
var rollbackTables = rollbackTargetCheckpointId !== undefined ? (function (sql) {
|
|
600
|
+
var promises = allEntities.map(function (entityConfig) {
|
|
601
|
+
return EntityHistory.rollback(sql, pgSchema, entityConfig.name, entityConfig.index, rollbackTargetCheckpointId);
|
|
602
|
+
});
|
|
603
|
+
promises.push(InternalTable.Checkpoints.rollback(sql, pgSchema, rollbackTargetCheckpointId));
|
|
604
|
+
return Promise.all(promises);
|
|
605
|
+
}) : undefined;
|
|
606
|
+
try {
|
|
607
|
+
await Promise.all([
|
|
608
|
+
sql.begin(async function (sql) {
|
|
609
|
+
if (rollbackTables !== undefined) {
|
|
610
|
+
await rollbackTables(sql);
|
|
611
|
+
}
|
|
612
|
+
var setOperations = Belt_Array.concat([
|
|
613
|
+
(function (sql) {
|
|
614
|
+
return InternalTable.Chains.setProgressedChains(sql, pgSchema, Utils.Dict.mapValuesToArray(batch.progressedChainsById, (function (chainAfterBatch) {
|
|
615
|
+
return {
|
|
616
|
+
chainId: chainAfterBatch.fetchState.chainId,
|
|
617
|
+
progressBlockNumber: chainAfterBatch.progressBlockNumber,
|
|
618
|
+
totalEventsProcessed: chainAfterBatch.totalEventsProcessed
|
|
619
|
+
};
|
|
620
|
+
})));
|
|
621
|
+
}),
|
|
622
|
+
setRawEvents
|
|
623
|
+
], setEntities);
|
|
624
|
+
if (shouldSaveHistory) {
|
|
625
|
+
setOperations.push(function (sql) {
|
|
626
|
+
return InternalTable.Checkpoints.insert(sql, pgSchema, batch.checkpointIds, batch.checkpointChainIds, batch.checkpointBlockNumbers, batch.checkpointBlockHashes, batch.checkpointEventsProcessed);
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
await Promise.all(Belt_Array.map(setOperations, (function (dbFunc) {
|
|
630
|
+
return dbFunc(sql);
|
|
631
|
+
})));
|
|
632
|
+
if (sinkPromise === undefined) {
|
|
633
|
+
return ;
|
|
634
|
+
}
|
|
635
|
+
var exn = await Caml_option.valFromOption(sinkPromise);
|
|
636
|
+
if (exn === undefined) {
|
|
637
|
+
return ;
|
|
638
|
+
}
|
|
639
|
+
throw exn;
|
|
640
|
+
}),
|
|
641
|
+
Promise.all(Belt_Array.map(updatedEffectsCache, (function (param) {
|
|
642
|
+
return setEffectCacheOrThrow(param.effect, param.items, param.shouldInitialize);
|
|
643
|
+
})))
|
|
644
|
+
]);
|
|
645
|
+
var specificError$1 = specificError.contents;
|
|
646
|
+
if (specificError$1 === undefined) {
|
|
647
|
+
return ;
|
|
648
|
+
}
|
|
649
|
+
throw specificError$1;
|
|
650
|
+
}
|
|
651
|
+
catch (raw_exn){
|
|
652
|
+
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
653
|
+
var specificError$2 = specificError.contents;
|
|
654
|
+
throw specificError$2 !== undefined ? specificError$2 : exn;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
catch (raw_exn$1){
|
|
658
|
+
var exn$1 = Caml_js_exceptions.internalToOCamlException(raw_exn$1);
|
|
659
|
+
if (exn$1.RE_EXN_ID === PgEncodingError) {
|
|
660
|
+
var escapeTables$1 = escapeTables !== undefined ? Caml_option.valFromOption(escapeTables) : new Set();
|
|
661
|
+
escapeTables$1.add(exn$1.table);
|
|
662
|
+
return await writeBatch(sql, batch, rawEvents, pgSchema, rollbackTargetCheckpointId, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, Caml_option.some(escapeTables$1));
|
|
663
|
+
}
|
|
664
|
+
throw exn$1;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function makeGetRollbackRestoredEntitiesQuery(entityConfig, pgSchema) {
|
|
669
|
+
var dataFieldNames = Belt_Array.keepMap(entityConfig.table.fields, (function (fieldOrDerived) {
|
|
670
|
+
if (fieldOrDerived.TAG === "Field") {
|
|
671
|
+
return Table.getDbFieldName(fieldOrDerived._0);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
}));
|
|
675
|
+
var dataFieldsCommaSeparated = Belt_Array.map(dataFieldNames, (function (name) {
|
|
676
|
+
return "\"" + name + "\"";
|
|
677
|
+
})).join(", ");
|
|
678
|
+
var historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
679
|
+
return "SELECT DISTINCT ON (" + Table.idFieldName + ") " + dataFieldsCommaSeparated + "\nFROM \"" + pgSchema + "\".\"" + historyTableName + "\"\nWHERE \"" + EntityHistory.checkpointIdFieldName + "\" <= $1\n AND EXISTS (\n SELECT 1\n FROM \"" + pgSchema + "\".\"" + historyTableName + "\" h\n WHERE h." + Table.idFieldName + " = \"" + historyTableName + "\"." + Table.idFieldName + "\n AND h.\"" + EntityHistory.checkpointIdFieldName + "\" > $1\n )\nORDER BY " + Table.idFieldName + ", \"" + EntityHistory.checkpointIdFieldName + "\" DESC";
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function makeGetRollbackRemovedIdsQuery(entityConfig, pgSchema) {
|
|
683
|
+
var historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
684
|
+
return "SELECT DISTINCT " + Table.idFieldName + "\nFROM \"" + pgSchema + "\".\"" + historyTableName + "\"\nWHERE \"" + EntityHistory.checkpointIdFieldName + "\" > $1\nAND NOT EXISTS (\n SELECT 1\n FROM \"" + pgSchema + "\".\"" + historyTableName + "\" h\n WHERE h." + Table.idFieldName + " = \"" + historyTableName + "\"." + Table.idFieldName + "\n AND h.\"" + EntityHistory.checkpointIdFieldName + "\" <= $1\n)";
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isHasuraEnabled, sink, onInitialize, onNewTables) {
|
|
364
688
|
var psqlExecOptions_env = Js_dict.fromArray([
|
|
365
689
|
[
|
|
366
690
|
"PGPASSWORD",
|
|
@@ -467,6 +791,9 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
467
791
|
})) {
|
|
468
792
|
Js_exn.raiseError("Cannot run Envio migrations on PostgreSQL schema \"" + pgSchema + "\" because it contains non-Envio tables. Running migrations would delete all data in this schema.\n\nTo resolve this:\n1. If you want to use this schema, first backup any important data, then drop it with: \"pnpm envio local db-migrate down\"\n2. Or specify a different schema name by setting the \"ENVIO_PG_PUBLIC_SCHEMA\" environment variable\n3. Or manually drop the schema in your database if you're certain the data is not needed.");
|
|
469
793
|
}
|
|
794
|
+
if (sink !== undefined) {
|
|
795
|
+
await sink.initialize(chainConfigs, entities, enums);
|
|
796
|
+
}
|
|
470
797
|
var queries = makeInitializeTransaction(pgSchema, pgUser, isHasuraEnabled, chainConfigs, entities, enums, Utils.$$Array.isEmpty(schemaTableNames));
|
|
471
798
|
await sql.begin(function (sql) {
|
|
472
799
|
return Promise.all(queries.map(function (query) {
|
|
@@ -649,14 +976,81 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
649
976
|
sql.unsafe(InternalTable.Checkpoints.makeCommitedCheckpointIdQuery(pgSchema)),
|
|
650
977
|
sql.unsafe(InternalTable.Checkpoints.makeGetReorgCheckpointsQuery(pgSchema))
|
|
651
978
|
]);
|
|
979
|
+
var checkpointId = match[2][0].id;
|
|
980
|
+
if (sink !== undefined) {
|
|
981
|
+
await sink.resume(checkpointId);
|
|
982
|
+
}
|
|
652
983
|
return {
|
|
653
984
|
cleanRun: false,
|
|
654
985
|
cache: match[0],
|
|
655
986
|
chains: match[1],
|
|
656
|
-
checkpointId:
|
|
987
|
+
checkpointId: checkpointId,
|
|
657
988
|
reorgCheckpoints: match[3]
|
|
658
989
|
};
|
|
659
990
|
};
|
|
991
|
+
var executeUnsafe = function (query) {
|
|
992
|
+
return sql.unsafe(query);
|
|
993
|
+
};
|
|
994
|
+
var hasEntityHistoryRows = async function () {
|
|
995
|
+
var historyTables = await sql.unsafe("SELECT table_name FROM information_schema.tables \n WHERE table_schema = '" + pgSchema + "' \n AND table_name LIKE 'envio_history_%';");
|
|
996
|
+
if (Utils.$$Array.isEmpty(historyTables)) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
var checks = await Promise.all(Belt_Array.map(historyTables, (async function (table) {
|
|
1000
|
+
try {
|
|
1001
|
+
var query = "SELECT EXISTS(SELECT 1 FROM \"" + pgSchema + "\".\"" + table.table_name + "\" LIMIT 1);";
|
|
1002
|
+
var result = await sql.unsafe(query);
|
|
1003
|
+
if (result.length !== 1) {
|
|
1004
|
+
return false;
|
|
1005
|
+
} else {
|
|
1006
|
+
return result[0].exists;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
catch (exn){
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
})));
|
|
1013
|
+
return Belt_Array.some(checks, (function (v) {
|
|
1014
|
+
return v;
|
|
1015
|
+
}));
|
|
1016
|
+
};
|
|
1017
|
+
var setChainMeta = function (chainsData) {
|
|
1018
|
+
return InternalTable.Chains.setMeta(sql, pgSchema, chainsData).then(function (param) {
|
|
1019
|
+
return undefined;
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
var pruneStaleCheckpoints = function (safeCheckpointId) {
|
|
1023
|
+
return InternalTable.Checkpoints.pruneStaleCheckpoints(sql, pgSchema, safeCheckpointId);
|
|
1024
|
+
};
|
|
1025
|
+
var pruneStaleEntityHistory = function (entityName, entityIndex, safeCheckpointId) {
|
|
1026
|
+
return EntityHistory.pruneStaleEntityHistory(sql, entityName, entityIndex, pgSchema, safeCheckpointId);
|
|
1027
|
+
};
|
|
1028
|
+
var getRollbackTargetCheckpoint = function (reorgChainId, lastKnownValidBlockNumber) {
|
|
1029
|
+
return InternalTable.Checkpoints.getRollbackTargetCheckpoint(sql, pgSchema, reorgChainId, lastKnownValidBlockNumber);
|
|
1030
|
+
};
|
|
1031
|
+
var getRollbackProgressDiff = function (rollbackTargetCheckpointId) {
|
|
1032
|
+
return InternalTable.Checkpoints.getRollbackProgressDiff(sql, pgSchema, rollbackTargetCheckpointId);
|
|
1033
|
+
};
|
|
1034
|
+
var getRollbackData = async function (entityConfig, rollbackTargetCheckpointId) {
|
|
1035
|
+
return await Promise.all([
|
|
1036
|
+
sql.unsafe(makeGetRollbackRemovedIdsQuery(entityConfig, pgSchema), [rollbackTargetCheckpointId], {prepare: true}),
|
|
1037
|
+
sql.unsafe(makeGetRollbackRestoredEntitiesQuery(entityConfig, pgSchema), [rollbackTargetCheckpointId], {prepare: true})
|
|
1038
|
+
]);
|
|
1039
|
+
};
|
|
1040
|
+
var writeBatchMethod = async function (batch, rawEvents, rollbackTargetCheckpointId, isInReorgThreshold, config, allEntities, updatedEffectsCache, updatedEntities) {
|
|
1041
|
+
var sinkPromise;
|
|
1042
|
+
if (sink !== undefined) {
|
|
1043
|
+
var timerRef = Hrtime.makeTimer();
|
|
1044
|
+
sinkPromise = Caml_option.some(sink.writeBatch(batch, updatedEntities).then(function () {
|
|
1045
|
+
Prometheus.SinkWrite.increment(sink.name, Hrtime.intFromMillis(Hrtime.toMillis(Hrtime.timeSince(timerRef))));
|
|
1046
|
+
}).catch(function (exn) {
|
|
1047
|
+
return exn;
|
|
1048
|
+
}));
|
|
1049
|
+
} else {
|
|
1050
|
+
sinkPromise = undefined;
|
|
1051
|
+
}
|
|
1052
|
+
return await writeBatch(sql, batch, rawEvents, pgSchema, rollbackTargetCheckpointId, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, undefined);
|
|
1053
|
+
};
|
|
660
1054
|
return {
|
|
661
1055
|
isInitialized: isInitialized,
|
|
662
1056
|
initialize: initialize,
|
|
@@ -665,7 +1059,16 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
665
1059
|
loadByFieldOrThrow: loadByFieldOrThrow,
|
|
666
1060
|
setOrThrow: setOrThrow$1,
|
|
667
1061
|
setEffectCacheOrThrow: setEffectCacheOrThrow,
|
|
668
|
-
dumpEffectCache: dumpEffectCache
|
|
1062
|
+
dumpEffectCache: dumpEffectCache,
|
|
1063
|
+
executeUnsafe: executeUnsafe,
|
|
1064
|
+
hasEntityHistoryRows: hasEntityHistoryRows,
|
|
1065
|
+
setChainMeta: setChainMeta,
|
|
1066
|
+
pruneStaleCheckpoints: pruneStaleCheckpoints,
|
|
1067
|
+
pruneStaleEntityHistory: pruneStaleEntityHistory,
|
|
1068
|
+
getRollbackTargetCheckpoint: getRollbackTargetCheckpoint,
|
|
1069
|
+
getRollbackProgressDiff: getRollbackProgressDiff,
|
|
1070
|
+
getRollbackData: getRollbackData,
|
|
1071
|
+
writeBatch: writeBatchMethod
|
|
669
1072
|
};
|
|
670
1073
|
}
|
|
671
1074
|
|
|
@@ -675,10 +1078,13 @@ exports.getCacheRowCountFnName = getCacheRowCountFnName;
|
|
|
675
1078
|
exports.makeCreateIndexQuery = makeCreateIndexQuery;
|
|
676
1079
|
exports.makeCreateTableIndicesQuery = makeCreateTableIndicesQuery;
|
|
677
1080
|
exports.makeCreateTableQuery = makeCreateTableQuery;
|
|
1081
|
+
exports.getEntityHistory = getEntityHistory;
|
|
678
1082
|
exports.makeInitializeTransaction = makeInitializeTransaction;
|
|
679
1083
|
exports.makeLoadByIdQuery = makeLoadByIdQuery;
|
|
680
1084
|
exports.makeLoadByFieldQuery = makeLoadByFieldQuery;
|
|
681
1085
|
exports.makeLoadByIdsQuery = makeLoadByIdsQuery;
|
|
1086
|
+
exports.makeDeleteByIdQuery = makeDeleteByIdQuery;
|
|
1087
|
+
exports.makeDeleteByIdsQuery = makeDeleteByIdsQuery;
|
|
682
1088
|
exports.makeLoadAllQuery = makeLoadAllQuery;
|
|
683
1089
|
exports.makeInsertUnnestSetQuery = makeInsertUnnestSetQuery;
|
|
684
1090
|
exports.makeInsertValuesSetQuery = makeInsertValuesSetQuery;
|
|
@@ -694,5 +1100,11 @@ exports.makeSchemaTableNamesQuery = makeSchemaTableNamesQuery;
|
|
|
694
1100
|
exports.cacheTablePrefixLength = cacheTablePrefixLength;
|
|
695
1101
|
exports.makeSchemaCacheTableInfoQuery = makeSchemaCacheTableInfoQuery;
|
|
696
1102
|
exports.getConnectedPsqlExec = getConnectedPsqlExec;
|
|
1103
|
+
exports.deleteByIdsOrThrow = deleteByIdsOrThrow;
|
|
1104
|
+
exports.makeInsertDeleteUpdatesQuery = makeInsertDeleteUpdatesQuery;
|
|
1105
|
+
exports.executeSet = executeSet;
|
|
1106
|
+
exports.writeBatch = writeBatch;
|
|
1107
|
+
exports.makeGetRollbackRestoredEntitiesQuery = makeGetRollbackRestoredEntitiesQuery;
|
|
1108
|
+
exports.makeGetRollbackRemovedIdsQuery = makeGetRollbackRemovedIdsQuery;
|
|
697
1109
|
exports.make = make;
|
|
698
1110
|
/* pgErrorMessageSchema Not a pure module */
|
package/src/Platform.res
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
type name = | @as("evm") Evm | @as("fuel") Fuel
|
|
2
|
+
|
|
3
|
+
type t = {
|
|
4
|
+
name: name,
|
|
5
|
+
blockFields: array<string>,
|
|
6
|
+
transactionFields: array<string>,
|
|
7
|
+
blockNumberName: string,
|
|
8
|
+
blockTimestampName: string,
|
|
9
|
+
blockHashName: string,
|
|
10
|
+
getNumber: Internal.eventBlock => int,
|
|
11
|
+
getTimestamp: Internal.eventBlock => int,
|
|
12
|
+
getId: Internal.eventBlock => string,
|
|
13
|
+
cleanUpRawEventFieldsInPlace: Js.Json.t => unit,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module Evm = {
|
|
17
|
+
@get external getNumber: Internal.eventBlock => int = "number"
|
|
18
|
+
@get external getTimestamp: Internal.eventBlock => int = "timestamp"
|
|
19
|
+
@get external getId: Internal.eventBlock => string = "hash"
|
|
20
|
+
|
|
21
|
+
let cleanUpRawEventFieldsInPlace: Js.Json.t => unit = %raw(`fields => {
|
|
22
|
+
delete fields.hash
|
|
23
|
+
delete fields.number
|
|
24
|
+
delete fields.timestamp
|
|
25
|
+
}`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let evm: t = {
|
|
29
|
+
name: Evm,
|
|
30
|
+
blockFields: [
|
|
31
|
+
"number",
|
|
32
|
+
"timestamp",
|
|
33
|
+
"hash",
|
|
34
|
+
"parentHash",
|
|
35
|
+
"nonce",
|
|
36
|
+
"sha3Uncles",
|
|
37
|
+
"logsBloom",
|
|
38
|
+
"transactionsRoot",
|
|
39
|
+
"stateRoot",
|
|
40
|
+
"receiptsRoot",
|
|
41
|
+
"miner",
|
|
42
|
+
"difficulty",
|
|
43
|
+
"totalDifficulty",
|
|
44
|
+
"extraData",
|
|
45
|
+
"size",
|
|
46
|
+
"gasLimit",
|
|
47
|
+
"gasUsed",
|
|
48
|
+
"uncles",
|
|
49
|
+
"baseFeePerGas",
|
|
50
|
+
"blobGasUsed",
|
|
51
|
+
"excessBlobGas",
|
|
52
|
+
"parentBeaconBlockRoot",
|
|
53
|
+
"withdrawalsRoot",
|
|
54
|
+
"l1BlockNumber",
|
|
55
|
+
"sendCount",
|
|
56
|
+
"sendRoot",
|
|
57
|
+
"mixHash",
|
|
58
|
+
],
|
|
59
|
+
transactionFields: [
|
|
60
|
+
"transactionIndex",
|
|
61
|
+
"hash",
|
|
62
|
+
"from",
|
|
63
|
+
"to",
|
|
64
|
+
"gas",
|
|
65
|
+
"gasPrice",
|
|
66
|
+
"maxPriorityFeePerGas",
|
|
67
|
+
"maxFeePerGas",
|
|
68
|
+
"cumulativeGasUsed",
|
|
69
|
+
"effectiveGasPrice",
|
|
70
|
+
"gasUsed",
|
|
71
|
+
"input",
|
|
72
|
+
"nonce",
|
|
73
|
+
"value",
|
|
74
|
+
"v",
|
|
75
|
+
"r",
|
|
76
|
+
"s",
|
|
77
|
+
"contractAddress",
|
|
78
|
+
"logsBloom",
|
|
79
|
+
"root",
|
|
80
|
+
"status",
|
|
81
|
+
"yParity",
|
|
82
|
+
"chainId",
|
|
83
|
+
"maxFeePerBlobGas",
|
|
84
|
+
"blobVersionedHashes",
|
|
85
|
+
"kind",
|
|
86
|
+
"l1Fee",
|
|
87
|
+
"l1GasPrice",
|
|
88
|
+
"l1GasUsed",
|
|
89
|
+
"l1FeeScalar",
|
|
90
|
+
"gasUsedForL1",
|
|
91
|
+
"accessList",
|
|
92
|
+
"authorizationList",
|
|
93
|
+
],
|
|
94
|
+
blockNumberName: "number",
|
|
95
|
+
blockTimestampName: "timestamp",
|
|
96
|
+
blockHashName: "hash",
|
|
97
|
+
getNumber: Evm.getNumber,
|
|
98
|
+
getTimestamp: Evm.getTimestamp,
|
|
99
|
+
getId: Evm.getId,
|
|
100
|
+
cleanUpRawEventFieldsInPlace: Evm.cleanUpRawEventFieldsInPlace,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module Fuel = {
|
|
104
|
+
@get external getNumber: Internal.eventBlock => int = "height"
|
|
105
|
+
@get external getTimestamp: Internal.eventBlock => int = "time"
|
|
106
|
+
@get external getId: Internal.eventBlock => string = "id"
|
|
107
|
+
|
|
108
|
+
let cleanUpRawEventFieldsInPlace: Js.Json.t => unit = %raw(`fields => {
|
|
109
|
+
delete fields.id
|
|
110
|
+
delete fields.height
|
|
111
|
+
delete fields.time
|
|
112
|
+
}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let fuel: t = {
|
|
116
|
+
name: Fuel,
|
|
117
|
+
blockFields: ["id", "height", "time"],
|
|
118
|
+
transactionFields: ["id"],
|
|
119
|
+
blockNumberName: "height",
|
|
120
|
+
blockTimestampName: "time",
|
|
121
|
+
blockHashName: "id",
|
|
122
|
+
getNumber: Fuel.getNumber,
|
|
123
|
+
getTimestamp: Fuel.getTimestamp,
|
|
124
|
+
getId: Fuel.getId,
|
|
125
|
+
cleanUpRawEventFieldsInPlace: Fuel.cleanUpRawEventFieldsInPlace,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let fromName = (name: name): t => {
|
|
129
|
+
switch name {
|
|
130
|
+
| Evm => evm
|
|
131
|
+
| Fuel => fuel
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create a block event object for block handlers based on platform
|
|
136
|
+
let makeBlockEvent = (~blockNumber: int, ~chainId: int, platform: t): Internal.blockEvent => {
|
|
137
|
+
let blockEvent = Js.Dict.empty()
|
|
138
|
+
blockEvent->Js.Dict.set("chainId", chainId->Utils.magic)
|
|
139
|
+
blockEvent->Js.Dict.set(platform.blockNumberName, blockNumber->Utils.magic)
|
|
140
|
+
blockEvent->Utils.magic
|
|
141
|
+
}
|