ponder 0.11.21 → 0.11.23
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 +12 -0
- package/dist/esm/bin/commands/createViews.js +9 -20
- package/dist/esm/bin/commands/createViews.js.map +1 -1
- package/dist/esm/bin/commands/dev.js +1 -1
- package/dist/esm/bin/commands/dev.js.map +1 -1
- package/dist/esm/bin/commands/list.js +4 -7
- package/dist/esm/bin/commands/list.js.map +1 -1
- package/dist/esm/bin/commands/prune.js +9 -21
- package/dist/esm/bin/commands/prune.js.map +1 -1
- package/dist/esm/bin/commands/serve.js +1 -1
- package/dist/esm/bin/commands/serve.js.map +1 -1
- package/dist/esm/bin/commands/start.js +3 -3
- package/dist/esm/bin/commands/start.js.map +1 -1
- package/dist/esm/bin/utils/run.js +159 -180
- package/dist/esm/bin/utils/run.js.map +1 -1
- package/dist/esm/build/index.js +1 -48
- package/dist/esm/build/index.js.map +1 -1
- package/dist/esm/build/plugin.js +1 -1
- package/dist/esm/client/index.js +9 -13
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/database/index.js +429 -141
- package/dist/esm/database/index.js.map +1 -1
- package/dist/esm/drizzle/index.js.map +1 -1
- package/dist/esm/drizzle/kit/index.js.map +1 -1
- package/dist/esm/drizzle/onchain.js +1 -8
- package/dist/esm/drizzle/onchain.js.map +1 -1
- package/dist/esm/graphql/index.js +16 -19
- package/dist/esm/graphql/index.js.map +1 -1
- package/dist/esm/graphql/middleware.js +7 -3
- package/dist/esm/graphql/middleware.js.map +1 -1
- package/dist/esm/indexing-store/cache.js +32 -26
- package/dist/esm/indexing-store/cache.js.map +1 -1
- package/dist/esm/indexing-store/historical.js +32 -23
- package/dist/esm/indexing-store/historical.js.map +1 -1
- package/dist/esm/indexing-store/index.js +18 -1
- package/dist/esm/indexing-store/index.js.map +1 -1
- package/dist/esm/indexing-store/realtime.js +140 -89
- package/dist/esm/indexing-store/realtime.js.map +1 -1
- package/dist/esm/internal/errors.js +0 -12
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/server/index.js +2 -10
- package/dist/esm/server/index.js.map +1 -1
- package/dist/esm/sync-store/index.js +432 -403
- package/dist/esm/sync-store/index.js.map +1 -1
- package/dist/esm/utils/rpc.js +3 -3
- package/dist/esm/utils/rpc.js.map +1 -1
- package/dist/esm/utils/wait.js +0 -2
- package/dist/esm/utils/wait.js.map +1 -1
- package/dist/types/bin/commands/createViews.d.ts.map +1 -1
- package/dist/types/bin/commands/list.d.ts.map +1 -1
- package/dist/types/bin/commands/prune.d.ts.map +1 -1
- package/dist/types/bin/commands/start.d.ts +0 -2
- package/dist/types/bin/commands/start.d.ts.map +1 -1
- package/dist/types/bin/utils/run.d.ts +1 -1
- package/dist/types/bin/utils/run.d.ts.map +1 -1
- package/dist/types/build/index.d.ts +1 -1
- package/dist/types/build/index.d.ts.map +1 -1
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/database/index.d.ts +73 -25
- package/dist/types/database/index.d.ts.map +1 -1
- package/dist/types/drizzle/index.d.ts +3 -2
- package/dist/types/drizzle/index.d.ts.map +1 -1
- package/dist/types/drizzle/kit/index.d.ts +4 -3
- package/dist/types/drizzle/kit/index.d.ts.map +1 -1
- package/dist/types/drizzle/onchain.d.ts +5 -12
- package/dist/types/drizzle/onchain.d.ts.map +1 -1
- package/dist/types/graphql/index.d.ts +4 -2
- package/dist/types/graphql/index.d.ts.map +1 -1
- package/dist/types/graphql/middleware.d.ts +1 -1
- package/dist/types/graphql/middleware.d.ts.map +1 -1
- package/dist/types/indexing-store/cache.d.ts +12 -5
- package/dist/types/indexing-store/cache.d.ts.map +1 -1
- package/dist/types/indexing-store/historical.d.ts +7 -2
- package/dist/types/indexing-store/historical.d.ts.map +1 -1
- package/dist/types/indexing-store/index.d.ts +2 -4
- package/dist/types/indexing-store/index.d.ts.map +1 -1
- package/dist/types/indexing-store/realtime.d.ts +3 -1
- package/dist/types/indexing-store/realtime.d.ts.map +1 -1
- package/dist/types/internal/errors.d.ts +0 -4
- package/dist/types/internal/errors.d.ts.map +1 -1
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/server/index.d.ts.map +1 -1
- package/dist/types/sync/index.d.ts +1 -1
- package/dist/types/sync-store/index.d.ts.map +1 -1
- package/dist/types/utils/rpc.d.ts.map +1 -1
- package/dist/types/utils/wait.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/bin/commands/createViews.ts +26 -37
- package/src/bin/commands/dev.ts +1 -1
- package/src/bin/commands/list.ts +4 -7
- package/src/bin/commands/prune.ts +17 -31
- package/src/bin/commands/serve.ts +1 -1
- package/src/bin/commands/start.ts +3 -4
- package/src/bin/utils/run.ts +210 -256
- package/src/build/index.ts +2 -53
- package/src/build/plugin.ts +1 -1
- package/src/client/index.ts +10 -21
- package/src/database/index.ts +742 -331
- package/src/drizzle/index.ts +3 -2
- package/src/drizzle/kit/index.ts +5 -2
- package/src/drizzle/onchain.ts +2 -26
- package/src/graphql/index.ts +26 -31
- package/src/graphql/middleware.ts +7 -5
- package/src/indexing-store/cache.ts +52 -35
- package/src/indexing-store/historical.ts +40 -28
- package/src/indexing-store/index.ts +27 -2
- package/src/indexing-store/realtime.ts +220 -176
- package/src/internal/errors.ts +0 -9
- package/src/server/index.ts +3 -14
- package/src/sync-store/index.ts +997 -870
- package/src/utils/rpc.ts +7 -6
- package/src/utils/wait.ts +0 -1
- package/dist/esm/database/queryBuilder.js +0 -206
- package/dist/esm/database/queryBuilder.js.map +0 -1
- package/dist/esm/database/utils.js +0 -100
- package/dist/esm/database/utils.js.map +0 -1
- package/dist/esm/drizzle/json.js +0 -119
- package/dist/esm/drizzle/json.js.map +0 -1
- package/dist/types/database/queryBuilder.d.ts +0 -37
- package/dist/types/database/queryBuilder.d.ts.map +0 -1
- package/dist/types/database/utils.d.ts +0 -25
- package/dist/types/database/utils.d.ts.map +0 -1
- package/dist/types/drizzle/json.d.ts +0 -51
- package/dist/types/drizzle/json.d.ts.map +0 -1
- package/src/database/queryBuilder.ts +0 -319
- package/src/database/utils.ts +0 -140
- package/src/drizzle/json.ts +0 -154
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { getPrimaryKeyColumns, getTableNames } from '../drizzle/index.js';
|
|
3
|
+
import { getColumnCasing, getReorgTable, sqlToReorgTableName, } from '../drizzle/kit/index.js';
|
|
3
4
|
import { NonRetryableError, ShutdownError } from '../internal/errors.js';
|
|
4
5
|
import { buildMigrationProvider } from '../sync-store/migrations.js';
|
|
5
|
-
import * as
|
|
6
|
-
import { min } from '../utils/checkpoint.js';
|
|
6
|
+
import * as ponderSyncSchema from '../sync-store/schema.js';
|
|
7
|
+
import { MAX_CHECKPOINT_STRING, decodeCheckpoint, min, } from '../utils/checkpoint.js';
|
|
7
8
|
import { formatEta } from '../utils/format.js';
|
|
8
9
|
import { createPool, createReadonlyPool } from '../utils/pg.js';
|
|
9
10
|
import { createPglite, createPgliteKyselyDialect } from '../utils/pglite.js';
|
|
10
11
|
import { startClock } from '../utils/timer.js';
|
|
11
12
|
import { wait } from '../utils/wait.js';
|
|
12
|
-
import { eq, getTableName, is, sql } from "drizzle-orm";
|
|
13
|
+
import { eq, getTableColumns, getTableName, is, lte, sql, } from "drizzle-orm";
|
|
13
14
|
import { drizzle as drizzleNodePg } from "drizzle-orm/node-postgres";
|
|
14
|
-
import { PgTable, pgSchema, pgTable } from "drizzle-orm/pg-core";
|
|
15
|
+
import { PgTable, pgSchema, pgTable, } from "drizzle-orm/pg-core";
|
|
15
16
|
import { drizzle as drizzlePglite } from "drizzle-orm/pglite";
|
|
16
17
|
import { Kysely, Migrator, PostgresDialect, WithSchemaPlugin } from "kysely";
|
|
17
18
|
import prometheus from "prom-client";
|
|
18
|
-
import { createQB, parseSqlError } from "./queryBuilder.js";
|
|
19
|
-
import { revert } from "./utils.js";
|
|
20
19
|
export const SCHEMATA = pgSchema("information_schema").table("schemata", (t) => ({
|
|
21
20
|
schemaName: t.text().primaryKey(),
|
|
22
21
|
}));
|
|
@@ -31,7 +30,7 @@ export const VIEWS = pgSchema("information_schema").table("views", (t) => ({
|
|
|
31
30
|
}));
|
|
32
31
|
const VERSION = "2";
|
|
33
32
|
export const getPonderMetaTable = (schema) => {
|
|
34
|
-
if (schema ===
|
|
33
|
+
if (schema === "public") {
|
|
35
34
|
return pgTable("_ponder_meta", (t) => ({
|
|
36
35
|
key: t.text().primaryKey().$type(),
|
|
37
36
|
value: t.jsonb().$type().notNull(),
|
|
@@ -42,15 +41,8 @@ export const getPonderMetaTable = (schema) => {
|
|
|
42
41
|
value: t.jsonb().$type().notNull(),
|
|
43
42
|
}));
|
|
44
43
|
};
|
|
45
|
-
/**
|
|
46
|
-
* - "safe" checkpoint: The closest-to-tip finalized and completed checkpoint.
|
|
47
|
-
* - "latest" checkpoint: The closest-to-tip completed checkpoint.
|
|
48
|
-
*
|
|
49
|
-
* @dev It is an invariant that every "latest" checkpoint is specific to that chain.
|
|
50
|
-
* In other words, `chainId === latestCheckpoint.chainId`.
|
|
51
|
-
*/
|
|
52
44
|
export const getPonderCheckpointTable = (schema) => {
|
|
53
|
-
if (schema ===
|
|
45
|
+
if (schema === "public") {
|
|
54
46
|
return pgTable("_ponder_checkpoint", (t) => ({
|
|
55
47
|
chainName: t.text().primaryKey(),
|
|
56
48
|
chainId: t.bigint({ mode: "number" }).notNull(),
|
|
@@ -73,10 +65,7 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
73
65
|
// Create schema, drivers, roles, and query builders
|
|
74
66
|
////////
|
|
75
67
|
let driver;
|
|
76
|
-
let
|
|
77
|
-
let adminQB;
|
|
78
|
-
let userQB;
|
|
79
|
-
let readonlyQB;
|
|
68
|
+
let qb;
|
|
80
69
|
const dialect = preBuild.databaseConfig.kind;
|
|
81
70
|
if (namespace.viewsSchema) {
|
|
82
71
|
common.logger.info({
|
|
@@ -92,7 +81,6 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
92
81
|
}
|
|
93
82
|
if (dialect === "pglite" || dialect === "pglite_test") {
|
|
94
83
|
driver = {
|
|
95
|
-
dialect: "pglite",
|
|
96
84
|
instance: dialect === "pglite"
|
|
97
85
|
? createPglite(preBuild.databaseConfig.options)
|
|
98
86
|
: preBuild.databaseConfig.instance,
|
|
@@ -100,7 +88,7 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
100
88
|
common.shutdown.add(async () => {
|
|
101
89
|
clearInterval(heartbeatInterval);
|
|
102
90
|
if (["start", "dev"].includes(common.options.command)) {
|
|
103
|
-
await
|
|
91
|
+
await qb.drizzle
|
|
104
92
|
.update(PONDER_META)
|
|
105
93
|
.set({ value: sql `jsonb_set(value, '{is_locked}', to_jsonb(0))` });
|
|
106
94
|
}
|
|
@@ -110,22 +98,20 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
110
98
|
});
|
|
111
99
|
await driver.instance.query(`CREATE SCHEMA IF NOT EXISTS "${namespace.schema}"`);
|
|
112
100
|
await driver.instance.query(`SET search_path TO "${namespace.schema}"`);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
schema: schemaBuild.schema,
|
|
128
|
-
}), { common });
|
|
101
|
+
qb = {
|
|
102
|
+
sync: drizzlePglite(driver.instance, {
|
|
103
|
+
casing: "snake_case",
|
|
104
|
+
schema: ponderSyncSchema,
|
|
105
|
+
}),
|
|
106
|
+
drizzle: drizzlePglite(driver.instance, {
|
|
107
|
+
casing: "snake_case",
|
|
108
|
+
schema: schemaBuild.schema,
|
|
109
|
+
}),
|
|
110
|
+
drizzleReadonly: drizzlePglite(driver.instance, {
|
|
111
|
+
casing: "snake_case",
|
|
112
|
+
schema: schemaBuild.schema,
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
129
115
|
}
|
|
130
116
|
else {
|
|
131
117
|
const internalMax = 2;
|
|
@@ -134,12 +120,7 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
134
120
|
? [preBuild.databaseConfig.poolConfig.max - internalMax, 0, 0]
|
|
135
121
|
: [equalMax, equalMax, equalMax];
|
|
136
122
|
driver = {
|
|
137
|
-
|
|
138
|
-
...preBuild.databaseConfig.poolConfig,
|
|
139
|
-
application_name: "ponder_sync",
|
|
140
|
-
max: syncMax,
|
|
141
|
-
}, common.logger),
|
|
142
|
-
admin: createPool({
|
|
123
|
+
internal: createPool({
|
|
143
124
|
...preBuild.databaseConfig.poolConfig,
|
|
144
125
|
application_name: `${namespace.schema}_internal`,
|
|
145
126
|
max: internalMax,
|
|
@@ -155,47 +136,42 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
155
136
|
application_name: `${namespace.schema}_readonly`,
|
|
156
137
|
max: readonlyMax,
|
|
157
138
|
}, common.logger, namespace.schema),
|
|
139
|
+
sync: createPool({
|
|
140
|
+
...preBuild.databaseConfig.poolConfig,
|
|
141
|
+
application_name: "ponder_sync",
|
|
142
|
+
max: syncMax,
|
|
143
|
+
}, common.logger),
|
|
158
144
|
listen: undefined,
|
|
159
145
|
};
|
|
160
|
-
await driver.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
drizzleNodePg(driver.user, {
|
|
176
|
-
casing: "snake_case",
|
|
177
|
-
schema: schemaBuild.schema,
|
|
178
|
-
}), { common });
|
|
179
|
-
readonlyQB = createQB(() =>
|
|
180
|
-
// @ts-expect-error
|
|
181
|
-
drizzleNodePg(driver.readonly, {
|
|
182
|
-
casing: "snake_case",
|
|
183
|
-
schema: schemaBuild.schema,
|
|
184
|
-
}), { common });
|
|
146
|
+
await driver.internal.query(`CREATE SCHEMA IF NOT EXISTS "${namespace.schema}"`);
|
|
147
|
+
qb = {
|
|
148
|
+
sync: drizzleNodePg(driver.sync, {
|
|
149
|
+
casing: "snake_case",
|
|
150
|
+
schema: ponderSyncSchema,
|
|
151
|
+
}),
|
|
152
|
+
drizzle: drizzleNodePg(driver.user, {
|
|
153
|
+
casing: "snake_case",
|
|
154
|
+
schema: schemaBuild.schema,
|
|
155
|
+
}),
|
|
156
|
+
drizzleReadonly: drizzleNodePg(driver.readonly, {
|
|
157
|
+
casing: "snake_case",
|
|
158
|
+
schema: schemaBuild.schema,
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
185
161
|
common.shutdown.add(async () => {
|
|
186
162
|
clearInterval(heartbeatInterval);
|
|
187
163
|
if (["start", "dev"].includes(common.options.command)) {
|
|
188
|
-
await
|
|
164
|
+
await qb.drizzle
|
|
189
165
|
.update(PONDER_META)
|
|
190
166
|
.set({ value: sql `jsonb_set(value, '{is_locked}', to_jsonb(0))` });
|
|
191
167
|
}
|
|
192
168
|
const d = driver;
|
|
193
169
|
d.listen?.release();
|
|
194
170
|
await Promise.all([
|
|
195
|
-
d.
|
|
196
|
-
d.admin.end(),
|
|
171
|
+
d.internal.end(),
|
|
197
172
|
d.user.end(),
|
|
198
173
|
d.readonly.end(),
|
|
174
|
+
d.sync.end(),
|
|
199
175
|
]);
|
|
200
176
|
});
|
|
201
177
|
// Register Postgres-only metrics
|
|
@@ -207,8 +183,8 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
207
183
|
labelNames: ["pool", "kind"],
|
|
208
184
|
registers: [common.metrics.registry],
|
|
209
185
|
collect() {
|
|
210
|
-
this.set({ pool: "
|
|
211
|
-
this.set({ pool: "
|
|
186
|
+
this.set({ pool: "internal", kind: "idle" }, d.internal.idleCount);
|
|
187
|
+
this.set({ pool: "internal", kind: "total" }, d.internal.totalCount);
|
|
212
188
|
this.set({ pool: "sync", kind: "idle" }, d.sync.idleCount);
|
|
213
189
|
this.set({ pool: "sync", kind: "total" }, d.sync.totalCount);
|
|
214
190
|
this.set({ pool: "user", kind: "idle" }, d.user.idleCount);
|
|
@@ -224,7 +200,7 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
224
200
|
labelNames: ["pool"],
|
|
225
201
|
registers: [common.metrics.registry],
|
|
226
202
|
collect() {
|
|
227
|
-
this.set({ pool: "
|
|
203
|
+
this.set({ pool: "internal" }, d.internal.waitingCount);
|
|
228
204
|
this.set({ pool: "sync" }, d.sync.waitingCount);
|
|
229
205
|
this.set({ pool: "user" }, d.user.waitingCount);
|
|
230
206
|
this.set({ pool: "readonly" }, d.readonly.waitingCount);
|
|
@@ -232,91 +208,253 @@ export const createDatabase = async ({ common, namespace, preBuild, schemaBuild,
|
|
|
232
208
|
});
|
|
233
209
|
}
|
|
234
210
|
const tables = Object.values(schemaBuild.schema).filter((table) => is(table, PgTable));
|
|
235
|
-
|
|
211
|
+
const database = {
|
|
236
212
|
driver,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
213
|
+
qb,
|
|
214
|
+
PONDER_META,
|
|
215
|
+
PONDER_CHECKPOINT,
|
|
216
|
+
async retry(fn) {
|
|
217
|
+
const RETRY_COUNT = 9;
|
|
218
|
+
const BASE_DURATION = 125;
|
|
219
|
+
// First error thrown is often the most useful
|
|
220
|
+
let firstError;
|
|
221
|
+
let hasError = false;
|
|
222
|
+
for (let i = 0; i <= RETRY_COUNT; i++) {
|
|
223
|
+
try {
|
|
224
|
+
if (common.shutdown.isKilled) {
|
|
225
|
+
throw new ShutdownError();
|
|
249
226
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
227
|
+
const result = await fn();
|
|
228
|
+
if (common.shutdown.isKilled) {
|
|
229
|
+
throw new ShutdownError();
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
catch (_error) {
|
|
234
|
+
const error = _error;
|
|
235
|
+
if (common.shutdown.isKilled) {
|
|
236
|
+
throw new ShutdownError();
|
|
237
|
+
}
|
|
238
|
+
if (!hasError) {
|
|
239
|
+
hasError = true;
|
|
240
|
+
firstError = error;
|
|
241
|
+
}
|
|
242
|
+
if (error instanceof NonRetryableError) {
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
if (i === RETRY_COUNT) {
|
|
246
|
+
throw firstError;
|
|
247
|
+
}
|
|
248
|
+
const duration = BASE_DURATION * 2 ** i;
|
|
249
|
+
await wait(duration);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
throw "unreachable";
|
|
253
|
+
},
|
|
254
|
+
async record(options, fn) {
|
|
255
|
+
const endClock = startClock();
|
|
256
|
+
const id = crypto.randomUUID().slice(0, 8);
|
|
257
|
+
if (options.includeTraceLogs) {
|
|
258
|
+
common.logger.trace({
|
|
259
|
+
service: "database",
|
|
260
|
+
msg: `Started '${options.method}' database method (id=${id})`,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
if (common.shutdown.isKilled) {
|
|
265
|
+
throw new ShutdownError();
|
|
266
|
+
}
|
|
267
|
+
const result = await fn();
|
|
268
|
+
common.metrics.ponder_database_method_duration.observe({ method: options.method }, endClock());
|
|
269
|
+
if (common.shutdown.isKilled) {
|
|
270
|
+
throw new ShutdownError();
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
catch (_error) {
|
|
275
|
+
const error = _error;
|
|
276
|
+
if (common.shutdown.isKilled) {
|
|
277
|
+
throw new ShutdownError();
|
|
278
|
+
}
|
|
279
|
+
common.metrics.ponder_database_method_duration.observe({ method: options.method }, endClock());
|
|
280
|
+
common.metrics.ponder_database_method_error_total.inc({
|
|
281
|
+
method: options.method,
|
|
282
|
+
});
|
|
283
|
+
common.logger.warn({
|
|
284
|
+
service: "database",
|
|
285
|
+
msg: `Failed '${options.method}' database method (id=${id})`,
|
|
286
|
+
error,
|
|
287
|
+
});
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
if (options.includeTraceLogs) {
|
|
292
|
+
common.logger.trace({
|
|
293
|
+
service: "database",
|
|
294
|
+
msg: `Completed '${options.method}' database method in ${Math.round(endClock())}ms (id=${id})`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
async wrap(options, fn) {
|
|
300
|
+
const RETRY_COUNT = 9;
|
|
301
|
+
const BASE_DURATION = 125;
|
|
302
|
+
// First error thrown is often the most useful
|
|
303
|
+
let firstError;
|
|
304
|
+
let hasError = false;
|
|
305
|
+
for (let i = 0; i <= RETRY_COUNT; i++) {
|
|
260
306
|
const endClock = startClock();
|
|
307
|
+
const id = crypto.randomUUID().slice(0, 8);
|
|
308
|
+
if (options.includeTraceLogs) {
|
|
309
|
+
common.logger.trace({
|
|
310
|
+
service: "database",
|
|
311
|
+
msg: `Started '${options.method}' database method (id=${id})`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
261
314
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
315
|
+
if (common.shutdown.isKilled) {
|
|
316
|
+
throw new ShutdownError();
|
|
317
|
+
}
|
|
318
|
+
const result = await fn();
|
|
319
|
+
common.metrics.ponder_database_method_duration.observe({ method: options.method }, endClock());
|
|
320
|
+
if (common.shutdown.isKilled) {
|
|
321
|
+
throw new ShutdownError();
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
267
324
|
}
|
|
268
325
|
catch (_error) {
|
|
269
|
-
const error =
|
|
326
|
+
const error = _error;
|
|
270
327
|
if (common.shutdown.isKilled) {
|
|
271
328
|
throw new ShutdownError();
|
|
272
329
|
}
|
|
273
|
-
common.metrics.ponder_database_method_duration.observe({ method:
|
|
330
|
+
common.metrics.ponder_database_method_duration.observe({ method: options.method }, endClock());
|
|
274
331
|
common.metrics.ponder_database_method_error_total.inc({
|
|
275
|
-
method:
|
|
332
|
+
method: options.method,
|
|
276
333
|
});
|
|
334
|
+
if (!hasError) {
|
|
335
|
+
hasError = true;
|
|
336
|
+
firstError = error;
|
|
337
|
+
}
|
|
277
338
|
if (error instanceof NonRetryableError) {
|
|
278
339
|
common.logger.warn({
|
|
279
340
|
service: "database",
|
|
280
|
-
msg: `Failed '
|
|
341
|
+
msg: `Failed '${options.method}' database method (id=${id})`,
|
|
281
342
|
error,
|
|
282
343
|
});
|
|
283
344
|
throw error;
|
|
284
345
|
}
|
|
285
|
-
if (i ===
|
|
346
|
+
if (i === RETRY_COUNT) {
|
|
286
347
|
common.logger.warn({
|
|
287
348
|
service: "database",
|
|
288
|
-
msg: `Failed '
|
|
349
|
+
msg: `Failed '${options.method}' database method after '${i + 1}' attempts (id=${id})`,
|
|
289
350
|
error,
|
|
290
351
|
});
|
|
291
|
-
throw
|
|
352
|
+
throw firstError;
|
|
292
353
|
}
|
|
293
|
-
const duration =
|
|
354
|
+
const duration = BASE_DURATION * 2 ** i;
|
|
294
355
|
common.logger.debug({
|
|
295
356
|
service: "database",
|
|
296
|
-
msg: `Failed '
|
|
357
|
+
msg: `Failed '${options.method}' database method, retrying after ${duration} milliseconds (id=${id})`,
|
|
297
358
|
error,
|
|
298
359
|
});
|
|
299
360
|
await wait(duration);
|
|
300
361
|
}
|
|
362
|
+
finally {
|
|
363
|
+
if (options.includeTraceLogs) {
|
|
364
|
+
common.logger.trace({
|
|
365
|
+
service: "database",
|
|
366
|
+
msg: `Completed '${options.method}' database method in ${Math.round(endClock())}ms (id=${id})`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
throw "unreachable";
|
|
372
|
+
},
|
|
373
|
+
async transaction(fn) {
|
|
374
|
+
if (dialect === "postgres") {
|
|
375
|
+
const client = await database.driver.user.connect();
|
|
376
|
+
try {
|
|
377
|
+
await client.query("BEGIN");
|
|
378
|
+
const tx = drizzleNodePg(client, {
|
|
379
|
+
casing: "snake_case",
|
|
380
|
+
schema: schemaBuild.schema,
|
|
381
|
+
});
|
|
382
|
+
const result = await fn(client, tx);
|
|
383
|
+
await client.query("COMMIT");
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
await client.query("ROLLBACK");
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
client.release();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const client = database.driver.instance;
|
|
396
|
+
try {
|
|
397
|
+
await client.query("BEGIN");
|
|
398
|
+
const tx = drizzlePglite(client, {
|
|
399
|
+
casing: "snake_case",
|
|
400
|
+
schema: schemaBuild.schema,
|
|
401
|
+
});
|
|
402
|
+
const result = await fn(client, tx);
|
|
403
|
+
await client.query("COMMIT");
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
await client?.query("ROLLBACK");
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
301
410
|
}
|
|
302
411
|
},
|
|
412
|
+
async migrateSync() {
|
|
413
|
+
await this.wrap({ method: "migrateSyncStore", includeTraceLogs: true }, async () => {
|
|
414
|
+
const kysely = new Kysely({
|
|
415
|
+
dialect: dialect === "postgres"
|
|
416
|
+
? new PostgresDialect({
|
|
417
|
+
pool: driver.internal,
|
|
418
|
+
})
|
|
419
|
+
: createPgliteKyselyDialect(driver.instance),
|
|
420
|
+
log(event) {
|
|
421
|
+
if (event.level === "query") {
|
|
422
|
+
common.metrics.ponder_postgres_query_total.inc({
|
|
423
|
+
pool: "migrate",
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
plugins: [new WithSchemaPlugin("ponder_sync")],
|
|
428
|
+
});
|
|
429
|
+
const migrationProvider = buildMigrationProvider(common.logger);
|
|
430
|
+
const migrator = new Migrator({
|
|
431
|
+
db: kysely,
|
|
432
|
+
provider: migrationProvider,
|
|
433
|
+
migrationTableSchema: "ponder_sync",
|
|
434
|
+
});
|
|
435
|
+
const { error } = await migrator.migrateToLatest();
|
|
436
|
+
if (error)
|
|
437
|
+
throw error;
|
|
438
|
+
});
|
|
439
|
+
},
|
|
303
440
|
async migrate({ buildId }) {
|
|
304
|
-
await
|
|
441
|
+
await this.wrap({ method: "createPonderSystemTables", includeTraceLogs: true }, async () => {
|
|
442
|
+
await qb.drizzle.execute(sql.raw(`
|
|
305
443
|
CREATE TABLE IF NOT EXISTS "${namespace.schema}"."_ponder_meta" (
|
|
306
444
|
"key" TEXT PRIMARY KEY,
|
|
307
445
|
"value" JSONB NOT NULL
|
|
308
446
|
)`));
|
|
309
|
-
|
|
447
|
+
await qb.drizzle.execute(sql.raw(`
|
|
310
448
|
CREATE TABLE IF NOT EXISTS "${namespace.schema}"."_ponder_checkpoint" (
|
|
311
449
|
"chain_name" TEXT PRIMARY KEY,
|
|
312
450
|
"chain_id" INTEGER NOT NULL,
|
|
313
451
|
"safe_checkpoint" VARCHAR(75) NOT NULL,
|
|
314
452
|
"latest_checkpoint" VARCHAR(75) NOT NULL
|
|
315
453
|
)`));
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
454
|
+
const trigger = "status_trigger";
|
|
455
|
+
const notification = "status_notify()";
|
|
456
|
+
const channel = `${namespace.schema}_status_channel`;
|
|
457
|
+
await qb.drizzle.execute(sql.raw(`
|
|
320
458
|
CREATE OR REPLACE FUNCTION "${namespace.schema}".${notification}
|
|
321
459
|
RETURNS TRIGGER
|
|
322
460
|
LANGUAGE plpgsql
|
|
@@ -326,15 +464,16 @@ NOTIFY "${channel}";
|
|
|
326
464
|
RETURN NULL;
|
|
327
465
|
END;
|
|
328
466
|
$$;`));
|
|
329
|
-
|
|
467
|
+
await qb.drizzle.execute(sql.raw(`
|
|
330
468
|
CREATE OR REPLACE TRIGGER "${trigger}"
|
|
331
469
|
AFTER INSERT OR UPDATE OR DELETE
|
|
332
470
|
ON "${namespace.schema}"._ponder_checkpoint
|
|
333
471
|
FOR EACH STATEMENT
|
|
334
472
|
EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
473
|
+
});
|
|
335
474
|
const createTables = async (tx) => {
|
|
336
475
|
for (let i = 0; i < schemaBuild.statements.tables.sql.length; i++) {
|
|
337
|
-
await tx
|
|
476
|
+
await tx
|
|
338
477
|
.execute(sql.raw(schemaBuild.statements.tables.sql[i]))
|
|
339
478
|
.catch((_error) => {
|
|
340
479
|
const error = _error;
|
|
@@ -348,7 +487,7 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
348
487
|
};
|
|
349
488
|
const createEnums = async (tx) => {
|
|
350
489
|
for (let i = 0; i < schemaBuild.statements.enums.sql.length; i++) {
|
|
351
|
-
await tx
|
|
490
|
+
await tx
|
|
352
491
|
.execute(sql.raw(schemaBuild.statements.enums.sql[i]))
|
|
353
492
|
.catch((_error) => {
|
|
354
493
|
const error = _error;
|
|
@@ -360,9 +499,9 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
360
499
|
});
|
|
361
500
|
}
|
|
362
501
|
};
|
|
363
|
-
const tryAcquireLockAndMigrate = () =>
|
|
502
|
+
const tryAcquireLockAndMigrate = () => this.wrap({ method: "migrate", includeTraceLogs: true }, () => qb.drizzle.transaction(async (tx) => {
|
|
364
503
|
// Note: All ponder versions are compatible with the next query (every version of the "_ponder_meta" table have the same columns)
|
|
365
|
-
const previousApp = await tx
|
|
504
|
+
const previousApp = await tx
|
|
366
505
|
.select({ value: PONDER_META.value })
|
|
367
506
|
.from(PONDER_META)
|
|
368
507
|
.where(eq(PONDER_META.key, "app"))
|
|
@@ -383,7 +522,7 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
383
522
|
service: "database",
|
|
384
523
|
msg: `Created tables [${tables.map(getTableName).join(", ")}]`,
|
|
385
524
|
});
|
|
386
|
-
await tx
|
|
525
|
+
await tx
|
|
387
526
|
.insert(PONDER_META)
|
|
388
527
|
.values({ key: "app", value: metadata });
|
|
389
528
|
return {
|
|
@@ -395,20 +534,20 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
395
534
|
(process.env.PONDER_EXPERIMENTAL_DB === "platform" &&
|
|
396
535
|
previousApp.build_id !== buildId)) {
|
|
397
536
|
for (const table of previousApp.table_names) {
|
|
398
|
-
await tx
|
|
399
|
-
await tx
|
|
537
|
+
await tx.execute(sql.raw(`DROP TABLE IF EXISTS "${namespace.schema}"."${table}" CASCADE`));
|
|
538
|
+
await tx.execute(sql.raw(`DROP TABLE IF EXISTS "${namespace.schema}"."${sqlToReorgTableName(table)}" CASCADE`));
|
|
400
539
|
}
|
|
401
540
|
for (const enumName of schemaBuild.statements.enums.json) {
|
|
402
|
-
await tx
|
|
541
|
+
await tx.execute(sql.raw(`DROP TYPE IF EXISTS "${namespace.schema}"."${enumName.name}"`));
|
|
403
542
|
}
|
|
404
|
-
await tx
|
|
543
|
+
await tx.execute(sql.raw(`TRUNCATE TABLE "${namespace.schema}"."${getTableName(PONDER_CHECKPOINT)}" CASCADE`));
|
|
405
544
|
await createEnums(tx);
|
|
406
545
|
await createTables(tx);
|
|
407
546
|
common.logger.info({
|
|
408
547
|
service: "database",
|
|
409
548
|
msg: `Created tables [${tables.map(getTableName).join(", ")}]`,
|
|
410
549
|
});
|
|
411
|
-
await tx
|
|
550
|
+
await tx.update(PONDER_META).set({ value: metadata });
|
|
412
551
|
return {
|
|
413
552
|
status: "success",
|
|
414
553
|
crashRecoveryCheckpoint: undefined,
|
|
@@ -426,7 +565,8 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
426
565
|
error.stack = undefined;
|
|
427
566
|
throw error;
|
|
428
567
|
}
|
|
429
|
-
const expiry = previousApp.heartbeat_at +
|
|
568
|
+
const expiry = previousApp.heartbeat_at +
|
|
569
|
+
common.options.databaseHeartbeatTimeout;
|
|
430
570
|
const isAppUnlocked = previousApp.is_locked === 0 || expiry <= Date.now();
|
|
431
571
|
if (isAppUnlocked === false) {
|
|
432
572
|
return { status: "locked", expiry };
|
|
@@ -435,7 +575,7 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
435
575
|
service: "database",
|
|
436
576
|
msg: `Detected crash recovery for build '${buildId}' in schema '${namespace.schema}' last active ${formatEta(Date.now() - previousApp.heartbeat_at)} ago`,
|
|
437
577
|
});
|
|
438
|
-
const checkpoints = await tx
|
|
578
|
+
const checkpoints = await tx.select().from(PONDER_CHECKPOINT);
|
|
439
579
|
const crashRecoveryCheckpoint = checkpoints.length === 0
|
|
440
580
|
? undefined
|
|
441
581
|
: checkpoints.map((c) => ({
|
|
@@ -443,16 +583,16 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
443
583
|
checkpoint: c.safeCheckpoint,
|
|
444
584
|
}));
|
|
445
585
|
if (previousApp.is_ready === 0) {
|
|
446
|
-
await tx
|
|
586
|
+
await tx.update(PONDER_META).set({ value: metadata });
|
|
447
587
|
return { status: "success", crashRecoveryCheckpoint };
|
|
448
588
|
}
|
|
449
589
|
// Remove triggers
|
|
450
590
|
for (const table of tables) {
|
|
451
|
-
await tx
|
|
591
|
+
await tx.execute(sql.raw(`DROP TRIGGER IF EXISTS "${getTableNames(table).trigger}" ON "${namespace.schema}"."${getTableName(table)}"`));
|
|
452
592
|
}
|
|
453
593
|
// Remove indexes
|
|
454
594
|
for (const indexStatement of schemaBuild.statements.indexes.json) {
|
|
455
|
-
await tx
|
|
595
|
+
await tx.execute(sql.raw(`DROP INDEX IF EXISTS "${namespace.schema}"."${indexStatement.data.name}"`));
|
|
456
596
|
common.logger.debug({
|
|
457
597
|
service: "database",
|
|
458
598
|
msg: `Dropped index '${indexStatement.data.name}' in schema '${namespace.schema}'`,
|
|
@@ -460,12 +600,12 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
460
600
|
}
|
|
461
601
|
// Note: it is an invariant that checkpoints.length > 0;
|
|
462
602
|
const revertCheckpoint = min(...checkpoints.map((c) => c.safeCheckpoint));
|
|
463
|
-
await
|
|
603
|
+
await this.revert({ checkpoint: revertCheckpoint, tx });
|
|
464
604
|
// Note: We don't update the `_ponder_checkpoint` table here, instead we wait for it to be updated
|
|
465
605
|
// in the runtime script.
|
|
466
|
-
await tx
|
|
606
|
+
await tx.update(PONDER_META).set({ value: metadata });
|
|
467
607
|
return { status: "success", crashRecoveryCheckpoint };
|
|
468
|
-
});
|
|
608
|
+
}));
|
|
469
609
|
let result = await tryAcquireLockAndMigrate();
|
|
470
610
|
if (result.status === "locked") {
|
|
471
611
|
const duration = result.expiry - Date.now();
|
|
@@ -488,9 +628,7 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
488
628
|
heartbeatInterval = setInterval(async () => {
|
|
489
629
|
try {
|
|
490
630
|
const heartbeat = Date.now();
|
|
491
|
-
await
|
|
492
|
-
.update(PONDER_META)
|
|
493
|
-
.set({
|
|
631
|
+
await qb.drizzle.update(PONDER_META).set({
|
|
494
632
|
value: sql `jsonb_set(value, '{heartbeat_at}', ${heartbeat})`,
|
|
495
633
|
});
|
|
496
634
|
common.logger.trace({
|
|
@@ -509,6 +647,156 @@ EXECUTE PROCEDURE "${namespace.schema}".${notification};`));
|
|
|
509
647
|
}, common.options.databaseHeartbeatInterval);
|
|
510
648
|
return result.crashRecoveryCheckpoint;
|
|
511
649
|
},
|
|
650
|
+
async createIndexes() {
|
|
651
|
+
for (const statement of schemaBuild.statements.indexes.sql) {
|
|
652
|
+
await this.wrap({ method: "createIndexes" }, async () => {
|
|
653
|
+
await qb.drizzle.transaction(async (tx) => {
|
|
654
|
+
await tx.execute("SET statement_timeout = 3600000;"); // 60 minutes
|
|
655
|
+
await tx.execute(statement);
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
async createTriggers() {
|
|
661
|
+
await this.wrap({ method: "createTriggers", includeTraceLogs: true }, async () => {
|
|
662
|
+
for (const table of tables) {
|
|
663
|
+
const columns = getTableColumns(table);
|
|
664
|
+
const columnNames = Object.values(columns).map((column) => `"${getColumnCasing(column, "snake_case")}"`);
|
|
665
|
+
await qb.drizzle.execute(sql.raw(`
|
|
666
|
+
CREATE OR REPLACE FUNCTION "${namespace.schema}".${getTableNames(table).triggerFn}
|
|
667
|
+
RETURNS TRIGGER AS $$
|
|
668
|
+
BEGIN
|
|
669
|
+
IF TG_OP = 'INSERT' THEN
|
|
670
|
+
INSERT INTO "${namespace.schema}"."${getTableName(getReorgTable(table))}" (${columnNames.join(",")}, operation, checkpoint)
|
|
671
|
+
VALUES (${columnNames.map((name) => `NEW.${name}`).join(",")}, 0, '${MAX_CHECKPOINT_STRING}');
|
|
672
|
+
ELSIF TG_OP = 'UPDATE' THEN
|
|
673
|
+
INSERT INTO "${namespace.schema}"."${getTableName(getReorgTable(table))}" (${columnNames.join(",")}, operation, checkpoint)
|
|
674
|
+
VALUES (${columnNames.map((name) => `OLD.${name}`).join(",")}, 1, '${MAX_CHECKPOINT_STRING}');
|
|
675
|
+
ELSIF TG_OP = 'DELETE' THEN
|
|
676
|
+
INSERT INTO "${namespace.schema}"."${getTableName(getReorgTable(table))}" (${columnNames.join(",")}, operation, checkpoint)
|
|
677
|
+
VALUES (${columnNames.map((name) => `OLD.${name}`).join(",")}, 2, '${MAX_CHECKPOINT_STRING}');
|
|
678
|
+
END IF;
|
|
679
|
+
RETURN NULL;
|
|
680
|
+
END;
|
|
681
|
+
$$ LANGUAGE plpgsql`));
|
|
682
|
+
await qb.drizzle.execute(sql.raw(`
|
|
683
|
+
CREATE OR REPLACE TRIGGER "${getTableNames(table).trigger}"
|
|
684
|
+
AFTER INSERT OR UPDATE OR DELETE ON "${namespace.schema}"."${getTableName(table)}"
|
|
685
|
+
FOR EACH ROW EXECUTE FUNCTION "${namespace.schema}".${getTableNames(table).triggerFn};
|
|
686
|
+
`));
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
},
|
|
690
|
+
async removeTriggers() {
|
|
691
|
+
await this.wrap({ method: "removeTriggers", includeTraceLogs: true }, async () => {
|
|
692
|
+
for (const table of tables) {
|
|
693
|
+
await qb.drizzle.execute(sql.raw(`DROP TRIGGER IF EXISTS "${getTableNames(table).trigger}" ON "${namespace.schema}"."${getTableName(table)}"`));
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
},
|
|
697
|
+
async setCheckpoints({ checkpoints, db }) {
|
|
698
|
+
if (checkpoints.length === 0)
|
|
699
|
+
return;
|
|
700
|
+
return this.wrap({ method: "setCheckpoints" }, async () => {
|
|
701
|
+
await db
|
|
702
|
+
.insert(PONDER_CHECKPOINT)
|
|
703
|
+
.values(checkpoints)
|
|
704
|
+
.onConflictDoUpdate({
|
|
705
|
+
target: PONDER_CHECKPOINT.chainName,
|
|
706
|
+
set: {
|
|
707
|
+
safeCheckpoint: sql `excluded.safe_checkpoint`,
|
|
708
|
+
latestCheckpoint: sql `excluded.latest_checkpoint`,
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
},
|
|
713
|
+
getCheckpoints() {
|
|
714
|
+
return this.wrap({ method: "getCheckpoints" }, () => qb.drizzle.select().from(PONDER_CHECKPOINT));
|
|
715
|
+
},
|
|
716
|
+
setReady() {
|
|
717
|
+
return this.wrap({ method: "setReady" }, async () => {
|
|
718
|
+
await qb.drizzle
|
|
719
|
+
.update(PONDER_META)
|
|
720
|
+
.set({ value: sql `jsonb_set(value, '{is_ready}', to_jsonb(1))` });
|
|
721
|
+
});
|
|
722
|
+
},
|
|
723
|
+
getReady() {
|
|
724
|
+
return this.wrap({ method: "getReady" }, async () => {
|
|
725
|
+
return qb.drizzle
|
|
726
|
+
.select()
|
|
727
|
+
.from(PONDER_META)
|
|
728
|
+
.then((result) => result[0]?.value.is_ready === 1 ?? false);
|
|
729
|
+
});
|
|
730
|
+
},
|
|
731
|
+
async revert({ checkpoint, tx }) {
|
|
732
|
+
await this.record({ method: "revert", includeTraceLogs: true }, () => Promise.all(tables.map(async (table) => {
|
|
733
|
+
const primaryKeyColumns = getPrimaryKeyColumns(table);
|
|
734
|
+
const result = await tx.execute(sql.raw(`
|
|
735
|
+
WITH reverted1 AS (
|
|
736
|
+
DELETE FROM "${namespace.schema}"."${getTableName(getReorgTable(table))}"
|
|
737
|
+
WHERE checkpoint > '${checkpoint}' RETURNING *
|
|
738
|
+
), reverted2 AS (
|
|
739
|
+
SELECT ${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")}, MIN(operation_id) AS operation_id FROM reverted1
|
|
740
|
+
GROUP BY ${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")}
|
|
741
|
+
), reverted3 AS (
|
|
742
|
+
SELECT ${Object.values(getTableColumns(table))
|
|
743
|
+
.map((column) => `reverted1."${getColumnCasing(column, "snake_case")}"`)
|
|
744
|
+
.join(", ")}, reverted1.operation FROM reverted2
|
|
745
|
+
INNER JOIN reverted1
|
|
746
|
+
ON ${primaryKeyColumns.map(({ sql }) => `reverted2."${sql}" = reverted1."${sql}"`).join("AND ")}
|
|
747
|
+
AND reverted2.operation_id = reverted1.operation_id
|
|
748
|
+
), inserted AS (
|
|
749
|
+
DELETE FROM "${namespace.schema}"."${getTableName(table)}" as t
|
|
750
|
+
WHERE EXISTS (
|
|
751
|
+
SELECT * FROM reverted3
|
|
752
|
+
WHERE ${primaryKeyColumns.map(({ sql }) => `t."${sql}" = reverted3."${sql}"`).join("AND ")}
|
|
753
|
+
AND OPERATION = 0
|
|
754
|
+
)
|
|
755
|
+
RETURNING *
|
|
756
|
+
), updated_or_deleted AS (
|
|
757
|
+
INSERT INTO "${namespace.schema}"."${getTableName(table)}"
|
|
758
|
+
SELECT ${Object.values(getTableColumns(table))
|
|
759
|
+
.map((column) => `"${getColumnCasing(column, "snake_case")}"`)
|
|
760
|
+
.join(", ")} FROM reverted3
|
|
761
|
+
WHERE operation = 1 OR operation = 2
|
|
762
|
+
ON CONFLICT (${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")})
|
|
763
|
+
DO UPDATE SET
|
|
764
|
+
${Object.values(getTableColumns(table))
|
|
765
|
+
.map((column) => `"${getColumnCasing(column, "snake_case")}" = EXCLUDED."${getColumnCasing(column, "snake_case")}"`)
|
|
766
|
+
.join(", ")}
|
|
767
|
+
RETURNING *
|
|
768
|
+
) SELECT COUNT(*) FROM reverted1 as count;
|
|
769
|
+
`));
|
|
770
|
+
common.logger.info({
|
|
771
|
+
service: "database",
|
|
772
|
+
// @ts-ignore
|
|
773
|
+
msg: `Reverted ${result.rows[0].count} unfinalized operations from '${getTableName(table)}'`,
|
|
774
|
+
});
|
|
775
|
+
})));
|
|
776
|
+
},
|
|
777
|
+
async finalize({ checkpoint, db }) {
|
|
778
|
+
await this.record({ method: "finalize", includeTraceLogs: true }, async () => {
|
|
779
|
+
await Promise.all(tables.map((table) => db
|
|
780
|
+
.delete(getReorgTable(table))
|
|
781
|
+
.where(lte(getReorgTable(table).checkpoint, checkpoint))));
|
|
782
|
+
});
|
|
783
|
+
const decoded = decodeCheckpoint(checkpoint);
|
|
784
|
+
common.logger.debug({
|
|
785
|
+
service: "database",
|
|
786
|
+
msg: `Updated finalized checkpoint to (timestamp=${decoded.blockTimestamp} chainId=${decoded.chainId} block=${decoded.blockNumber})`,
|
|
787
|
+
});
|
|
788
|
+
},
|
|
789
|
+
async commitBlock({ checkpoint, db }) {
|
|
790
|
+
await Promise.all(tables.map((table) => this.wrap({ method: "complete" }, async () => {
|
|
791
|
+
const reorgTable = getReorgTable(table);
|
|
792
|
+
await db
|
|
793
|
+
.update(reorgTable)
|
|
794
|
+
.set({ checkpoint })
|
|
795
|
+
.where(eq(reorgTable.checkpoint, MAX_CHECKPOINT_STRING));
|
|
796
|
+
})));
|
|
797
|
+
},
|
|
512
798
|
};
|
|
799
|
+
// @ts-ignore
|
|
800
|
+
return database;
|
|
513
801
|
};
|
|
514
802
|
//# sourceMappingURL=index.js.map
|