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.
@@ -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 Belt_Option = require("rescript/lib/js/belt_Option.js");
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 makeCreateIndexQuery(tableName, indexFields, pgSchema) {
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 makeCreateTableIndicesQuery(table, pgSchema) {
24
+ function makeCreateTableIndicesSql(table, pgSchema) {
26
25
  var tableName = table.tableName;
27
26
  var createIndex = function (indexField) {
28
- return makeCreateIndexQuery(tableName, [indexField], pgSchema);
27
+ return makeCreateIndexSql(tableName, [indexField], pgSchema);
29
28
  };
30
29
  var createCompositeIndex = function (indexFields) {
31
- return makeCreateIndexQuery(tableName, indexFields, pgSchema);
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 makeCreateTableQuery(table, pgSchema) {
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, reuseExistingPgSchemaOpt) {
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 reuseExistingPgSchema = reuseExistingPgSchemaOpt !== undefined ? reuseExistingPgSchemaOpt : false;
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
- reuseExistingPgSchema ? "" : "DROP SCHEMA IF EXISTS \"" + pgSchema + "\" CASCADE;\nCREATE SCHEMA \"" + pgSchema + "\";\n"
78
- ) + ("GRANT ALL ON SCHEMA \"" + pgSchema + "\" TO \"" + pgUser + "\";\nGRANT ALL ON SCHEMA \"" + pgSchema + "\" TO public;")
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" + enumCreateQuery;
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" + makeCreateTableQuery(table, pgSchema);
88
+ query.contents = query.contents + "\n" + makeCreateTableSql(table, pgSchema);
88
89
  });
89
90
  allTables.forEach(function (table) {
90
- var indices = makeCreateTableIndicesQuery(table, pgSchema);
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" + makeCreateIndexQuery(derivedFromField.derivedFromEntity, [indexField], pgSchema);
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 makeLoadByIdQuery(pgSchema, tableName) {
111
+ function makeLoadByIdSql(pgSchema, tableName) {
111
112
  return "SELECT * FROM \"" + pgSchema + "\".\"" + tableName + "\" WHERE id = $1 LIMIT 1;";
112
113
  }
113
114
 
114
- function makeLoadByFieldQuery(pgSchema, tableName, fieldName, operator) {
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 makeInsertUnnestSetQuery(pgSchema, table, itemSchema, isRawEvents) {
123
- var match = Table.toSqlParams(table, itemSchema, pgSchema);
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 makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount) {
140
- var match = Table.toSqlParams(table, itemSchema, pgSchema);
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, pgSchema);
170
+ var match = Table.toSqlParams(table, itemSchema);
176
171
  var isRawEvents = table.tableName === rawEventsTableName;
177
172
  if (isRawEvents || !match.hasArrayField) {
178
173
  return {
179
- query: makeInsertUnnestSetQuery(pgSchema, table, itemSchema, isRawEvents),
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
- query: makeInsertValuesSetQuery(pgSchema, table, itemSchema, 500),
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 data;
211
+ var query;
235
212
  if (cached !== undefined) {
236
- data = Caml_option.valFromOption(cached);
213
+ query = Caml_option.valFromOption(cached);
237
214
  } else {
238
215
  var newQuery = makeTableBatchSetQuery(pgSchema, table, itemSchema);
239
216
  setQueryCache.set(table, newQuery);
240
- data = newQuery;
217
+ query = newQuery;
241
218
  }
219
+ var sqlQuery = query.sql;
242
220
  try {
243
- if (!data.isInsertValues) {
244
- return await sql.unsafe(data.query, data.convertOrThrow(items), {prepare: true});
221
+ var payload = query.convertOrThrow(items);
222
+ if (!query.isInsertValues) {
223
+ return await sql.unsafe(sqlQuery, payload, {prepare: true});
245
224
  }
246
- var chunks = chunkArray(items, 500);
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 === 500;
251
- var response = sql.unsafe(isFullChunk ? data.query : makeInsertValuesSetQuery(pgSchema, table, itemSchema, chunkSize), data.convertOrThrow(chunk), {prepare: true});
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 envioTables = await sql.unsafe("SELECT table_schema FROM information_schema.tables WHERE table_schema = '" + pgSchema + "' AND table_name = '" + eventSyncStateTableName + "';");
294
- return Utils.$$Array.notEmpty(envioTables);
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 schemaTableNames = await sql.unsafe(makeSchemaTableNamesQuery(pgSchema));
301
- if (Utils.$$Array.notEmpty(schemaTableNames) && !schemaTableNames.some(function (table) {
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(makeLoadByIdsQuery(pgSchema, table.tableName), [ids], {prepare: true}) : sql.unsafe(makeLoadByIdQuery(pgSchema, table.tableName), ids, {prepare: true})
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.makeCreateIndexQuery = makeCreateIndexQuery;
411
- exports.makeCreateTableIndicesQuery = makeCreateTableIndicesQuery;
412
- exports.makeCreateTableQuery = makeCreateTableQuery;
318
+ exports.makeCreateIndexSql = makeCreateIndexSql;
319
+ exports.makeCreateTableIndicesSql = makeCreateTableIndicesSql;
320
+ exports.makeCreateTableSql = makeCreateTableSql;
413
321
  exports.makeInitializeTransaction = makeInitializeTransaction;
414
- exports.makeLoadByIdQuery = makeLoadByIdQuery;
415
- exports.makeLoadByFieldQuery = makeLoadByFieldQuery;
416
- exports.makeLoadByIdsQuery = makeLoadByIdsQuery;
417
- exports.makeInsertUnnestSetQuery = makeInsertUnnestSetQuery;
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
- /* pgEncodingErrorSchema Not a pure module */
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 = {
@@ -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, ~pgSchema, ~schema: S.t<'entity>): t<'entity> => {
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 = `"${pgSchema}"."${historyTableName}"`
232
- let originTablePath = `"${pgSchema}"."${originTableName}"`
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 fromTable(table, pgSchema, schema) {
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 = "\"" + pgSchema + "\".\"" + historyTableName + "\"";
238
- var originTablePath = "\"" + pgSchema + "\".\"" + originTableName + "\"";
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, ~pgSchema) => {
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)}[]::"${pgSchema}".${(fieldType :> 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
+ }