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.
Files changed (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/esm/bin/commands/createViews.js +28 -11
  3. package/dist/esm/bin/commands/createViews.js.map +1 -1
  4. package/dist/esm/bin/commands/dev.js +42 -22
  5. package/dist/esm/bin/commands/dev.js.map +1 -1
  6. package/dist/esm/bin/commands/prune.js +3 -0
  7. package/dist/esm/bin/commands/prune.js.map +1 -1
  8. package/dist/esm/bin/commands/serve.js +4 -1
  9. package/dist/esm/bin/commands/serve.js.map +1 -1
  10. package/dist/esm/bin/commands/start.js +18 -6
  11. package/dist/esm/bin/commands/start.js.map +1 -1
  12. package/dist/esm/bin/isolatedController.js +200 -0
  13. package/dist/esm/bin/isolatedController.js.map +1 -0
  14. package/dist/esm/bin/isolatedWorker.js +146 -0
  15. package/dist/esm/bin/isolatedWorker.js.map +1 -0
  16. package/dist/esm/build/config.js +322 -402
  17. package/dist/esm/build/config.js.map +1 -1
  18. package/dist/esm/build/index.js +8 -11
  19. package/dist/esm/build/index.js.map +1 -1
  20. package/dist/esm/build/pre.js +1 -4
  21. package/dist/esm/build/pre.js.map +1 -1
  22. package/dist/esm/build/schema.js +25 -3
  23. package/dist/esm/build/schema.js.map +1 -1
  24. package/dist/esm/client/index.js +306 -42
  25. package/dist/esm/client/index.js.map +1 -1
  26. package/dist/esm/database/actions.js +264 -104
  27. package/dist/esm/database/actions.js.map +1 -1
  28. package/dist/esm/database/index.js +39 -33
  29. package/dist/esm/database/index.js.map +1 -1
  30. package/dist/esm/database/queryBuilder.js +1 -0
  31. package/dist/esm/database/queryBuilder.js.map +1 -1
  32. package/dist/esm/drizzle/index.js +11 -7
  33. package/dist/esm/drizzle/index.js.map +1 -1
  34. package/dist/esm/drizzle/onchain.js +18 -0
  35. package/dist/esm/drizzle/onchain.js.map +1 -1
  36. package/dist/esm/indexing/client.js +32 -25
  37. package/dist/esm/indexing/client.js.map +1 -1
  38. package/dist/esm/indexing/index.js +110 -178
  39. package/dist/esm/indexing/index.js.map +1 -1
  40. package/dist/esm/indexing/profile.js +1 -1
  41. package/dist/esm/indexing/profile.js.map +1 -1
  42. package/dist/esm/indexing-store/cache.js +196 -274
  43. package/dist/esm/indexing-store/cache.js.map +1 -1
  44. package/dist/esm/indexing-store/historical.js +17 -13
  45. package/dist/esm/indexing-store/historical.js.map +1 -1
  46. package/dist/esm/indexing-store/index.js +10 -1
  47. package/dist/esm/indexing-store/index.js.map +1 -1
  48. package/dist/esm/indexing-store/profile.js +3 -3
  49. package/dist/esm/indexing-store/profile.js.map +1 -1
  50. package/dist/esm/indexing-store/realtime.js +27 -2
  51. package/dist/esm/indexing-store/realtime.js.map +1 -1
  52. package/dist/esm/internal/errors.js +28 -0
  53. package/dist/esm/internal/errors.js.map +1 -1
  54. package/dist/esm/internal/metrics.js +279 -82
  55. package/dist/esm/internal/metrics.js.map +1 -1
  56. package/dist/esm/internal/options.js +1 -0
  57. package/dist/esm/internal/options.js.map +1 -1
  58. package/dist/esm/internal/telemetry.js +1 -1
  59. package/dist/esm/internal/telemetry.js.map +1 -1
  60. package/dist/esm/rpc/http.js +130 -0
  61. package/dist/esm/rpc/http.js.map +1 -0
  62. package/dist/esm/rpc/index.js +38 -7
  63. package/dist/esm/rpc/index.js.map +1 -1
  64. package/dist/esm/runtime/events.js +179 -212
  65. package/dist/esm/runtime/events.js.map +1 -1
  66. package/dist/esm/runtime/filter.js +71 -0
  67. package/dist/esm/runtime/filter.js.map +1 -1
  68. package/dist/esm/runtime/fragments.js +78 -73
  69. package/dist/esm/runtime/fragments.js.map +1 -1
  70. package/dist/esm/runtime/historical.js +306 -130
  71. package/dist/esm/runtime/historical.js.map +1 -1
  72. package/dist/esm/runtime/index.js +183 -58
  73. package/dist/esm/runtime/index.js.map +1 -1
  74. package/dist/esm/runtime/isolated.js +462 -0
  75. package/dist/esm/runtime/isolated.js.map +1 -0
  76. package/dist/esm/runtime/multichain.js +80 -73
  77. package/dist/esm/runtime/multichain.js.map +1 -1
  78. package/dist/esm/runtime/omnichain.js +82 -75
  79. package/dist/esm/runtime/omnichain.js.map +1 -1
  80. package/dist/esm/runtime/realtime.js +198 -66
  81. package/dist/esm/runtime/realtime.js.map +1 -1
  82. package/dist/esm/sync-historical/index.js +416 -457
  83. package/dist/esm/sync-historical/index.js.map +1 -1
  84. package/dist/esm/sync-realtime/bloom.js +3 -3
  85. package/dist/esm/sync-realtime/bloom.js.map +1 -1
  86. package/dist/esm/sync-realtime/index.js +27 -46
  87. package/dist/esm/sync-realtime/index.js.map +1 -1
  88. package/dist/esm/sync-store/index.js +112 -63
  89. package/dist/esm/sync-store/index.js.map +1 -1
  90. package/dist/esm/utils/abi.js +20 -32
  91. package/dist/esm/utils/abi.js.map +1 -1
  92. package/dist/esm/utils/chunk.js +8 -0
  93. package/dist/esm/utils/chunk.js.map +1 -0
  94. package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
  95. package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
  96. package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
  97. package/dist/esm/utils/sql-parse.js.map +1 -0
  98. package/dist/types/bin/commands/createViews.d.ts.map +1 -1
  99. package/dist/types/bin/commands/dev.d.ts.map +1 -1
  100. package/dist/types/bin/commands/prune.d.ts.map +1 -1
  101. package/dist/types/bin/commands/serve.d.ts.map +1 -1
  102. package/dist/types/bin/commands/start.d.ts.map +1 -1
  103. package/dist/types/bin/isolatedController.d.ts +13 -0
  104. package/dist/types/bin/isolatedController.d.ts.map +1 -0
  105. package/dist/types/bin/isolatedWorker.d.ts +9 -0
  106. package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
  107. package/dist/types/build/config.d.ts +29 -11
  108. package/dist/types/build/config.d.ts.map +1 -1
  109. package/dist/types/build/index.d.ts +3 -2
  110. package/dist/types/build/index.d.ts.map +1 -1
  111. package/dist/types/build/pre.d.ts +1 -1
  112. package/dist/types/build/pre.d.ts.map +1 -1
  113. package/dist/types/build/schema.d.ts +5 -3
  114. package/dist/types/build/schema.d.ts.map +1 -1
  115. package/dist/types/client/index.d.ts +1 -1
  116. package/dist/types/client/index.d.ts.map +1 -1
  117. package/dist/types/config/index.d.ts +3 -3
  118. package/dist/types/config/index.d.ts.map +1 -1
  119. package/dist/types/database/actions.d.ts +53 -7
  120. package/dist/types/database/actions.d.ts.map +1 -1
  121. package/dist/types/database/index.d.ts +21 -21
  122. package/dist/types/database/index.d.ts.map +1 -1
  123. package/dist/types/database/queryBuilder.d.ts.map +1 -1
  124. package/dist/types/drizzle/index.d.ts +4 -5
  125. package/dist/types/drizzle/index.d.ts.map +1 -1
  126. package/dist/types/drizzle/onchain.d.ts +6 -0
  127. package/dist/types/drizzle/onchain.d.ts.map +1 -1
  128. package/dist/types/indexing/client.d.ts.map +1 -1
  129. package/dist/types/indexing/index.d.ts +2 -5
  130. package/dist/types/indexing/index.d.ts.map +1 -1
  131. package/dist/types/indexing-store/cache.d.ts +3 -2
  132. package/dist/types/indexing-store/cache.d.ts.map +1 -1
  133. package/dist/types/indexing-store/historical.d.ts +2 -1
  134. package/dist/types/indexing-store/historical.d.ts.map +1 -1
  135. package/dist/types/indexing-store/index.d.ts +1 -0
  136. package/dist/types/indexing-store/index.d.ts.map +1 -1
  137. package/dist/types/indexing-store/realtime.d.ts +2 -1
  138. package/dist/types/indexing-store/realtime.d.ts.map +1 -1
  139. package/dist/types/internal/errors.d.ts +5 -0
  140. package/dist/types/internal/errors.d.ts.map +1 -1
  141. package/dist/types/internal/metrics.d.ts +21 -0
  142. package/dist/types/internal/metrics.d.ts.map +1 -1
  143. package/dist/types/internal/options.d.ts +2 -0
  144. package/dist/types/internal/options.d.ts.map +1 -1
  145. package/dist/types/internal/types.d.ts +66 -58
  146. package/dist/types/internal/types.d.ts.map +1 -1
  147. package/dist/types/rpc/http.d.ts +17 -0
  148. package/dist/types/rpc/http.d.ts.map +1 -0
  149. package/dist/types/rpc/index.d.ts.map +1 -1
  150. package/dist/types/runtime/events.d.ts +4 -4
  151. package/dist/types/runtime/events.d.ts.map +1 -1
  152. package/dist/types/runtime/filter.d.ts +5 -1
  153. package/dist/types/runtime/filter.d.ts.map +1 -1
  154. package/dist/types/runtime/fragments.d.ts +4 -3
  155. package/dist/types/runtime/fragments.d.ts.map +1 -1
  156. package/dist/types/runtime/historical.d.ts +29 -13
  157. package/dist/types/runtime/historical.d.ts.map +1 -1
  158. package/dist/types/runtime/index.d.ts +49 -6
  159. package/dist/types/runtime/index.d.ts.map +1 -1
  160. package/dist/types/runtime/init.d.ts +5 -5
  161. package/dist/types/runtime/init.d.ts.map +1 -1
  162. package/dist/types/runtime/isolated.d.ts +14 -0
  163. package/dist/types/runtime/isolated.d.ts.map +1 -0
  164. package/dist/types/runtime/multichain.d.ts.map +1 -1
  165. package/dist/types/runtime/omnichain.d.ts.map +1 -1
  166. package/dist/types/runtime/realtime.d.ts +21 -10
  167. package/dist/types/runtime/realtime.d.ts.map +1 -1
  168. package/dist/types/sync-historical/index.d.ts +18 -8
  169. package/dist/types/sync-historical/index.d.ts.map +1 -1
  170. package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
  171. package/dist/types/sync-realtime/index.d.ts +2 -2
  172. package/dist/types/sync-realtime/index.d.ts.map +1 -1
  173. package/dist/types/sync-store/index.d.ts +9 -9
  174. package/dist/types/sync-store/index.d.ts.map +1 -1
  175. package/dist/types/utils/abi.d.ts +3 -34
  176. package/dist/types/utils/abi.d.ts.map +1 -1
  177. package/dist/types/utils/chunk.d.ts +2 -0
  178. package/dist/types/utils/chunk.d.ts.map +1 -0
  179. package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
  180. package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
  181. package/dist/types/utils/sql-parse.d.ts +21 -0
  182. package/dist/types/utils/sql-parse.d.ts.map +1 -0
  183. package/package.json +2 -2
  184. package/src/bin/commands/createViews.ts +35 -15
  185. package/src/bin/commands/dev.ts +43 -21
  186. package/src/bin/commands/prune.ts +6 -0
  187. package/src/bin/commands/serve.ts +4 -1
  188. package/src/bin/commands/start.ts +20 -5
  189. package/src/bin/isolatedController.ts +300 -0
  190. package/src/bin/isolatedWorker.ts +192 -0
  191. package/src/build/config.ts +570 -632
  192. package/src/build/index.ts +14 -14
  193. package/src/build/pre.ts +1 -4
  194. package/src/build/schema.ts +49 -4
  195. package/src/client/index.ts +386 -48
  196. package/src/config/index.ts +3 -3
  197. package/src/database/actions.ts +469 -120
  198. package/src/database/index.ts +85 -58
  199. package/src/database/queryBuilder.ts +1 -0
  200. package/src/drizzle/index.ts +15 -7
  201. package/src/drizzle/onchain.ts +19 -0
  202. package/src/indexing/client.ts +38 -25
  203. package/src/indexing/index.ts +137 -230
  204. package/src/indexing/profile.ts +1 -1
  205. package/src/indexing-store/cache.ts +285 -414
  206. package/src/indexing-store/historical.ts +20 -10
  207. package/src/indexing-store/index.ts +16 -0
  208. package/src/indexing-store/profile.ts +3 -3
  209. package/src/indexing-store/realtime.ts +28 -0
  210. package/src/internal/errors.ts +26 -0
  211. package/src/internal/metrics.ts +341 -111
  212. package/src/internal/options.ts +4 -0
  213. package/src/internal/telemetry.ts +1 -1
  214. package/src/internal/types.ts +70 -87
  215. package/src/rpc/http.ts +164 -0
  216. package/src/rpc/index.ts +39 -7
  217. package/src/runtime/events.ts +195 -240
  218. package/src/runtime/filter.ts +85 -1
  219. package/src/runtime/fragments.ts +109 -113
  220. package/src/runtime/historical.ts +467 -189
  221. package/src/runtime/index.ts +337 -69
  222. package/src/runtime/init.ts +5 -5
  223. package/src/runtime/isolated.ts +768 -0
  224. package/src/runtime/multichain.ts +137 -102
  225. package/src/runtime/omnichain.ts +138 -106
  226. package/src/runtime/realtime.ts +322 -123
  227. package/src/sync-historical/index.ts +556 -692
  228. package/src/sync-realtime/bloom.ts +7 -3
  229. package/src/sync-realtime/index.ts +31 -46
  230. package/src/sync-store/index.ts +189 -95
  231. package/src/utils/abi.ts +33 -90
  232. package/src/utils/chunk.ts +7 -0
  233. package/src/utils/promiseAllSettledWithThrow.ts +27 -0
  234. package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
  235. package/dist/esm/client/parse.js.map +0 -1
  236. package/dist/types/client/parse.d.ts +0 -14
  237. 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, RetryableError, ShutdownError, } from '../internal/errors.js';
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 && eventCount[event.name] % SAMPLING_RATE === 1) {
189
- if (profile.has(event.name) === false) {
190
- profile.set(event.name, new Map());
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).count++;
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
- // Note `isFlushRetry` is true when the previous flush failed. When `isFlushRetry` is false, this
289
- // function takes an optimized fast path, with support for small batch sizes. PGlite always takes
290
- // the fast path because it doesn't support delayed insert errors.
291
- if (isFlushRetry && qb.$dialect === "postgres") {
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 tableCache = cache.get(table);
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
- try {
305
- const text = getCopyText(table, insertValues.map(({ row }) => row));
306
- await new Promise(setImmediate);
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
- catch (_error) {
310
- let error = _error;
311
- const result = await recoverBatchError(insertValues, async (values) => {
312
- await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
313
- const text = getCopyText(table, values.map(({ row }) => row));
314
- await copy(table, text);
315
- await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
316
- });
317
- if (result.status === "error") {
318
- error = new DelayedInsertError(result.error.message);
319
- error.stack = undefined;
320
- addErrorMeta(error, `db.insert arguments:\n${prettyPrint(result.value.row)}`);
321
- if (result.value.metadata.event) {
322
- addErrorMeta(error, toErrorMeta(result.value.metadata.event));
323
- common.logger.warn({
324
- msg: "Failed to write cached database rows",
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 "${getTableName(table)}"
382
- AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${getTableName(table)}"
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
- try {
399
- const text = getCopyText(table, updateValues.map(({ row }) => row));
400
- await new Promise(setImmediate);
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
- catch (_error) {
404
- let error = _error;
405
- const result = await recoverBatchError(updateValues, async (values) => {
406
- await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
407
- const text = getCopyText(table, values.map(({ row }) => row));
408
- await copy(table, text, false);
409
- await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
410
- });
411
- if (result.status === "error") {
412
- error = new DelayedInsertError(result.error.message);
413
- error.stack = undefined;
414
- addErrorMeta(error, `db.update arguments:\n${prettyPrint(result.value.row)}`);
415
- if (result.value.metadata.event) {
416
- addErrorMeta(error, toErrorMeta(result.value.metadata.event));
417
- common.logger.warn({
418
- msg: "Failed to write cached database rows",
419
- event: result.value.metadata.event.name,
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);