ponder 0.14.13 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/esm/bin/commands/createViews.js +28 -11
- package/dist/esm/bin/commands/createViews.js.map +1 -1
- package/dist/esm/bin/commands/dev.js +42 -22
- package/dist/esm/bin/commands/dev.js.map +1 -1
- package/dist/esm/bin/commands/prune.js +3 -0
- package/dist/esm/bin/commands/prune.js.map +1 -1
- package/dist/esm/bin/commands/serve.js +4 -1
- package/dist/esm/bin/commands/serve.js.map +1 -1
- package/dist/esm/bin/commands/start.js +18 -6
- package/dist/esm/bin/commands/start.js.map +1 -1
- package/dist/esm/bin/isolatedController.js +200 -0
- package/dist/esm/bin/isolatedController.js.map +1 -0
- package/dist/esm/bin/isolatedWorker.js +146 -0
- package/dist/esm/bin/isolatedWorker.js.map +1 -0
- package/dist/esm/build/config.js +322 -402
- package/dist/esm/build/config.js.map +1 -1
- package/dist/esm/build/index.js +8 -11
- package/dist/esm/build/index.js.map +1 -1
- package/dist/esm/build/pre.js +1 -4
- package/dist/esm/build/pre.js.map +1 -1
- package/dist/esm/build/schema.js +25 -3
- package/dist/esm/build/schema.js.map +1 -1
- package/dist/esm/client/index.js +306 -42
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/database/actions.js +264 -104
- package/dist/esm/database/actions.js.map +1 -1
- package/dist/esm/database/index.js +39 -33
- package/dist/esm/database/index.js.map +1 -1
- package/dist/esm/database/queryBuilder.js +1 -0
- package/dist/esm/database/queryBuilder.js.map +1 -1
- package/dist/esm/drizzle/index.js +11 -7
- package/dist/esm/drizzle/index.js.map +1 -1
- package/dist/esm/drizzle/onchain.js +18 -0
- package/dist/esm/drizzle/onchain.js.map +1 -1
- package/dist/esm/indexing/client.js +32 -25
- package/dist/esm/indexing/client.js.map +1 -1
- package/dist/esm/indexing/index.js +110 -178
- package/dist/esm/indexing/index.js.map +1 -1
- package/dist/esm/indexing/profile.js +1 -1
- package/dist/esm/indexing/profile.js.map +1 -1
- package/dist/esm/indexing-store/cache.js +196 -274
- package/dist/esm/indexing-store/cache.js.map +1 -1
- package/dist/esm/indexing-store/historical.js +17 -13
- package/dist/esm/indexing-store/historical.js.map +1 -1
- package/dist/esm/indexing-store/index.js +10 -1
- package/dist/esm/indexing-store/index.js.map +1 -1
- package/dist/esm/indexing-store/profile.js +3 -3
- package/dist/esm/indexing-store/profile.js.map +1 -1
- package/dist/esm/indexing-store/realtime.js +27 -2
- package/dist/esm/indexing-store/realtime.js.map +1 -1
- package/dist/esm/internal/errors.js +28 -0
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/internal/metrics.js +279 -82
- package/dist/esm/internal/metrics.js.map +1 -1
- package/dist/esm/internal/options.js +1 -0
- package/dist/esm/internal/options.js.map +1 -1
- package/dist/esm/internal/telemetry.js +1 -1
- package/dist/esm/internal/telemetry.js.map +1 -1
- package/dist/esm/rpc/http.js +130 -0
- package/dist/esm/rpc/http.js.map +1 -0
- package/dist/esm/rpc/index.js +38 -7
- package/dist/esm/rpc/index.js.map +1 -1
- package/dist/esm/runtime/events.js +179 -212
- package/dist/esm/runtime/events.js.map +1 -1
- package/dist/esm/runtime/filter.js +71 -0
- package/dist/esm/runtime/filter.js.map +1 -1
- package/dist/esm/runtime/fragments.js +78 -73
- package/dist/esm/runtime/fragments.js.map +1 -1
- package/dist/esm/runtime/historical.js +306 -130
- package/dist/esm/runtime/historical.js.map +1 -1
- package/dist/esm/runtime/index.js +183 -58
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/isolated.js +462 -0
- package/dist/esm/runtime/isolated.js.map +1 -0
- package/dist/esm/runtime/multichain.js +80 -73
- package/dist/esm/runtime/multichain.js.map +1 -1
- package/dist/esm/runtime/omnichain.js +82 -75
- package/dist/esm/runtime/omnichain.js.map +1 -1
- package/dist/esm/runtime/realtime.js +198 -66
- package/dist/esm/runtime/realtime.js.map +1 -1
- package/dist/esm/sync-historical/index.js +416 -457
- package/dist/esm/sync-historical/index.js.map +1 -1
- package/dist/esm/sync-realtime/bloom.js +3 -3
- package/dist/esm/sync-realtime/bloom.js.map +1 -1
- package/dist/esm/sync-realtime/index.js +27 -46
- package/dist/esm/sync-realtime/index.js.map +1 -1
- package/dist/esm/sync-store/index.js +112 -63
- package/dist/esm/sync-store/index.js.map +1 -1
- package/dist/esm/utils/abi.js +20 -32
- package/dist/esm/utils/abi.js.map +1 -1
- package/dist/esm/utils/chunk.js +8 -0
- package/dist/esm/utils/chunk.js.map +1 -0
- package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
- package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
- package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
- package/dist/esm/utils/sql-parse.js.map +1 -0
- package/dist/types/bin/commands/createViews.d.ts.map +1 -1
- package/dist/types/bin/commands/dev.d.ts.map +1 -1
- package/dist/types/bin/commands/prune.d.ts.map +1 -1
- package/dist/types/bin/commands/serve.d.ts.map +1 -1
- package/dist/types/bin/commands/start.d.ts.map +1 -1
- package/dist/types/bin/isolatedController.d.ts +13 -0
- package/dist/types/bin/isolatedController.d.ts.map +1 -0
- package/dist/types/bin/isolatedWorker.d.ts +9 -0
- package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
- package/dist/types/build/config.d.ts +29 -11
- package/dist/types/build/config.d.ts.map +1 -1
- package/dist/types/build/index.d.ts +3 -2
- package/dist/types/build/index.d.ts.map +1 -1
- package/dist/types/build/pre.d.ts +1 -1
- package/dist/types/build/pre.d.ts.map +1 -1
- package/dist/types/build/schema.d.ts +5 -3
- package/dist/types/build/schema.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/config/index.d.ts +3 -3
- package/dist/types/config/index.d.ts.map +1 -1
- package/dist/types/database/actions.d.ts +53 -7
- package/dist/types/database/actions.d.ts.map +1 -1
- package/dist/types/database/index.d.ts +21 -21
- package/dist/types/database/index.d.ts.map +1 -1
- package/dist/types/database/queryBuilder.d.ts.map +1 -1
- package/dist/types/drizzle/index.d.ts +4 -5
- package/dist/types/drizzle/index.d.ts.map +1 -1
- package/dist/types/drizzle/onchain.d.ts +6 -0
- package/dist/types/drizzle/onchain.d.ts.map +1 -1
- package/dist/types/indexing/client.d.ts.map +1 -1
- package/dist/types/indexing/index.d.ts +2 -5
- package/dist/types/indexing/index.d.ts.map +1 -1
- package/dist/types/indexing-store/cache.d.ts +3 -2
- package/dist/types/indexing-store/cache.d.ts.map +1 -1
- package/dist/types/indexing-store/historical.d.ts +2 -1
- package/dist/types/indexing-store/historical.d.ts.map +1 -1
- package/dist/types/indexing-store/index.d.ts +1 -0
- package/dist/types/indexing-store/index.d.ts.map +1 -1
- package/dist/types/indexing-store/realtime.d.ts +2 -1
- package/dist/types/indexing-store/realtime.d.ts.map +1 -1
- package/dist/types/internal/errors.d.ts +5 -0
- package/dist/types/internal/errors.d.ts.map +1 -1
- package/dist/types/internal/metrics.d.ts +21 -0
- package/dist/types/internal/metrics.d.ts.map +1 -1
- package/dist/types/internal/options.d.ts +2 -0
- package/dist/types/internal/options.d.ts.map +1 -1
- package/dist/types/internal/types.d.ts +66 -58
- package/dist/types/internal/types.d.ts.map +1 -1
- package/dist/types/rpc/http.d.ts +17 -0
- package/dist/types/rpc/http.d.ts.map +1 -0
- package/dist/types/rpc/index.d.ts.map +1 -1
- package/dist/types/runtime/events.d.ts +4 -4
- package/dist/types/runtime/events.d.ts.map +1 -1
- package/dist/types/runtime/filter.d.ts +5 -1
- package/dist/types/runtime/filter.d.ts.map +1 -1
- package/dist/types/runtime/fragments.d.ts +4 -3
- package/dist/types/runtime/fragments.d.ts.map +1 -1
- package/dist/types/runtime/historical.d.ts +29 -13
- package/dist/types/runtime/historical.d.ts.map +1 -1
- package/dist/types/runtime/index.d.ts +49 -6
- package/dist/types/runtime/index.d.ts.map +1 -1
- package/dist/types/runtime/init.d.ts +5 -5
- package/dist/types/runtime/init.d.ts.map +1 -1
- package/dist/types/runtime/isolated.d.ts +14 -0
- package/dist/types/runtime/isolated.d.ts.map +1 -0
- package/dist/types/runtime/multichain.d.ts.map +1 -1
- package/dist/types/runtime/omnichain.d.ts.map +1 -1
- package/dist/types/runtime/realtime.d.ts +21 -10
- package/dist/types/runtime/realtime.d.ts.map +1 -1
- package/dist/types/sync-historical/index.d.ts +18 -8
- package/dist/types/sync-historical/index.d.ts.map +1 -1
- package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
- package/dist/types/sync-realtime/index.d.ts +2 -2
- package/dist/types/sync-realtime/index.d.ts.map +1 -1
- package/dist/types/sync-store/index.d.ts +9 -9
- package/dist/types/sync-store/index.d.ts.map +1 -1
- package/dist/types/utils/abi.d.ts +3 -34
- package/dist/types/utils/abi.d.ts.map +1 -1
- package/dist/types/utils/chunk.d.ts +2 -0
- package/dist/types/utils/chunk.d.ts.map +1 -0
- package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
- package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
- package/dist/types/utils/sql-parse.d.ts +21 -0
- package/dist/types/utils/sql-parse.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/bin/commands/createViews.ts +35 -15
- package/src/bin/commands/dev.ts +43 -21
- package/src/bin/commands/prune.ts +6 -0
- package/src/bin/commands/serve.ts +4 -1
- package/src/bin/commands/start.ts +20 -5
- package/src/bin/isolatedController.ts +300 -0
- package/src/bin/isolatedWorker.ts +192 -0
- package/src/build/config.ts +570 -632
- package/src/build/index.ts +14 -14
- package/src/build/pre.ts +1 -4
- package/src/build/schema.ts +49 -4
- package/src/client/index.ts +386 -48
- package/src/config/index.ts +3 -3
- package/src/database/actions.ts +469 -120
- package/src/database/index.ts +85 -58
- package/src/database/queryBuilder.ts +1 -0
- package/src/drizzle/index.ts +15 -7
- package/src/drizzle/onchain.ts +19 -0
- package/src/indexing/client.ts +38 -25
- package/src/indexing/index.ts +137 -230
- package/src/indexing/profile.ts +1 -1
- package/src/indexing-store/cache.ts +285 -414
- package/src/indexing-store/historical.ts +20 -10
- package/src/indexing-store/index.ts +16 -0
- package/src/indexing-store/profile.ts +3 -3
- package/src/indexing-store/realtime.ts +28 -0
- package/src/internal/errors.ts +26 -0
- package/src/internal/metrics.ts +341 -111
- package/src/internal/options.ts +4 -0
- package/src/internal/telemetry.ts +1 -1
- package/src/internal/types.ts +70 -87
- package/src/rpc/http.ts +164 -0
- package/src/rpc/index.ts +39 -7
- package/src/runtime/events.ts +195 -240
- package/src/runtime/filter.ts +85 -1
- package/src/runtime/fragments.ts +109 -113
- package/src/runtime/historical.ts +467 -189
- package/src/runtime/index.ts +337 -69
- package/src/runtime/init.ts +5 -5
- package/src/runtime/isolated.ts +768 -0
- package/src/runtime/multichain.ts +137 -102
- package/src/runtime/omnichain.ts +138 -106
- package/src/runtime/realtime.ts +322 -123
- package/src/sync-historical/index.ts +556 -692
- package/src/sync-realtime/bloom.ts +7 -3
- package/src/sync-realtime/index.ts +31 -46
- package/src/sync-store/index.ts +189 -95
- package/src/utils/abi.ts +33 -90
- package/src/utils/chunk.ts +7 -0
- package/src/utils/promiseAllSettledWithThrow.ts +27 -0
- package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
- package/dist/esm/client/parse.js.map +0 -1
- package/dist/types/client/parse.d.ts +0 -14
- package/dist/types/client/parse.d.ts.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { getPrimaryKeyColumns } from '../drizzle/index.js';
|
|
1
|
+
import { getPartitionName, getPrimaryKeyColumns } from '../drizzle/index.js';
|
|
2
2
|
import { getColumnCasing } from '../drizzle/kit/index.js';
|
|
3
3
|
import { addErrorMeta, toErrorMeta } from '../indexing/index.js';
|
|
4
|
-
import { CopyFlushError, DelayedInsertError,
|
|
4
|
+
import { CopyFlushError, DelayedInsertError, ShutdownError, } from '../internal/errors.js';
|
|
5
5
|
import { dedupe } from '../utils/dedupe.js';
|
|
6
6
|
import { prettyPrint } from '../utils/print.js';
|
|
7
|
+
import { promiseAllSettledWithThrow } from '../utils/promiseAllSettledWithThrow.js';
|
|
7
8
|
import { startClock } from '../utils/timer.js';
|
|
8
9
|
import { getTableColumns, getTableName, isTable, or, sql, } from "drizzle-orm";
|
|
9
10
|
import { getTableConfig } from "drizzle-orm/pg-core";
|
|
@@ -96,12 +97,12 @@ export const getCopyText = (table, rows) => {
|
|
|
96
97
|
}
|
|
97
98
|
return result;
|
|
98
99
|
};
|
|
99
|
-
export const getCopyHelper = (qb) => {
|
|
100
|
+
export const getCopyHelper = (qb, chainId) => {
|
|
100
101
|
if (qb.$dialect === "pglite") {
|
|
101
102
|
return async (table, text, includeSchema = true) => {
|
|
102
103
|
const target = includeSchema
|
|
103
|
-
? `"${getTableConfig(table).schema ?? "public"}"."${getTableName(table)}"`
|
|
104
|
-
: `"${getTableName(table)}"`;
|
|
104
|
+
? `"${getTableConfig(table).schema ?? "public"}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`
|
|
105
|
+
: `"${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`;
|
|
105
106
|
await qb.$client
|
|
106
107
|
.query(`COPY ${target} FROM '/dev/blob'`, [], {
|
|
107
108
|
blob: new Blob([text]),
|
|
@@ -116,8 +117,8 @@ export const getCopyHelper = (qb) => {
|
|
|
116
117
|
else {
|
|
117
118
|
return async (table, text, includeSchema = true) => {
|
|
118
119
|
const target = includeSchema
|
|
119
|
-
? `"${getTableConfig(table).schema ?? "public"}"."${getTableName(table)}"`
|
|
120
|
-
: `"${getTableName(table)}"`;
|
|
120
|
+
? `"${getTableConfig(table).schema ?? "public"}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`
|
|
121
|
+
: `"${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`;
|
|
121
122
|
const copyStream = qb.$client.query(copy.from(`COPY ${target} FROM STDIN`));
|
|
122
123
|
await new Promise((resolve, reject) => {
|
|
123
124
|
copyStream.on("finish", resolve);
|
|
@@ -152,7 +153,7 @@ export const recoverBatchError = async (values, callback) => {
|
|
|
152
153
|
return { status: "success" };
|
|
153
154
|
}
|
|
154
155
|
};
|
|
155
|
-
export const createIndexingCache = ({ common, schemaBuild: { schema }, crashRecoveryCheckpoint, eventCount, }) => {
|
|
156
|
+
export const createIndexingCache = ({ common, schemaBuild: { schema }, crashRecoveryCheckpoint, eventCount, chainId, }) => {
|
|
156
157
|
let event;
|
|
157
158
|
let qb = undefined;
|
|
158
159
|
const cache = new Map();
|
|
@@ -160,7 +161,6 @@ export const createIndexingCache = ({ common, schemaBuild: { schema }, crashReco
|
|
|
160
161
|
const updateBuffer = new Map();
|
|
161
162
|
/** Profiling data about access patterns for each event. */
|
|
162
163
|
const profile = new Map();
|
|
163
|
-
let isFlushRetry = false;
|
|
164
164
|
const tables = Object.values(schema).filter(isTable);
|
|
165
165
|
const primaryKeyCache = getPrimaryKeyCache(tables);
|
|
166
166
|
for (const table of tables) {
|
|
@@ -185,22 +185,24 @@ export const createIndexingCache = ({ common, schemaBuild: { schema }, crashReco
|
|
|
185
185
|
updateBuffer.get(table).has(ck));
|
|
186
186
|
},
|
|
187
187
|
async get({ table, key }) {
|
|
188
|
-
if (event &&
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
if (event &&
|
|
189
|
+
eventCount[event.eventCallback.name] % SAMPLING_RATE === 1) {
|
|
190
|
+
if (profile.has(event.eventCallback.name) === false) {
|
|
191
|
+
profile.set(event.eventCallback.name, new Map());
|
|
191
192
|
for (const table of tables) {
|
|
192
|
-
profile.get(event.name).set(table, new Map());
|
|
193
|
+
profile.get(event.eventCallback.name).set(table, new Map());
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
|
-
const pattern = recordProfilePattern(event, table, key, Array.from(profile.get(event.name).get(table).values()).map(({ pattern }) => pattern), primaryKeyCache);
|
|
196
|
+
const pattern = recordProfilePattern(event, table, key, Array.from(profile.get(event.eventCallback.name).get(table).values()).map(({ pattern }) => pattern), primaryKeyCache);
|
|
196
197
|
if (pattern) {
|
|
197
198
|
const key = getProfilePatternKey(pattern);
|
|
198
|
-
if (profile.get(event.name).get(table).has(key)) {
|
|
199
|
-
profile.get(event.name).get(table).get(key)
|
|
199
|
+
if (profile.get(event.eventCallback.name).get(table).has(key)) {
|
|
200
|
+
profile.get(event.eventCallback.name).get(table).get(key)
|
|
201
|
+
.count++;
|
|
200
202
|
}
|
|
201
203
|
else {
|
|
202
204
|
profile
|
|
203
|
-
.get(event.name)
|
|
205
|
+
.get(event.eventCallback.name)
|
|
204
206
|
.get(table)
|
|
205
207
|
.set(key, { pattern, count: 1 });
|
|
206
208
|
}
|
|
@@ -284,67 +286,167 @@ export const createIndexingCache = ({ common, schemaBuild: { schema }, crashReco
|
|
|
284
286
|
logger: common.logger.child({ action: "flush_database_rows" }),
|
|
285
287
|
};
|
|
286
288
|
const flushEndClock = startClock();
|
|
287
|
-
const copy = getCopyHelper(qb);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
const copy = getCopyHelper(qb, chainId);
|
|
290
|
+
const flushTable = async (table) => {
|
|
291
|
+
const shouldRecordBytes = cache.get(table).isCacheComplete;
|
|
292
|
+
if (tableNames !== undefined &&
|
|
293
|
+
tableNames.has(getTableName(table)) === false) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const tableCache = cache.get(table);
|
|
297
|
+
const target = chainId === undefined
|
|
298
|
+
? getTableName(table)
|
|
299
|
+
: getPartitionName(table, chainId);
|
|
300
|
+
const insertValues = Array.from(insertBuffer.get(table).values());
|
|
301
|
+
const updateValues = Array.from(updateBuffer.get(table).values());
|
|
302
|
+
if (insertValues.length > 0) {
|
|
303
|
+
const endClock = startClock();
|
|
304
|
+
if (insertValues.length > LOW_BATCH_THRESHOLD) {
|
|
305
|
+
const text = getCopyText(table, insertValues.map(({ row }) => row));
|
|
306
|
+
await new Promise(setImmediate);
|
|
307
|
+
await copy(table, text);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
await qb.wrap((db) => db.insert(table).values(insertValues.map(({ row }) => row)), context);
|
|
311
|
+
}
|
|
312
|
+
common.metrics.ponder_indexing_cache_query_duration.observe({
|
|
313
|
+
table: getTableName(table),
|
|
314
|
+
method: "flush",
|
|
315
|
+
}, endClock());
|
|
316
|
+
let bytes = 0;
|
|
317
|
+
for (const [key, entry] of insertBuffer.get(table)) {
|
|
318
|
+
if (shouldRecordBytes && tableCache.cache.has(key) === false) {
|
|
319
|
+
bytes += getBytes(entry.row) + getBytes(key);
|
|
320
|
+
}
|
|
321
|
+
tableCache.cache.set(key, entry.row);
|
|
322
|
+
}
|
|
323
|
+
tableCache.bytes += bytes;
|
|
324
|
+
insertBuffer.get(table).clear();
|
|
325
|
+
await new Promise(setImmediate);
|
|
326
|
+
}
|
|
327
|
+
if (updateValues.length > 0) {
|
|
328
|
+
const primaryKeys = getPrimaryKeyColumns(table);
|
|
329
|
+
const endClock = startClock();
|
|
330
|
+
if (updateValues.length > LOW_BATCH_THRESHOLD) {
|
|
331
|
+
// Steps for flushing "update" entries:
|
|
332
|
+
// 1. Create temp table
|
|
333
|
+
// 2. Copy into temp table
|
|
334
|
+
// 3. Update target table with data from temp
|
|
335
|
+
const createTempTableQuery = `
|
|
336
|
+
CREATE TEMP TABLE IF NOT EXISTS "${target}"
|
|
337
|
+
AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${target}"
|
|
338
|
+
WITH NO DATA;`;
|
|
339
|
+
const updateQuery = `
|
|
340
|
+
UPDATE "${getTableConfig(table).schema ?? "public"}"."${target}" as target
|
|
341
|
+
SET ${Object.values(getTableColumns(table))
|
|
342
|
+
.map((column) => `"${getColumnCasing(column, "snake_case")}" = source."${getColumnCasing(column, "snake_case")}"`)
|
|
343
|
+
.join(",\n")}
|
|
344
|
+
FROM "${target}" source
|
|
345
|
+
WHERE ${primaryKeys
|
|
346
|
+
.map(({ sql }) => `target."${sql}" = source."${sql}"`)
|
|
347
|
+
.join(" AND ")};`;
|
|
348
|
+
await qb.wrap((db) => db.execute(createTempTableQuery), context);
|
|
349
|
+
const text = getCopyText(table, updateValues.map(({ row }) => row));
|
|
350
|
+
await new Promise(setImmediate);
|
|
351
|
+
await copy(table, text, false);
|
|
352
|
+
await qb.wrap((db) => db.execute(updateQuery), context);
|
|
353
|
+
await qb.wrap((db) => db.execute(`TRUNCATE TABLE "${target}"`), context);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
await qb.wrap((db) => db
|
|
357
|
+
.insert(table)
|
|
358
|
+
.values(updateValues.map(({ row }) => row))
|
|
359
|
+
.onConflictDoUpdate({
|
|
360
|
+
// @ts-ignore
|
|
361
|
+
target: primaryKeys.map(({ js }) => table[js]),
|
|
362
|
+
set: Object.fromEntries(Object.entries(getTableColumns(table)).map(([columnName, column]) => [
|
|
363
|
+
columnName,
|
|
364
|
+
sql.raw(`excluded."${getColumnCasing(column, "snake_case")}"`),
|
|
365
|
+
])),
|
|
366
|
+
}), context);
|
|
367
|
+
}
|
|
368
|
+
common.metrics.ponder_indexing_cache_query_duration.observe({
|
|
369
|
+
table: getTableName(table),
|
|
370
|
+
method: "flush",
|
|
371
|
+
}, endClock());
|
|
372
|
+
let bytes = 0;
|
|
373
|
+
for (const [key, entry] of updateBuffer.get(table)) {
|
|
374
|
+
if (shouldRecordBytes && tableCache.cache.has(key) === false) {
|
|
375
|
+
bytes += getBytes(entry.row) + getBytes(key);
|
|
376
|
+
}
|
|
377
|
+
tableCache.cache.set(key, entry.row);
|
|
378
|
+
}
|
|
379
|
+
tableCache.bytes += bytes;
|
|
380
|
+
updateBuffer.get(table).clear();
|
|
381
|
+
await new Promise(setImmediate);
|
|
382
|
+
}
|
|
383
|
+
if (insertValues.length > 0 || updateValues.length > 0) {
|
|
384
|
+
common.logger.debug({
|
|
385
|
+
msg: "Wrote database rows",
|
|
386
|
+
table: target,
|
|
387
|
+
row_count: insertValues.length + updateValues.length,
|
|
388
|
+
duration: flushEndClock(),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
try {
|
|
393
|
+
if (qb.$dialect === "postgres") {
|
|
394
|
+
await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
|
|
395
|
+
await promiseAllSettledWithThrow(Array.from(cache.keys()).map(flushTable));
|
|
396
|
+
await qb.wrap((db) => db.execute("RELEASE flush"), context);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// Note: pglite must run sequentially
|
|
400
|
+
for (const table of cache.keys()) {
|
|
401
|
+
await flushTable(table);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (_error) {
|
|
406
|
+
let error = _error;
|
|
407
|
+
if (error instanceof ShutdownError || qb.$dialect === "pglite") {
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
// Note `isFlushRetry` is true when the previous flush failed. When `isFlushRetry` is false, this
|
|
411
|
+
// function takes an optimized fast path, with support for small batch sizes. PGlite always takes
|
|
412
|
+
// the fast path because it doesn't support delayed insert errors.
|
|
413
|
+
await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
|
|
292
414
|
for (const table of cache.keys()) {
|
|
293
|
-
const shouldRecordBytes = cache.get(table).isCacheComplete;
|
|
294
415
|
if (tableNames !== undefined &&
|
|
295
416
|
tableNames.has(getTableName(table)) === false) {
|
|
296
417
|
continue;
|
|
297
418
|
}
|
|
298
|
-
const
|
|
419
|
+
const target = chainId === undefined
|
|
420
|
+
? getTableName(table)
|
|
421
|
+
: getPartitionName(table, chainId);
|
|
299
422
|
const insertValues = Array.from(insertBuffer.get(table).values());
|
|
300
423
|
const updateValues = Array.from(updateBuffer.get(table).values());
|
|
301
424
|
if (insertValues.length > 0) {
|
|
302
425
|
const endClock = startClock();
|
|
303
426
|
await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
427
|
+
const result = await recoverBatchError(insertValues, async (values) => {
|
|
428
|
+
await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
|
|
429
|
+
const text = getCopyText(table, values.map(({ row }) => row));
|
|
307
430
|
await copy(table, text);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
event: result.value.metadata.event.name,
|
|
326
|
-
type: "insert",
|
|
327
|
-
table: getTableName(table),
|
|
328
|
-
row_count: insertValues.length,
|
|
329
|
-
duration: endClock(),
|
|
330
|
-
error,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
common.logger.warn({
|
|
335
|
-
msg: "Failed to write cached database rows",
|
|
336
|
-
type: "insert",
|
|
337
|
-
table: getTableName(table),
|
|
338
|
-
row_count: insertValues.length,
|
|
339
|
-
duration: endClock(),
|
|
340
|
-
error,
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
// @ts-ignore remove meta from error
|
|
344
|
-
error.meta = undefined;
|
|
431
|
+
await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
|
|
432
|
+
});
|
|
433
|
+
if (result.status === "error") {
|
|
434
|
+
error = new DelayedInsertError(result.error.message);
|
|
435
|
+
error.stack = undefined;
|
|
436
|
+
addErrorMeta(error, `db.insert arguments:\n${prettyPrint(result.value.row)}`);
|
|
437
|
+
if (result.value.metadata.event) {
|
|
438
|
+
addErrorMeta(error, toErrorMeta(result.value.metadata.event));
|
|
439
|
+
common.logger.warn({
|
|
440
|
+
msg: "Failed to write cached database rows",
|
|
441
|
+
event: result.value.metadata.event.eventCallback.name,
|
|
442
|
+
type: "insert",
|
|
443
|
+
table: getTableName(table),
|
|
444
|
+
row_count: insertValues.length,
|
|
445
|
+
duration: endClock(),
|
|
446
|
+
error,
|
|
447
|
+
});
|
|
345
448
|
}
|
|
346
449
|
else {
|
|
347
|
-
error.stack = undefined;
|
|
348
450
|
common.logger.warn({
|
|
349
451
|
msg: "Failed to write cached database rows",
|
|
350
452
|
type: "insert",
|
|
@@ -356,89 +458,44 @@ export const createIndexingCache = ({ common, schemaBuild: { schema }, crashReco
|
|
|
356
458
|
}
|
|
357
459
|
throw error;
|
|
358
460
|
}
|
|
359
|
-
common.metrics.ponder_indexing_cache_query_duration.observe({
|
|
360
|
-
table: getTableName(table),
|
|
361
|
-
method: "flush",
|
|
362
|
-
}, endClock());
|
|
363
|
-
let bytes = 0;
|
|
364
|
-
for (const [key, entry] of insertBuffer.get(table)) {
|
|
365
|
-
if (shouldRecordBytes && tableCache.cache.has(key) === false) {
|
|
366
|
-
bytes += getBytes(entry.row) + getBytes(key);
|
|
367
|
-
}
|
|
368
|
-
tableCache.cache.set(key, entry.row);
|
|
369
|
-
}
|
|
370
|
-
tableCache.bytes += bytes;
|
|
371
|
-
insertBuffer.get(table).clear();
|
|
372
|
-
await new Promise(setImmediate);
|
|
373
461
|
}
|
|
374
462
|
if (updateValues.length > 0) {
|
|
375
463
|
// Steps for flushing "update" entries:
|
|
376
464
|
// 1. Create temp table
|
|
377
465
|
// 2. Copy into temp table
|
|
378
466
|
// 3. Update target table with data from temp
|
|
379
|
-
const primaryKeys = getPrimaryKeyColumns(table);
|
|
380
467
|
const createTempTableQuery = `
|
|
381
|
-
CREATE TEMP TABLE IF NOT EXISTS "${
|
|
382
|
-
AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${
|
|
468
|
+
CREATE TEMP TABLE IF NOT EXISTS "${target}"
|
|
469
|
+
AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${target}"
|
|
383
470
|
WITH NO DATA;
|
|
384
|
-
`;
|
|
385
|
-
const updateQuery = `
|
|
386
|
-
UPDATE "${getTableConfig(table).schema ?? "public"}"."${getTableName(table)}" as target
|
|
387
|
-
SET ${Object.values(getTableColumns(table))
|
|
388
|
-
.map((column) => `"${getColumnCasing(column, "snake_case")}" = source."${getColumnCasing(column, "snake_case")}"`)
|
|
389
|
-
.join(",\n")}
|
|
390
|
-
FROM "${getTableName(table)}" source
|
|
391
|
-
WHERE ${primaryKeys
|
|
392
|
-
.map(({ sql }) => `target."${sql}" = source."${sql}"`)
|
|
393
|
-
.join(" AND ")};
|
|
394
471
|
`;
|
|
395
472
|
const endClock = startClock();
|
|
396
473
|
await qb.wrap((db) => db.execute(createTempTableQuery), context);
|
|
397
474
|
await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
475
|
+
const result = await recoverBatchError(updateValues, async (values) => {
|
|
476
|
+
await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
|
|
477
|
+
const text = getCopyText(table, values.map(({ row }) => row));
|
|
401
478
|
await copy(table, text, false);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
type: "update",
|
|
421
|
-
table: getTableName(table),
|
|
422
|
-
row_count: updateValues.length,
|
|
423
|
-
duration: endClock(),
|
|
424
|
-
error,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
common.logger.warn({
|
|
429
|
-
msg: "Failed to write cached database rows",
|
|
430
|
-
type: "update",
|
|
431
|
-
table: getTableName(table),
|
|
432
|
-
row_count: updateValues.length,
|
|
433
|
-
duration: endClock(),
|
|
434
|
-
error,
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
// @ts-ignore remove meta from error
|
|
438
|
-
error.meta = undefined;
|
|
479
|
+
await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
|
|
480
|
+
});
|
|
481
|
+
await qb.wrap((db) => db.execute(`TRUNCATE TABLE "${target}"`), context);
|
|
482
|
+
if (result.status === "error") {
|
|
483
|
+
error = new DelayedInsertError(result.error.message);
|
|
484
|
+
error.stack = undefined;
|
|
485
|
+
addErrorMeta(error, `db.update arguments:\n${prettyPrint(result.value.row)}`);
|
|
486
|
+
if (result.value.metadata.event) {
|
|
487
|
+
addErrorMeta(error, toErrorMeta(result.value.metadata.event));
|
|
488
|
+
common.logger.warn({
|
|
489
|
+
msg: "Failed to write cached database rows",
|
|
490
|
+
event: result.value.metadata.event.eventCallback.name,
|
|
491
|
+
type: "update",
|
|
492
|
+
table: getTableName(table),
|
|
493
|
+
row_count: updateValues.length,
|
|
494
|
+
duration: endClock(),
|
|
495
|
+
error,
|
|
496
|
+
});
|
|
439
497
|
}
|
|
440
498
|
else {
|
|
441
|
-
error.stack = undefined;
|
|
442
499
|
common.logger.warn({
|
|
443
500
|
msg: "Failed to write cached database rows",
|
|
444
501
|
type: "update",
|
|
@@ -450,147 +507,12 @@ export const createIndexingCache = ({ common, schemaBuild: { schema }, crashReco
|
|
|
450
507
|
}
|
|
451
508
|
throw error;
|
|
452
509
|
}
|
|
453
|
-
await qb.wrap((db) => db.execute(updateQuery), context);
|
|
454
|
-
await qb.wrap((db) => db.execute(`TRUNCATE TABLE "${getTableName(table)}"`), context);
|
|
455
|
-
common.metrics.ponder_indexing_cache_query_duration.observe({
|
|
456
|
-
table: getTableName(table),
|
|
457
|
-
method: "flush",
|
|
458
|
-
}, endClock());
|
|
459
|
-
let bytes = 0;
|
|
460
|
-
for (const [key, entry] of updateBuffer.get(table)) {
|
|
461
|
-
if (shouldRecordBytes && tableCache.cache.has(key) === false) {
|
|
462
|
-
bytes += getBytes(entry.row) + getBytes(key);
|
|
463
|
-
}
|
|
464
|
-
tableCache.cache.set(key, entry.row);
|
|
465
|
-
}
|
|
466
|
-
tableCache.bytes += bytes;
|
|
467
|
-
updateBuffer.get(table).clear();
|
|
468
|
-
await new Promise(setImmediate);
|
|
469
|
-
}
|
|
470
|
-
if (insertValues.length > 0 || updateValues.length > 0) {
|
|
471
|
-
common.logger.debug({
|
|
472
|
-
msg: "Wrote database rows",
|
|
473
|
-
table: getTableName(table),
|
|
474
|
-
row_count: insertValues.length + updateValues.length,
|
|
475
|
-
duration: flushEndClock(),
|
|
476
|
-
});
|
|
477
|
-
await qb.wrap((db) => db.execute("RELEASE flush"), context);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
isFlushRetry = true;
|
|
483
|
-
// Note: Must use `Promise.allSettled` to avoid short-circuiting while queries are running.
|
|
484
|
-
const results = await Promise.allSettled(Array.from(cache.keys()).map(async (table) => {
|
|
485
|
-
const shouldRecordBytes = cache.get(table).isCacheComplete;
|
|
486
|
-
if (tableNames !== undefined &&
|
|
487
|
-
tableNames.has(getTableName(table)) === false) {
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
const tableCache = cache.get(table);
|
|
491
|
-
const insertValues = Array.from(insertBuffer.get(table).values());
|
|
492
|
-
const updateValues = Array.from(updateBuffer.get(table).values());
|
|
493
|
-
if (insertValues.length > 0) {
|
|
494
|
-
const endClock = startClock();
|
|
495
|
-
if (insertValues.length > LOW_BATCH_THRESHOLD) {
|
|
496
|
-
const text = getCopyText(table, insertValues.map(({ row }) => row));
|
|
497
|
-
await new Promise(setImmediate);
|
|
498
|
-
await copy(table, text);
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
await qb.wrap((db) => db.insert(table).values(insertValues.map(({ row }) => row)), context);
|
|
502
|
-
}
|
|
503
|
-
common.metrics.ponder_indexing_cache_query_duration.observe({
|
|
504
|
-
table: getTableName(table),
|
|
505
|
-
method: "flush",
|
|
506
|
-
}, endClock());
|
|
507
|
-
let bytes = 0;
|
|
508
|
-
for (const [key, entry] of insertBuffer.get(table)) {
|
|
509
|
-
if (shouldRecordBytes && tableCache.cache.has(key) === false) {
|
|
510
|
-
bytes += getBytes(entry.row) + getBytes(key);
|
|
511
|
-
}
|
|
512
|
-
tableCache.cache.set(key, entry.row);
|
|
513
|
-
}
|
|
514
|
-
tableCache.bytes += bytes;
|
|
515
|
-
insertBuffer.get(table).clear();
|
|
516
|
-
await new Promise(setImmediate);
|
|
517
|
-
}
|
|
518
|
-
if (updateValues.length > 0) {
|
|
519
|
-
const primaryKeys = getPrimaryKeyColumns(table);
|
|
520
|
-
const endClock = startClock();
|
|
521
|
-
if (updateValues.length > LOW_BATCH_THRESHOLD) {
|
|
522
|
-
// Steps for flushing "update" entries:
|
|
523
|
-
// 1. Create temp table
|
|
524
|
-
// 2. Copy into temp table
|
|
525
|
-
// 3. Update target table with data from temp
|
|
526
|
-
const createTempTableQuery = `
|
|
527
|
-
CREATE TEMP TABLE IF NOT EXISTS "${getTableName(table)}"
|
|
528
|
-
AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${getTableName(table)}"
|
|
529
|
-
WITH NO DATA;
|
|
530
|
-
`;
|
|
531
|
-
const updateQuery = `
|
|
532
|
-
UPDATE "${getTableConfig(table).schema ?? "public"}"."${getTableName(table)}" as target
|
|
533
|
-
SET ${Object.values(getTableColumns(table))
|
|
534
|
-
.map((column) => `"${getColumnCasing(column, "snake_case")}" = source."${getColumnCasing(column, "snake_case")}"`)
|
|
535
|
-
.join(",\n")}
|
|
536
|
-
FROM "${getTableName(table)}" source
|
|
537
|
-
WHERE ${primaryKeys
|
|
538
|
-
.map(({ sql }) => `target."${sql}" = source."${sql}"`)
|
|
539
|
-
.join(" AND ")};
|
|
540
|
-
`;
|
|
541
|
-
await qb.wrap((db) => db.execute(createTempTableQuery), context);
|
|
542
|
-
const text = getCopyText(table, updateValues.map(({ row }) => row));
|
|
543
|
-
await new Promise(setImmediate);
|
|
544
|
-
await copy(table, text, false);
|
|
545
|
-
await qb.wrap((db) => db.execute(updateQuery), context);
|
|
546
|
-
await qb.wrap((db) => db.execute(`TRUNCATE TABLE "${getTableName(table)}"`), context);
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
await qb.wrap((db) => db
|
|
550
|
-
.insert(table)
|
|
551
|
-
.values(updateValues.map(({ row }) => row))
|
|
552
|
-
.onConflictDoUpdate({
|
|
553
|
-
// @ts-ignore
|
|
554
|
-
target: primaryKeys.map(({ js }) => table[js]),
|
|
555
|
-
set: Object.fromEntries(Object.entries(getTableColumns(table)).map(([columnName, column]) => [
|
|
556
|
-
columnName,
|
|
557
|
-
sql.raw(`excluded."${getColumnCasing(column, "snake_case")}"`),
|
|
558
|
-
])),
|
|
559
|
-
}), context);
|
|
560
|
-
}
|
|
561
|
-
common.metrics.ponder_indexing_cache_query_duration.observe({
|
|
562
|
-
table: getTableName(table),
|
|
563
|
-
method: "flush",
|
|
564
|
-
}, endClock());
|
|
565
|
-
let bytes = 0;
|
|
566
|
-
for (const [key, entry] of updateBuffer.get(table)) {
|
|
567
|
-
if (shouldRecordBytes && tableCache.cache.has(key) === false) {
|
|
568
|
-
bytes += getBytes(entry.row) + getBytes(key);
|
|
569
|
-
}
|
|
570
|
-
tableCache.cache.set(key, entry.row);
|
|
571
|
-
}
|
|
572
|
-
tableCache.bytes += bytes;
|
|
573
|
-
updateBuffer.get(table).clear();
|
|
574
|
-
await new Promise(setImmediate);
|
|
575
|
-
}
|
|
576
|
-
if (insertValues.length > 0 || updateValues.length > 0) {
|
|
577
|
-
common.logger.debug({
|
|
578
|
-
msg: "Wrote database rows",
|
|
579
|
-
table: getTableName(table),
|
|
580
|
-
row_count: insertValues.length + updateValues.length,
|
|
581
|
-
duration: flushEndClock(),
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
}));
|
|
585
|
-
if (results.some((result) => result.status === "rejected")) {
|
|
586
|
-
const rejected = results.find((result) => result.status === "rejected");
|
|
587
|
-
if (rejected.reason instanceof ShutdownError) {
|
|
588
|
-
throw rejected.reason;
|
|
589
510
|
}
|
|
590
|
-
throw new RetryableError(rejected.reason.message);
|
|
591
511
|
}
|
|
512
|
+
// Note: if we weren't able to find the exact row that caused the error,
|
|
513
|
+
// throw the original error.
|
|
514
|
+
throw error;
|
|
592
515
|
}
|
|
593
|
-
isFlushRetry = false;
|
|
594
516
|
},
|
|
595
517
|
async prefetch({ events }) {
|
|
596
518
|
const context = {
|
|
@@ -631,15 +553,15 @@ export const createIndexingCache = ({ common, schemaBuild: { schema }, crashReco
|
|
|
631
553
|
prediction.set(table, new Map());
|
|
632
554
|
}
|
|
633
555
|
for (const event of events) {
|
|
634
|
-
if (profile.has(event.name)) {
|
|
556
|
+
if (profile.has(event.eventCallback.name)) {
|
|
635
557
|
for (const table of tables) {
|
|
636
558
|
if (cache.get(table).isCacheComplete)
|
|
637
559
|
continue;
|
|
638
560
|
for (const [, { count, pattern }] of profile
|
|
639
|
-
.get(event.name)
|
|
561
|
+
.get(event.eventCallback.name)
|
|
640
562
|
.get(table)) {
|
|
641
563
|
// Expected value of times the prediction will be used.
|
|
642
|
-
const ev = (count * SAMPLING_RATE) / eventCount[event.name];
|
|
564
|
+
const ev = (count * SAMPLING_RATE) / eventCount[event.eventCallback.name];
|
|
643
565
|
if (ev > PREDICTION_THRESHOLD) {
|
|
644
566
|
const row = recoverProfilePattern(pattern, event);
|
|
645
567
|
const key = getCacheKey(table, row, primaryKeyCache);
|