envio 3.5.0-alpha.1 → 3.5.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +10 -6
- package/package.json +6 -6
- package/src/BatchProcessing.res +27 -0
- package/src/BatchProcessing.res.mjs +12 -0
- package/src/ChainFetching.res +5 -5
- package/src/ChainFetching.res.mjs +3 -4
- package/src/ChainState.res +48 -25
- package/src/ChainState.res.mjs +23 -29
- package/src/ChainState.resi +2 -6
- package/src/Change.res +3 -3
- package/src/Config.res +2 -6
- package/src/Config.res.mjs +2 -12
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +33 -10
- package/src/CrossChainState.res.mjs +21 -6
- package/src/CrossChainState.resi +3 -0
- package/src/EntityId.res +15 -0
- package/src/EntityId.res.mjs +9 -0
- package/src/EventConfigBuilder.res +1 -58
- package/src/EventConfigBuilder.res.mjs +1 -33
- package/src/FetchState.res +510 -460
- package/src/FetchState.res.mjs +413 -428
- package/src/FinalizeBackfill.res +34 -0
- package/src/FinalizeBackfill.res.mjs +26 -0
- package/src/HandlerRegister.res +45 -0
- package/src/HandlerRegister.res.mjs +43 -0
- package/src/InMemoryStore.res +11 -4
- package/src/InMemoryTable.res +26 -24
- package/src/InMemoryTable.res.mjs +19 -18
- package/src/IndexerState.res +71 -2
- package/src/IndexerState.res.mjs +67 -3
- package/src/IndexerState.resi +5 -0
- package/src/Internal.res +17 -8
- package/src/LoadLayer.res +9 -1
- package/src/LoadLayer.res.mjs +1 -0
- package/src/LogSelection.res +2 -1
- package/src/Metrics.res +37 -6
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +12 -1
- package/src/PgStorage.res +411 -57
- package/src/PgStorage.res.mjs +262 -41
- package/src/SimulateItems.res +2 -2
- package/src/TestIndexer.res +9 -5
- package/src/TestIndexer.res.mjs +5 -2
- package/src/UserContext.res +3 -3
- package/src/Writing.res +12 -2
- package/src/Writing.res.mjs +13 -2
- package/src/bindings/ClickHouse.res +5 -3
- package/src/bindings/ClickHouse.res.mjs +2 -5
- package/src/bindings/Vitest.res +22 -1
- package/src/bindings/Vitest.res.mjs +15 -0
- package/src/db/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/EntityHistory.res +16 -7
- package/src/db/EntityHistory.res.mjs +7 -6
- package/src/db/IndexRegistry.res +219 -0
- package/src/db/IndexRegistry.res.mjs +195 -0
- package/src/db/InternalTable.res +7 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/db/Table.res +53 -22
- package/src/db/Table.res.mjs +56 -18
- package/src/sources/AddressSet.res +48 -0
- package/src/sources/AddressSet.res.mjs +11 -0
- package/src/sources/AddressStore.res +152 -0
- package/src/sources/AddressStore.res.mjs +97 -0
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +6 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcClient.res +6 -3
- package/src/sources/EvmRpcClient.res.mjs +3 -3
- package/src/sources/FuelHyperSync.res +4 -3
- package/src/sources/FuelHyperSync.res.mjs +3 -3
- package/src/sources/FuelHyperSync.resi +2 -1
- package/src/sources/FuelHyperSyncClient.res +12 -6
- package/src/sources/FuelHyperSyncClient.res.mjs +2 -2
- package/src/sources/FuelHyperSyncSource.res +7 -4
- package/src/sources/FuelHyperSyncSource.res.mjs +3 -3
- package/src/sources/HyperSync.res +6 -4
- package/src/sources/HyperSync.res.mjs +2 -3
- package/src/sources/HyperSync.resi +1 -1
- package/src/sources/HyperSyncClient.res +18 -9
- package/src/sources/HyperSyncClient.res.mjs +4 -4
- package/src/sources/RpcSource.res +15 -10
- package/src/sources/RpcSource.res.mjs +3 -4
- package/src/sources/SimulateSource.res +26 -11
- package/src/sources/SimulateSource.res.mjs +13 -5
- package/src/sources/Source.res +4 -2
- package/src/sources/SourceManager.res +2 -3
- package/src/sources/SourceManager.res.mjs +2 -3
- package/src/sources/Svm.res +1 -2
- package/src/sources/Svm.res.mjs +1 -1
- package/src/sources/SvmHyperSyncClient.res +12 -3
- package/src/sources/SvmHyperSyncClient.res.mjs +2 -2
- package/src/sources/SvmHyperSyncSource.res +10 -5
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/src/IndexingAddresses.res +0 -151
- package/src/IndexingAddresses.res.mjs +0 -130
- package/src/IndexingAddresses.resi +0 -39
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "../Utils.res.mjs";
|
|
4
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
5
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
6
|
+
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
7
|
+
|
|
8
|
+
function fromIndexFields(indexFields) {
|
|
9
|
+
return indexFields.map(param => ({
|
|
10
|
+
name: param.fieldName,
|
|
11
|
+
direction: param.direction
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeKey(tableName, columns, method) {
|
|
16
|
+
return tableName + `|` + method + `|` + columns.map(param => {
|
|
17
|
+
let name = param.name;
|
|
18
|
+
if (param.direction === "Asc") {
|
|
19
|
+
return name;
|
|
20
|
+
} else {
|
|
21
|
+
return name + " DESC";
|
|
22
|
+
}
|
|
23
|
+
}).join(",");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let catalogRowsSchema = S$RescriptSchema.array(S$RescriptSchema.object(s => ({
|
|
27
|
+
tableName: s.f("tableName", S$RescriptSchema.string),
|
|
28
|
+
indexName: s.f("indexName", S$RescriptSchema.string),
|
|
29
|
+
method: s.f("method", S$RescriptSchema.string),
|
|
30
|
+
isValid: s.f("isValid", S$RescriptSchema.int),
|
|
31
|
+
isPlain: s.f("isPlain", S$RescriptSchema.int),
|
|
32
|
+
columns: s.f("columns", S$RescriptSchema.array(S$RescriptSchema.string)),
|
|
33
|
+
directions: s.f("directions", S$RescriptSchema.array(S$RescriptSchema.string))
|
|
34
|
+
})));
|
|
35
|
+
|
|
36
|
+
function makeCatalogQuery(pgSchema) {
|
|
37
|
+
return `SELECT
|
|
38
|
+
t.relname AS "tableName",
|
|
39
|
+
i.relname AS "indexName",
|
|
40
|
+
am.amname AS "method",
|
|
41
|
+
CASE WHEN ix.indisvalid AND ix.indisready THEN 1 ELSE 0 END AS "isValid",
|
|
42
|
+
CASE
|
|
43
|
+
WHEN ix.indisunique OR ix.indpred IS NOT NULL OR ix.indexprs IS NOT NULL THEN 0
|
|
44
|
+
ELSE 1
|
|
45
|
+
END AS "isPlain",
|
|
46
|
+
array_agg(
|
|
47
|
+
CASE WHEN k.attnum = 0
|
|
48
|
+
THEN pg_get_indexdef(ix.indexrelid, k.ord::int, true)
|
|
49
|
+
ELSE a.attname
|
|
50
|
+
END ORDER BY k.ord
|
|
51
|
+
) AS "columns",
|
|
52
|
+
array_agg(
|
|
53
|
+
CASE WHEN (ix.indoption[k.ord - 1] & 1) = 1 THEN 'DESC' ELSE 'ASC' END ORDER BY k.ord
|
|
54
|
+
) AS "directions"
|
|
55
|
+
FROM pg_index ix
|
|
56
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
57
|
+
JOIN pg_class t ON t.oid = ix.indrelid
|
|
58
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
59
|
+
JOIN pg_am am ON am.oid = i.relam
|
|
60
|
+
JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON k.ord <= ix.indnkeyatts
|
|
61
|
+
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
62
|
+
WHERE n.nspname = '` + pgSchema + `'
|
|
63
|
+
GROUP BY t.relname, i.relname, am.amname, ix.indisvalid, ix.indisready,
|
|
64
|
+
ix.indisunique, ix.indpred, ix.indexprs;`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function make() {
|
|
68
|
+
return {
|
|
69
|
+
keys: new Set(),
|
|
70
|
+
invalidByName: {},
|
|
71
|
+
inflight: {},
|
|
72
|
+
tableQueue: {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function has(registry, key) {
|
|
77
|
+
return registry.keys.has(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function add(registry, key) {
|
|
81
|
+
registry.keys.add(key);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function size(registry) {
|
|
85
|
+
return registry.keys.size;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toArray(registry) {
|
|
89
|
+
return Array.from(registry.keys).toSorted(Primitive_string.compare);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getInvalid(registry, name) {
|
|
93
|
+
return registry.invalidByName[name];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function clearInvalidName(registry, name) {
|
|
97
|
+
Utils.Dict.deleteInPlace(registry.invalidByName, name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function reload(registry, rows) {
|
|
101
|
+
registry.keys.clear();
|
|
102
|
+
Object.keys(registry.invalidByName).forEach(name => Utils.Dict.deleteInPlace(registry.invalidByName, name));
|
|
103
|
+
let invalidIndexNames = [];
|
|
104
|
+
rows.forEach(row => {
|
|
105
|
+
let key = makeKey(row.tableName, row.columns.map((name, idx) => {
|
|
106
|
+
let match = row.directions[idx];
|
|
107
|
+
let tmp;
|
|
108
|
+
tmp = match === "DESC" ? "Desc" : "Asc";
|
|
109
|
+
return {
|
|
110
|
+
name: name,
|
|
111
|
+
direction: tmp
|
|
112
|
+
};
|
|
113
|
+
}), row.method);
|
|
114
|
+
if (row.isValid === 0) {
|
|
115
|
+
invalidIndexNames.push(row.indexName);
|
|
116
|
+
registry.invalidByName[row.indexName] = {
|
|
117
|
+
key: key,
|
|
118
|
+
isRepairable: row.isPlain === 1
|
|
119
|
+
};
|
|
120
|
+
return;
|
|
121
|
+
} else {
|
|
122
|
+
return add(registry, key);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
return invalidIndexNames;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function ensure(registry, key, tableName, build) {
|
|
129
|
+
if (registry.keys.has(key)) {
|
|
130
|
+
return Promise.resolve();
|
|
131
|
+
}
|
|
132
|
+
let promise = registry.inflight[key];
|
|
133
|
+
if (promise !== undefined) {
|
|
134
|
+
return promise;
|
|
135
|
+
}
|
|
136
|
+
let tail = registry.tableQueue[tableName];
|
|
137
|
+
let tail$1 = tail !== undefined ? tail : Promise.resolve();
|
|
138
|
+
let promise$1 = tail$1.then(() => {
|
|
139
|
+
if (registry.keys.has(key)) {
|
|
140
|
+
return Promise.resolve();
|
|
141
|
+
} else {
|
|
142
|
+
return build();
|
|
143
|
+
}
|
|
144
|
+
}).then(() => add(registry, key));
|
|
145
|
+
registry.inflight[key] = promise$1;
|
|
146
|
+
registry.tableQueue[tableName] = Utils.$$Promise.silentCatch(promise$1);
|
|
147
|
+
return promise$1.finally(() => Utils.Dict.deleteInPlace(registry.inflight, key));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function truncateIndexName(description) {
|
|
151
|
+
if (description.length > 63) {
|
|
152
|
+
return description.slice(0, 63);
|
|
153
|
+
} else {
|
|
154
|
+
return description;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function validateIndexNamesOrThrow(descriptions) {
|
|
159
|
+
let byName = {};
|
|
160
|
+
descriptions.forEach(description => {
|
|
161
|
+
let name = truncateIndexName(description);
|
|
162
|
+
let existing = byName[name];
|
|
163
|
+
if (existing !== undefined && existing !== description) {
|
|
164
|
+
return Stdlib_JsError.throwWithMessage(`Index names "` + existing + `" and "` + description + `" both truncate to "` + name + `" at PostgreSQL's ` + (63).toString() + `-character identifier limit. Rename a field or an entity so the generated index names stay distinct.`);
|
|
165
|
+
} else {
|
|
166
|
+
byName[name] = description;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let btree = "btree";
|
|
173
|
+
|
|
174
|
+
let pgMaxIdentifierLength = 63;
|
|
175
|
+
|
|
176
|
+
export {
|
|
177
|
+
btree,
|
|
178
|
+
fromIndexFields,
|
|
179
|
+
makeKey,
|
|
180
|
+
catalogRowsSchema,
|
|
181
|
+
makeCatalogQuery,
|
|
182
|
+
make,
|
|
183
|
+
has,
|
|
184
|
+
add,
|
|
185
|
+
size,
|
|
186
|
+
toArray,
|
|
187
|
+
getInvalid,
|
|
188
|
+
clearInvalidName,
|
|
189
|
+
reload,
|
|
190
|
+
ensure,
|
|
191
|
+
pgMaxIdentifierLength,
|
|
192
|
+
truncateIndexName,
|
|
193
|
+
validateIndexNamesOrThrow,
|
|
194
|
+
}
|
|
195
|
+
/* catalogRowsSchema Not a pure module */
|
package/src/db/InternalTable.res
CHANGED
|
@@ -164,6 +164,13 @@ SET ${setClauses->Array.joinUnsafe(",\n ")}
|
|
|
164
164
|
WHERE "${(#id: field :> string)}" = $1;`
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// Written in the same transaction as the deferred schema indexes, so a chain
|
|
168
|
+
// is never reported ready without the indexes the schema promises.
|
|
169
|
+
let makeSetReadyAtQuery = (~pgSchema) =>
|
|
170
|
+
`UPDATE "${pgSchema}"."${table.tableName}"
|
|
171
|
+
SET "${(#ready_at: field :> string)}" = $1
|
|
172
|
+
WHERE "${(#id: field :> string)}" = ANY($2::int[]);`
|
|
173
|
+
|
|
167
174
|
type rawInitialState = {
|
|
168
175
|
id: int,
|
|
169
176
|
startBlock: int,
|
|
@@ -101,6 +101,12 @@ SET ` + setClauses.join(",\n ") + `
|
|
|
101
101
|
WHERE "` + "id" + `" = $1;`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function makeSetReadyAtQuery(pgSchema) {
|
|
105
|
+
return `UPDATE "` + pgSchema + `"."` + table.tableName + `"
|
|
106
|
+
SET "` + "ready_at" + `" = $1
|
|
107
|
+
WHERE "` + "id" + `" = ANY($2::int[]);`;
|
|
108
|
+
}
|
|
109
|
+
|
|
104
110
|
function makeGetInitialStateQuery(pgSchema) {
|
|
105
111
|
return `SELECT "` + "id" + `" as "id",
|
|
106
112
|
"` + "start_block" + `" as "startBlock",
|
|
@@ -213,6 +219,7 @@ let Chains = {
|
|
|
213
219
|
makeInitialValuesQuery: makeInitialValuesQuery,
|
|
214
220
|
metaFields: metaFields,
|
|
215
221
|
makeMetaFieldsUpdateQuery: makeMetaFieldsUpdateQuery,
|
|
222
|
+
makeSetReadyAtQuery: makeSetReadyAtQuery,
|
|
216
223
|
makeGetInitialStateQuery: makeGetInitialStateQuery,
|
|
217
224
|
makeGetIndexingAddressesQuery: makeGetIndexingAddressesQuery,
|
|
218
225
|
getInitialState: getInitialState,
|
package/src/db/Table.res
CHANGED
|
@@ -31,7 +31,6 @@ type fieldType =
|
|
|
31
31
|
| Json
|
|
32
32
|
| Date
|
|
33
33
|
| Enum({config: enumConfig<enum>})
|
|
34
|
-
| Entity({name: string})
|
|
35
34
|
|
|
36
35
|
type field = {
|
|
37
36
|
fieldName: string,
|
|
@@ -168,7 +167,6 @@ let getPgFieldType = (
|
|
|
168
167
|
| Date =>
|
|
169
168
|
(isNullable ? Postgres.TimestampWithTimezoneNull : Postgres.TimestampWithTimezone :> string)
|
|
170
169
|
| Enum({config}) => `"${pgSchema}".${config.name}`
|
|
171
|
-
| Entity(_) => (Postgres.Text :> string) // FIXME: Will it work correctly if id is not a text column?
|
|
172
170
|
}
|
|
173
171
|
|
|
174
172
|
// Workaround for Hasura bug https://github.com/enviodev/hyperindex/issues/788
|
|
@@ -192,14 +190,14 @@ type compositeIndexField = {
|
|
|
192
190
|
type table = {
|
|
193
191
|
tableName: string,
|
|
194
192
|
fields: array<fieldOrDerived>,
|
|
195
|
-
|
|
193
|
+
compositeIndexes: array<array<compositeIndexField>>,
|
|
196
194
|
description: option<string>,
|
|
197
195
|
}
|
|
198
196
|
|
|
199
|
-
let mkTable = (tableName, ~
|
|
197
|
+
let mkTable = (tableName, ~compositeIndexes=[], ~fields, ~description=?) => {
|
|
200
198
|
tableName,
|
|
201
199
|
fields,
|
|
202
|
-
|
|
200
|
+
compositeIndexes,
|
|
203
201
|
description,
|
|
204
202
|
}
|
|
205
203
|
|
|
@@ -248,6 +246,40 @@ let getDerivedFromFields = table =>
|
|
|
248
246
|
let getFieldByName = (table, fieldName) =>
|
|
249
247
|
table.fields->Array.find(field => field->getUserDefinedFieldName === fieldName)
|
|
250
248
|
|
|
249
|
+
exception NoIdField(string)
|
|
250
|
+
|
|
251
|
+
// The `id` primary-key field. Its type drives both the id column and every
|
|
252
|
+
// foreign key that references the entity, so id-typed SQL (delete-by-id,
|
|
253
|
+
// history backfill) reads the column type and value schema from here.
|
|
254
|
+
let getIdFieldOrThrow = (table): field =>
|
|
255
|
+
switch table->getFieldByName(idFieldName) {
|
|
256
|
+
| Some(Field(field)) => field
|
|
257
|
+
| _ => throw(NoIdField(table.tableName))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let getIdPgFieldType = (table, ~pgSchema) =>
|
|
261
|
+
getPgFieldType(
|
|
262
|
+
~fieldType=(table->getIdFieldOrThrow).fieldType,
|
|
263
|
+
~pgSchema,
|
|
264
|
+
~isArray=false,
|
|
265
|
+
~isNumericArrayAsText=false,
|
|
266
|
+
~isNullable=false,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
// Schema for a single id value, typed opaquely so id-generic code can serialize
|
|
270
|
+
// ids regardless of the underlying scalar.
|
|
271
|
+
let getIdSchema = (table): S.t<EntityId.t> =>
|
|
272
|
+
(table->getIdFieldOrThrow).fieldSchema->(Utils.magic: S.t<unknown> => S.t<EntityId.t>)
|
|
273
|
+
|
|
274
|
+
// Serializes an array of ids to the JSON form the SQL layer binds. The array
|
|
275
|
+
// schema is memoized per table so its serializer compiles once, not on every
|
|
276
|
+
// (high-frequency) delete/history write.
|
|
277
|
+
let idsArraySchema: table => S.t<array<EntityId.t>> = Utils.WeakMap.memoize(table =>
|
|
278
|
+
S.array(table->getIdSchema)
|
|
279
|
+
)
|
|
280
|
+
let encodeIdsToJson = (table, ids: array<EntityId.t>): JSON.t =>
|
|
281
|
+
ids->S.reverseConvertToJsonOrThrow(table->idsArraySchema)
|
|
282
|
+
|
|
251
283
|
// TODO: Test whether it should be passed via args and match the column type
|
|
252
284
|
|
|
253
285
|
let getFieldByApiName = (table, apiFieldName) =>
|
|
@@ -297,13 +329,12 @@ let makeRowsSchema = (table, ~rowFieldName) =>
|
|
|
297
329
|
S.array(
|
|
298
330
|
S.object(s => {
|
|
299
331
|
let dict = Dict.make()
|
|
300
|
-
table.fields->Array.forEach(
|
|
301
|
-
field
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
},
|
|
332
|
+
table.fields->Array.forEach(field =>
|
|
333
|
+
switch field {
|
|
334
|
+
| Field(field) =>
|
|
335
|
+
dict->Dict.set(field->getApiFieldName, s.field(field->rowFieldName, field.fieldSchema))
|
|
336
|
+
| DerivedFrom(_) => ()
|
|
337
|
+
}
|
|
307
338
|
)
|
|
308
339
|
dict
|
|
309
340
|
})->(Utils.magic: S.t<dict<unknown>> => S.t<unknown>),
|
|
@@ -324,11 +355,11 @@ let pgRowsSchema: table => S.t<array<unknown>> = Utils.WeakMap.memoize(table =>
|
|
|
324
355
|
exception NonExistingTableField(string)
|
|
325
356
|
|
|
326
357
|
/*
|
|
327
|
-
Gets all composite
|
|
358
|
+
Gets all composite indexes (whether they are single indexes or not)
|
|
328
359
|
And maps the fields defined to their actual db name (some have _id suffix)
|
|
329
360
|
*/
|
|
330
|
-
let
|
|
331
|
-
table.
|
|
361
|
+
let getUnfilteredCompositeIndexesUnsafe = (table): array<array<compositeIndexField>> => {
|
|
362
|
+
table.compositeIndexes->Array.map(compositeIndex =>
|
|
332
363
|
compositeIndex->Array.map(indexField => {
|
|
333
364
|
let dbFieldName = switch table->getFieldByName(indexField.fieldName) {
|
|
334
365
|
| Some(field) => field->getPgFieldName
|
|
@@ -438,10 +469,10 @@ let toSqlParams = (table: table, ~schema, ~pgSchema) => {
|
|
|
438
469
|
}
|
|
439
470
|
|
|
440
471
|
/*
|
|
441
|
-
Gets all single
|
|
472
|
+
Gets all single indexes
|
|
442
473
|
And maps the fields defined to their actual db name (some have _id suffix)
|
|
443
474
|
*/
|
|
444
|
-
let
|
|
475
|
+
let getSingleIndexes = (table): array<string> => {
|
|
445
476
|
let indexFields = table.fields->Array.filterMap(field =>
|
|
446
477
|
switch field {
|
|
447
478
|
| Field(field) if field.isIndex => Some(field->getPgDbFieldName)
|
|
@@ -450,8 +481,8 @@ let getSingleIndices = (table): array<string> => {
|
|
|
450
481
|
)
|
|
451
482
|
|
|
452
483
|
table
|
|
453
|
-
->
|
|
454
|
-
//get all composite
|
|
484
|
+
->getUnfilteredCompositeIndexesUnsafe
|
|
485
|
+
//get all composite indexes with only 1 field defined
|
|
455
486
|
//this is still a single index
|
|
456
487
|
->Array.filterMap(cidx =>
|
|
457
488
|
switch cidx {
|
|
@@ -467,11 +498,11 @@ let getSingleIndices = (table): array<string> => {
|
|
|
467
498
|
}
|
|
468
499
|
|
|
469
500
|
/*
|
|
470
|
-
Gets all composite
|
|
501
|
+
Gets all composite indexes
|
|
471
502
|
And maps the fields defined to their actual db name (some have _id suffix)
|
|
472
503
|
*/
|
|
473
|
-
let
|
|
504
|
+
let getCompositeIndexes = (table): array<array<compositeIndexField>> => {
|
|
474
505
|
table
|
|
475
|
-
->
|
|
506
|
+
->getUnfilteredCompositeIndexesUnsafe
|
|
476
507
|
->Array.filter(ind => ind->Array.length > 1)
|
|
477
508
|
}
|
package/src/db/Table.res.mjs
CHANGED
|
@@ -94,6 +94,8 @@ function getPgFieldName(fieldOrDerived) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
let idFieldName = "id";
|
|
98
|
+
|
|
97
99
|
function getPgFieldType(fieldType, pgSchema, isArray, isNumericArrayAsText, isNullable) {
|
|
98
100
|
let columnType;
|
|
99
101
|
if (typeof fieldType !== "object") {
|
|
@@ -142,9 +144,6 @@ function getPgFieldType(fieldType, pgSchema, isArray, isNumericArrayAsText, isNu
|
|
|
142
144
|
case "Enum" :
|
|
143
145
|
columnType = `"` + pgSchema + `".` + fieldType.config.name;
|
|
144
146
|
break;
|
|
145
|
-
case "Entity" :
|
|
146
|
-
columnType = "TEXT";
|
|
147
|
-
break;
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
149
|
let isNumericAsText = isArray && isNumericArrayAsText;
|
|
@@ -154,12 +153,12 @@ function getPgFieldType(fieldType, pgSchema, isArray, isNumericArrayAsText, isNu
|
|
|
154
153
|
);
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
function mkTable(tableName,
|
|
158
|
-
let
|
|
156
|
+
function mkTable(tableName, compositeIndexesOpt, fields, description) {
|
|
157
|
+
let compositeIndexes = compositeIndexesOpt !== undefined ? compositeIndexesOpt : [];
|
|
159
158
|
return {
|
|
160
159
|
tableName: tableName,
|
|
161
160
|
fields: fields,
|
|
162
|
-
|
|
161
|
+
compositeIndexes: compositeIndexes,
|
|
163
162
|
description: description
|
|
164
163
|
};
|
|
165
164
|
}
|
|
@@ -226,6 +225,41 @@ function getFieldByName(table, fieldName) {
|
|
|
226
225
|
return table.fields.find(field => getUserDefinedFieldName(field) === fieldName);
|
|
227
226
|
}
|
|
228
227
|
|
|
228
|
+
let NoIdField = /* @__PURE__ */Primitive_exceptions.create("Table.NoIdField");
|
|
229
|
+
|
|
230
|
+
function getIdFieldOrThrow(table) {
|
|
231
|
+
let match = getFieldByName(table, idFieldName);
|
|
232
|
+
if (match !== undefined) {
|
|
233
|
+
if (match.TAG === "Field") {
|
|
234
|
+
return match._0;
|
|
235
|
+
}
|
|
236
|
+
throw {
|
|
237
|
+
RE_EXN_ID: NoIdField,
|
|
238
|
+
_1: table.tableName,
|
|
239
|
+
Error: new Error()
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
throw {
|
|
243
|
+
RE_EXN_ID: NoIdField,
|
|
244
|
+
_1: table.tableName,
|
|
245
|
+
Error: new Error()
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getIdPgFieldType(table, pgSchema) {
|
|
250
|
+
return getPgFieldType(getIdFieldOrThrow(table).fieldType, pgSchema, false, false, false);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getIdSchema(table) {
|
|
254
|
+
return getIdFieldOrThrow(table).fieldSchema;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let idsArraySchema = Utils.$$WeakMap.memoize(table => S$RescriptSchema.array(getIdFieldOrThrow(table).fieldSchema));
|
|
258
|
+
|
|
259
|
+
function encodeIdsToJson(table, ids) {
|
|
260
|
+
return S$RescriptSchema.reverseConvertToJsonOrThrow(ids, idsArraySchema(table));
|
|
261
|
+
}
|
|
262
|
+
|
|
229
263
|
function getFieldByApiName(table, apiFieldName) {
|
|
230
264
|
return table.fields.find(field => {
|
|
231
265
|
let tmp;
|
|
@@ -270,8 +304,8 @@ let pgRowsSchema = Utils.$$WeakMap.memoize(table => makeRowsSchema(table, getPgD
|
|
|
270
304
|
|
|
271
305
|
let NonExistingTableField = /* @__PURE__ */Primitive_exceptions.create("Table.NonExistingTableField");
|
|
272
306
|
|
|
273
|
-
function
|
|
274
|
-
return table.
|
|
307
|
+
function getUnfilteredCompositeIndexesUnsafe(table) {
|
|
308
|
+
return table.compositeIndexes.map(compositeIndex => compositeIndex.map(indexField => {
|
|
275
309
|
let field = getFieldByName(table, indexField.fieldName);
|
|
276
310
|
let dbFieldName;
|
|
277
311
|
if (field !== undefined) {
|
|
@@ -385,7 +419,7 @@ function toSqlParams(table, schema, pgSchema) {
|
|
|
385
419
|
};
|
|
386
420
|
}
|
|
387
421
|
|
|
388
|
-
function
|
|
422
|
+
function getSingleIndexes(table) {
|
|
389
423
|
let indexFields = Stdlib_Array.filterMap(table.fields, field => {
|
|
390
424
|
if (field.TAG !== "Field") {
|
|
391
425
|
return;
|
|
@@ -395,7 +429,7 @@ function getSingleIndices(table) {
|
|
|
395
429
|
return getPgDbFieldName(field$1);
|
|
396
430
|
}
|
|
397
431
|
});
|
|
398
|
-
return Array.from(new Set(Stdlib_Array.filterMap(
|
|
432
|
+
return Array.from(new Set(Stdlib_Array.filterMap(getUnfilteredCompositeIndexesUnsafe(table), cidx => {
|
|
399
433
|
if (cidx.length !== 1) {
|
|
400
434
|
return;
|
|
401
435
|
}
|
|
@@ -404,12 +438,10 @@ function getSingleIndices(table) {
|
|
|
404
438
|
}).concat([indexFields]).flat())).toSorted(Primitive_string.compare);
|
|
405
439
|
}
|
|
406
440
|
|
|
407
|
-
function
|
|
408
|
-
return
|
|
441
|
+
function getCompositeIndexes(table) {
|
|
442
|
+
return getUnfilteredCompositeIndexesUnsafe(table).filter(ind => ind.length > 1);
|
|
409
443
|
}
|
|
410
444
|
|
|
411
|
-
let idFieldName = "id";
|
|
412
|
-
|
|
413
445
|
export {
|
|
414
446
|
makeEnumConfig,
|
|
415
447
|
mkField,
|
|
@@ -429,15 +461,21 @@ export {
|
|
|
429
461
|
getLinkedEntityFields,
|
|
430
462
|
getDerivedFromFields,
|
|
431
463
|
getFieldByName,
|
|
464
|
+
NoIdField,
|
|
465
|
+
getIdFieldOrThrow,
|
|
466
|
+
getIdPgFieldType,
|
|
467
|
+
getIdSchema,
|
|
468
|
+
idsArraySchema,
|
|
469
|
+
encodeIdsToJson,
|
|
432
470
|
getFieldByApiName,
|
|
433
471
|
queryFields,
|
|
434
472
|
makeRowsSchema,
|
|
435
473
|
rowsSchema,
|
|
436
474
|
pgRowsSchema,
|
|
437
475
|
NonExistingTableField,
|
|
438
|
-
|
|
476
|
+
getUnfilteredCompositeIndexesUnsafe,
|
|
439
477
|
toSqlParams,
|
|
440
|
-
|
|
441
|
-
|
|
478
|
+
getSingleIndexes,
|
|
479
|
+
getCompositeIndexes,
|
|
442
480
|
}
|
|
443
|
-
/*
|
|
481
|
+
/* idsArraySchema Not a pure module */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Binding to the Rust `AddressSet` napi class: an immutable, ordered snapshot
|
|
2
|
+
// of some of a chain's indexed addresses, handed to a fetch-state partition
|
|
3
|
+
// instead of a JS address array. Sets are ordered by
|
|
4
|
+
// `(effectiveStartBlock, address)`, so the same addresses always produce the
|
|
5
|
+
// same set however they were registered or restored. Everything a query derives
|
|
6
|
+
// from one (padded topics, the routing owner index, per-contract counts) is
|
|
7
|
+
// built once inside Rust and shared by every query the partition makes.
|
|
8
|
+
type t
|
|
9
|
+
|
|
10
|
+
type startBlockGroup = {startBlock: int, count: int}
|
|
11
|
+
|
|
12
|
+
@send external size: t => int = "size"
|
|
13
|
+
|
|
14
|
+
// Live addresses of one contract, derived once per set inside Rust — so the
|
|
15
|
+
// fetch state can size and split partitions without ever walking the addresses.
|
|
16
|
+
@send external countFor: (t, string) => int = "countFor"
|
|
17
|
+
|
|
18
|
+
// The chain-wide address gate, reachable through any set of the store. Not set
|
|
19
|
+
// membership: the simulate source has no native query boundary, so it applies
|
|
20
|
+
// the same gate every real source applies while routing.
|
|
21
|
+
@send external has: (t, Address.t, string, int) => bool = "has"
|
|
22
|
+
|
|
23
|
+
@send external contractNames: t => array<string> = "contractNames"
|
|
24
|
+
|
|
25
|
+
// Distinct effective start blocks in this set, ascending — how partition
|
|
26
|
+
// creation decides where one partition ends and the next begins.
|
|
27
|
+
@send external startBlockGroups: t => array<startBlockGroup> = "startBlockGroups"
|
|
28
|
+
|
|
29
|
+
@send external slice: (t, ~offset: int, ~limit: option<int>) => t = "slice"
|
|
30
|
+
|
|
31
|
+
// Keeps only the named contracts' addresses, in set order.
|
|
32
|
+
@send external filterByContracts: (t, array<string>) => t = "filterByContracts"
|
|
33
|
+
|
|
34
|
+
// Drops addresses registered after the target block, and any the store has
|
|
35
|
+
// tombstoned since this set was built.
|
|
36
|
+
@send external filterByRegistrationBlock: (t, int) => t = "filterByRegistrationBlock"
|
|
37
|
+
|
|
38
|
+
// Union with another set of the same store, keeping set order; duplicates
|
|
39
|
+
// collapse.
|
|
40
|
+
@send external merge: (t, t) => t = "merge"
|
|
41
|
+
|
|
42
|
+
// Canonical address strings in set order. Not on a query path — queries read
|
|
43
|
+
// the set's cached slices inside Rust.
|
|
44
|
+
@send external addresses: t => array<Address.t> = "addresses"
|
|
45
|
+
|
|
46
|
+
@send external entries: t => array<Internal.indexingContract> = "entries"
|
|
47
|
+
|
|
48
|
+
let isEmpty = (set: t) => set->size === 0
|