envio 3.12.0 → 3.12.1
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/package.json +6 -6
- package/src/ChainFetching.res +7 -3
- package/src/ChainFetching.res.mjs +1 -1
- package/src/ChainState.res +12 -3
- package/src/ChainState.res.mjs +3 -3
- package/src/ChainState.resi +1 -1
- package/src/CrossChainState.res +1 -1
- package/src/CrossChainState.res.mjs +1 -1
- package/src/FetchState.res +51 -46
- package/src/FetchState.res.mjs +54 -36
- package/src/InMemoryTable.res +155 -67
- package/src/InMemoryTable.res.mjs +151 -60
- package/src/LoadLayer.res +57 -34
- package/src/LoadLayer.res.mjs +45 -62
- package/src/LoadLayer.resi +1 -1
- package/src/PgStorage.res +92 -62
- package/src/PgStorage.res.mjs +77 -50
- package/src/TestIndexer.res +9 -25
- package/src/TestIndexer.res.mjs +4 -3
- package/src/UserContext.res +13 -32
- package/src/UserContext.res.mjs +1 -7
- package/src/Utils.res +1 -4
- package/src/Utils.res.mjs +7 -16
- package/src/db/EntityFilter.res +487 -275
- package/src/db/EntityFilter.res.mjs +557 -309
- package/src/db/Table.res +21 -6
- package/src/db/Table.res.mjs +13 -4
- package/src/sources/BlockStore.res +7 -2
- package/src/sources/EvmHyperSyncSource.res +2 -0
- package/src/sources/EvmHyperSyncSource.res.mjs +2 -2
- package/src/sources/FuelHyperSyncSource.res +1 -0
- package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
- package/src/sources/HyperSync.res +4 -0
- package/src/sources/HyperSync.res.mjs +4 -2
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +3 -0
- package/src/sources/HyperSyncSSE.res +1 -1
- package/src/sources/HyperSyncSSE.res.mjs +4 -10
- package/src/sources/RpcSource.res +1 -0
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SimulateSource.res +1 -0
- package/src/sources/SimulateSource.res.mjs +1 -1
- package/src/sources/Source.res +7 -0
- package/src/sources/SourceManager.res +4 -3
- package/src/sources/SourceManager.res.mjs +2 -2
- package/src/sources/SvmHyperSyncClient.res +5 -0
- package/src/sources/SvmHyperSyncSource.res +3 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
package/src/LoadLayer.res
CHANGED
|
@@ -6,23 +6,6 @@ let scopeKeySuffix = (scope: Internal.chainScope) =>
|
|
|
6
6
|
| Chain(chainId) => `.${chainId->ChainId.toString}`
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
// Narrows a query to the scope's chain. Cross-chain entities have no chain-id
|
|
10
|
-
// column, so their filter is left untouched.
|
|
11
|
-
let scopeFilter = (filter: EntityFilter.t, ~table: Table.table, ~scope: Internal.chainScope) =>
|
|
12
|
-
switch (scope, table->Table.getChainIdField) {
|
|
13
|
-
| (Chain(chainId), Some(field)) =>
|
|
14
|
-
EntityFilter.And({
|
|
15
|
-
filters: [
|
|
16
|
-
filter,
|
|
17
|
-
Eq({
|
|
18
|
-
fieldName: field.fieldName,
|
|
19
|
-
fieldValue: chainId->(Utils.magic: ChainId.t => unknown),
|
|
20
|
-
}),
|
|
21
|
-
],
|
|
22
|
-
})
|
|
23
|
-
| _ => filter
|
|
24
|
-
}
|
|
25
|
-
|
|
26
9
|
let loadById = (
|
|
27
10
|
~loadManager,
|
|
28
11
|
~persistence: Persistence.t,
|
|
@@ -48,10 +31,10 @@ let loadById = (
|
|
|
48
31
|
(
|
|
49
32
|
await storage.loadOrThrow(
|
|
50
33
|
~table=entityConfig.table,
|
|
51
|
-
~filter=EntityFilter.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
~filter=EntityFilter.byIds(idsToLoad)->EntityFilter.scoped(
|
|
35
|
+
~table=entityConfig.table,
|
|
36
|
+
~scope,
|
|
37
|
+
),
|
|
55
38
|
)
|
|
56
39
|
)->(Utils.magic: array<unknown> => array<Internal.entity>)
|
|
57
40
|
} catch {
|
|
@@ -273,15 +256,9 @@ let loadEffect = (
|
|
|
273
256
|
let {outputSchema} = effect.storageMeta
|
|
274
257
|
|
|
275
258
|
let dbEntities = try {
|
|
276
|
-
(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
~filter=EntityFilter.In({
|
|
280
|
-
fieldName: Table.idFieldName,
|
|
281
|
-
fieldValue: idsToLoad->(Utils.magic: array<string> => array<unknown>),
|
|
282
|
-
}),
|
|
283
|
-
)
|
|
284
|
-
)->(Utils.magic: array<unknown> => array<Internal.effectCacheItem>)
|
|
259
|
+
(await storage.loadOrThrow(~table, ~filter=EntityFilter.byIds(idsToLoad)))->(
|
|
260
|
+
Utils.magic: array<unknown> => array<Internal.effectCacheItem>
|
|
261
|
+
)
|
|
285
262
|
} catch {
|
|
286
263
|
| exn =>
|
|
287
264
|
Ecosystem.getItemLogger(item, ~ecosystem)->Logging.childWarn({
|
|
@@ -355,7 +332,7 @@ let loadEffect = (
|
|
|
355
332
|
)
|
|
356
333
|
}
|
|
357
334
|
|
|
358
|
-
let
|
|
335
|
+
let loadByParsedFilter = (
|
|
359
336
|
~loadManager,
|
|
360
337
|
~persistence: Persistence.t,
|
|
361
338
|
~entityConfig: Internal.entityConfig,
|
|
@@ -378,7 +355,9 @@ let loadByFilter = (
|
|
|
378
355
|
|
|
379
356
|
let size = ref(0)
|
|
380
357
|
|
|
381
|
-
filters->Array.forEach(filter =>
|
|
358
|
+
filters->Array.forEach(filter =>
|
|
359
|
+
inMemTable->InMemoryTable.Entity.addEmptyIndex(~filter, ~table=entityConfig.table)
|
|
360
|
+
)
|
|
382
361
|
|
|
383
362
|
// Any non-derived field can be filtered on, so the columns this query reads
|
|
384
363
|
// are indexed on demand before it runs rather than promised by the schema.
|
|
@@ -399,7 +378,7 @@ let loadByFilter = (
|
|
|
399
378
|
(
|
|
400
379
|
await storage.loadOrThrow(
|
|
401
380
|
~table=entityConfig.table,
|
|
402
|
-
~filter=filter->
|
|
381
|
+
~filter=filter->EntityFilter.scoped(~table=entityConfig.table, ~scope),
|
|
403
382
|
)
|
|
404
383
|
)->(Utils.magic: array<unknown> => array<Internal.entity>)
|
|
405
384
|
|
|
@@ -412,6 +391,10 @@ let loadByFilter = (
|
|
|
412
391
|
)
|
|
413
392
|
})
|
|
414
393
|
|
|
394
|
+
// Every row this filter's values could match is now in the table, so a
|
|
395
|
+
// later getWhere naming any of them needs no round trip of its own.
|
|
396
|
+
inMemTable->InMemoryTable.Entity.recordLoadedValues(~filter, ~table=entityConfig.table)
|
|
397
|
+
|
|
415
398
|
size := size.contents + entities->Array.length
|
|
416
399
|
} catch {
|
|
417
400
|
| Persistence.StorageError({message, reason}) =>
|
|
@@ -442,13 +425,53 @@ let loadByFilter = (
|
|
|
442
425
|
)
|
|
443
426
|
}
|
|
444
427
|
|
|
428
|
+
// Keying an _in walks every value, so it's computed once here and handed to
|
|
429
|
+
// the load manager rather than recomputed by the hasher.
|
|
430
|
+
let filterKey = filter->EntityFilter.toString(~table=entityConfig.table)
|
|
431
|
+
|
|
432
|
+
if !(inMemTable->InMemoryTable.Entity.hasIndex)(filterKey) {
|
|
433
|
+
inMemTable->InMemoryTable.Entity.tryIndexFromLoadedValues(~filter, ~table=entityConfig.table)
|
|
434
|
+
}
|
|
435
|
+
|
|
445
436
|
loadManager->LoadManager.call(
|
|
446
437
|
~key,
|
|
447
438
|
~load,
|
|
448
439
|
~input=filter,
|
|
449
440
|
~shouldGroup,
|
|
450
|
-
~hasher=
|
|
441
|
+
~hasher=_ => filterKey,
|
|
451
442
|
~getUnsafeInMemory=inMemTable->InMemoryTable.Entity.getUnsafeOnIndex,
|
|
452
443
|
~hasInMemory=inMemTable->InMemoryTable.Entity.hasIndex,
|
|
453
444
|
)
|
|
454
445
|
}
|
|
446
|
+
|
|
447
|
+
let loadByFilter = (
|
|
448
|
+
~loadManager,
|
|
449
|
+
~persistence,
|
|
450
|
+
~entityConfig: Internal.entityConfig,
|
|
451
|
+
~scope,
|
|
452
|
+
~indexerState,
|
|
453
|
+
~shouldGroup,
|
|
454
|
+
~item,
|
|
455
|
+
~ecosystem,
|
|
456
|
+
~filter: dict<dict<unknown>>,
|
|
457
|
+
) =>
|
|
458
|
+
// Rejecting rather than throwing keeps a bad filter failing only its own
|
|
459
|
+
// call, even when the caller batches several with Promise.all.
|
|
460
|
+
try {
|
|
461
|
+
loadByParsedFilter(
|
|
462
|
+
~loadManager,
|
|
463
|
+
~persistence,
|
|
464
|
+
~entityConfig,
|
|
465
|
+
~scope,
|
|
466
|
+
~indexerState,
|
|
467
|
+
~shouldGroup,
|
|
468
|
+
~item,
|
|
469
|
+
~ecosystem,
|
|
470
|
+
~filter=filter->EntityFilter.parseOrThrow(
|
|
471
|
+
~entityName=entityConfig.name,
|
|
472
|
+
~table=entityConfig.table,
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
} catch {
|
|
476
|
+
| exn => Promise.reject(exn->Utils.prettifyExn)
|
|
477
|
+
}
|
package/src/LoadLayer.res.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Table from "./db/Table.res.mjs";
|
|
4
3
|
import * as Utils from "./Utils.res.mjs";
|
|
5
4
|
import * as ChainId from "./ChainId.res.mjs";
|
|
6
5
|
import * as Logging from "./Logging.res.mjs";
|
|
@@ -27,25 +26,6 @@ function scopeKeySuffix(scope) {
|
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
function scopeFilter(filter, table, scope) {
|
|
31
|
-
let match = Table.getChainIdField(table);
|
|
32
|
-
if (scope === "crossChain" || match === undefined) {
|
|
33
|
-
return filter;
|
|
34
|
-
} else {
|
|
35
|
-
return {
|
|
36
|
-
operator: "and",
|
|
37
|
-
filters: [
|
|
38
|
-
filter,
|
|
39
|
-
{
|
|
40
|
-
operator: "=",
|
|
41
|
-
fieldName: match.fieldName,
|
|
42
|
-
fieldValue: scope
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
29
|
function loadById(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, entityId) {
|
|
50
30
|
let key = entityConfig.name + `.get` + scopeKeySuffix(scope);
|
|
51
31
|
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
|
|
@@ -54,11 +34,7 @@ function loadById(loadManager, persistence, entityConfig, scope, indexerState, s
|
|
|
54
34
|
let timerRef = IndexerState.startStorageLoad(indexerState, storage.name, key);
|
|
55
35
|
let dbEntities;
|
|
56
36
|
try {
|
|
57
|
-
dbEntities = await storage.loadOrThrow(
|
|
58
|
-
operator: "in",
|
|
59
|
-
fieldName: Table.idFieldName,
|
|
60
|
-
fieldValue: idsToLoad
|
|
61
|
-
}, entityConfig.table, scope), entityConfig.table);
|
|
37
|
+
dbEntities = await storage.loadOrThrow(EntityFilter.scoped(EntityFilter.byIds(idsToLoad), entityConfig.table, scope), entityConfig.table);
|
|
62
38
|
} catch (raw_exn) {
|
|
63
39
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
64
40
|
if (exn.RE_EXN_ID === Persistence.StorageError) {
|
|
@@ -157,11 +133,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, scope, indexer
|
|
|
157
133
|
let outputSchema = match$1.outputSchema;
|
|
158
134
|
let dbEntities;
|
|
159
135
|
try {
|
|
160
|
-
dbEntities = await storage.loadOrThrow(
|
|
161
|
-
operator: "in",
|
|
162
|
-
fieldName: Table.idFieldName,
|
|
163
|
-
fieldValue: idsToLoad
|
|
164
|
-
}, table);
|
|
136
|
+
dbEntities = await storage.loadOrThrow(EntityFilter.byIds(idsToLoad), table);
|
|
165
137
|
} catch (raw_exn) {
|
|
166
138
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
167
139
|
Logging.childWarn(Ecosystem.getItemLogger(item, ecosystem), {
|
|
@@ -211,38 +183,49 @@ function loadEffect(loadManager, persistence, effect, effectArgs, scope, indexer
|
|
|
211
183
|
}
|
|
212
184
|
|
|
213
185
|
function loadByFilter(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, filter) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let
|
|
218
|
-
let
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
186
|
+
try {
|
|
187
|
+
let filter$1 = EntityFilter.parseOrThrow(filter, entityConfig.name, entityConfig.table);
|
|
188
|
+
let key = EntityFilter.toOperationKey(filter$1, entityConfig.name) + scopeKeySuffix(scope);
|
|
189
|
+
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
|
|
190
|
+
let load = async (filters, param) => {
|
|
191
|
+
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
192
|
+
let timerRef = IndexerState.startStorageLoad(indexerState, storage.name, key);
|
|
193
|
+
let size = {
|
|
194
|
+
contents: 0
|
|
195
|
+
};
|
|
196
|
+
filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter, entityConfig.table));
|
|
197
|
+
await storage.ensureQueryIndexes(entityConfig, scope, filters);
|
|
198
|
+
let queries = EntityFilter.merge(filters);
|
|
199
|
+
await Promise.all(queries.map(async filter => {
|
|
200
|
+
try {
|
|
201
|
+
let entities = await storage.loadOrThrow(EntityFilter.scoped(filter, entityConfig.table, scope), entityConfig.table);
|
|
202
|
+
let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
|
|
203
|
+
entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entity.id, entity));
|
|
204
|
+
InMemoryTable.Entity.recordLoadedValues(inMemTable, filter, entityConfig.table);
|
|
205
|
+
size.contents = size.contents + entities.length | 0;
|
|
206
|
+
return;
|
|
207
|
+
} catch (raw_exn) {
|
|
208
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
209
|
+
if (exn.RE_EXN_ID === Persistence.StorageError) {
|
|
210
|
+
return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(Ecosystem.getItemLogger(item, ecosystem), {
|
|
211
|
+
operation: key,
|
|
212
|
+
params: EntityFilter.getParams(filter)
|
|
213
|
+
}), exn.message, exn.reason);
|
|
214
|
+
}
|
|
215
|
+
throw exn;
|
|
239
216
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
217
|
+
}));
|
|
218
|
+
return IndexerState.endStorageLoad(indexerState, timerRef, storage.name, key, Stdlib_Array.reduce(queries, 0, (acc, query) => acc + EntityFilter.valuesCount(query) | 0), size.contents);
|
|
219
|
+
};
|
|
220
|
+
let filterKey = EntityFilter.toString(filter$1, entityConfig.table);
|
|
221
|
+
if (!InMemoryTable.Entity.hasIndex(inMemTable)(filterKey)) {
|
|
222
|
+
InMemoryTable.Entity.tryIndexFromLoadedValues(inMemTable, filter$1, entityConfig.table);
|
|
223
|
+
}
|
|
224
|
+
return LoadManager.call(loadManager, filter$1, key, load, param => filterKey, shouldGroup, InMemoryTable.Entity.hasIndex(inMemTable), InMemoryTable.Entity.getUnsafeOnIndex(inMemTable));
|
|
225
|
+
} catch (raw_exn) {
|
|
226
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
227
|
+
return Promise.reject(Utils.prettifyExn(exn));
|
|
228
|
+
}
|
|
246
229
|
}
|
|
247
230
|
|
|
248
231
|
export {
|
|
@@ -250,4 +233,4 @@ export {
|
|
|
250
233
|
loadByFilter,
|
|
251
234
|
loadEffect,
|
|
252
235
|
}
|
|
253
|
-
/*
|
|
236
|
+
/* Utils Not a pure module */
|
package/src/LoadLayer.resi
CHANGED
package/src/PgStorage.res
CHANGED
|
@@ -409,9 +409,10 @@ let makeLoadQuery = (~pgSchema, ~tableName, ~condition) => {
|
|
|
409
409
|
// Field names are spliced as quoted identifiers only after the queryFields
|
|
410
410
|
// lookup proves they exist on the table (and they originate from
|
|
411
411
|
// codegen-validated schemas), so the interpolation can't be abused.
|
|
412
|
-
let
|
|
412
|
+
let makeFilterCondition = (
|
|
413
413
|
~filter: EntityFilter.t,
|
|
414
414
|
~table: Table.table,
|
|
415
|
+
~pgSchema,
|
|
415
416
|
~params: array<unknown>,
|
|
416
417
|
) => {
|
|
417
418
|
// Filters reference fields by API name, while the SQL references columns
|
|
@@ -447,60 +448,89 @@ let rec makeFilterCondition = (
|
|
|
447
448
|
params->Array.push(param)->ignore
|
|
448
449
|
`$${params->Array.length->Int.toString}`
|
|
449
450
|
}
|
|
450
|
-
|
|
451
|
+
|
|
452
|
+
let condition = ref("")
|
|
453
|
+
filter
|
|
454
|
+
->EntityFilter.entries
|
|
455
|
+
->Utils.Dict.forEachWithKey((operators, fieldName) => {
|
|
451
456
|
let queryField = getQueryFieldOrThrow(fieldName)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
457
|
+
operators->Utils.Dict.forEachWithKey((fieldValue, operator) => {
|
|
458
|
+
let column = `"${queryField.pgDbFieldName}"`
|
|
459
|
+
let part = switch operator {
|
|
460
|
+
// A per-chain entity's table is partitioned by its chain-id column, and
|
|
461
|
+
// Postgres can only prune a plan it caches when that column is a constant
|
|
462
|
+
// in the SQL. Bound, the cached plan has to keep every partition, and the
|
|
463
|
+
// planner ends up throwing it away and re-planning on every execution
|
|
464
|
+
// instead — measured at 315us per load against 218us with the id written
|
|
465
|
+
// in, on 30 chains.
|
|
466
|
+
//
|
|
467
|
+
// The cost is that each chain gets its own query text, so Postgres caches
|
|
468
|
+
// a prepared statement per (entity, chain, filter shape) rather than per
|
|
469
|
+
// (entity, filter shape). Measured at ~8KB of plan cache each, which is
|
|
470
|
+
// ~10MB per connection for 40 entities across 30 chains — accepted, since
|
|
471
|
+
// the alternative is a cached plan that can't prune.
|
|
472
|
+
//
|
|
473
|
+
// `EntityFilter.scoped` is what puts this filter here, and the value is
|
|
474
|
+
// range-checked to a non-negative safe integer, so it can carry nothing
|
|
475
|
+
// but digits.
|
|
476
|
+
| "_eq" if queryField.isChainId =>
|
|
477
|
+
`${column} = ${fieldValue->ChainId.normalizeOrThrow->ChainId.toString}`
|
|
478
|
+
// Postgres arrays are rectangular, so candidates for a list column can't
|
|
479
|
+
// be bound as one array unless they all have the same length, and
|
|
480
|
+
// postgres.js can't bind a boolean array at all
|
|
481
|
+
// (https://github.com/porsager/postgres/issues/471). One equality per
|
|
482
|
+
// candidate has neither problem.
|
|
483
|
+
| "_in" if queryField.isArray || queryField.fieldType === Boolean =>
|
|
484
|
+
switch fieldValue->EntityFilter.asArray {
|
|
485
|
+
| [] => "FALSE"
|
|
486
|
+
| candidates =>
|
|
487
|
+
`(${candidates
|
|
488
|
+
->Array.map(
|
|
489
|
+
candidate =>
|
|
490
|
+
`${column} = ${serializeParamOrThrow(
|
|
491
|
+
~queryField,
|
|
492
|
+
~fieldName,
|
|
493
|
+
~fieldValue=candidate,
|
|
494
|
+
~isArray=false,
|
|
495
|
+
)}`,
|
|
496
|
+
)
|
|
497
|
+
->Array.join(" OR ")})`
|
|
498
|
+
}
|
|
499
|
+
| "_in" =>
|
|
500
|
+
let param = serializeParamOrThrow(~queryField, ~fieldName, ~fieldValue, ~isArray=true)
|
|
501
|
+
switch queryField.fieldType {
|
|
502
|
+
// A bound array of strings is text[], which has no equality with an
|
|
503
|
+
// enum. The insert casts the same way.
|
|
504
|
+
| Enum({config}) => `${column} = ANY(${param}::TEXT[]::"${pgSchema}".${config.name}[])`
|
|
505
|
+
| _ => `${column} = ANY(${param})`
|
|
506
|
+
}
|
|
507
|
+
| _ =>
|
|
508
|
+
let sqlOperator = switch operator {
|
|
509
|
+
| "_eq" => "="
|
|
510
|
+
| "_gt" => ">"
|
|
511
|
+
| "_lt" => "<"
|
|
512
|
+
| "_gte" => ">="
|
|
513
|
+
| "_lte" => "<="
|
|
514
|
+
| _ =>
|
|
515
|
+
throw(
|
|
516
|
+
Persistence.StorageError({
|
|
517
|
+
message: `Failed loading "${table.tableName}" from storage. Unknown filter operator "${operator}".`,
|
|
518
|
+
reason: Utils.Error.make(`Unknown filter operator "${operator}"`),
|
|
519
|
+
}),
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
`${column} ${sqlOperator} ${serializeParamOrThrow(
|
|
523
|
+
~queryField,
|
|
524
|
+
~fieldName,
|
|
525
|
+
~fieldValue,
|
|
526
|
+
~isArray=false,
|
|
527
|
+
)}`
|
|
528
|
+
}
|
|
529
|
+
condition := (condition.contents === "" ? part : condition.contents ++ " AND " ++ part)
|
|
530
|
+
})
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
condition.contents
|
|
504
534
|
}
|
|
505
535
|
|
|
506
536
|
// The chain-id predicate a per-chain entity's row-level SQL needs, already
|
|
@@ -1709,7 +1739,7 @@ let make = (
|
|
|
1709
1739
|
// Must match PG_CONTAINER in packages/cli/src/docker_env.rs
|
|
1710
1740
|
let containerName = "envio-postgres"
|
|
1711
1741
|
let psqlExecOptions: NodeJs.ChildProcess.execOptions = {
|
|
1712
|
-
env:
|
|
1742
|
+
env: dict{"PGPASSWORD": pgPassword, "PATH": %raw(`process.env.PATH`)},
|
|
1713
1743
|
}
|
|
1714
1744
|
|
|
1715
1745
|
let cacheDirPath = NodeJs.Path.resolve([
|
|
@@ -2002,7 +2032,7 @@ let make = (
|
|
|
2002
2032
|
|
|
2003
2033
|
let loadOrThrow = async (~filter: EntityFilter.t, ~table: Table.table) => {
|
|
2004
2034
|
let params = []
|
|
2005
|
-
let condition = makeFilterCondition(~filter, ~table, ~params)
|
|
2035
|
+
let condition = makeFilterCondition(~filter, ~table, ~pgSchema, ~params)
|
|
2006
2036
|
switch await sql->Postgres.preparedUnsafe(
|
|
2007
2037
|
makeLoadQuery(~pgSchema, ~tableName=table.tableName, ~condition),
|
|
2008
2038
|
params->Obj.magic,
|
|
@@ -2033,9 +2063,10 @@ let make = (
|
|
|
2033
2063
|
let queryFields = table->Table.queryFields
|
|
2034
2064
|
let columns = []
|
|
2035
2065
|
let seen = Utils.Set.make()
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2066
|
+
filters->Array.forEach(filter =>
|
|
2067
|
+
filter
|
|
2068
|
+
->EntityFilter.entries
|
|
2069
|
+
->Utils.Dict.forEachWithKey((_, fieldName) =>
|
|
2039
2070
|
switch queryFields->Utils.Dict.dangerouslyGetNonOption(fieldName) {
|
|
2040
2071
|
| Some({pgDbFieldName}) =>
|
|
2041
2072
|
if !(seen->Utils.Set.has(pgDbFieldName)) {
|
|
@@ -2044,9 +2075,8 @@ let make = (
|
|
|
2044
2075
|
}
|
|
2045
2076
|
| None => ()
|
|
2046
2077
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
filters->Array.forEach(collect)
|
|
2078
|
+
)
|
|
2079
|
+
)
|
|
2050
2080
|
columns
|
|
2051
2081
|
}
|
|
2052
2082
|
|