envio 2.26.0-alpha.9 → 2.26.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/evm.schema.json +0 -7
- package/fuel.schema.json +0 -7
- package/index.d.ts +0 -2
- package/package.json +5 -5
- package/src/Envio.gen.ts +1 -3
- package/src/Envio.res +0 -3
- package/src/Envio.res.js +0 -2
- package/src/FetchState.res +4 -1
- package/src/Internal.res +0 -1
- package/src/Logging.res +0 -8
- package/src/Logging.res.js +0 -29
- package/src/Persistence.res +19 -46
- package/src/Persistence.res.js +13 -23
- package/src/PgStorage.res +84 -214
- package/src/PgStorage.res.js +54 -153
- package/src/Utils.res +0 -17
- package/src/db/EntityHistory.res +28 -5
- package/src/db/EntityHistory.res.js +23 -4
- package/src/db/Table.res +61 -3
- package/src/db/Table.res.js +42 -3
package/src/PgStorage.res.js
CHANGED
|
@@ -7,14 +7,13 @@ var Utils = require("./Utils.res.js");
|
|
|
7
7
|
var Js_exn = require("rescript/lib/js/js_exn.js");
|
|
8
8
|
var Schema = require("./db/Schema.res.js");
|
|
9
9
|
var Belt_Array = require("rescript/lib/js/belt_Array.js");
|
|
10
|
-
var
|
|
10
|
+
var Caml_int32 = require("rescript/lib/js/caml_int32.js");
|
|
11
11
|
var Caml_option = require("rescript/lib/js/caml_option.js");
|
|
12
12
|
var Persistence = require("./Persistence.res.js");
|
|
13
|
-
var Caml_exceptions = require("rescript/lib/js/caml_exceptions.js");
|
|
14
13
|
var S$RescriptSchema = require("rescript-schema/src/S.res.js");
|
|
15
14
|
var Caml_js_exceptions = require("rescript/lib/js/caml_js_exceptions.js");
|
|
16
15
|
|
|
17
|
-
function
|
|
16
|
+
function makeCreateIndexSql(tableName, indexFields, pgSchema) {
|
|
18
17
|
var indexName = tableName + "_" + indexFields.join("_");
|
|
19
18
|
var index = Belt_Array.map(indexFields, (function (idx) {
|
|
20
19
|
return "\"" + idx + "\"";
|
|
@@ -22,20 +21,20 @@ function makeCreateIndexQuery(tableName, indexFields, pgSchema) {
|
|
|
22
21
|
return "CREATE INDEX IF NOT EXISTS \"" + indexName + "\" ON \"" + pgSchema + "\".\"" + tableName + "\"(" + index + ");";
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
function
|
|
24
|
+
function makeCreateTableIndicesSql(table, pgSchema) {
|
|
26
25
|
var tableName = table.tableName;
|
|
27
26
|
var createIndex = function (indexField) {
|
|
28
|
-
return
|
|
27
|
+
return makeCreateIndexSql(tableName, [indexField], pgSchema);
|
|
29
28
|
};
|
|
30
29
|
var createCompositeIndex = function (indexFields) {
|
|
31
|
-
return
|
|
30
|
+
return makeCreateIndexSql(tableName, indexFields, pgSchema);
|
|
32
31
|
};
|
|
33
32
|
var singleIndices = Table.getSingleIndices(table);
|
|
34
33
|
var compositeIndices = Table.getCompositeIndices(table);
|
|
35
34
|
return Belt_Array.map(singleIndices, createIndex).join("\n") + Belt_Array.map(compositeIndices, createCompositeIndex).join("\n");
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
function
|
|
37
|
+
function makeCreateTableSql(table, pgSchema) {
|
|
39
38
|
var fieldsMapped = Belt_Array.map(Table.getFields(table), (function (field) {
|
|
40
39
|
var defaultValue = field.defaultValue;
|
|
41
40
|
var fieldType = field.fieldType;
|
|
@@ -59,11 +58,11 @@ function makeCreateTableQuery(table, pgSchema) {
|
|
|
59
58
|
) + ");";
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
function makeInitializeTransaction(pgSchema, pgUser, generalTablesOpt, entitiesOpt, enumsOpt,
|
|
61
|
+
function makeInitializeTransaction(pgSchema, pgUser, generalTablesOpt, entitiesOpt, enumsOpt, cleanRunOpt) {
|
|
63
62
|
var generalTables = generalTablesOpt !== undefined ? generalTablesOpt : [];
|
|
64
63
|
var entities = entitiesOpt !== undefined ? entitiesOpt : [];
|
|
65
64
|
var enums = enumsOpt !== undefined ? enumsOpt : [];
|
|
66
|
-
var
|
|
65
|
+
var cleanRun = cleanRunOpt !== undefined ? cleanRunOpt : false;
|
|
67
66
|
var allTables = $$Array.copy(generalTables);
|
|
68
67
|
var allEntityTables = [];
|
|
69
68
|
entities.forEach(function (entity) {
|
|
@@ -74,20 +73,22 @@ function makeInitializeTransaction(pgSchema, pgUser, generalTablesOpt, entitiesO
|
|
|
74
73
|
var derivedSchema = Schema.make(allEntityTables);
|
|
75
74
|
var query = {
|
|
76
75
|
contents: (
|
|
77
|
-
|
|
78
|
-
) + ("GRANT ALL ON SCHEMA \"" + pgSchema + "\" TO
|
|
76
|
+
cleanRun ? "DROP SCHEMA IF EXISTS \"" + pgSchema + "\" CASCADE;\nCREATE SCHEMA \"" + pgSchema + "\";" : "CREATE SCHEMA IF NOT EXISTS \"" + pgSchema + "\";"
|
|
77
|
+
) + ("GRANT ALL ON SCHEMA \"" + pgSchema + "\" TO " + pgUser + ";\nGRANT ALL ON SCHEMA \"" + pgSchema + "\" TO public;")
|
|
79
78
|
};
|
|
80
79
|
enums.forEach(function (enumConfig) {
|
|
81
80
|
var enumCreateQuery = "CREATE TYPE \"" + pgSchema + "\"." + enumConfig.name + " AS ENUM(" + enumConfig.variants.map(function (v) {
|
|
82
81
|
return "'" + v + "'";
|
|
83
82
|
}).join(", ") + ");";
|
|
84
|
-
query.contents = query.contents + "\n" +
|
|
83
|
+
query.contents = query.contents + "\n" + (
|
|
84
|
+
cleanRun ? enumCreateQuery : "IF NOT EXISTS (\n SELECT 1 FROM pg_type \n WHERE typname = '" + enumConfig.name.toLowerCase() + "' \n AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '" + pgSchema + "')\n) THEN \n " + enumCreateQuery + "\nEND IF;"
|
|
85
|
+
);
|
|
85
86
|
});
|
|
86
87
|
allTables.forEach(function (table) {
|
|
87
|
-
query.contents = query.contents + "\n" +
|
|
88
|
+
query.contents = query.contents + "\n" + makeCreateTableSql(table, pgSchema);
|
|
88
89
|
});
|
|
89
90
|
allTables.forEach(function (table) {
|
|
90
|
-
var indices =
|
|
91
|
+
var indices = makeCreateTableIndicesSql(table, pgSchema);
|
|
91
92
|
if (indices !== "") {
|
|
92
93
|
query.contents = query.contents + "\n" + indices;
|
|
93
94
|
return ;
|
|
@@ -101,26 +102,22 @@ function makeInitializeTransaction(pgSchema, pgUser, generalTablesOpt, entitiesO
|
|
|
101
102
|
functionsQuery.contents = functionsQuery.contents + "\n" + entity.entityHistory.createInsertFnQuery;
|
|
102
103
|
Table.getDerivedFromFields(entity.table).forEach(function (derivedFromField) {
|
|
103
104
|
var indexField = Utils.unwrapResultExn(Schema.getDerivedFromFieldName(derivedSchema, derivedFromField));
|
|
104
|
-
query.contents = query.contents + "\n" +
|
|
105
|
+
query.contents = query.contents + "\n" + makeCreateIndexSql(derivedFromField.derivedFromEntity, [indexField], pgSchema);
|
|
105
106
|
});
|
|
106
107
|
});
|
|
107
|
-
return [query.contents].concat(functionsQuery.contents !== "" ? [functionsQuery.contents] : []);
|
|
108
|
+
return [cleanRun || Utils.$$Array.isEmpty(enums) ? query.contents : "DO $$ BEGIN " + query.contents + " END $$;"].concat(functionsQuery.contents !== "" ? [functionsQuery.contents] : []);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
function
|
|
111
|
+
function makeLoadByIdSql(pgSchema, tableName) {
|
|
111
112
|
return "SELECT * FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE id = $1 LIMIT 1;";
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
function
|
|
115
|
-
return "SELECT * FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE \"" + fieldName + "\" " + operator + " $1;";
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function makeLoadByIdsQuery(pgSchema, tableName) {
|
|
115
|
+
function makeLoadByIdsSql(pgSchema, tableName) {
|
|
119
116
|
return "SELECT * FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE id = ANY($1::text[]);";
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
function
|
|
123
|
-
var match = Table.toSqlParams(table, itemSchema
|
|
119
|
+
function makeInsertUnnestSetSql(pgSchema, table, itemSchema, isRawEvents) {
|
|
120
|
+
var match = Table.toSqlParams(table, itemSchema);
|
|
124
121
|
var quotedNonPrimaryFieldNames = match.quotedNonPrimaryFieldNames;
|
|
125
122
|
var primaryKeyFieldNames = Table.getPrimaryKeyFieldNames(table);
|
|
126
123
|
return "INSERT INTO \"" + pgSchema + "\".\"" + table.tableName + "\" (" + match.quotedFieldNames.join(", ") + ")\nSELECT * FROM unnest(" + match.arrayFieldTypes.map(function (arrayFieldType, idx) {
|
|
@@ -136,8 +133,8 @@ function makeInsertUnnestSetQuery(pgSchema, table, itemSchema, isRawEvents) {
|
|
|
136
133
|
) + ";";
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
function
|
|
140
|
-
var match = Table.toSqlParams(table, itemSchema
|
|
136
|
+
function makeInsertValuesSetSql(pgSchema, table, itemSchema, itemsCount) {
|
|
137
|
+
var match = Table.toSqlParams(table, itemSchema);
|
|
141
138
|
var quotedNonPrimaryFieldNames = match.quotedNonPrimaryFieldNames;
|
|
142
139
|
var quotedFieldNames = match.quotedFieldNames;
|
|
143
140
|
var primaryKeyFieldNames = Table.getPrimaryKeyFieldNames(table);
|
|
@@ -169,20 +166,18 @@ function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount) {
|
|
|
169
166
|
|
|
170
167
|
var rawEventsTableName = "raw_events";
|
|
171
168
|
|
|
172
|
-
var eventSyncStateTableName = "event_sync_state";
|
|
173
|
-
|
|
174
169
|
function makeTableBatchSetQuery(pgSchema, table, itemSchema) {
|
|
175
|
-
var match = Table.toSqlParams(table, itemSchema
|
|
170
|
+
var match = Table.toSqlParams(table, itemSchema);
|
|
176
171
|
var isRawEvents = table.tableName === rawEventsTableName;
|
|
177
172
|
if (isRawEvents || !match.hasArrayField) {
|
|
178
173
|
return {
|
|
179
|
-
|
|
174
|
+
sql: makeInsertUnnestSetSql(pgSchema, table, itemSchema, isRawEvents),
|
|
180
175
|
convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.unnest(match.dbSchema), "Output", "Input", "Sync", false),
|
|
181
176
|
isInsertValues: false
|
|
182
177
|
};
|
|
183
178
|
} else {
|
|
184
179
|
return {
|
|
185
|
-
|
|
180
|
+
sql: makeInsertValuesSetSql(pgSchema, table, itemSchema, 500),
|
|
186
181
|
convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.preprocess(S$RescriptSchema.unnest(itemSchema), (function (param) {
|
|
187
182
|
return {
|
|
188
183
|
s: (function (prim) {
|
|
@@ -206,24 +201,6 @@ function chunkArray(arr, chunkSize) {
|
|
|
206
201
|
return chunks;
|
|
207
202
|
}
|
|
208
203
|
|
|
209
|
-
function removeInvalidUtf8InPlace(entities) {
|
|
210
|
-
entities.forEach(function (item) {
|
|
211
|
-
Utils.Dict.forEachWithKey(item, (function (key, value) {
|
|
212
|
-
if (typeof value === "string") {
|
|
213
|
-
item[key] = value.replaceAll("\x00", "");
|
|
214
|
-
return ;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
}));
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
var pgEncodingErrorSchema = S$RescriptSchema.object(function (s) {
|
|
222
|
-
s.tag("message", "invalid byte sequence for encoding \"UTF8\": 0x00");
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
var PgEncodingError = /* @__PURE__ */Caml_exceptions.create("PgStorage.PgEncodingError");
|
|
226
|
-
|
|
227
204
|
var setQueryCache = new WeakMap();
|
|
228
205
|
|
|
229
206
|
async function setOrThrow(sql, items, table, itemSchema, pgSchema) {
|
|
@@ -231,24 +208,30 @@ async function setOrThrow(sql, items, table, itemSchema, pgSchema) {
|
|
|
231
208
|
return ;
|
|
232
209
|
}
|
|
233
210
|
var cached = setQueryCache.get(table);
|
|
234
|
-
var
|
|
211
|
+
var query;
|
|
235
212
|
if (cached !== undefined) {
|
|
236
|
-
|
|
213
|
+
query = Caml_option.valFromOption(cached);
|
|
237
214
|
} else {
|
|
238
215
|
var newQuery = makeTableBatchSetQuery(pgSchema, table, itemSchema);
|
|
239
216
|
setQueryCache.set(table, newQuery);
|
|
240
|
-
|
|
217
|
+
query = newQuery;
|
|
241
218
|
}
|
|
219
|
+
var sqlQuery = query.sql;
|
|
242
220
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
221
|
+
var payload = query.convertOrThrow(items);
|
|
222
|
+
if (!query.isInsertValues) {
|
|
223
|
+
return await sql.unsafe(sqlQuery, payload, {prepare: true});
|
|
245
224
|
}
|
|
246
|
-
var
|
|
225
|
+
var match = itemSchema.t;
|
|
226
|
+
var fieldsCount;
|
|
227
|
+
fieldsCount = typeof match !== "object" || match.TAG !== "object" ? Js_exn.raiseError("Expected an object schema for table") : match.items.length;
|
|
228
|
+
var maxChunkSize = Math.imul(500, fieldsCount);
|
|
229
|
+
var chunks = chunkArray(payload, maxChunkSize);
|
|
247
230
|
var responses = [];
|
|
248
231
|
chunks.forEach(function (chunk) {
|
|
249
232
|
var chunkSize = chunk.length;
|
|
250
|
-
var isFullChunk = chunkSize ===
|
|
251
|
-
var response = sql.unsafe(isFullChunk ?
|
|
233
|
+
var isFullChunk = chunkSize === maxChunkSize;
|
|
234
|
+
var response = sql.unsafe(isFullChunk ? sqlQuery : makeInsertValuesSetSql(pgSchema, table, itemSchema, Caml_int32.div(chunkSize, fieldsCount)), chunk, {prepare: true});
|
|
252
235
|
responses.push(response);
|
|
253
236
|
});
|
|
254
237
|
await Promise.all(responses);
|
|
@@ -273,61 +256,28 @@ async function setOrThrow(sql, items, table, itemSchema, pgSchema) {
|
|
|
273
256
|
}
|
|
274
257
|
}
|
|
275
258
|
|
|
276
|
-
function setEntityHistoryOrThrow(sql, entityHistory, rows, shouldCopyCurrentEntity, shouldRemoveInvalidUtf8Opt) {
|
|
277
|
-
var shouldRemoveInvalidUtf8 = shouldRemoveInvalidUtf8Opt !== undefined ? shouldRemoveInvalidUtf8Opt : false;
|
|
278
|
-
return Promise.all(Belt_Array.map(rows, (function (historyRow) {
|
|
279
|
-
var row = S$RescriptSchema.reverseConvertToJsonOrThrow(historyRow, entityHistory.schema);
|
|
280
|
-
if (shouldRemoveInvalidUtf8) {
|
|
281
|
-
removeInvalidUtf8InPlace([row]);
|
|
282
|
-
}
|
|
283
|
-
return entityHistory.insertFn(sql, row, shouldCopyCurrentEntity !== undefined ? shouldCopyCurrentEntity : !Belt_Option.getWithDefault(historyRow.containsRollbackDiffChange, false));
|
|
284
|
-
})));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function makeSchemaTableNamesQuery(pgSchema) {
|
|
288
|
-
return "SELECT table_name FROM information_schema.tables WHERE table_schema = '" + pgSchema + "';";
|
|
289
|
-
}
|
|
290
|
-
|
|
291
259
|
function make(sql, pgSchema, pgUser) {
|
|
292
260
|
var isInitialized = async function () {
|
|
293
|
-
var
|
|
294
|
-
return Utils.$$Array.notEmpty(
|
|
261
|
+
var schemas = await sql.unsafe("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '" + pgSchema + "';");
|
|
262
|
+
return Utils.$$Array.notEmpty(schemas);
|
|
295
263
|
};
|
|
296
|
-
var initialize = async function (entitiesOpt, generalTablesOpt, enumsOpt) {
|
|
264
|
+
var initialize = async function (entitiesOpt, generalTablesOpt, enumsOpt, cleanRunOpt) {
|
|
297
265
|
var entities = entitiesOpt !== undefined ? entitiesOpt : [];
|
|
298
266
|
var generalTables = generalTablesOpt !== undefined ? generalTablesOpt : [];
|
|
299
267
|
var enums = enumsOpt !== undefined ? enumsOpt : [];
|
|
300
|
-
var
|
|
301
|
-
|
|
302
|
-
return table.table_name === eventSyncStateTableName;
|
|
303
|
-
})) {
|
|
304
|
-
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.");
|
|
305
|
-
}
|
|
306
|
-
var queries = makeInitializeTransaction(pgSchema, pgUser, generalTables, entities, enums, Utils.$$Array.isEmpty(schemaTableNames));
|
|
268
|
+
var cleanRun = cleanRunOpt !== undefined ? cleanRunOpt : false;
|
|
269
|
+
var queries = makeInitializeTransaction(pgSchema, pgUser, generalTables, entities, enums, cleanRun);
|
|
307
270
|
await sql.begin(function (sql) {
|
|
308
271
|
return queries.map(function (query) {
|
|
309
272
|
return sql.unsafe(query);
|
|
310
273
|
});
|
|
311
274
|
});
|
|
312
275
|
};
|
|
313
|
-
var loadEffectCaches = async function () {
|
|
314
|
-
var schemaTableNames = await sql.unsafe(makeSchemaTableNamesQuery(pgSchema));
|
|
315
|
-
return Belt_Array.keepMapU(schemaTableNames, (function (schemaTableName) {
|
|
316
|
-
if (schemaTableName.table_name.startsWith("effect_cache_")) {
|
|
317
|
-
return {
|
|
318
|
-
name: schemaTableName.table_name,
|
|
319
|
-
size: 0,
|
|
320
|
-
table: undefined
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
}));
|
|
325
|
-
};
|
|
326
276
|
var loadByIdsOrThrow = async function (ids, table, rowsSchema) {
|
|
327
277
|
var rows;
|
|
328
278
|
try {
|
|
329
279
|
rows = await (
|
|
330
|
-
ids.length !== 1 ? sql.unsafe(
|
|
280
|
+
ids.length !== 1 ? sql.unsafe(makeLoadByIdsSql(pgSchema, table.tableName), [ids], {prepare: true}) : sql.unsafe(makeLoadByIdSql(pgSchema, table.tableName), ids, {prepare: true})
|
|
331
281
|
);
|
|
332
282
|
}
|
|
333
283
|
catch (raw_exn){
|
|
@@ -352,81 +302,32 @@ function make(sql, pgSchema, pgUser) {
|
|
|
352
302
|
};
|
|
353
303
|
}
|
|
354
304
|
};
|
|
355
|
-
var loadByFieldOrThrow = async function (fieldName, fieldSchema, fieldValue, operator, table, rowsSchema) {
|
|
356
|
-
var params;
|
|
357
|
-
try {
|
|
358
|
-
params = [S$RescriptSchema.reverseConvertToJsonOrThrow(fieldValue, fieldSchema)];
|
|
359
|
-
}
|
|
360
|
-
catch (raw_exn){
|
|
361
|
-
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
362
|
-
throw {
|
|
363
|
-
RE_EXN_ID: Persistence.StorageError,
|
|
364
|
-
message: "Failed loading \"" + table.tableName + "\" from storage by field \"" + fieldName + "\". Couldn't serialize provided value.",
|
|
365
|
-
reason: exn,
|
|
366
|
-
Error: new Error()
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
var rows;
|
|
370
|
-
try {
|
|
371
|
-
rows = await sql.unsafe(makeLoadByFieldQuery(pgSchema, table.tableName, fieldName, operator), params, {prepare: true});
|
|
372
|
-
}
|
|
373
|
-
catch (raw_exn$1){
|
|
374
|
-
var exn$1 = Caml_js_exceptions.internalToOCamlException(raw_exn$1);
|
|
375
|
-
throw {
|
|
376
|
-
RE_EXN_ID: Persistence.StorageError,
|
|
377
|
-
message: "Failed loading \"" + table.tableName + "\" from storage by field \"" + fieldName + "\"",
|
|
378
|
-
reason: exn$1,
|
|
379
|
-
Error: new Error()
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
return S$RescriptSchema.parseOrThrow(rows, rowsSchema);
|
|
384
|
-
}
|
|
385
|
-
catch (raw_exn$2){
|
|
386
|
-
var exn$2 = Caml_js_exceptions.internalToOCamlException(raw_exn$2);
|
|
387
|
-
throw {
|
|
388
|
-
RE_EXN_ID: Persistence.StorageError,
|
|
389
|
-
message: "Failed to parse \"" + table.tableName + "\" loaded from storage by ids",
|
|
390
|
-
reason: exn$2,
|
|
391
|
-
Error: new Error()
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
305
|
var setOrThrow$1 = function (items, table, itemSchema) {
|
|
396
306
|
return setOrThrow(sql, items, table, itemSchema, pgSchema);
|
|
397
307
|
};
|
|
398
308
|
return {
|
|
399
309
|
isInitialized: isInitialized,
|
|
400
310
|
initialize: initialize,
|
|
401
|
-
loadEffectCaches: loadEffectCaches,
|
|
402
311
|
loadByIdsOrThrow: loadByIdsOrThrow,
|
|
403
|
-
loadByFieldOrThrow: loadByFieldOrThrow,
|
|
404
312
|
setOrThrow: setOrThrow$1
|
|
405
313
|
};
|
|
406
314
|
}
|
|
407
315
|
|
|
408
316
|
var maxItemsPerQuery = 500;
|
|
409
317
|
|
|
410
|
-
exports.
|
|
411
|
-
exports.
|
|
412
|
-
exports.
|
|
318
|
+
exports.makeCreateIndexSql = makeCreateIndexSql;
|
|
319
|
+
exports.makeCreateTableIndicesSql = makeCreateTableIndicesSql;
|
|
320
|
+
exports.makeCreateTableSql = makeCreateTableSql;
|
|
413
321
|
exports.makeInitializeTransaction = makeInitializeTransaction;
|
|
414
|
-
exports.
|
|
415
|
-
exports.
|
|
416
|
-
exports.
|
|
417
|
-
exports.
|
|
418
|
-
exports.makeInsertValuesSetQuery = makeInsertValuesSetQuery;
|
|
322
|
+
exports.makeLoadByIdSql = makeLoadByIdSql;
|
|
323
|
+
exports.makeLoadByIdsSql = makeLoadByIdsSql;
|
|
324
|
+
exports.makeInsertUnnestSetSql = makeInsertUnnestSetSql;
|
|
325
|
+
exports.makeInsertValuesSetSql = makeInsertValuesSetSql;
|
|
419
326
|
exports.rawEventsTableName = rawEventsTableName;
|
|
420
|
-
exports.eventSyncStateTableName = eventSyncStateTableName;
|
|
421
327
|
exports.maxItemsPerQuery = maxItemsPerQuery;
|
|
422
328
|
exports.makeTableBatchSetQuery = makeTableBatchSetQuery;
|
|
423
329
|
exports.chunkArray = chunkArray;
|
|
424
|
-
exports.removeInvalidUtf8InPlace = removeInvalidUtf8InPlace;
|
|
425
|
-
exports.pgEncodingErrorSchema = pgEncodingErrorSchema;
|
|
426
|
-
exports.PgEncodingError = PgEncodingError;
|
|
427
330
|
exports.setQueryCache = setQueryCache;
|
|
428
331
|
exports.setOrThrow = setOrThrow;
|
|
429
|
-
exports.setEntityHistoryOrThrow = setEntityHistoryOrThrow;
|
|
430
|
-
exports.makeSchemaTableNamesQuery = makeSchemaTableNamesQuery;
|
|
431
332
|
exports.make = make;
|
|
432
|
-
/*
|
|
333
|
+
/* setQueryCache Not a pure module */
|
package/src/Utils.res
CHANGED
|
@@ -301,23 +301,6 @@ module String = {
|
|
|
301
301
|
str->Js.String2.slice(~from=0, ~to_=1)->Js.String.toUpperCase ++
|
|
302
302
|
str->Js.String2.sliceToEnd(~from=1)
|
|
303
303
|
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
`replaceAll(str, substr, newSubstr)` returns a new `string` which is
|
|
307
|
-
identical to `str` except with all matching instances of `substr` replaced
|
|
308
|
-
by `newSubstr`. `substr` is treated as a verbatim string to match, not a
|
|
309
|
-
regular expression.
|
|
310
|
-
See [`String.replaceAll`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll) on MDN.
|
|
311
|
-
|
|
312
|
-
## Examples
|
|
313
|
-
|
|
314
|
-
```rescript
|
|
315
|
-
String.replaceAll("old old string", "old", "new") == "new new string"
|
|
316
|
-
String.replaceAll("the cat and the dog", "the", "this") == "this cat and this dog"
|
|
317
|
-
```
|
|
318
|
-
*/
|
|
319
|
-
@send
|
|
320
|
-
external replaceAll: (string, string, string) => string = "replaceAll"
|
|
321
304
|
}
|
|
322
305
|
|
|
323
306
|
module Result = {
|
package/src/db/EntityHistory.res
CHANGED
|
@@ -148,12 +148,33 @@ type t<'entity> = {
|
|
|
148
148
|
insertFn: (Postgres.sql, Js.Json.t, ~shouldCopyCurrentEntity: bool) => promise<unit>,
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
let insertRow = (
|
|
152
|
+
self: t<'entity>,
|
|
153
|
+
~sql,
|
|
154
|
+
~historyRow: historyRow<'entity>,
|
|
155
|
+
~shouldCopyCurrentEntity,
|
|
156
|
+
) => {
|
|
157
|
+
let row = historyRow->S.reverseConvertToJsonOrThrow(self.schema)
|
|
158
|
+
self.insertFn(sql, row, ~shouldCopyCurrentEntity)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let batchInsertRows = (self: t<'entity>, ~sql, ~rows: array<historyRow<'entity>>) => {
|
|
162
|
+
rows
|
|
163
|
+
->Belt.Array.map(historyRow => {
|
|
164
|
+
let containsRollbackDiffChange =
|
|
165
|
+
historyRow.containsRollbackDiffChange->Belt.Option.getWithDefault(false)
|
|
166
|
+
let shouldCopyCurrentEntity = !containsRollbackDiffChange
|
|
167
|
+
self->insertRow(~sql, ~historyRow, ~shouldCopyCurrentEntity)
|
|
168
|
+
})
|
|
169
|
+
->Promise.all
|
|
170
|
+
->Promise.thenResolve(_ => ())
|
|
171
|
+
}
|
|
172
|
+
|
|
151
173
|
type entityInternal
|
|
152
174
|
|
|
153
175
|
external castInternal: t<'entity> => t<entityInternal> = "%identity"
|
|
154
|
-
external eval: string => 'a = "eval"
|
|
155
176
|
|
|
156
|
-
let fromTable = (table: table, ~
|
|
177
|
+
let fromTable = (table: table, ~schema: S.t<'entity>): t<'entity> => {
|
|
157
178
|
let entity_history_block_timestamp = "entity_history_block_timestamp"
|
|
158
179
|
let entity_history_chain_id = "entity_history_chain_id"
|
|
159
180
|
let entity_history_block_number = "entity_history_block_number"
|
|
@@ -214,10 +235,12 @@ let fromTable = (table: table, ~pgSchema, ~schema: S.t<'entity>): t<'entity> =>
|
|
|
214
235
|
let dataFieldNames = dataFields->Belt.Array.map(field => field->getFieldName)
|
|
215
236
|
|
|
216
237
|
let originTableName = table.tableName
|
|
238
|
+
let originSchemaName = table.schemaName
|
|
217
239
|
let historyTableName = originTableName ++ "_history"
|
|
218
240
|
//ignore composite indices
|
|
219
241
|
let table = mkTable(
|
|
220
242
|
historyTableName,
|
|
243
|
+
~schemaName=originSchemaName,
|
|
221
244
|
~fields=Belt.Array.concatMany([
|
|
222
245
|
currentHistoryFields,
|
|
223
246
|
previousHistoryFields,
|
|
@@ -228,8 +251,8 @@ let fromTable = (table: table, ~pgSchema, ~schema: S.t<'entity>): t<'entity> =>
|
|
|
228
251
|
|
|
229
252
|
let insertFnName = `"insert_${table.tableName}"`
|
|
230
253
|
let historyRowArg = "history_row"
|
|
231
|
-
let historyTablePath = `"${
|
|
232
|
-
let originTablePath = `"${
|
|
254
|
+
let historyTablePath = `"${originSchemaName}"."${historyTableName}"`
|
|
255
|
+
let originTablePath = `"${originSchemaName}"."${originTableName}"`
|
|
233
256
|
|
|
234
257
|
let previousHistoryFieldsAreNullStr =
|
|
235
258
|
previousChangeFieldNames
|
|
@@ -312,7 +335,7 @@ let fromTable = (table: table, ~pgSchema, ~schema: S.t<'entity>): t<'entity> =>
|
|
|
312
335
|
\${shouldCopyCurrentEntity});\``
|
|
313
336
|
|
|
314
337
|
let insertFn: (Postgres.sql, Js.Json.t, ~shouldCopyCurrentEntity: bool) => promise<unit> =
|
|
315
|
-
insertFnString->eval
|
|
338
|
+
insertFnString->Table.PostgresInterop.eval
|
|
316
339
|
|
|
317
340
|
let schema = makeHistoryRowSchema(schema)
|
|
318
341
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
var Table = require("./Table.res.js");
|
|
5
5
|
var Js_exn = require("rescript/lib/js/js_exn.js");
|
|
6
6
|
var Belt_Array = require("rescript/lib/js/belt_Array.js");
|
|
7
|
+
var Belt_Option = require("rescript/lib/js/belt_Option.js");
|
|
7
8
|
var S$RescriptSchema = require("rescript-schema/src/S.res.js");
|
|
8
9
|
|
|
9
10
|
var variants = [
|
|
@@ -157,7 +158,22 @@ function makeHistoryRowSchema(entitySchema) {
|
|
|
157
158
|
}));
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
function
|
|
161
|
+
function insertRow(self, sql, historyRow, shouldCopyCurrentEntity) {
|
|
162
|
+
var row = S$RescriptSchema.reverseConvertToJsonOrThrow(historyRow, self.schema);
|
|
163
|
+
return self.insertFn(sql, row, shouldCopyCurrentEntity);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function batchInsertRows(self, sql, rows) {
|
|
167
|
+
return Promise.all(Belt_Array.map(rows, (function (historyRow) {
|
|
168
|
+
var containsRollbackDiffChange = Belt_Option.getWithDefault(historyRow.containsRollbackDiffChange, false);
|
|
169
|
+
var shouldCopyCurrentEntity = !containsRollbackDiffChange;
|
|
170
|
+
return insertRow(self, sql, historyRow, shouldCopyCurrentEntity);
|
|
171
|
+
}))).then(function (param) {
|
|
172
|
+
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function fromTable(table, schema) {
|
|
161
177
|
var currentChangeFieldNames = [
|
|
162
178
|
"entity_history_block_timestamp",
|
|
163
179
|
"entity_history_chain_id",
|
|
@@ -222,8 +238,9 @@ function fromTable(table, pgSchema, schema) {
|
|
|
222
238
|
return Table.getFieldName(field);
|
|
223
239
|
}));
|
|
224
240
|
var originTableName = table.tableName;
|
|
241
|
+
var originSchemaName = table.schemaName;
|
|
225
242
|
var historyTableName = originTableName + "_history";
|
|
226
|
-
var table$1 = Table.mkTable(historyTableName, undefined, Belt_Array.concatMany([
|
|
243
|
+
var table$1 = Table.mkTable(historyTableName, originSchemaName, undefined, Belt_Array.concatMany([
|
|
227
244
|
currentHistoryFields,
|
|
228
245
|
previousHistoryFields,
|
|
229
246
|
dataFields,
|
|
@@ -234,8 +251,8 @@ function fromTable(table, pgSchema, schema) {
|
|
|
234
251
|
]));
|
|
235
252
|
var insertFnName = "\"insert_" + table$1.tableName + "\"";
|
|
236
253
|
var historyRowArg = "history_row";
|
|
237
|
-
var historyTablePath = "\"" +
|
|
238
|
-
var originTablePath = "\"" +
|
|
254
|
+
var historyTablePath = "\"" + originSchemaName + "\".\"" + historyTableName + "\"";
|
|
255
|
+
var originTablePath = "\"" + originSchemaName + "\".\"" + originTableName + "\"";
|
|
239
256
|
var previousHistoryFieldsAreNullStr = Belt_Array.map(previousChangeFieldNames, (function (fieldName) {
|
|
240
257
|
return historyRowArg + "." + fieldName + " IS NULL";
|
|
241
258
|
})).join(" OR ");
|
|
@@ -284,5 +301,7 @@ exports.entityIdOnlySchema = entityIdOnlySchema;
|
|
|
284
301
|
exports.previousHistoryFieldsSchema = previousHistoryFieldsSchema;
|
|
285
302
|
exports.currentHistoryFieldsSchema = currentHistoryFieldsSchema;
|
|
286
303
|
exports.makeHistoryRowSchema = makeHistoryRowSchema;
|
|
304
|
+
exports.insertRow = insertRow;
|
|
305
|
+
exports.batchInsertRows = batchInsertRows;
|
|
287
306
|
exports.fromTable = fromTable;
|
|
288
307
|
/* schema Not a pure module */
|
package/src/db/Table.res
CHANGED
|
@@ -89,12 +89,14 @@ let getFieldType = (field: field) => {
|
|
|
89
89
|
|
|
90
90
|
type table = {
|
|
91
91
|
tableName: string,
|
|
92
|
+
schemaName: string,
|
|
92
93
|
fields: array<fieldOrDerived>,
|
|
93
94
|
compositeIndices: array<array<string>>,
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
let mkTable = (tableName, ~compositeIndices=[], ~fields) => {
|
|
97
|
+
let mkTable = (tableName, ~schemaName, ~compositeIndices=[], ~fields) => {
|
|
97
98
|
tableName,
|
|
99
|
+
schemaName,
|
|
98
100
|
fields,
|
|
99
101
|
compositeIndices,
|
|
100
102
|
}
|
|
@@ -185,7 +187,7 @@ type sqlParams<'entity> = {
|
|
|
185
187
|
hasArrayField: bool,
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
let toSqlParams = (table: table, ~schema
|
|
190
|
+
let toSqlParams = (table: table, ~schema) => {
|
|
189
191
|
let quotedFieldNames = []
|
|
190
192
|
let quotedNonPrimaryFieldNames = []
|
|
191
193
|
let arrayFieldTypes = []
|
|
@@ -240,7 +242,7 @@ let toSqlParams = (table: table, ~schema, ~pgSchema) => {
|
|
|
240
242
|
switch field {
|
|
241
243
|
| Field(f) =>
|
|
242
244
|
switch f.fieldType {
|
|
243
|
-
| Custom(fieldType) => `${(Text :> string)}[]
|
|
245
|
+
| Custom(fieldType) => `${(Text :> string)}[]::${(fieldType :> string)}`
|
|
244
246
|
| Boolean => `${(Integer :> string)}[]::${(f.fieldType :> string)}`
|
|
245
247
|
| fieldType => (fieldType :> string)
|
|
246
248
|
}
|
|
@@ -297,3 +299,59 @@ let getCompositeIndices = (table): array<array<string>> => {
|
|
|
297
299
|
->getUnfilteredCompositeIndicesUnsafe
|
|
298
300
|
->Array.keep(ind => ind->Array.length > 1)
|
|
299
301
|
}
|
|
302
|
+
|
|
303
|
+
module PostgresInterop = {
|
|
304
|
+
type pgFn<'payload, 'return> = (Postgres.sql, 'payload) => promise<'return>
|
|
305
|
+
type batchSetFn<'a> = (Postgres.sql, array<'a>) => promise<unit>
|
|
306
|
+
external eval: string => 'a = "eval"
|
|
307
|
+
|
|
308
|
+
let makeBatchSetFnString = (table: table) => {
|
|
309
|
+
let fieldNamesInQuotes =
|
|
310
|
+
table->getNonDefaultFieldNames->Array.map(fieldName => `"${fieldName}"`)
|
|
311
|
+
`(sql, rows) => {
|
|
312
|
+
return sql\`
|
|
313
|
+
INSERT INTO "${table.schemaName}"."${table.tableName}"
|
|
314
|
+
\${sql(rows, ${fieldNamesInQuotes->Js.Array2.joinWith(", ")})}
|
|
315
|
+
ON CONFLICT(${table->getPrimaryKeyFieldNames->Js.Array2.joinWith(", ")}) DO UPDATE
|
|
316
|
+
SET
|
|
317
|
+
${fieldNamesInQuotes
|
|
318
|
+
->Array.map(fieldNameInQuotes => `${fieldNameInQuotes} = EXCLUDED.${fieldNameInQuotes}`)
|
|
319
|
+
->Js.Array2.joinWith(", ")};\`
|
|
320
|
+
}`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let chunkBatchQuery = (
|
|
324
|
+
sql,
|
|
325
|
+
entityDataArray: array<'entity>,
|
|
326
|
+
queryToExecute: pgFn<array<'entity>, 'return>,
|
|
327
|
+
~maxItemsPerQuery=500,
|
|
328
|
+
): promise<array<'return>> => {
|
|
329
|
+
let responses = []
|
|
330
|
+
let i = ref(0)
|
|
331
|
+
let shouldContinue = () => i.contents < entityDataArray->Array.length
|
|
332
|
+
// Split entityDataArray into chunks of maxItemsPerQuery
|
|
333
|
+
while shouldContinue() {
|
|
334
|
+
let chunk =
|
|
335
|
+
entityDataArray->Js.Array2.slice(~start=i.contents, ~end_=i.contents + maxItemsPerQuery)
|
|
336
|
+
let response = queryToExecute(sql, chunk)
|
|
337
|
+
responses->Js.Array2.push(response)->ignore
|
|
338
|
+
i := i.contents + maxItemsPerQuery
|
|
339
|
+
}
|
|
340
|
+
Promise.all(responses)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let makeBatchSetFn = (~table, ~schema: S.t<'a>): batchSetFn<'a> => {
|
|
344
|
+
let batchSetFn: pgFn<array<Js.Json.t>, unit> = table->makeBatchSetFnString->eval
|
|
345
|
+
let parseOrThrow = S.compile(
|
|
346
|
+
S.array(schema),
|
|
347
|
+
~input=Value,
|
|
348
|
+
~output=Json,
|
|
349
|
+
~mode=Sync,
|
|
350
|
+
~typeValidation=true,
|
|
351
|
+
)
|
|
352
|
+
async (sql, rows) => {
|
|
353
|
+
let rowsJson = rows->parseOrThrow->(Utils.magic: Js.Json.t => array<Js.Json.t>)
|
|
354
|
+
let _res = await chunkBatchQuery(sql, rowsJson, batchSetFn)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|