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/InMemoryTable.res
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
module Entity = {
|
|
2
2
|
type relatedEntityId = string
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
type
|
|
3
|
+
// The matcher is specialized from the filter once at index creation and
|
|
4
|
+
// reused for every entity write against this index.
|
|
5
|
+
type index = {
|
|
6
|
+
matcher: EntityFilter.matcher,
|
|
7
|
+
ids: Utils.Set.t<relatedEntityId>,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Indexes on one field that a single value can resolve — _eq, and _in under
|
|
11
|
+
// each of its values — found by the field's value rather than by running
|
|
12
|
+
// their matchers, so a write costs a lookup per indexed field instead of a
|
|
13
|
+
// pass over every registered index. Several indexes can share a value: an
|
|
14
|
+
// _in and an _eq naming it, or two _in filters overlapping on it.
|
|
15
|
+
type eqBucket = {
|
|
16
|
+
keyOf: unknown => unknown,
|
|
17
|
+
byValue: Utils.Map.t<unknown, array<index>>,
|
|
18
|
+
// Values a completed query has already loaded every row for. A later
|
|
19
|
+
// filter naming only these is answered from the table instead of a
|
|
20
|
+
// second round trip for rows that are already in it.
|
|
21
|
+
loadedValues: Utils.Set.t<unknown>,
|
|
22
|
+
}
|
|
6
23
|
|
|
7
|
-
type entityFilters = Utils.Set.t<EntityFilter.t>
|
|
8
24
|
type t = {
|
|
9
25
|
latestEntityChangeById: dict<Change.t<Internal.entity>>,
|
|
10
26
|
// Recorded changes (new latest ids + prevEntityChanges pushes), tracked
|
|
@@ -13,8 +29,14 @@ module Entity = {
|
|
|
13
29
|
// Swapped out when a write starts so processing keeps appending while the
|
|
14
30
|
// previous changes persist in the background.
|
|
15
31
|
mutable prevEntityChanges: array<Change.t<Internal.entity>>,
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
// Every index an entity currently belongs to, so a write can drop it from
|
|
33
|
+
// the ones it stopped matching without consulting the others.
|
|
34
|
+
mutable indexesByEntityId: dict<Utils.Set.t<index>>,
|
|
35
|
+
// Keyed by EntityFilter.toString, for lookups coming from the load manager.
|
|
36
|
+
mutable indexesByKey: dict<index>,
|
|
37
|
+
mutable eqBucketsByField: dict<eqBucket>,
|
|
38
|
+
// Ranges and composites, which no single field value can resolve.
|
|
39
|
+
mutable scanIndexes: array<index>,
|
|
18
40
|
}
|
|
19
41
|
|
|
20
42
|
// Helper to extract an entity's id as a dict key. The raw id may be a
|
|
@@ -30,21 +52,22 @@ module Entity = {
|
|
|
30
52
|
)
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
|
37
|
-
|
|
38
|
-
self.filtersByEntityId->Dict.set(entityId, s)
|
|
39
|
-
s
|
|
55
|
+
let addToIndex = (self: t, ~index: index, ~entityId) => {
|
|
56
|
+
index.ids->Utils.Set.add(entityId)->ignore
|
|
57
|
+
switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
58
|
+
| Some(indexes) => indexes->Utils.Set.add(index)->ignore
|
|
59
|
+
| None => self.indexesByEntityId->Dict.set(entityId, Utils.Set.fromArray([index]))
|
|
40
60
|
}
|
|
61
|
+
}
|
|
41
62
|
|
|
42
63
|
let make = (): t => {
|
|
43
64
|
latestEntityChangeById: Dict.make(),
|
|
44
65
|
changesCount: 0.,
|
|
45
66
|
prevEntityChanges: [],
|
|
46
|
-
|
|
47
|
-
|
|
67
|
+
indexesByEntityId: Dict.make(),
|
|
68
|
+
indexesByKey: Dict.make(),
|
|
69
|
+
eqBucketsByField: Dict.make(),
|
|
70
|
+
scanIndexes: [],
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
// Changes to persist for checkpoints in (committedCheckpointId, upToCheckpointId].
|
|
@@ -97,50 +120,52 @@ module Entity = {
|
|
|
97
120
|
})
|
|
98
121
|
keysToDelete->Array.forEach(key => self.latestEntityChangeById->Utils.Dict.deleteInPlace(key))
|
|
99
122
|
self.changesCount = self.changesCount -. keysToDelete->Array.length->Int.toFloat
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
123
|
+
self.indexesByEntityId = Dict.make()
|
|
124
|
+
self.indexesByKey = Dict.make()
|
|
125
|
+
self.eqBucketsByField = Dict.make()
|
|
126
|
+
self.scanIndexes = []
|
|
102
127
|
}
|
|
103
128
|
|
|
104
129
|
let updateIndexes = (self: t, ~entity: Internal.entity) => {
|
|
105
130
|
let entityId = entity->getEntityIdUnsafe
|
|
106
|
-
let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
|
|
107
131
|
|
|
108
|
-
|
|
109
|
-
switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
132
|
+
switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
110
133
|
| None => ()
|
|
111
|
-
| Some(
|
|
112
|
-
|
|
113
|
-
if !
|
|
114
|
-
|
|
134
|
+
| Some(indexes) =>
|
|
135
|
+
indexes->Utils.Set.forEach(index =>
|
|
136
|
+
if !index.matcher(entity) {
|
|
137
|
+
index.ids->Utils.Set.delete(entityId)->ignore
|
|
138
|
+
indexes->Utils.Set.delete(index)->ignore
|
|
115
139
|
}
|
|
116
|
-
|
|
140
|
+
)
|
|
117
141
|
}
|
|
118
142
|
|
|
119
|
-
self.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
143
|
+
self.eqBucketsByField->Utils.Dict.forEachWithKey((bucket, fieldName) => {
|
|
144
|
+
let fieldValue = entity->EntityFilter.getField(fieldName)
|
|
145
|
+
|
|
146
|
+
// A nullish column matches no equality index, and the key projections
|
|
147
|
+
// would throw on it.
|
|
148
|
+
if !(fieldValue->EntityFilter.nullish) {
|
|
149
|
+
switch bucket.byValue->Utils.Map.get(bucket.keyOf(fieldValue)) {
|
|
150
|
+
| Some(indexes) => indexes->Array.forEach(index => self->addToIndex(~index, ~entityId))
|
|
151
|
+
| None => ()
|
|
152
|
+
}
|
|
126
153
|
}
|
|
127
154
|
})
|
|
155
|
+
|
|
156
|
+
self.scanIndexes->Array.forEach(index =>
|
|
157
|
+
if index.matcher(entity) {
|
|
158
|
+
self->addToIndex(~index, ~entityId)
|
|
159
|
+
}
|
|
160
|
+
)
|
|
128
161
|
}
|
|
129
162
|
|
|
130
163
|
let deleteEntityFromIndexes = (self: t, ~entityId: string) =>
|
|
131
|
-
switch self.
|
|
164
|
+
switch self.indexesByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
132
165
|
| None => ()
|
|
133
|
-
| Some(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
filter->EntityFilter.toString,
|
|
137
|
-
) {
|
|
138
|
-
| Some((_filter, relatedEntityIds)) =>
|
|
139
|
-
let _wasRemoved = relatedEntityIds->Utils.Set.delete(entityId)
|
|
140
|
-
| None => () //Unexpected filter index should exist if it is in entityFilters
|
|
141
|
-
}
|
|
142
|
-
let _wasRemoved = entityFilters->Utils.Set.delete(filter)
|
|
143
|
-
})
|
|
166
|
+
| Some(indexes) =>
|
|
167
|
+
indexes->Utils.Set.forEach(index => index.ids->Utils.Set.delete(entityId)->ignore)
|
|
168
|
+
self.indexesByEntityId->Utils.Dict.deleteInPlace(entityId)
|
|
144
169
|
}
|
|
145
170
|
|
|
146
171
|
let set = (inMemTable: t, ~committedCheckpointId, change: Change.t<Internal.entity>) => {
|
|
@@ -200,47 +225,110 @@ module Entity = {
|
|
|
200
225
|
|
|
201
226
|
let hasIndex = (inMemTable: t) =>
|
|
202
227
|
(filterKey: string) =>
|
|
203
|
-
inMemTable.
|
|
228
|
+
inMemTable.indexesByKey->Utils.Dict.dangerouslyGetNonOption(filterKey) !== None
|
|
204
229
|
|
|
205
|
-
let getUnsafeOnIndex = (inMemTable: t) =>
|
|
206
|
-
let getEntity = inMemTable->getUnsafe
|
|
230
|
+
let getUnsafeOnIndex = (inMemTable: t) =>
|
|
207
231
|
(filterKey: string) => {
|
|
208
|
-
switch inMemTable.
|
|
232
|
+
switch inMemTable.indexesByKey->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
209
233
|
| None =>
|
|
210
234
|
JsError.throwWithMessage(`Unexpected error. Must have an index for the filter ${filterKey}`)
|
|
211
|
-
| Some(
|
|
212
|
-
|
|
235
|
+
| Some({ids}) =>
|
|
236
|
+
ids
|
|
213
237
|
->Utils.Set.toArray
|
|
214
|
-
->Array.filterMap(entityId =>
|
|
215
|
-
switch inMemTable.latestEntityChangeById->Dict.
|
|
216
|
-
|
|
|
217
|
-
|
|
|
238
|
+
->Array.filterMap(entityId =>
|
|
239
|
+
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
240
|
+
| Some(change) => change->mapChangeToEntity
|
|
241
|
+
| None => None
|
|
218
242
|
}
|
|
219
|
-
|
|
243
|
+
)
|
|
220
244
|
}
|
|
221
245
|
}
|
|
222
|
-
}
|
|
223
246
|
|
|
224
|
-
let
|
|
225
|
-
|
|
226
|
-
|
|
247
|
+
let getOrCreateEqBucket = (inMemTable: t, ~table: Table.table, ~fieldName) =>
|
|
248
|
+
switch inMemTable.eqBucketsByField->Utils.Dict.dangerouslyGetNonOption(fieldName) {
|
|
249
|
+
| Some(bucket) => bucket
|
|
250
|
+
| None =>
|
|
251
|
+
let bucket = {
|
|
252
|
+
keyOf: EntityFilter.makeValueKey(~table, ~fieldName),
|
|
253
|
+
byValue: Utils.Map.make(),
|
|
254
|
+
loadedValues: Utils.Set.make(),
|
|
255
|
+
}
|
|
256
|
+
inMemTable.eqBucketsByField->Dict.set(fieldName, bucket)
|
|
257
|
+
bucket
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// The values a filter constrains a single field to exactly, which is what a
|
|
261
|
+
// bucket and the loaded set are keyed by. Everything else — ranges, several
|
|
262
|
+
// fields, several operators — has no such list.
|
|
263
|
+
let singleFieldValues = (filter: EntityFilter.t) =>
|
|
264
|
+
switch filter->EntityFilter.asSingleOperator {
|
|
265
|
+
| Some((fieldName, "_eq", fieldValue)) => Some((fieldName, [fieldValue]))
|
|
266
|
+
| Some((fieldName, "_in", fieldValue)) => Some((fieldName, fieldValue->EntityFilter.asArray))
|
|
267
|
+
| Some(_) | None => None
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let addEmptyIndex = (inMemTable: t, ~filter: EntityFilter.t, ~table: Table.table) => {
|
|
271
|
+
let filterKey = filter->EntityFilter.toString(~table)
|
|
272
|
+
switch inMemTable.indexesByKey->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
227
273
|
| Some(_) => () //Should not happen, this means the index already exists
|
|
228
274
|
| None =>
|
|
229
|
-
let
|
|
275
|
+
let index = {matcher: filter->EntityFilter.makeMatcher(~table), ids: Utils.Set.make()}
|
|
276
|
+
inMemTable.indexesByKey->Dict.set(filterKey, index)
|
|
277
|
+
|
|
278
|
+
switch filter->singleFieldValues {
|
|
279
|
+
| Some((fieldName, fieldValues)) =>
|
|
280
|
+
let bucket = inMemTable->getOrCreateEqBucket(~table, ~fieldName)
|
|
281
|
+
fieldValues->Array.forEach(fieldValue => {
|
|
282
|
+
let valueKey = bucket.keyOf(fieldValue)
|
|
283
|
+
switch bucket.byValue->Utils.Map.get(valueKey) {
|
|
284
|
+
| Some(indexes) => indexes->Array.push(index)->ignore
|
|
285
|
+
| None => bucket.byValue->Utils.Map.set(valueKey, [index])->ignore
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
| None => inMemTable.scanIndexes->Array.push(index)->ignore
|
|
289
|
+
}
|
|
290
|
+
|
|
230
291
|
inMemTable.latestEntityChangeById->Utils.Dict.forEach(change => {
|
|
231
292
|
switch change->mapChangeToEntity {
|
|
232
293
|
| Some(entity) =>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if filter->EntityFilter.matches(~entity=entityAsDict) {
|
|
236
|
-
let entityId = entity->getEntityIdUnsafe
|
|
237
|
-
let _ = inMemTable->getOrCreateEntityFilters(~entityId)->Utils.Set.add(filter)
|
|
238
|
-
let _ = relatedEntityIds->Utils.Set.add(entityId)
|
|
294
|
+
if index.matcher(entity) {
|
|
295
|
+
inMemTable->addToIndex(~index, ~entityId=entity->getEntityIdUnsafe)
|
|
239
296
|
}
|
|
240
297
|
| None => ()
|
|
241
298
|
}
|
|
242
299
|
})
|
|
243
|
-
inMemTable.filterIndexes->Dict.set(filterKey, (filter, relatedEntityIds))
|
|
244
300
|
}
|
|
245
301
|
}
|
|
302
|
+
|
|
303
|
+
// Called once a query's rows are in the table, so every row the filter's
|
|
304
|
+
// values could match is now in memory.
|
|
305
|
+
let recordLoadedValues = (inMemTable: t, ~filter: EntityFilter.t, ~table: Table.table) =>
|
|
306
|
+
switch filter->singleFieldValues {
|
|
307
|
+
| Some((fieldName, fieldValues)) =>
|
|
308
|
+
let bucket = inMemTable->getOrCreateEqBucket(~table, ~fieldName)
|
|
309
|
+
fieldValues->Array.forEach(fieldValue =>
|
|
310
|
+
bucket.loadedValues->Utils.Set.add(bucket.keyOf(fieldValue))->ignore
|
|
311
|
+
)
|
|
312
|
+
| None => ()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let isFullyLoaded = (inMemTable: t, ~filter: EntityFilter.t) =>
|
|
316
|
+
switch filter->singleFieldValues {
|
|
317
|
+
| Some((fieldName, fieldValues)) =>
|
|
318
|
+
switch inMemTable.eqBucketsByField->Utils.Dict.dangerouslyGetNonOption(fieldName) {
|
|
319
|
+
| Some(bucket) =>
|
|
320
|
+
fieldValues->Array.every(fieldValue =>
|
|
321
|
+
bucket.loadedValues->Utils.Set.has(bucket.keyOf(fieldValue))
|
|
322
|
+
)
|
|
323
|
+
| None => false
|
|
324
|
+
}
|
|
325
|
+
| None => false
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Builds the index for a filter whose every value an earlier query already
|
|
329
|
+
// loaded, so the call is served from the table instead of going to storage.
|
|
330
|
+
let tryIndexFromLoadedValues = (inMemTable: t, ~filter: EntityFilter.t, ~table: Table.table) =>
|
|
331
|
+
if inMemTable->isFullyLoaded(~filter) {
|
|
332
|
+
inMemTable->addEmptyIndex(~filter, ~table)
|
|
333
|
+
}
|
|
246
334
|
}
|
|
@@ -24,14 +24,14 @@ function getEntityIdUnsafe(entity) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
function addToIndex(self, index, entityId) {
|
|
28
|
+
index.ids.add(entityId);
|
|
29
|
+
let indexes = self.indexesByEntityId[entityId];
|
|
30
|
+
if (indexes !== undefined) {
|
|
31
|
+
Primitive_option.valFromOption(indexes).add(index);
|
|
32
|
+
} else {
|
|
33
|
+
self.indexesByEntityId[entityId] = new Set([index]);
|
|
31
34
|
}
|
|
32
|
-
let s$1 = new Set();
|
|
33
|
-
self.filtersByEntityId[entityId] = s$1;
|
|
34
|
-
return s$1;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function make() {
|
|
@@ -39,8 +39,10 @@ function make() {
|
|
|
39
39
|
latestEntityChangeById: {},
|
|
40
40
|
changesCount: 0,
|
|
41
41
|
prevEntityChanges: [],
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
indexesByEntityId: {},
|
|
43
|
+
indexesByKey: {},
|
|
44
|
+
eqBucketsByField: {},
|
|
45
|
+
scanIndexes: []
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -83,47 +85,51 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
|
|
|
83
85
|
});
|
|
84
86
|
keysToDelete.forEach(key => Utils.Dict.deleteInPlace(self.latestEntityChangeById, key));
|
|
85
87
|
self.changesCount = self.changesCount - keysToDelete.length;
|
|
86
|
-
self.
|
|
87
|
-
self.
|
|
88
|
+
self.indexesByEntityId = {};
|
|
89
|
+
self.indexesByKey = {};
|
|
90
|
+
self.eqBucketsByField = {};
|
|
91
|
+
self.scanIndexes = [];
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
function updateIndexes(self, entity) {
|
|
91
95
|
let entityId = getEntityIdUnsafe(entity);
|
|
92
|
-
let
|
|
93
|
-
if (
|
|
94
|
-
let
|
|
95
|
-
|
|
96
|
-
if (!
|
|
97
|
-
|
|
96
|
+
let indexes = self.indexesByEntityId[entityId];
|
|
97
|
+
if (indexes !== undefined) {
|
|
98
|
+
let indexes$1 = Primitive_option.valFromOption(indexes);
|
|
99
|
+
indexes$1.forEach(index => {
|
|
100
|
+
if (!index.matcher(entity)) {
|
|
101
|
+
index.ids.delete(entityId);
|
|
102
|
+
indexes$1.delete(index);
|
|
98
103
|
return;
|
|
99
104
|
}
|
|
100
105
|
});
|
|
101
106
|
}
|
|
102
|
-
Utils.Dict.
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
Utils.Dict.forEachWithKey(self.eqBucketsByField, (bucket, fieldName) => {
|
|
108
|
+
let fieldValue = entity[fieldName];
|
|
109
|
+
if (EntityFilter.nullish(fieldValue)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let indexes = bucket.byValue.get(bucket.keyOf(fieldValue));
|
|
113
|
+
if (indexes !== undefined) {
|
|
114
|
+
indexes.forEach(index => addToIndex(self, index, entityId));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
self.scanIndexes.forEach(index => {
|
|
119
|
+
if (index.matcher(entity)) {
|
|
120
|
+
return addToIndex(self, index, entityId);
|
|
110
121
|
}
|
|
111
122
|
});
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
function deleteEntityFromIndexes(self, entityId) {
|
|
115
|
-
let
|
|
116
|
-
if (
|
|
117
|
-
|
|
126
|
+
let indexes = self.indexesByEntityId[entityId];
|
|
127
|
+
if (indexes !== undefined) {
|
|
128
|
+
Primitive_option.valFromOption(indexes).forEach(index => {
|
|
129
|
+
index.ids.delete(entityId);
|
|
130
|
+
});
|
|
131
|
+
return Utils.Dict.deleteInPlace(self.indexesByEntityId, entityId);
|
|
118
132
|
}
|
|
119
|
-
let entityFilters$1 = Primitive_option.valFromOption(entityFilters);
|
|
120
|
-
entityFilters$1.forEach(filter => {
|
|
121
|
-
let match = self.filterIndexes[EntityFilter.toString(filter)];
|
|
122
|
-
if (match !== undefined) {
|
|
123
|
-
match[1].delete(entityId);
|
|
124
|
-
}
|
|
125
|
-
entityFilters$1.delete(filter);
|
|
126
|
-
});
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
function set(inMemTable, committedCheckpointId, change) {
|
|
@@ -174,17 +180,17 @@ function getUnsafe(inMemTable) {
|
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
function hasIndex(inMemTable) {
|
|
177
|
-
return filterKey => inMemTable.
|
|
183
|
+
return filterKey => inMemTable.indexesByKey[filterKey] !== undefined;
|
|
178
184
|
}
|
|
179
185
|
|
|
180
186
|
function getUnsafeOnIndex(inMemTable) {
|
|
181
|
-
let getEntity = getUnsafe(inMemTable);
|
|
182
187
|
return filterKey => {
|
|
183
|
-
let match = inMemTable.
|
|
188
|
+
let match = inMemTable.indexesByKey[filterKey];
|
|
184
189
|
if (match !== undefined) {
|
|
185
|
-
return Stdlib_Array.filterMap(Array.from(match
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
return Stdlib_Array.filterMap(Array.from(match.ids), entityId => {
|
|
191
|
+
let change = inMemTable.latestEntityChangeById[entityId];
|
|
192
|
+
if (change !== undefined) {
|
|
193
|
+
return mapChangeToEntity(change);
|
|
188
194
|
}
|
|
189
195
|
});
|
|
190
196
|
} else {
|
|
@@ -193,35 +199,115 @@ function getUnsafeOnIndex(inMemTable) {
|
|
|
193
199
|
};
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
function
|
|
197
|
-
let
|
|
198
|
-
|
|
202
|
+
function getOrCreateEqBucket(inMemTable, table, fieldName) {
|
|
203
|
+
let bucket = inMemTable.eqBucketsByField[fieldName];
|
|
204
|
+
if (bucket !== undefined) {
|
|
205
|
+
return bucket;
|
|
206
|
+
}
|
|
207
|
+
let bucket_keyOf = EntityFilter.makeValueKey(table, fieldName);
|
|
208
|
+
let bucket_byValue = new Map();
|
|
209
|
+
let bucket_loadedValues = new Set();
|
|
210
|
+
let bucket$1 = {
|
|
211
|
+
keyOf: bucket_keyOf,
|
|
212
|
+
byValue: bucket_byValue,
|
|
213
|
+
loadedValues: bucket_loadedValues
|
|
214
|
+
};
|
|
215
|
+
inMemTable.eqBucketsByField[fieldName] = bucket$1;
|
|
216
|
+
return bucket$1;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function singleFieldValues(filter) {
|
|
220
|
+
let match = EntityFilter.asSingleOperator(filter);
|
|
221
|
+
if (match === undefined) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
let fieldName = match[0];
|
|
225
|
+
switch (match[1]) {
|
|
226
|
+
case "_eq" :
|
|
227
|
+
return [
|
|
228
|
+
fieldName,
|
|
229
|
+
[match[2]]
|
|
230
|
+
];
|
|
231
|
+
case "_in" :
|
|
232
|
+
return [
|
|
233
|
+
fieldName,
|
|
234
|
+
EntityFilter.asArray(match[2])
|
|
235
|
+
];
|
|
236
|
+
default:
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function addEmptyIndex(inMemTable, filter, table) {
|
|
242
|
+
let filterKey = EntityFilter.toString(filter, table);
|
|
243
|
+
let match = inMemTable.indexesByKey[filterKey];
|
|
199
244
|
if (match !== undefined) {
|
|
200
245
|
return;
|
|
201
246
|
}
|
|
202
|
-
let
|
|
247
|
+
let index_matcher = EntityFilter.makeMatcher(filter, table);
|
|
248
|
+
let index_ids = new Set();
|
|
249
|
+
let index = {
|
|
250
|
+
matcher: index_matcher,
|
|
251
|
+
ids: index_ids
|
|
252
|
+
};
|
|
253
|
+
inMemTable.indexesByKey[filterKey] = index;
|
|
254
|
+
let match$1 = singleFieldValues(filter);
|
|
255
|
+
if (match$1 !== undefined) {
|
|
256
|
+
let bucket = getOrCreateEqBucket(inMemTable, table, match$1[0]);
|
|
257
|
+
match$1[1].forEach(fieldValue => {
|
|
258
|
+
let valueKey = bucket.keyOf(fieldValue);
|
|
259
|
+
let indexes = bucket.byValue.get(valueKey);
|
|
260
|
+
if (indexes !== undefined) {
|
|
261
|
+
indexes.push(index);
|
|
262
|
+
} else {
|
|
263
|
+
bucket.byValue.set(valueKey, [index]);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
} else {
|
|
267
|
+
inMemTable.scanIndexes.push(index);
|
|
268
|
+
}
|
|
203
269
|
Utils.Dict.forEach(inMemTable.latestEntityChangeById, change => {
|
|
204
270
|
let entity = mapChangeToEntity(change);
|
|
205
|
-
if (entity
|
|
206
|
-
return;
|
|
271
|
+
if (entity !== undefined && index_matcher(entity)) {
|
|
272
|
+
return addToIndex(inMemTable, index, getEntityIdUnsafe(entity));
|
|
207
273
|
}
|
|
208
|
-
if (!EntityFilter.matches(filter, entity)) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
let entityId = getEntityIdUnsafe(entity);
|
|
212
|
-
getOrCreateEntityFilters(inMemTable, entityId).add(filter);
|
|
213
|
-
relatedEntityIds.add(entityId);
|
|
214
274
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function recordLoadedValues(inMemTable, filter, table) {
|
|
278
|
+
let match = singleFieldValues(filter);
|
|
279
|
+
if (match === undefined) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
let bucket = getOrCreateEqBucket(inMemTable, table, match[0]);
|
|
283
|
+
match[1].forEach(fieldValue => {
|
|
284
|
+
bucket.loadedValues.add(bucket.keyOf(fieldValue));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function isFullyLoaded(inMemTable, filter) {
|
|
289
|
+
let match = singleFieldValues(filter);
|
|
290
|
+
if (match === undefined) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
let bucket = inMemTable.eqBucketsByField[match[0]];
|
|
294
|
+
if (bucket !== undefined) {
|
|
295
|
+
return match[1].every(fieldValue => bucket.loadedValues.has(bucket.keyOf(fieldValue)));
|
|
296
|
+
} else {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function tryIndexFromLoadedValues(inMemTable, filter, table) {
|
|
302
|
+
if (isFullyLoaded(inMemTable, filter)) {
|
|
303
|
+
return addEmptyIndex(inMemTable, filter, table);
|
|
304
|
+
}
|
|
219
305
|
}
|
|
220
306
|
|
|
221
307
|
let Entity = {
|
|
222
308
|
UnexpectedIdNotDefinedOnEntity: UnexpectedIdNotDefinedOnEntity,
|
|
223
309
|
getEntityIdUnsafe: getEntityIdUnsafe,
|
|
224
|
-
|
|
310
|
+
addToIndex: addToIndex,
|
|
225
311
|
make: make,
|
|
226
312
|
snapshotChanges: snapshotChanges,
|
|
227
313
|
dropCommittedChanges: dropCommittedChanges,
|
|
@@ -233,7 +319,12 @@ let Entity = {
|
|
|
233
319
|
getUnsafe: getUnsafe,
|
|
234
320
|
hasIndex: hasIndex,
|
|
235
321
|
getUnsafeOnIndex: getUnsafeOnIndex,
|
|
236
|
-
|
|
322
|
+
getOrCreateEqBucket: getOrCreateEqBucket,
|
|
323
|
+
singleFieldValues: singleFieldValues,
|
|
324
|
+
addEmptyIndex: addEmptyIndex,
|
|
325
|
+
recordLoadedValues: recordLoadedValues,
|
|
326
|
+
isFullyLoaded: isFullyLoaded,
|
|
327
|
+
tryIndexFromLoadedValues: tryIndexFromLoadedValues
|
|
237
328
|
};
|
|
238
329
|
|
|
239
330
|
export {
|